How to make camera rotate around model in ViewPort Frame while mantaining set distance and angles

local initAngle = CFrame.new(Vector3.new(), Vector3.new(-0.5, -0.4, -0.5))

function getCameraOffset(fov, targetSize)
	local x, y, z = targetSize.x, targetSize.y, targetSize.Z
	local maxSize = math.sqrt(x^2 + y^2 + z^2)
	local fac = math.tan(math.rad(fov)/2)
	
	local depth = 0.5*maxSize/fac
	
	return depth+maxSize/2
end

model3D:SetPrimaryPartCFrame(CFrame.new(Vector3.new(-0.5, -0.4, -0.5).unit*(getCameraOffset(camera.FieldOfView, model3D:GetExtentsSize())*0.85)))

runService:BindToRenderStep('CameraRotation', Enum.RenderPriority.Camera.Value + 1, function()
	rotation = (rotation + 1 < 360 and rotation + 1 or 0) 
	camera3D.CFrame = model3D.PrimaryPart.CFrame*CFrame.Angles(0, math.rad(rotation), 0)
end)

So let me explain. initAngle is the angle the Camera.CFrame is set to as the default. Model3D is the model being put in the ViewPortFrame. What can’t change is that initAngle and the Model3D PrimaryPart position, or the getCameraOffset function. The line that should be getting changed is this

camera3D.CFrame = model3D.PrimaryPart.CFrame*CFrame.Angles(0, math.rad(rotation), 0)

But I’m not sure how to include the initAngle to this, so the part is actually a distance away.
com-video-to-gif%20(1)
So for the the 3 frames you see, the camera is set to the initAngle. When I hover over them it brings up the frame which has the viewport in it and the rotating image, however the model appears too big. I tried going

*CFrame.new(0, 0, 10)

at the end, but that just makes the smaller models too small and larger models still too big, so the point of setting the models PrimaryPart was to use GetExtentsSize so that way all the models look the same size

10 Likes

You could just rotate the model if you don’t want to handle the few steps involved, but you’d only need to offset the camera you currently have by a factor of x studs backwards (the negative lookVector of the camera CFrame). Something like:

local start = model3D.PrimaryPart.CFrame
local rotation = CFrame.Angles(0, math.rad(rotation), 0)
local distance = 10 -- studs (you can change to something dynamic)
local cf = start * rotation
cf = cf - (cf.lookVector * distance)
camera3D.CFrame = cf
6 Likes

Moving the camera is more efficient then rotating the part, it was mentioned somewhere.

Here https://developer.roblox.com/articles/Camera-manipulation take a look this will help you, they have a sample script that makes the camera circle arounds a part.

2 Likes

Ye thats what I’m currently already doing. The camera already rotates around the part, however it’s too close to the model. However, I can’t just move the camera back a set amount as some models are larger than others. Moving the camera back say 10 studs would make smaller models appear smaller, while larger models might still be too big for the camera

Have you tried Model.ExtendedSize?

Something along those lines

There is already :GetExtentsSize(). Apparently the multiplyer there needs to be increased slightly. Tweak around the numbers; trial and error.

Then don’t use a static distance, but instead make it dynamic. Base it on the size of the model instead. If your FOV is static then the relationship is linear between model size and distance needed to appear the same. As others have mentioned you can use GetExtentsSize and just get the diagonal length or something like that multiplied by a constant factor of your liking.

EDIT: This reply is an old response. Checkout this post for a module that does a better calculation
ViewportFrame Model Fitter


Okay, so I think I have a decent enough solution going here.

So things you have to take into account:

  1. You want to be rotating around the center of the model, not necessarily the primary part. As such you should use the:GetBoundingBox() method.

  2. As you have noted, your best bet to fit something to the camera is to use the field of view and find the distance based on that.

I did this by finding the max extent value and then treating that as the vertical limit. Then I offset the camera by the half diagonal length created by the other two extent components.

local modelCF, modelSize = model:GetBoundingBox()	

local rotInv = (modelCF - modelCF.p):inverse()
modelCF = modelCF * rotInv
modelSize = rotInv * modelSize
modelSize = Vector3.new(math.abs(modelSize.x), math.abs(modelSize.y), math.abs(modelSize.z))

local diagonal = 0
local maxExtent = math.max(modelSize.x, modelSize.y, modelSize.z)
local tan = math.tan(math.rad(camera.FieldOfView/2))

if (maxExtent == modelSize.x) then
	diagonal = math.sqrt(modelSize.y*modelSize.y + modelSize.z*modelSize.z)/2
elseif (maxExtent == modelSize.y) then
	diagonal = math.sqrt(modelSize.x*modelSize.x + modelSize.z*modelSize.z)/2
