Need help with viewport math

Hello!

I am looking to dynamically change this viewport’s FOV but the problem is that I am not sure how I would go about getting the math for it?

My viewport’s current FOV is 45º and it works fine for large accessories, but for small accessories, it’s basically invisible. Any ideas?
Tiny accessory:
Screen Shot 2021-07-25 at 4.53.24 PM
Large accessory:
Screen Shot 2021-07-25 at 4.53.37 PM

Also should add that in my script, I do get the bounding box of the accessory to render it properly in the viewport, however I am unsure as to how I would approach decreasing/increasing the field of view of the camera accurately based on the size of the accessory.

Script:

local newObject = Instance.new('Model')
	local boundingBox = Instance.new('Part')
	for i,v in pairs(selectedObject.Value:GetDescendants()) do
		if v:IsA('BasePart') then
			local clonedObject = v:Clone()
			clonedObject.Parent = newObject
			clonedObject.Anchored = false
		end
	end
	local cframe, size = newObject:GetBoundingBox()
	boundingBox.CFrame = cframe
	boundingBox.Size = size
	boundingBox.Parent = newObject
	boundingBox.Transparency = 1
	boundingBox.Anchored = false
	boundingBox.CanCollide = false
	newObject.PrimaryPart = boundingBox
	for i,v in pairs(newObject:GetChildren()) do
		if v:IsA('BasePart') then
			local weldConstraint = Instance.new('WeldConstraint')
			weldConstraint.Parent = v
			weldConstraint.Part0 = boundingBox
			weldConstraint.Part1 = v
		end
	end
	newObject:SetPrimaryPartCFrame(weldPart.CFrame)
	local weldConstraint = Instance.new('WeldConstraint')
	weldConstraint.Parent = newObject
	weldConstraint.Part0 = weldPart
	weldConstraint.Part1 = boundingBox
	boundingBox.CFrame = weldPart.CFrame
	newObject.Parent = worldModel
	oldObject = newObject

Edit: I was thinking I would be able to use the size of the Y axis of the model’s bounding box so I looked up how to find the angle between two vectors but there’s an issue: I have no idea how to use the formula, nor do I know how to translate it into 3D space as I’m not a mathematician. Any ideas?

Edit 2: I forgot to include the camera’s position, its position is 0,5,10 and the orientation in degrees is -26.569999694824, 0, 0

Edit 3: I’ve just tried :WorldToScreenPoint and :WorldToViewportPoint and they ended up being inaccurate, no idea how to proceed. Any help would be really appreciated.

1 Like

Could you post the code related to the FOV of the viewport? Or is that something that’s set in studio? I’m not particularly familiar with viewports, but couldn’t you position the camera relative to the position of the bounding box? That way it would always be x,y,z orientation/studs away from the subject.

In terms of the math you brought up there’s a function on Roblox which simplifies the math for you. It’s called the dot product (A:Dot(B)). You can calculate the angle between two vectors (Vec1 and Vec2) by dividing the dot product by the vector’s respective multiplied magnitudes. (Since the dot product returns Cos(θ) * Vec1.Magnitude * Vect2.Magnitude)

math.acos(Vec1:Dot(Vec2) / (Vec1.Magnitude * Vec2.Magnitude))

Try using the bounding box to get the correct position of the camera. I would do smthg like this (Note that this is untested so you might have to make a few adjustments / bug fixes):

function  getcampos(newObject)
	local cframe, size = newObject:GetBoundingBox()
	local pos = cframe.Position
	local dist
	local dist2
	if size.X >= size.Y and size.Y >= size.Z then
		dist = size.X*1.5
		dist2 = size.X
	elseif size.Z >= size.Y and size.Y >= size.X then
		dist = size.Z*1.5
		dist2 = size.Z
	elseif size.Y >= size.X and size.X >= size.Z then
		dist = size.Y*1.5
		dist2 = size.Y
	end
	return CFrame.new(Vector3.new(pos.X, pos.Y+math.sqrt(dist^2+dist2^2), pos.Z+dist), pos)
end

Sorry for the late response, I had to go out as soon as you posted this.

I’m not sure, pretty sure it’s just set on 45º regardless of display size, it’s just what I have it set on for the viewport, I haven’t made any adjustments to the camera aside from changing its CFrame.
This is the part that does that:

local camera = Instance.new('Camera')
objectViewport.CurrentCamera = camera
camera.Parent = objectViewport
camera.CFrame = CFrame.lookAt(Vector3.new(0,5,10), Vector3.new())
camera.FieldOfView = 45

local initFOV = camera.FieldOfView

The problem with the second part is that I’m not really sure how I would get Vec1 or Vec2, my guess is that I would use the bottom and top parts of the bounding box though.

I tried that out and it seems to not work for whatever reason, I may be doing something wrong though, as printing fov outputs nan. I tried looking up the formula for it and it said it was because one of the vectors had an axis that is 0? Really not sure haha.

Printing lowerVec, upperVec outputs this though which I find strange because it should print 0, (something), 0
lowerVec:
-2.2737367544323e-13, 0, -8.5265128291212e-14
upperVec
-2.2737367544323e-13, 0, -8.5265128291212e-14

