R6 Inverse Kinematics

Im kinda stuck with resetting the arm back to normal, how do i do that?

Very good module, currently using it on some of my projects.

Pretty good module, I hate r15 tbh but it was my only option to use ikcontrols since roblox but it was really buggy and pretty bad looking, tried for 2 days straight to solve it but the only option was changing to R6IK
I expected it to be pretty limited but it surprised me how well it worked!

Might need to put some way to stop the ikcontrol instead of just destroying it like I did

1 Like

Hey, Can u reply with the source, because I can’t get it from the link.

i also hate r15
found another module

i really want the roblox’s ik control to work for r6 without bugging

2 Likes
local function SolveArmIK(originCFF, targetPos, l1, l2, C1Off, Side : "Left" | "Right")	
	-- build intial values for solving
	local originCF
	if Side == "Left" then
		originCF = originCFF*CFrame.new(0,0,C1Off.X)
	elseif Side == "Right" then
		originCF = originCFF*CFrame.new(0,0,-C1Off.X)
	end
	local localized = originCF:pointToObjectSpace(targetPos)
	local localizedUnit = localized.unit
	local l3 = localized.magnitude

	-- build a "rolled" planeCF for a more natural arm look
	local axis = Vector3.new(0, 0, -1):Cross(localizedUnit)
	local angle = math.acos(-localizedUnit.Z)
	local planeCF = originCF * CFrame.fromAxisAngle(axis, angle)

	-- case: point is to close, unreachable
	-- return: push back planeCF so the "hand" still reaches, angles fully compressed
	if l3 < math.max(l2, l1) - math.min(l2, l1) then
		return planeCF * CFrame.new(0, 0,  math.max(l2, l1) - math.min(l2, l1) - l3), -math.pi/2, math.pi

		-- case: point is to far, unreachable
		-- return: for forward planeCF so the "hand" still reaches, angles fully extended
	elseif l3 > l1 + l2 then
		return planeCF, math.pi/2, 0, l3

		-- case: point is reachable
		-- return: planeCF is fine, solve the angles of the triangle
	else
		local a1 = -math.acos((-(l2 * l2) + (l1 * l1) + (l3 * l3)) / (2 * l1 * l3))
		local a2 = math.acos(((l2  * l2) - (l1 * l1) + (l3 * l3)) / (2 * l2 * l3))

		return planeCF, a1 + math.pi/2, a2 - a1
	end
end

local function SolveLegIK(originCFF, targetPos, l1, l2, C1Off)	
	-- build intial values for solving
	local originCF = originCFF*CFrame.new(-C1Off.X,0,0)
	local localized = originCF:pointToObjectSpace(targetPos)
	local localizedUnit = localized.unit
	local l3 = localized.magnitude

	-- build a "rolled" planeCF for a more natural arm look
	local axis = Vector3.new(0, 0, -1):Cross(-localizedUnit)
	local angle = math.acos(-localizedUnit.Z)
	local planeCF = originCF * CFrame.fromAxisAngle(axis, angle):Inverse()

	-- case: point is to close, unreachable
	-- return: push back planeCF so the "hand" still reaches, angles fully compressed
	if l3 < math.max(l2, l1) - math.min(l2, l1) then
		return planeCF * CFrame.new(0, 0,  math.max(l2, l1) - math.min(l2, l1) - l3), -math.pi/2, math.pi

		-- case: point is to far, unreachable
		-- return: for forward planeCF so the "hand" still reaches, angles fully extended
	elseif l3 > l1 + l2 then
		return planeCF, math.pi/2, 0, l3

		-- case: point is reachable
		-- return: planeCF is fine, solve the angles of the triangle
	else
		local a1 = -math.acos((-(l2 * l2) + (l1 * l1) + (l3 * l3)) / (2 * l1 * l3))
		local a2 = math.acos(((l2  * l2) - (l1 * l1) + (l3 * l3)) / (2 * l2 * l3))

		return planeCF, math.pi/2-a1, -(a2 - a1)
	end
end

local function WorldCFrameToC0ObjectSpace(motor6DJoint,worldCFrame)
	local part1CF = motor6DJoint.Part1.CFrame
	local part0 = motor6DJoint.Part0
	local c1Store = motor6DJoint.C1
	local c0Store = motor6DJoint.C0
	local relativeToPart0 = part0.CFrame:inverse() * worldCFrame * c1Store

	local goalC0CFrame = relativeToPart0

	return goalC0CFrame
end

local R6IK = {}
R6IK.__index = R6IK

