Help with fitting a model into a viewport dynamically

Hello, DevForum Community

I’ve recently been stuck on a problem occurring in my game where the zoom system wouldn’t fit the object in the screen dynamically depending on the screen’s aspect ratio. I’ve tried countless hours troubleshooting but to no avail, I’ve looked up several posts on the DevForum but never found a solution.

I am wondering if any more high experienced programmers than me could look into this.
Thank you!

Screenshot on my laptop (perfectly fine)

Screenshot on my iPad (not good)

	local CentreCFrame, Size = nil

	if ZoomObject:IsA("Model") then
		CentreCFrame, Size = ZoomObject:GetBoundingBox()
	else
		CentreCFrame = ZoomObject.CFrame
		Size = ZoomObject.Size
	end

	local AspectRatio = Camera.ViewportSize.X / Camera.ViewportSize.Y

	local VerticalFOV = math.rad(DefaultFOV)
	local HorizontalFOV = 2 * math.atan(math.tan(VerticalFOV / 2) * AspectRatio)

	local DistanceV = (Size.Y / 2) / math.tan(VerticalFOV / 2)
	local DistanceH = (Size.X / 2) / math.tan(HorizontalFOV / 2)

	local Distance = math.max(DistanceV, DistanceH, Size.Z / 2)

	local Padding = 3
	Distance = Distance + Padding

	local CameraPosition = CentreCFrame.Position + CentreCFrame.LookVector * Distance

	RequestCam:Fire("false")
	Camera.CameraType = Enum.CameraType.Scriptable
	
	Tween(Camera, 0.8, {CFrame = CFrame.new(CameraPosition, CentreCFrame.Position)})

Please note: I did use some AI just to help at the calculations part (I am not that efficent at maths)

2 Likes

try adjust the ViewportFrame’s size or try putting the UIAspectRatioConstraint in it

1 Like

It is not a viewport frame, the script tweens the players camera to show the object in place.

I know Crusherfire has a module specifically for this exact thing, the example in this video has some springs attached to it to give it a cool effect, but you could just remove that if you want to.
(Modules are in an uncopylocked place in the description)

1 Like

thank you, this works perfectly for non angled objects except for the Y axis, the zooming is incorrect and I have no idea what is causing this:

What it should look like (no rotation on the y axis):

What it looks like with rotation on the y axis:


We are so close.

local CameraUtilities = {}

function CameraUtilities:GetAspectRatio(camera)
	local Camera = camera or game.Workspace.CurrentCamera
	return Camera.ViewportSize.X / Camera.ViewportSize.Y
end

function CameraUtilities:GetViewportCenter(camera)
	local Camera = camera or game.Workspace.CurrentCamera
	return Vector2.new(Camera.ViewportSize.X / 2, Camera.ViewportSize.Y / 2)
end

function CameraUtilities:FitCameraAlignedBoundingBoxToCamera(size, cameraFovDeg, aspectRatio): number
	
	local vFov = math.rad(cameraFovDeg)

	local hFov = 2 * math.atan(aspectRatio * math.tan(vFov / 2))

	local boxWidth  = size.X
	local boxHeight = size.Y

	local boxAspect = boxWidth / boxHeight
	local screenAspect = aspectRatio

	if boxAspect > screenAspect then
		return (boxWidth / 2) / math.tan(hFov / 2)
	else
		return (boxHeight / 2) / math.tan(vFov / 2)
	end
	
end

