Before I get into it: I’m not asking about converting a 3D position to a screen position or anything. I think my situation is more complex than that.
I’m making an inverse kinematics system for a crane. I’m using a fairly simple method that calculates the angles based on the intersection points of two circles:
How the inverse kinematics method works is irrelevant, but the center of Circle 1 is the origin point of the shoulder joint; the center of Circle 2 is the target point.
So, obviously the script needs the positions of these circles. My problem is that the shoulder and target points are in 3D space. I need to convert these positions to 2D so that I have circles.
This is easy enough in certain situations where they line up with the 3D coordinate system axes, but if they don’t, it doesn’t work:
As you can see in the gif, the crane works when the target point and shoulder joint perfectly line up with the X axis. In this case, here’s how I define the centers of the circles in the script:
local origin = Vector2.new(shoulder.Position.X, shoulder.Position.Y)
local target = Vector2.new(targetPos.X, targetPos.Y)
If I switch it to use the Z positions instead of X, the crane only works when the target and shoulder line up on the Z axis.
So my question is, how can I convert the positions of the shoulder and target in the 3D coordinate system to a 2D representation so that I can have two accurate circle locations?
You would probably ignore studsToTheRight and treat studsUp like the Y axis in the XY plane and studsForward like the X axis in the XY plane
originOfArm would be formed using whatever position you want(probably the center of the circle or whatever), a modified look vector of the arm removing the y component so its flat, and the right vector of the arm
(NOTE THIS ASSUMES YOUR ARMS ARE CORRECTLY ORIENTED AKA. Look is forwards, Right is right, up is up)
The scenario and gif sounds really similar to 2 Joints 2 limbs scenario however the method they use was 2D to 3D so the scenario ends up different.
The issue here is that the initial method to convert 3d into 2d is to project it into XY plane or XZ plane which well yeah only works in those two planes.
I believe the solution should be to resolve it to a plane that is relative to the crane system by using the parts right vector as a normal vector for the plane and the position vector of the first joint to form the plane of the arm. Closest point to plane projection should be the math term here, generally the way to convert 3d to 2d.
Otherwise, I believe I can whip up something similar using my CCDIK module so that may be an option if you want it.
How would I go about projecting the points onto a 2D plane? I think I know that I need the normal vector of the plane (which for whatever reason I didn’t think of using the origin’s right vector as the normal before), but I don’t know how to actually do it.
As a side note, I already have the inverse kinematics working perfectly with a different method, I just want to explore my options by experimenting with multiple methods. This circle intersection one is the only method that I’ve tried thus far that I haven’t got working.
I have done this before for my FABRIK IK lol. I OOP’ed it for no reason but here it is in function form:
local function SquaredDistance(Vector)
return Vector.X^2 + Vector.Y^2 + Vector.Z^2
end
--[[
Finds the closest point from a point to plane
Returns a vector 3 point in world space
]]
local function findClosestPointOnPlane(planePoint,normalVector,inputPoint)
--Create a new line with direction of the plane normal and located at the InputPoint
--lamda is the scalar of the line direction vector
local lambda = (planePoint - inputPoint):Dot(normalVector) / SquaredDistance(normalVector)
return inputPoint+ lambda*normalVector
end
Edit: Im double checking this formula rn I have a feeling something is off
Sorry for asking for many questions, but if you don’t mind, how would I find planePoint? It’s kind of confusing me. Just a distance from the inputPoint following the direction of the normal vector?
local planePart = script.Parent
local inputPointPart = workspace.inputPoint
local function SquaredDistance(Vector)
return Vector.X^2 + Vector.Y^2 + Vector.Z^2
end
--[[
Finds the closest point from a point to plane
Returns a vector 3 point in world space
]]
local function findClosestPointOnPlane(planePoint,normalVector,inputPoint)
--Create a new line with direction of the plane normal and located at the InputPoint
--lamda is the scalar of the line direction vector
local lambda = (planePoint - inputPoint):Dot(normalVector) / SquaredDistance(normalVector)
return inputPoint+ lambda*normalVector
end
local projectionPart = Instance.new("Part")
projectionPart.Size = Vector3.new(1,1,1)
projectionPart.Anchored = true
projectionPart.BrickColor = BrickColor.Random()
projectionPart.Parent = workspace
while true do
wait()
local normalVector = planePart.CFrame.UpVector
local inputPoint = inputPointPart.Position
local planePointPosition = planePart.Position
local projectionPoint = findClosestPointOnPlane(planePointPosition,normalVector,inputPoint)
projectionPart.Position = projectionPoint
end
Edit: More Info
planePoint is used to define the plane. The term for it is Point–normal form and general form of the equation of a plane wikipedia should help.
I might just be misunderstanding something, but I need something here that returns a 2D vector. The findClosestPointOnPlane function returns a Vector3. This is what I have currently:
local right = shoulder.CFrame.RightVector
local planePoint = shoulder.Position + (right * 5)
local origin = findClosestPointOnPlane(planePoint, right, shoulder.Position)
local target = findClosestPointOnPlane(planePoint, right, targetPos)
local x0, y0 = origin.X, origin.Y
local x1, y1 = target.X, target.Y
I need origin and target to be 2D points on the same plane so that I can check for intersections from their respective circles. Their center points are defined by (x0, y0) and (x1, y1)
It is entirely possible that I still am not interpreting planePoint correctly. Right now it’s set at some point in the same direction as the normal vector, 5 studs out.
Hmm, looks like we need one more step in order to solve the problem. Here is a stack overflow question which is similar to this problem. Step one is done which is resolving the 3d points to a 2d plane.
The next step is to define the y axis, x axis, and origin within this plane to get the 2d geometry and resolve those points to these defined axises, brain is kinda fried atm hopefully you can draw out the problem from there and work on your own.
What might be easier is to just imagine the the direction vector between the two centers of the circle as being one basis vector for our 2d coordinate system. In this case we can treat the origin point of center of the first circle as the center of the world ( 0,0 ).
( you might notice the basis vectors are in 3d, ill talk about this shortly )
We have 1 basis vector for our 2d coordinate system, however u might realize that we need 2 in order to span all of our 2d space. To get our second basis vector we get the normal to our first basis vector, using the cross product.
With our 2 basis vectors we can now span all of 2d space. We know that the origin point of our first circle lies at [0,0] and the origin of the second circle lies at [ (target - origin).magnitude, 0]
If we wish to convert a point in 3d space to our 2d space, its now as simple as getting the projection of our (point - origin) onto these two basis vectors. Example code on that is done is shown below
-- 3d vectors
local basisX = ...
local basisY = ...
-- origin point in 3d space of first circle
local origin = ...
local function convert2d(point)
local relative = point - origin
local projX = relative:Dot(basisX)
local projY = relative:Dot(basisY)
return Vector2.new(projX,projY)
end