function R6IK.New(Character)
	local self = {}
	self.Torso = Character.Torso
	self.HumanoidRootPart = Character.HumanoidRootPart
	self.LeftArm = Character["Left Arm"]
	self.RightArm = Character["Right Arm"]
	self.LeftLeg = Character["Left Leg"]
	self.RightLeg = Character["Right Leg"]
	
	self.Motor6Ds = {
		["Left Shoulder"] = self.Torso["Left Shoulder"],
		["Right Shoulder"] = self.Torso["Right Shoulder"],
		["Left Hip"] = self.Torso["Left Hip"],
		["Right Hip"] = self.Torso["Right Hip"],
		["RootJoint"] = self.HumanoidRootPart["RootJoint"]
	}
	
	self.C0s = {
		["Left Shoulder"] = self.Motor6Ds["Left Shoulder"].C0,
		["Right Shoulder"] = self.Motor6Ds["Right Shoulder"].C0,
		["Left Hip"] = self.Motor6Ds["Left Hip"].C0,
		["Right Hip"] = self.Motor6Ds["Right Hip"].C0,
		["RootJoint"] = self.Motor6Ds["RootJoint"].C0
	}
	
	self.C1s = {
		["Left Shoulder"] = self.Motor6Ds["Left Shoulder"].C1,
		["Right Shoulder"] = self.Motor6Ds["Right Shoulder"].C1,
		["Left Hip"] = self.Motor6Ds["Left Hip"].C1,
		["Right Hip"] = self.Motor6Ds["Right Hip"].C1,
		["RootJoint"] = self.Motor6Ds["RootJoint"].C1
	}
	
	self.Part0s = {
		["Left Shoulder"] = self.Motor6Ds["Left Shoulder"].Part0,
		["Right Shoulder"] = self.Motor6Ds["Right Shoulder"].Part0,
		["Left Hip"] = self.Motor6Ds["Left Hip"].Part0,
		["Right Hip"] = self.Motor6Ds["Right Hip"].Part0,
		["RootJoint"] = self.Motor6Ds["RootJoint"].Part0
	}
	
	self.Part1s = {
		["Left Shoulder"] = self.Motor6Ds["Left Shoulder"].Part1,
		["Right Shoulder"] = self.Motor6Ds["Right Shoulder"].Part1,
		["Left Hip"] = self.Motor6Ds["Left Hip"].Part1,
		["Right Hip"] = self.Motor6Ds["Right Hip"].Part1,
		["RootJoint"] = self.Motor6Ds["RootJoint"].Part1
	}
	
	self.LeftUpperArmLength = 1
	self.LeftLowerArmLength = 1
	
	self.RightUpperArmLength = 1
	self.RightLowerArmLength = 1
	
	self.LeftUpperLegLength = 1
	self.LeftLowerLegLength = 1
	
	self.RightUpperLegLength = 1
	self.RightLowerLegLength = 1
	
	self.TorsoIK = false
	
	return setmetatable(self, R6IK)
end

function R6IK:ArmIK(Side : "Left" | "Right", Position:Vector3)
	if Side == "Left" then
		local OriginCF = self.Torso.CFrame*self.C0s["Left Shoulder"]
		local C1Off = self.C1s["Left Shoulder"]
		local PlaneCF,ShoulderAngle,ElbowAngle, l3 = SolveArmIK(OriginCF, Position, self.LeftUpperArmLength, self.LeftLowerArmLength, C1Off, Side)
		
		local ShoulderAngleCFrame, ElbowAngleCFrame = CFrame.Angles(ShoulderAngle, 0, 0), CFrame.Angles(ElbowAngle, 0, 0)
		
		local ShoulderCF = PlaneCF * ShoulderAngleCFrame*CFrame.new(0,-self.LeftUpperArmLength * 0.5,0)
		local ElbowCF = ShoulderCF*CFrame.new(0,-self.LeftUpperArmLength * 0.5,0)* ElbowAngleCFrame * CFrame.new(0, -self.LeftLowerArmLength*0.5, 0)*CFrame.new(0,(self.LeftArm.Size.Y-self.LeftLowerArmLength)*0.5,0)
		
		self.Motor6Ds["Left Shoulder"].C0 = WorldCFrameToC0ObjectSpace(self.Motor6Ds["Left Shoulder"], ElbowCF)
	elseif Side == "Right" then
		local OriginCF = self.Torso.CFrame*self.C0s["Right Shoulder"]
		local C1Off = self.C1s["Right Shoulder"]
		local PlaneCF,ShoulderAngle,ElbowAngle, l3 = SolveArmIK(OriginCF, Position, self.RightUpperArmLength, self.RightLowerArmLength, C1Off, Side)

		local ShoulderAngleCFrame, ElbowAngleCFrame = CFrame.Angles(ShoulderAngle, 0, 0), CFrame.Angles(ElbowAngle, 0, 0)

		local ShoulderCF = PlaneCF * ShoulderAngleCFrame*CFrame.new(0,-self.RightUpperArmLength * 0.5,0)
		local ElbowCF = ShoulderCF*CFrame.new(0,-self.RightUpperArmLength * 0.5,0)* ElbowAngleCFrame * CFrame.new(0, -self.RightLowerArmLength*0.5, 0)*CFrame.new(0,(self.RightArm.Size.Y-self.RightLowerArmLength)*0.5,0)

		self.Motor6Ds["Right Shoulder"].C0 = WorldCFrameToC0ObjectSpace(self.Motor6Ds["Right Shoulder"], ElbowCF)
	end