function CameraUtilities:GetCameraAlignedBoundingBox(instance, camera): (CFrame, Vector3)
	
	local camera = camera or workspace.CurrentCamera
	if not camera then
		return CFrame.identity, Vector3.zero
	end

	local baseCFrame, size do
		if instance:IsA("BasePart") then
			baseCFrame, size = instance.CFrame, instance.Size
		elseif instance:IsA("Model") then
			baseCFrame, size = instance:GetBoundingBox()
		else
			return CFrame.identity, Vector3.zero
		end
	end

	local halfSize = size * 0.5
	local corners = {
		Vector3.new(-halfSize.X, -halfSize.Y, -halfSize.Z),
		Vector3.new(-halfSize.X, -halfSize.Y,  halfSize.Z),
		Vector3.new(-halfSize.X,  halfSize.Y, -halfSize.Z),
		Vector3.new(-halfSize.X,  halfSize.Y,  halfSize.Z),
		Vector3.new( halfSize.X, -halfSize.Y, -halfSize.Z),
		Vector3.new( halfSize.X, -halfSize.Y,  halfSize.Z),
		Vector3.new( halfSize.X,  halfSize.Y, -halfSize.Z),
		Vector3.new( halfSize.X,  halfSize.Y,  halfSize.Z),
	}

	local camCF = camera.CFrame
	local minX, minY, minZ = math.huge, math.huge, math.huge
	local maxX, maxY, maxZ = -math.huge, -math.huge, -math.huge

	for _, cornerLocal in ipairs(corners) do
		local cornerWorld = baseCFrame:PointToWorldSpace(cornerLocal)
		local cornerCam   = camCF:PointToObjectSpace(cornerWorld)

		if cornerCam.X < minX then minX = cornerCam.X end
		if cornerCam.Y < minY then minY = cornerCam.Y end
		if cornerCam.Z < minZ then minZ = cornerCam.Z end

		if cornerCam.X > maxX then maxX = cornerCam.X end
		if cornerCam.Y > maxY then maxY = cornerCam.Y end
		if cornerCam.Z > maxZ then maxZ = cornerCam.Z end
	end

	local minCam = Vector3.new(minX, minY, minZ)
	local maxCam = Vector3.new(maxX, maxY, maxZ)
	local centerCam = (minCam + maxCam) * 0.5
	local extentCam = maxCam - minCam

	local centerWorld = camCF:PointToWorldSpace(centerCam)
	local rotationOnly = camCF - camCF.Position

	local boundingBoxCF = rotationOnly + centerWorld

	return boundingBoxCF, extentCam
	
end

return CameraUtilities

Note: the game is a 2.5d game where the camera is on the x axis (I dont know If it makes a difference)

Could you give a screenshot in studio of these two images please, I can’t quite tell what the difference is from the in-game screenshots.

Nevermind, if the zooming is activated from a different angle it dosent fit the screen properly.

Expected outcome:

Unexpected outcome (the camera did not adjust)

here is a better example of the issue that is occurring.

If you swap the X and Z coordinates you pass to the function, does that reverse which ones the problem occurs on?

yes it does, that may be the problem.

Okay, what your problem probably is it that somewhere, one of your functions is using a CFrame positioned in object space, so it’s position and rotation are relative to some other object, and then elsewhere in your code you’re using that CFrame as if it where in world space, so it’s position is relative to (0, 0, 0), or vice versa. I’d can’t say for certain without seeing your full code where exactly this is happening, but I’d reckon it’s either right as you call the method to get the camera position, or right after it, so you might want to mess around with CFrame:ToObjectSpace() and CFrame:ToWorldSpace() to see if that fixes it.

(Sorry for the late replies by the way, every time you replied I had stepped away from my computer and totally missed your notification)

1 Like

Oh ok, no worries about the late replies, here are some code snippets:

Camera Utility Module (from Crusherfire)

local CameraUtilities = {}

function CameraUtilities:GetAspectRatio(camera)
	local Camera = camera or game.Workspace.CurrentCamera
	return Camera.ViewportSize.X / Camera.ViewportSize.Y
end

function CameraUtilities:GetViewportCenter(camera)
	local Camera = camera or game.Workspace.CurrentCamera
	return Vector2.new(Camera.ViewportSize.X / 2, Camera.ViewportSize.Y / 2)
end

function CameraUtilities:FitCameraAlignedBoundingBoxToCamera(size, cameraFovDeg, aspectRatio): number
	
	local vFov = math.rad(cameraFovDeg)

	local hFov = 2 * math.atan(aspectRatio * math.tan(vFov / 2))
	
	local boxWidth  = size.X
	local boxHeight = size.Y
	
	if size.X > size.Z then
		boxWidth = size.X
		print("size x")
	else
		boxWidth = size.Z
		print("size z")
	end

	local boxAspect = boxWidth / boxHeight
	local screenAspect = aspectRatio

	if boxAspect > screenAspect then
		return (boxWidth / 2) / math.tan(hFov / 2)
	else
		return (boxHeight / 2) / math.tan(vFov / 2)
	end
	
end

