If we were working in the Workspace using workspace.CurrentCamera, the process would be straightforward. It would be enough to get the screen size via Camera.ViewportSize, define the four 2D corners (0,0), (xMax, 0), (0, yMax), and (xMax, yMax), and project them into 3D space using Camera:ViewportPointToRay(X, Y, depth) to reconstruct the part.
However, inside a ViewportFrame, the camera behaves differently. While the Workspace camera handles full screen resolutions (like 1920x1080), the ViewportSize property of a ViewportFrame camera stays internally locked at a normalized ratio of (1, 1).
If we try to use the default (0, 1) values for the edges of the X-axis, the projected 3D positions will form a perfectly centered square instead of adapting to the container. This causes the points along the X-axis to become misaligned and pulled toward the center.
To fix this and find the actual 2D coordinates, we calculate the aspect ratio by comparing the GUI’s real size (Viewport.AbsoluteSize) with the ViewportFrame value of (1, 1) (calculated using a direct proportion / rule of three):
X = Viewport.AbsoluteSize.X / Viewport.AbsoluteSize.Y
Since the height (Y-axis) remains stable between 0 and 1, we only need to expand and center the range of the X-axis to compensate for the distortion. We achieve this by calculating the new boundaries (the default ones being 0 to 1):
xMin = (1 - aspectRatio) / 2
xMax = (1 + aspectRatio) / 2
With the X-axis properly adjusted, we can now correctly project the four true corners into 3D space using Camera:ViewportPointToRay() with the points (xMin, 0), (xMax, 0), (xMin, 1), and (xMax, 1).
Once we have obtained the four 3D vertices (topLeft, topRight, bottomLeft, bottomRight), we calculate the dimensions and the center position of the part:
- Width:
local width = (topRight - topLeft).Magnitude
- Height:
local height = (topLeft - bottomLeft).Magnitude
- Center:
local centerPosition = (topLeft + bottomRight) / 2
Finally, we align the part with the camera’s orientation using: local baseCFrame = CFrame.new(centerPosition) * CameraClone.CFrame.Rotation
To prevent half of the part’s thickness (depth) from clipping awkwardly into view, we offset the part backward by exactly half of its Z-axis thickness: Part.CFrame = baseCFrame * CFrame.new(0, 0, -Part.Size.Z / 2)