end

function R6IK:LegIK(Side : "Left" | "Right", Position:Vector3)
	if Side == "Left" then
		local OriginCF = self.Torso.CFrame*self.C0s["Left Hip"]
		local C1Off = self.C1s["Left Hip"]
		local PlaneCF,ShoulderAngle,ElbowAngle, l3 = SolveLegIK(OriginCF*CFrame.Angles(0, math.pi/2, 0), Position, self.LeftUpperLegLength, self.LeftLowerLegLength, C1Off)

		local HipAngleCFrame, KneeAngleCFrame = CFrame.Angles(ShoulderAngle, 0, 0), CFrame.Angles(ElbowAngle, 0, 0)

		local HipCF = PlaneCF * HipAngleCFrame*CFrame.new(0,-self.LeftUpperLegLength * 0.5,0)
		local KneeCF = HipCF*CFrame.new(0,-self.LeftUpperLegLength * 0.5,0)* KneeAngleCFrame * CFrame.new(0, -self.LeftLowerLegLength*0.5, 0)*CFrame.new(0,(self.LeftLeg.Size.Y-self.LeftLowerLegLength)*0.5,0)

		self.Motor6Ds["Left Hip"].C0 = WorldCFrameToC0ObjectSpace(self.Motor6Ds["Left Hip"], KneeCF)
	elseif Side == "Right" then
		local OriginCF = self.Torso.CFrame*self.C0s["Right Hip"]
		local C1Off = self.C1s["Right Hip"]
		local PlaneCF,ShoulderAngle,ElbowAngle, l3 = SolveLegIK(OriginCF*CFrame.Angles(0, -math.pi/2, 0), Position, self.RightUpperLegLength, self.RightLowerLegLength, C1Off)

		local HipAngleCFrame, KneeAngleCFrame = CFrame.Angles(ShoulderAngle, 0, 0), CFrame.Angles(ElbowAngle, 0, 0)

		local HipCF = PlaneCF * HipAngleCFrame*CFrame.new(0,-self.RightUpperLegLength * 0.5,0)
		local KneeCF = HipCF*CFrame.new(0,-self.RightUpperLegLength * 0.5,0)* KneeAngleCFrame * CFrame.new(0, -self.RightLowerLegLength*0.5, 0)*CFrame.new(0,(self.RightLeg.Size.Y-self.RightLowerLegLength)*0.5,0)

		self.Motor6Ds["Right Hip"].C0 = WorldCFrameToC0ObjectSpace(self.Motor6Ds["Right Hip"], KneeCF)
	end
end

return R6IK

1 Like

Hey there
I know this post is a bit old but I’m looking forward to using this module and I just can’t figure out how to IK a limb dynamically/repeatedly (like in the showcase) without it freaking out.

did you use the .New function once and not every time you want to use the IK?

1 Like

Hi, im new to ik and i was playing around with this module trying to create a foot planting system and i got stuck trying to figure out how to change the roation of the ik to make it look better.

Heres an example:


What im trying to achive:


Heres how im trying to handle the foot planting system:

local function getTerrainHeight(position)
	local ray = Ray.new(position + Vector3.new(0, 50, 0), Vector3.new(0, -100, 0))
	local part, hitPosition = Workspace:FindPartOnRay(ray, character)
	return hitPosition
end


local function updateLegPositions()
	local rootPart = character.HumanoidRootPart
	local rootCF = rootPart.CFrame
	local rootPos = rootPart.Position

	local leftFootOffset = Vector3.new(-0.5, -character.Humanoid.HipHeight - ik.LeftUpperLegLength - ik.LeftLowerLegLength / 0.1, 0)
	local rightFootOffset = Vector3.new(0.5, -character.Humanoid.HipHeight - ik.RightUpperLegLength - ik.RightLowerLegLength / 0.1, 0)

	local leftFootTarget = rootCF:PointToWorldSpace(leftFootOffset)
	local rightFootTarget = rootCF:PointToWorldSpace(rightFootOffset)

	local leftFootTerrainHeight = getTerrainHeight(leftFootTarget)
	local rightFootTerrainHeight = getTerrainHeight(rightFootTarget)

	
	leftFootTarget = Vector3.new(leftFootTarget.X, leftFootTerrainHeight.Y, leftFootTarget.Z)
	rightFootTarget = Vector3.new(rightFootTarget.X, rightFootTerrainHeight.Y, rightFootTarget.Z)

	
	ik:LegIK("Left", leftFootTarget)
	ik:LegIK("Right", rightFootTarget)