function CameraUtilities:GetCameraAlignedBoundingBox(instance, camera): (CFrame, Vector3)
	
	local camera = camera or workspace.CurrentCamera
	if not camera then
		return CFrame.identity, Vector3.zero
	end

	local baseCFrame, size do
		if instance:IsA("BasePart") then
			baseCFrame, size = instance.CFrame, instance.Size
		elseif instance:IsA("Model") then
			baseCFrame, size = instance:GetBoundingBox()
		else
			return CFrame.identity, Vector3.zero
		end
	end

	local halfSize = size * 0.5
	local corners = {
		Vector3.new(-halfSize.X, -halfSize.Y, -halfSize.Z),
		Vector3.new(-halfSize.X, -halfSize.Y,  halfSize.Z),
		Vector3.new(-halfSize.X,  halfSize.Y, -halfSize.Z),
		Vector3.new(-halfSize.X,  halfSize.Y,  halfSize.Z),
		Vector3.new( halfSize.X, -halfSize.Y, -halfSize.Z),
		Vector3.new( halfSize.X, -halfSize.Y,  halfSize.Z),
		Vector3.new( halfSize.X,  halfSize.Y, -halfSize.Z),
		Vector3.new( halfSize.X,  halfSize.Y,  halfSize.Z),
	}

	local camCF = camera.CFrame
	local minX, minY, minZ = math.huge, math.huge, math.huge
	local maxX, maxY, maxZ = -math.huge, -math.huge, -math.huge

	for _, cornerLocal in ipairs(corners) do
		local cornerWorld = baseCFrame:PointToWorldSpace(cornerLocal)
		local cornerCam   = camCF:PointToObjectSpace(cornerWorld)

		if cornerCam.X < minX then minX = cornerCam.X end
		if cornerCam.Y < minY then minY = cornerCam.Y end
		if cornerCam.Z < minZ then minZ = cornerCam.Z end

		if cornerCam.X > maxX then maxX = cornerCam.X end
		if cornerCam.Y > maxY then maxY = cornerCam.Y end
		if cornerCam.Z > maxZ then maxZ = cornerCam.Z end
	end

	local minCam = Vector3.new(minX, minY, minZ)
	local maxCam = Vector3.new(maxX, maxY, maxZ)
	local centerCam = (minCam + maxCam) * 0.5
	local extentCam = maxCam - minCam

	local centerWorld = camCF:PointToWorldSpace(centerCam)
	local rotationOnly = camCF - camCF.Position

	local boundingBoxCF = rotationOnly + centerWorld

	return boundingBoxCF, extentCam
	
end

return CameraUtilities

Note: In the above module I added something to change which one is the width (x or z) depending on the longest part which fixes the issue but i’ll let you decide.

	if size.X > size.Z then
		boxWidth = size.X
		print("size x")
	else
		boxWidth = size.Z
		print("size z")
	end

Client script that handles zooming (snippet):

	RequestCam:Fire("false") -- disables 2d x axis camera system
	Camera.CameraType = Enum.CameraType.Scriptable

	
	local ObjectCFrame, Size = CameraUtilitiesModule:GetCameraAlignedBoundingBox(ZoomObject, Camera)
	local FieldOfView = 70

	local AspectRatio = CameraUtilitiesModule:GetAspectRatio()
	local PaddingInStuds = 2
	local Distance = CameraUtilitiesModule:FitCameraAlignedBoundingBoxToCamera(Size, FieldOfView, AspectRatio) + PaddingInStuds

	local GoalPosition = ZoomObject.Position + (ZoomObject.CFrame.LookVector * Distance)
	local GoalCFrame = CFrame.lookAt(GoalPosition, ZoomObject.Position, Vector3.yAxis)

	Tween(Camera, 0.8, {CFrame = GoalCFrame}) -- tween function

Sorry for the late reply again, got really busy. Everything that I see there looks to be right, the only thing I could possibly think of that could be causing the issue is if ZoomObject’s CFrame wasn’t in world space, what does the hierarchy look like on the instances you’re passing to GetCameraAlignedBoundingBox? Otherwise, there’s also the “if it ain’t broke don’t fix it” mindset too, so if simply flipping the X and Z are working for you you might want to just not touch it.

thank you a lot for the help, if theres no reason to fix it then I should leave it really.
this is the part that is being passed into the function with the explorer view.

(they are all located in workspace and organised using folders.)

I finally got some time to mess around with it myself, and I’m starting to think this might be an actual bug in the module itself, as I was able to get similarly weird results from the demo place itself just by rotating the provided part.

