This is an old topic but I was wondering this as well so I decided to give it a shot in case anyone in the future was wondering how to go about this.
A couple functions we will need, both of which we can omit the depth check:
Ray-plane intersection (obtained from c++ - How to calculate a ray plane intersection - Stack Overflow):
local function rayPlaneIntersection(planePoint, planeNormal, origin, direction)
return -((origin - planePoint):Dot(planeNormal)) / (planeNormal:Dot(direction))
end
Closest point on line (obtained from Calculating closest point on a line? - #9 by Jaycbee05):
local function closestPointOnLine(v, a, b)
local ab = b - a
local abx = ab.X
local aby = ab.Y
local av = v - a
local i = av:Dot(ab) / (abx * abx + aby * aby)
return a + i * ab
end
My first iteration simply created a supposed plane where the dragger’s normal is and did a plane line intersection where the mouse was, then constrained it such that the only movement was the movement along the dragger’s normal.
What I was using to move the part only along the LookVector:
game:GetService('RunService').RenderStepped:Connect(function()
if not isDragging then
return
end
local camera = workspace.CurrentCamera
local coneCFrame = adornee.CFrame * CFrame.new(coneHandleAdornment.SizeRelativeOffset * adornee.Size) * coneHandleAdornment.CFrame
local axis = initialCFrame.LookVector
local mouseLocation = userInputService:GetMouseLocation()
local pointWorldSpace = camera:ViewportPointToRay(mouseLocation.X, mouseLocation.Y)
-- find where the
local planeIntersectionZ = rayPlaneIntersection(initialCFrame.Position, initialCFrame.UpVector, pointWorldSpace.Origin, pointWorldSpace.Direction)
-- where the ray intersects the ray in 3d
local planeIntersectWorldSpace = pointWorldSpace.Origin + planeIntersectionZ * pointWorldSpace.Direction
-- constrain it to the axis
local objectSpace = initialCFrame:PointToObjectSpace(planeIntersectWorldSpace) * axis
local worldSpace = initialCFrame:PointToWorldSpace(objectSpace)
adornee.CFrame = CFrame.new(worldSpace - (CFrame.new(coneHandleAdornment.SizeRelativeOffset * adornee.Size) * coneHandleAdornment.CFrame).Position) * initialCFrame.Rotation
end)
While this was somewhat functional, I noticed a few issues. Namely that the part goes far away when you put your cursor far away in the supposed plane
What I had to figure out now was what the draggers were doing to negate this issue.
I noticed from the new dragger beta that there is a 2d line that is drawn across the screen that goes along the normal of the dragger. It looks like the mouse’s position is projected along this line, and that projected point is where the new position relative to the dragger ends up. This is why the part doesn’t fly off when you put your mouse far along the supposed plane in the background.
A couple of things we have to do to mimic this:
1- Convert the dragger’s normal to screen space:
local coneCFrameScreen = camera:WorldToViewportPoint(coneCFrame.Position)
local coneDirScreen = camera:WorldToViewportPoint((coneCFrame + axis).Position)
local dirScreenSpace = (coneCFrameScreen - coneDirScreen).Unit
2- Find the closest point on that line to the mouse position:
local closestPointToMouse = closestPointOnLine(mouseLocation, coneCFrameScreen, coneCFrameScreen + dirScreenSpace)
3- Use this point instead of the mouse position for the ray-plane intersection:
local pointWorldSpace = camera:ViewportPointToRay(closestPointToMouse.X, closestPointToMouse.Y)
This leaves me with something functionally pretty close to the original draggers:
Here’s a placefile with a full implementation, including making everything relative to the adornee (scripts in StarterGui):
coneDraggers.rbxl (55.6 KB)