end

I hope you understand the problem and someone can help me

3 Likes

I see the what you’re aiming for with the leg IK, sadly I’m unsure on how to change the leg IK algorithm to fit your needs. Maybe the R6 IK algorithm in this post might help?

1 Like

This was actually surprisingly easy to setup and use, and it works well, thanks for making this module!

1 Like

how would you go about returning a limb back to its original position?

1 Like

i just found out… you need to reset the motor 6d attached to the torso to its original c0 and c1

2 Likes

Is it possible to adjust the Motors6Ds C1 without it combating against and if so how to achieve that while keeping same functionality.

So things like this could be done:
image

1 Like

I believe it is possible if you adjust the C0 or the C1 data saved within the module itself. You can access them like this

local R6IK = [[path to the module]]
local Dummy = workspace.Rig

local IKController = R6IK.New(Dummy)

IKController.C0s["Left Shoulder"] = CFrame.new(...)
IKController.C1s["Left Shoulder"] = CFrame.new(...)
2 Likes

Hi I was having some trouble with the module where I can’t seem to get it to work, I setup the IK when a player gets added. Then I tried running the IK when a player used a ProximityPrompt and ended up finding 2 errors that i commented into the script.

Script in SeverScriptService:

local RS = game:GetService("ReplicatedStorage")
local Players = game:GetService("Players")

local R6IK = require(RS:WaitForChild("R6IK"))

Players.PlayerAdded:Connect(function(plr)
	local char = plr.Character or plr.CharacterAdded:Wait()
	R6IK.New(char)
end)

Script attached to ProximityPrompt:

local RS = game:GetService("ReplicatedStorage")
local TS = game:GetService("TweenService")

local R6IK = require(RS:WaitForChild("R6IK"))

local button = script.Parent.Parent
local door = workspace.Map.BlastDoor

script.Parent.PromptButtonHoldBegan:Connect(function(plr)
	R6IK.ArmIK(plr.Character, "Left", button.Position)
	--Returns error "C0s is not a vlid member of Model 'Workspace.TheFlairFox'"
end)

script.Parent.PromptButtonHoldEnded:Connect(function(plr)
	R6IK.ArmIK("Left", nil)
	--Sets the Side value to the position
end)

script.Parent.Triggered:Connect(function()
	if door.Open.Value then
		script.Parent.Enabled = false
		TS:Create(door, TweenInfo.new(2.75), {Position = Vector3.new(-24.5, 4, 92)}):Play()
		door.Sound:Play()
		wait(3)
		script.Parent.Enabled = true
		door.Open.Value = false
	elseif not door.Open.Value then
		script.Parent.Enabled = false
		TS:Create(door, TweenInfo.new(2.75), {Position = Vector3.new(-24.5, 11.6, 92)}):Play()
		door.Sound:Play()
		wait(3)
		script.Parent.Enabled = true
		door.Open.Value = true
	end
end)

Thank you.

Sorry for the late response, but for your problem seems to be similar as this one:

and to add on to that, the .New() returns R6IK Object that has the capabilities to do the IK, so I suggest for you to make the R6IK Object for the character in the server script and then a bindable event for it to be fired from the proximity prompt to the server script, in which the sever script will handle the IK

1 Like

Alright I got it working, thank you

Question, since your ik6 module does not replicate to server.

Would you ever consider replicating to server?

How would you replicated? Describe briefly.

For the time i have used this module i used 2 methods:

  1. Server handles the IK
  2. Client handles the IK

for number 1 the server will handle the IKs of all players. I usually do this with Client → Server communication where at first the server creates the IK Object when the player joins and the Client will send the target position to the server and the server handles it.
Pros: easier to script and handle
Cons: will put stress to the server

for number 2 all clients will handle other clients IK. I have done this once and to do this will use the system of Client → Server → Other Clients. So when the player joins first the server send a signal to all clients that the player joined and each client will make an IK object for that player. And if a player wants to do an IK it will send the target to the server, then the server sends the target information to all clients in which all clients will do the IK simulation of that player locally
Pros: less stress on the server
Cons: difficult to create and handle

1 Like