The weird thing with this, aside from the obvious camera flipping, is that seems to overshoot the desired position every single time, going a little farther away than it normally would, and then compensating for it later, which the original demo with it not rotated vertically doesn’t.

I made a slight change to the code to make it work off a ClickDetector and keybind to make it easier to test, and I tried to leave as much unchanged as possible, but here’s the full code anyways

Code
local ReplicatedStorage = game:GetService("ReplicatedStorage")
local RunService = game:GetService("RunService")
local UserInputService = game:GetService("UserInputService")

local FunctionUtils = require(ReplicatedStorage.Utilities.FunctionUtils)
local ModuleUtils = require(ReplicatedStorage.Utilities.ModuleUtils)

local rotSpring = ModuleUtils.Spring.new(Vector3.zero, 0.9, 12)
local posSpring = ModuleUtils.Spring.new(Vector3.zero, 0.9, 12)

local part = script.Parent.Part
local zonePart = script.Parent.ZonePart
local zone = ModuleUtils.ZoneModule.fromParts({ zonePart })
zone:BindToHeartbeat()

local trove = ModuleUtils.Trove.new()
part.ClickDetector.MouseClick:Connect(function()
	workspace.CurrentCamera.CameraType = Enum.CameraType.Scriptable
	local camCFrame = workspace.CurrentCamera.CFrame
	posSpring.Position = camCFrame.Position
	rotSpring.Position = Vector3.new(camCFrame:ToOrientation())
	trove:Connect(RunService.PreRender, function()
		local cframe, size = FunctionUtils.Math.getCameraAlignedBoundingBox(part)
		local fov = workspace.CurrentCamera.FieldOfView
		local ratio = FunctionUtils.Camera.getAspectRatio()
		local paddingInStuds = fov * 0.02
		local dist = FunctionUtils.Camera.fitCameraAlignedBoundingBoxToCamera(size, fov, ratio) + paddingInStuds
		local viewportCenter = FunctionUtils.Camera.getViewportCenter()

		local goalPosition = part.Position + (part.CFrame.LookVector * dist)
		local offset = (UserInputService:GetMouseLocation() - viewportCenter) / 600
		local baseCFrame = CFrame.lookAt(goalPosition, part.Position, Vector3.yAxis)
		local rotationDelta = CFrame.Angles(-offset.Y, -offset.X * 0.75, 0):Lerp(CFrame.identity, 0.95)
		local goalCFrame = baseCFrame * rotationDelta

		posSpring.Target = goalCFrame.Position
		rotSpring.Target = Vector3.new(goalCFrame:ToOrientation())

		local rot = rotSpring.Position
		workspace.CurrentCamera.CFrame = CFrame.new(posSpring.Position) * CFrame.fromOrientation(rot.X, rot.Y, rot.Z)
	end)
end)

game:GetService("UserInputService").InputBegan:Connect(function(input: InputObject)
	if input.KeyCode ~= Enum.KeyCode.X then
		return
	end
	
	workspace.CurrentCamera.CameraType = Enum.CameraType.Custom
	trove:Clean()
end)

Here’s another test I did where I made it so that spring’s target wasn’t updated every frame, only when it was initially clicked, and that same bug we were seeing earlier was happening where it was going to the wrong place, but this time using the code straight out of the demo place. (The times where it changed position while zoomed into it were simply me clicking on the part again)

Code
local ReplicatedStorage = game:GetService("ReplicatedStorage")
local RunService = game:GetService("RunService")
local UserInputService = game:GetService("UserInputService")

local FunctionUtils = require(ReplicatedStorage.Utilities.FunctionUtils)
local ModuleUtils = require(ReplicatedStorage.Utilities.ModuleUtils)

local rotSpring = ModuleUtils.Spring.new(Vector3.zero, 0.9, 12)
local posSpring = ModuleUtils.Spring.new(Vector3.zero, 0.9, 12)

local part = script.Parent.Part
local zonePart = script.Parent.ZonePart
local zone = ModuleUtils.ZoneModule.fromParts({ zonePart })
zone:BindToHeartbeat()