else
	diagonal = math.sqrt(modelSize.x*modelSize.x + modelSize.y*modelSize.y)/2
end

local minDist = (maxExtent/2)/tan + diagonal

ViewFrameFit.rbxl (497.3 KB)

Note: One caveat is that because of how we do the distance FOV calculation we are only accounting for the vertical. This means if you have a frame with an aspect ration that favours the vertical then the whole model may not be shown.

Hope that helps!

90 Likes

Is is possible to rotate an object in the ViewPortFrame instead of rotating a camera around it?

You can - you simply reverse the intended operation by rotating the focus of a ViewportFrame. This should be something you ask in a separate thread since it’s unrelated to rotating a camera around a model in a VPF and worth a separate discussion on it’s own.

I hope you do understand the performance implications between rotating a model and rotating a camera, though.

No, I don’t . So, which method requires less work for GPU/CPU? Thanks.

1 Like

Rotating the camera around the object is less expensive than rotating the entire model. There’s also significantly less computation involved overall.

4 Likes

how do i make it so when u click it doesn’t change to the next model?
how do i make it so there can be multiple frames? Because in the script it only uses the models from from the folder

Delete this paragraph here:

 		vpf.InputBegan:Connect(function(input)
				if (input.UserInputType == Enum.UserInputType.MouseButton1) then
					setIndexActive(index + 1 > #children and 1 or index + 1)
				end
			end)

and it should work how you wish :slight_smile:

2 Likes

I have found a solution here

As you are a UI designer any suggestions on how i can improve on making sore the positioning and sizing is correct on most screens. Thanks :slight_smile:

I already made a post for this

1 Like

Hello!

I used your viewport frame fit system in a pet system I was making I used MouseEnter() to start the rotation, and MouseLeave() to stop the rotation (:Disconnect()).

However, I found that if I move my mouse fast enough from one frame to another, it does not rotate the camera. (Screen capture - 1129127258d847571963ae34c69b4f58 - Gyazo)

Do you know how I can fix this? Thank you!

1 Like

Its likely an issue w/ the MouseEnter and MouseLeave events. I’ve had issues with them not firing properly in the past.

2 Likes

Do you know anyway around this? I added a print to check if MouseEnter() and MouseLeave() where actually getting fired, and they were.

I figured out that the issue seemed to come from the setRotationEvent(). Here is the code that shows which print()'s are being called and which are not.

local event = nil

function itemPreview:setRotationEvent(model, camera)
	local currentAngle = restingAngle
	local modelCF, modelSize = model:GetBoundingBox()	

	local rotInv = (modelCF - modelCF.p):inverse()
	modelCF = modelCF * rotInv
	modelSize = rotInv * modelSize
	modelSize = Vector3.new(math.abs(modelSize.x), math.abs(modelSize.y), math.abs(modelSize.z))

	local diagonal = 0
	local maxExtent = math.max(modelSize.x, modelSize.y, modelSize.z)
	local tan = math.tan(math.rad(camera.FieldOfView/.7))

	if (maxExtent == modelSize.x) then
		diagonal = math.sqrt(modelSize.y*modelSize.y + modelSize.z*modelSize.z)/2
	elseif (maxExtent == modelSize.y) then
		diagonal = math.sqrt(modelSize.x*modelSize.x + modelSize.z*modelSize.z)/2
	else
		diagonal = math.sqrt(modelSize.x*modelSize.x + modelSize.y*modelSize.y)/2
	end

	local minDist = (maxExtent/2)/tan + diagonal
	
	print("Check1") -- Always prints "Check1" even when I move my mouse quickly from one frame to another.

	return game:GetService("RunService").RenderStepped:Connect(function(dt)
		print("Check2") -- Does not print "Check2" when I move my mouse quickly from one frame to another.
		currentAngle = currentAngle + 1*dt*100
		camera.CFrame = modelCF * CFrame.fromEulerAnglesYXZ(-.4, math.rad(currentAngle), 0) * CFrame.new(0, 0, minDist + 3)
	end)
end

function itemPreview:rotate(model, camera)
	-- I call "rotate" when MouseEnter is fired.
	if (event) then
		event:Disconnect()
	end

	event = itemPreview:setRotationEvent(model, camera)
    print("Check3") -- Always prints "Check3" even when I move my mouse quickly from one frame to another.
end

function itemPreview:stop(model, camera)
	-- I call "stop" when MouseLeave is fired.
	if (event) then
		event:Disconnect()
		camera.CFrame = itemPreview:getRestingPosition(camera, model)
	end
end

It almost seems like the time my mouse moves from one UI frame to the other, a full RenderStepped frame hasn’t happened yet, but I’m not 100% sure.

1 Like