Anyway, this is my script thus far with after your explanation:

    local lowerVec = boundingBox.Position - Vector3.new(0, boundingBox.Size / 2, 0)
	local upperVec = boundingBox.Position + Vector3.new(0, boundingBox.Size / 2, 0)
	
	print(lowerVec, upperVec)
	
	local fov = math.acos(lowerVec:Dot(upperVec) / (lowerVec.Magnitude * upperVec.Magnitude))
	
	camera.FieldOfView = fov
	print(fov) -- nan

Trying that rn.

Edit: it seems to error a lot because you’re only taking into account three possibilities: (x >= y and y >= z) or (z >= y and y >= x) or (y >= x and x >= z), shouldn’t there be 6 other possibilities if we take into account the axes?

Yeah you should try editing my code a bit (I forgot about those conditions)

1 Like

I’m pretty sure the reason why the math is faulty is because you’re using two points in 3D space, rather than 2 vectors with a starting and ending point. You could probably simulate vectors with a starting and ending point using raycasting. Having said that I’m not sure I understand how you’d implement it to achieve what you want - it probably wouldn’t be the simplest method either.

Couldn’t you just do something like this?

camera.CFrame = CFrame.lookAt(boundbox.Position*Vector3.new(0,5,10), boundbox.Position)

You cannot multiply vectors. (Char minimum lol)

Edit: I’m 99.9999999% sure I’m wrong lol

Yeah, I am not the best in math so I find it quite difficult to express what I am trying to do. Now that you mention it though, I think EgoMoose did something similar to what I want on Twitter awhile ago, I’ll have a look there in a sec.

Edit: found it, https://twitter.com/egomoose/status/1414626748784910338?s=21

And no, I don’t think I would be able to do that because the position of the BoundingBox is always about 0,0,0 which would make all axes 0 because math.

Also @Pure_Bacn, you can multiply vectors, each axis would be multiplied by its respective axis.

(ex. V3(1,1,1) * V3(2,3,4) = V3(2,3,4))
or
(V3(5,2,3) * 5 = V3(25,10,15))

It’s documented here under math operations:
https://developer.roblox.com/en-us/api-reference/datatype/Vector3

I also tried out your method and it seems to work somewhat decently but the issue is that sometimes things are still really close or really far.

Screen Shot 2021-07-25 at 8.58.25 PM

Screen Shot 2021-07-25 at 8.57.38 PM

Yeah, I’m pretty sure EgoMoose did a video tutorial and gitbhub tutorial on it a while ago. I think using the dot product to calculate the FOV could be interesting, I’m not sure that it would solve the problem though.

That’s my bad, it’s late and I forgot about vector math (even if you can multiply 2 vectors). What about if you tried something like this instead:

camera.CFrame = CFrame.lookAt(Vector3.new(boundbox.Position.x,(boundbox.Position.y + 5),(boundbox.Position.z + 10)), boundbox.Position)

edit: If that doesn’t work (which it might not if the object is too small) you might be able to add the Size.y * 2 and Size.z * 2 instead of the arbitrary 10 and 5 values. That would mean you’re adding less to the objects with smaller boundboxes and vice versa.

edit2: Issue with viewport frame width scale

1 Like

Sorry, it took me awhile to get to this.

Alrighty, definitely getting closer. The problem now is the big models, they’re wayyy too big. It would be cool if there was a way to make something small get exponentially bigger whereas something really big got exponentially smaller. Sort of like an inverse-relationship if that makes sense.

I also just randomly thought of scaling the mesh up/down since I’m using assets directly imported from the catalog, the hitbox size would be somewhat close to the size of the mesh visually, and since FileMeshes have a scale property, I could just scale it up/down as needed. It could also work with multiple parts as I made a “tutorial” in response to someone asking how to scale up/down a model proportionately. What do you think?

Here’s that post: Size model without distorting it? - #12 by 7z99

Also about the link, I had a look there when I was trying to figure out the math by myself, preceding this post but it didn’t really seem to make much sense as I have very little advanced mathematical knowledge haha.

Yeah, I followed my tutorial and that seems to have given the best results. I’ll mark this as solution in the morning if nobody else has responded.

Ty @PerilousPanther and @Pure_Bacn, you guys really helped me!

2 Likes

I’m surprised that’s the case since the Size property should make it so that the distance is now proportionate to the size of the object. I think that method could work if you tinkered with it enough (maybe if you created a multiplier which took into account size).

Having said that the method which EgoMoose uses is definitely the most accurate and transferable way of calculating the optimal distance. It essentially creates an imaginary circle around the object and calculates the minimum distance at which the object is fully visible using SOHCAHTOA. This is done by creating a triangle between the outer bounds of the circle (as defined by the FOV), the radius of the circle, and the distance between the object and the camera (which is the calculated value).

The rest of the math essentially just makes it work when the viewport frame isn’t regularly sized. He seems to achieve this through a multiplier which regulates the FOV depending on the dimensions of the viewport frame, although admittedly I’m not totally sure on the math he uses for this.

If that works then it’s probably the easiest, most comprehensive work around (as long as you’re only using a single mesh).

1 Like