local trove = ModuleUtils.Trove.new()
part.ClickDetector.MouseClick:Connect(function()
	workspace.CurrentCamera.CameraType = Enum.CameraType.Scriptable
	local camCFrame = workspace.CurrentCamera.CFrame
	posSpring.Position = camCFrame.Position
	rotSpring.Position = Vector3.new(camCFrame:ToOrientation())
	
	--Intially in trove:Connect(RunService.PreRender)
		local cframe, size = FunctionUtils.Math.getCameraAlignedBoundingBox(part)
		local fov = workspace.CurrentCamera.FieldOfView
		local ratio = FunctionUtils.Camera.getAspectRatio()
		local paddingInStuds = fov * 0.02
		local dist = FunctionUtils.Camera.fitCameraAlignedBoundingBoxToCamera(size, fov, ratio) + paddingInStuds
		local viewportCenter = FunctionUtils.Camera.getViewportCenter()

		local goalPosition = part.Position + (part.CFrame.LookVector * dist)
		local offset = (UserInputService:GetMouseLocation() - viewportCenter) / 600
		local baseCFrame = CFrame.lookAt(goalPosition, part.Position, Vector3.yAxis)
		local rotationDelta = CFrame.Angles(-offset.Y, -offset.X * 0.75, 0):Lerp(CFrame.identity, 0.95)
		local goalCFrame = baseCFrame * rotationDelta

		posSpring.Target = goalCFrame.Position
		rotSpring.Target = Vector3.new(goalCFrame:ToOrientation())
	--End of code initially in trove:Connect(RunService.PreRender)
	
	trove:Connect(RunService.PreRender, function()
		local rot = rotSpring.Position
		workspace.CurrentCamera.CFrame = CFrame.new(posSpring.Position) * CFrame.fromOrientation(rot.X, rot.Y, rot.Z)
	end)
end)

game:GetService("UserInputService").InputBegan:Connect(function(input: InputObject)
	if input.KeyCode ~= Enum.KeyCode.X then
		return
	end
	
	workspace.CurrentCamera.CameraType = Enum.CameraType.Custom
	trove:Clean()
end)

To make a long story short, I don’t think this is a problem on your end, I’m starting to think this might be a problem with the module itself not supporting all types of rotation properly.

@crusherfire Sorry to bother you, but are we simply doing something wrong here and missing a key detail, or is this a bug in the module?

1 Like

oh that is very unusual and weird, I am also thinking that it is a bug in the module too.

The math is correct and the module is fine! The problem is that your version only calculates the correct distance for the camera’s position and rotation at that moment. After that, the camera moves, so that distance becomes wrong. The part’s camera-aligned bounding box changes when the camera moves.

The original code calculates the distances every frame, so it constantly self-corrects and keeps the part on screen.

Mouse movement also fails because you take a snapshot of it once and never check again in PreRender.

As for the 360-degree flips, that’s because ToOrientation returns yaw within a clamped range and loops around when you’re at the edge of the range. But since springs work linearly, it sees it has a huge jump from negative to positive (and vice versa), so it chases a 360-degree flip. This was an oversight on my part!

To fix it, you can create a function that calculates the shortest path to the angle you want (based on the current angle) and use that value for the spring:

local function alignAngle(current: number, goal: number): number
	local tau = math.pi * 2
	local diff = (goal - current + math.pi) % tau - math.pi
	return current + diff
end

local function alignAngles(current: Vector3, goal: Vector3): Vector3
	return Vector3.new(
		alignAngle(current.X, goal.X),
		alignAngle(current.Y, goal.Y),
		alignAngle(current.Z, goal.Z)
	)
end

So the finalized code for the testing place should look like this:

local ReplicatedStorage = game:GetService("ReplicatedStorage")
local RunService = game:GetService("RunService")
local UserInputService = game:GetService("UserInputService")

local FunctionUtils = require(ReplicatedStorage.Utilities.FunctionUtils)
local ModuleUtils = require(ReplicatedStorage.Utilities.ModuleUtils)

local rotSpring = ModuleUtils.Spring.new(Vector3.zero, 0.9, 12)
local posSpring = ModuleUtils.Spring.new(Vector3.zero, 0.9, 12)

local part = script.Parent.Part
local zonePart = script.Parent.ZonePart
local zone = ModuleUtils.ZoneModule.fromParts({ zonePart })
zone:BindToHeartbeat()

local function alignAngle(current: number, goal: number): number
	local tau = math.pi * 2
	local diff = (goal - current + math.pi) % tau - math.pi
	return current + diff
