For Nexus VR Character Model V2, I need to map character arms and legs to VR inputs. The torso is defined already by the head, and the hands are defined by the controllers, so all that is needed is to solve the upper and lower arms and legs. I have done this before with the first version and Self-Driving Simulator, but they both have issues when trying to use Roblox’s attachments. I am attempting to redo the code for it, and I haven’t had any luck getting a reliable result for the past few hours. Sometimes, the result is acceptable but breaks the wrist, and sometimes it is unusable.
The implementation I attempted was with LMH_Hutch’s joint solving with a code block I have used for a few years. However, when attempting to blindly use it to get the angles and facing angle to the wrist and use Roblox’s attachments, Roblox’s attachments on some rigs results in the arm going off because they don’t follow a straight line through the arm/leg.
I then attempted to apply a correction to the rotation based on where the end is and where it should be, which results in the first screenshot. The following is the code used for the demo as well as a demo place file:
--[[
TheNexusAvenger
Solves the R15 arm for a given shoulder CFrame
and CFrame to hold.
--]]
--[[
Solves a joint using the "naive" approach. Recycled
from several projects.
--]]
local function SolveJoint(OriginCFrame,TargetPosition,Length1,Length2)
local LocalizedPosition = OriginCFrame:pointToObjectSpace(TargetPosition)
local LocalizedUnit = LocalizedPosition.unit
local Hypotenuse = LocalizedPosition.magnitude
--Get the axis and correct it if it is 0.
local Axis = Vector3.new(0,0,-1):Cross(LocalizedUnit)
if Axis == Vector3.new(0,0,0) then
if LocalizedPosition.Z < 0 then
Axis = Vector3.new(0,0,0.001)
else
Axis = Vector3.new(0,0,-0.001)
end
end
--Calculate and return the angles.
local PlaneRotation = math.acos(-LocalizedUnit.Z)
local PlaneCFrame = OriginCFrame * CFrame.fromAxisAngle(Axis,PlaneRotation)
if Hypotenuse < math.max(Length2,Length1) - math.min(Length2,Length1) then
local ShoulderAngle,ElbowAngle = -math.pi/2,math.pi
return PlaneCFrame * CFrame.new(0,0,math.max(Length2,Length1) - math.min(Length2,Length1) - Hypotenuse),ShoulderAngle,ElbowAngle
elseif Hypotenuse > Length1 + Length2 then
local ShoulderAngle,ElbowAngle = math.pi/2, 0
return PlaneCFrame * CFrame.new(0,0,Length1 + Length2 - Hypotenuse),ShoulderAngle,ElbowAngle
else
local Angle1 = -math.acos((-(Length2 * Length2) + (Length1 * Length1) + (Hypotenuse * Hypotenuse)) / (2 * Length1 * Hypotenuse))
local Angle2 = math.acos(((Length2 * Length2) - (Length1 * Length1) + (Hypotenuse * Hypotenuse)) / (2 * Length2 * Hypotenuse))
return PlaneCFrame,Angle1 + math.pi/2,Angle2 - Angle1
end
end
--[[
Returns the CFrame of the upper arm, lower arm,
and hand of the right arm.
--]]
return function (StartCFrame,HoldCFrame,RightUpperArm,RightLowerArm,RightHand)
--Determine the appendage end CFrame.
local AppendageEndCFrame = HoldCFrame * RightHand:WaitForChild("RightGripAttachment").CFrame:Inverse()
local AppendageEndJointCFrame = AppendageEndCFrame * RightHand:WaitForChild("RightWristRigAttachment").CFrame
--Get the attachment CFrames.
local UpperLimbStartCFrame = RightUpperArm:WaitForChild("RightShoulderRigAttachment").CFrame
local UpperLimbJointCFrame = RightUpperArm:WaitForChild("RightElbowRigAttachment").CFrame
local LowerLimbJointCFrame = RightLowerArm:WaitForChild("RightElbowRigAttachment").CFrame
local LowerLimbEndCFrame = RightLowerArm:WaitForChild("RightWristRigAttachment").CFrame
--Solve the joint.
local UpperLimbLength = (UpperLimbStartCFrame.Position - UpperLimbJointCFrame.Position).Y
local LowerLimbLength = (LowerLimbJointCFrame.Position - LowerLimbEndCFrame.Position).Y
local PlaneCFrame,ShoulderAngle,ElbowAngle = SolveJoint(StartCFrame,AppendageEndJointCFrame.Position,UpperLimbLength,LowerLimbLength)
--Correct the rotation of the start joint so that it targets the end correctly.
local TargetGoal = CFrame.new(PlaneCFrame.Position,AppendageEndJointCFrame.Position)
local TargetActual = CFrame.new(PlaneCFrame.Position,(PlaneCFrame * CFrame.Angles(ShoulderAngle,0,0) * UpperLimbStartCFrame:Inverse() * UpperLimbJointCFrame * CFrame.Angles(ElbowAngle,0,0) * LowerLimbJointCFrame:Inverse() * LowerLimbEndCFrame).Position)
local StartJointCorrection = TargetActual:Inverse() * TargetGoal
--StartJointCorrection = CFrame.new() --Quick way to disable the correction attempt.
--Calculate the part CFrames.
local PlaneCFrameRotation = CFrame.new(-PlaneCFrame.Position) * PlaneCFrame
local StartJointCFrame = CFrame.new(PlaneCFrame.Position) * StartJointCorrection * PlaneCFrameRotation * CFrame.Angles(ShoulderAngle,0,0)
local UpperLimbCFrame = StartJointCFrame * UpperLimbStartCFrame:Inverse()
local JointCFrame = UpperLimbCFrame * UpperLimbJointCFrame * CFrame.Angles(ElbowAngle,0,0)
local LowerLimbCFrame = JointCFrame * LowerLimbJointCFrame:Inverse()
--Return the part CFrames.
return UpperLimbCFrame,LowerLimbCFrame,AppendageEndCFrame
end
R15 IK Arm.rbxl (29.3 KB)
I am not sure how to go from here to make it work reliably. I don’t need it to be perfect in every case as it would rely on the other components to be perfect, which the torso won’t be when the user is taking off a headset.