end

local function alignAngles(current: Vector3, goal: Vector3): Vector3
	return Vector3.new(
		alignAngle(current.X, goal.X),
		alignAngle(current.Y, goal.Y),
		alignAngle(current.Z, goal.Z)
	)
end

local trove = ModuleUtils.Trove.new()
zone:ListenTo("LocalPlayer", "Entered", function()
	workspace.CurrentCamera.CameraType = Enum.CameraType.Scriptable
	local camCFrame = workspace.CurrentCamera.CFrame
	posSpring.Position = camCFrame.Position
	rotSpring.Position = Vector3.new(camCFrame:ToOrientation())
	
	trove:Connect(RunService.PreRender, function()
		-- keep these calculations in PreRender!
		local cframe, size = FunctionUtils.Math.getCameraAlignedBoundingBox(part)
		local fov = workspace.CurrentCamera.FieldOfView
		local ratio = FunctionUtils.Camera.getAspectRatio()
		local paddingInStuds = fov * 0.02
		local dist = FunctionUtils.Camera.fitCameraAlignedBoundingBoxToCamera(size, fov, ratio) + paddingInStuds
		local viewportCenter = FunctionUtils.Camera.getViewportCenter()

		local goalPosition = part.Position + (part.CFrame.LookVector * dist)
		local offset = (UserInputService:GetMouseLocation() - viewportCenter) / 600
		local baseCFrame = CFrame.lookAt(goalPosition, part.Position, Vector3.yAxis)
		local rotationDelta = CFrame.Angles(-offset.Y, -offset.X * 0.75, 0):Lerp(CFrame.identity, 0.95)
		local goalCFrame = baseCFrame * rotationDelta

		posSpring.Target = goalCFrame.Position
		local rawGoalAngles = Vector3.new(goalCFrame:ToOrientation())
		local alignedGoalAngles = alignAngles(rotSpring.Position, rawGoalAngles)
		rotSpring.Target = alignedGoalAngles
		
		local rot = rotSpring.Position
		workspace.CurrentCamera.CFrame = CFrame.new(posSpring.Position) * CFrame.fromOrientation(rot.X, rot.Y, rot.Z)
	end)
end)

zone:ListenTo("LocalPlayer", "Exited", function()
	workspace.CurrentCamera.CameraType = Enum.CameraType.Custom
	trove:Clean()
end)
2 Likes

Thank you so much for your help, I assumed the function already accounted for the shifting bounding box as the camera moved, I didn’t realise that this wasn’t accounted for and needed to be updated continuously. I love your educational videos by the way, I think you do an excellent job explaining concepts without the boilerplate that I’ve seen from a lot of others. Thanks for your help!


In terms of fixing the problem then, you’d likely have to replace your current function that zooms into a part with another that saves it to variable, and then each frame simply check to see if you have something saved to that variable, and if so, so execute the code that you had before to calculate the position, and then manually interpolate between your current camera position and the goal position, something like below.

local Workspace = game:GetService("Workspace")
local RunService = game:GetService("RunService")

local SPEED = 3

local zoomedPart: Instance = nil
local interpolationPercent = 0

local function zoomInOnPart(part: Instance): nil
    zoomedPart = part
    interpolationPercent = 0
    Workspace.CurrentCamera.CameraType = Enum.CameraType.Scriptable
end

function zoomOut()
    zoomedPart = nil
    -- You could also tween the camera back to where it was instead of just teleporting it
    -- The Roblox Learn channel has a really good video about this and other Camera manipulation too
    -- https://youtu.be/Iht0ddcLWFU?si=_uVElDg5EQ-ZDLfH&t=245
    Workspace.CurrentCamera.CameraType = Enum.CameraType.Custom
end

RunService.PreRender:Connect(function(deltaTime: number)
    if (not zoomedPart) or interpolationPercent > 1 then
		return
	end
	
	local goalCFrame = GOAL_CFRAME
	
	interpolationPercent = math.min(interpolationPercent + deltaTime / SPEED, 1)
	local interpolatedCFrame = Workspace.CurrentCamera.CFrame:Lerp(goalCFrame, interpolationPercent)
	
	Workspace.CurrentCamera.CFrame = interpolatedCFrame
end)
1 Like

Thank you everyone for the help, I truely mean it, the problem has been resolved now

1 Like