CCDIKController - Alternate inverse kinematics method for Motor6D rigs

Do you animate server-side or are these movements only visible to the client?

I’m animating on the server currently so it replicates and it works perfect on the server, however the view on the client is offset from what I see on the server. The client’s local animation doesn’t get disabled by resetting the motor transform values on the server.

I think I’m going to have to reset motor transforms on the client too.

3 Likes

The transform resets and IK is all clientside, following the steering wheel which is physically replicated.

8 Likes

This is one of the most underrated and brilliant modules I’ve seen - really huge thanks for releasing this! My only “complaints” would be on whether you’d be able to setup more examples? I’m sure this module would gain tons of times more traction if you provided with sample footplanting code, either R6 or R15.
Other than that, I’ve no complaints! I’ll check out how this module fares in some of my projects :grinning_face_with_smiling_eyes:

3 Likes

Hey, sorry if I’m missing something, but I can’t get this module to work on my R6 character no matter how I try. I copied pretty much the exact same code as in the basic setup tutorial, but I only get some pretty weird results:
https://cdn.discordapp.com/attachments/785870115463626765/842444834614542396/mFsR6R4THn.mp4
If I add this

RunService.Stepped:Connect(function()
	RightShoulder.Transform = CFrame.new()
end)

then the right arm is just permanently stuck as if it has no animation applied to it.
This is the test place:
IK_Test.rbxl (49.1 KB)
If you could help figuring out what the issue is, that’d be great.
Many thanks!

EDIT: Nevermind! Only now noticed that there is a test place attached.
EDIT 2:
For those interested in what the issue is, for R6 characters, you must set CCDIKController.UseLastMotor to true.

6 Likes

Hello everyone,

I’ve been messing around with this module and it works fine EXCEPT it’s kind of choppy when the r15 rig I am using is running.

Video:

Script
local RUS = game:GetService("RunService")

local IKSolver = require(script.CCDIKController)

local Folder = script.Parent
local TestModel = Folder.Test
local EndEffector = TestModel.HumanoidRootPart.LeftEndEffector-- Folder.EndEffector

local Motors = {}

for i, v in pairs(TestModel:GetDescendants()) do
	if v:IsA("Motor6D") then 
		Motors[v.Name] = v
	end
end

local LeftHip = Motors.LeftHip
local LeftKnee = Motors.LeftKnee
local LeftAnkle = Motors.LeftAnkle

local RightHip = Motors.RightHip
local RightKnee = Motors.RightKnee
local RightAnkle = Motors.RightAnkle

local LeftLimb = {LeftHip, LeftKnee, LeftAnkle}
local RightLimb = {RightHip, RightKnee, RightAnkle}

local LeftLimbSolver = IKSolver.new(LeftLimb)
local RightLimbSolver = IKSolver.new(RightLimb)

local CoolDown = false
local PreviousGoal = nil

LeftLimbSolver:GetConstraints()
RightLimbSolver:GetConstraints()

local function SetCoolDown(Duration)
	if CoolDown then return false end
	
	CoolDown = true
	delay(Duration, function()
		CoolDown = false
	end)
	
	return true
end

local function UpdateSolver(dt, Solver)
	local Goal = (CoolDown and PreviousGoal) or (SetCoolDown(0.3) and EndEffector.WorldPosition)

	Solver.LerpAlpha = 0.7 * dt * 60
	Solver:CCDIKIterateOnce(Goal)
	PreviousGoal = Goal
end

RUS.Heartbeat:Connect(function(dt)
	UpdateSolver(dt, LeftLimbSolver)
	-- UpdateSolver(dt, RightLimbSolver)
end)

As you can see in the video, the solver stutters a little bit when the goal is close to it. The script proves that I haven’t put any yield functions like wait() that would pause its iteration. Am I doing something wrong (like changing the LerpAlpha or something) or is there a bug that I’m unaware of? Thanks for your help

p.s: I’m not sure if I should post here so let me know if I should switch this to scripting-support thanks :slight_smile:

2 Likes

Sorry, I realized I haven’t made it clear that there is a parameter in the second parameter called tolerance.

CCDIKIterateOnce(goalPosition,tolerance,step)

If the limb is within stud distance goal of this tolerance it’ll stop updating as the goal is reached and reduce lag. However this also causes the limb to be unresponsive so try setting tolerance to zero.

Also you didn’t put dt or step into the step parameter as well of the iteration function so it’ll not lerp and not be smooth.

2 Likes

Thanks for the clarification! I didn’t realize there were more than one parameter on the function.

Now it works! :slight_smile:

2 Likes

Hey, I’ve noticed that the controller performs quite poorly with R6, especially in terms of how accurate it is with it’s end destination.
image
image
Is there some way to fix this behavior?

3 Likes

The movement of the arms is dependent on two things, the joint position and the end effector.

The joint will rotate from the position of the joint, such that the end effector will reach the goal.

JointPosition modified to be at the top of the arm, with the end position located at the tip of the hand:

Resulting motion:
w2RlsZeBRE

I believe you can try adjusting the position of the Motor6D Joint until you get the behavior you want, but with the default R6 Joint position with the joint located at the torso you will get some weird results.

For reference default R6 rig position with same end effector setup:

Someone fix his shoulder please:

Oh My Shoulder

3 Likes

Thank you so much for the in-depth explanation! Works phenomenally now :smiley:
image
I can’t get over how easy this module is to use… I’ve had so many issues trying to use FABRIK that after a week of trying I just gave up. Amazing module!

5 Likes

Hey, quick follow-up question: what is the step parameter in CCDIKIterateUntil? Is it expected to be a value between [0, 1], or is it just

RunService.Heartbeat:Connect(function(Delta)
	Step += Delta

, with no clamping?

1 Like

Oh just realized having step in IterateUntil is pretty weird, never tested IterateUntil, never had any use of it for now.

So yeah step is deltatime returned from a run service according to API naming conventions RunService | Roblox Creator Documentation. It’s expected to be between [0,1] but theres a safety measure with math.min there so it should be fine, no clamping should be required.

The only function in CCDIK which needs step:

rotate joint from to with Lerp
--Controls the primary CCDIK Method but instead of going fully towards the goal it lerps slowly towards it instead
function CCDIKController:rotateJointFromToWithLerp(motor6DJoint : Motor6D,u,v,axis,step)

	local rotationCFrame = getRotationBetween(u,v,axis)
	rotationCFrame = motor6DJoint.Part0.CFrame:Inverse()*rotationCFrame*motor6DJoint.Part1.CFrame
	rotationCFrame = rotationCFrame-rotationCFrame.Position
	local goalC0CFrame = CFrame.new(motor6DJoint.C0.Position)*rotationCFrame
	local lerpAlpha = self.LerpAlpha

	local currentC0 = motor6DJoint.C0

	if step and self.ConstantLerpSpeed then
		local angularDistance = VectorUtil.AngleBetween(currentC0.LookVector,goalC0CFrame.LookVector)
		local estimatedTime = self.AngularSpeed/angularDistance
		lerpAlpha = math.min(step*estimatedTime,1)
	end

	motor6DJoint.C0 = currentC0:Lerp(goalC0CFrame,lerpAlpha)
end
2 Likes

Ah, seems like I shouldn’t really be using it then. Is there an alternate method for smoothly ‘tweening’ an arm towards a target goal? I’ve noticed the properties such as AngularSpeed, but I’m not fully sure on how to utilize them properly.

1 Like

To those interested, I made a quick and easy AnimateGoal function which works flawlessly for my intents :smiley:

local CurrentlyAnimating = {}
local function AnimateGoal(Controller, Target, Length)
	local AlreadyAnimating = CurrentlyAnimating[Controller]
	if AlreadyAnimating then
		AlreadyAnimating.Iteration:Disconnect()
		AlreadyAnimating.Transform:Disconnect()
	end

	local IterationConn
	local TransformConn

	local Step = 0
	IterationConn = RunService.Heartbeat:Connect(function(Delta)
		Step += Delta
		if Step > Length then
			IterationConn:Disconnect()
			TransformConn:Disconnect()
		end

		Controller:CCDIKIterateOnce(Target, 0.05, Step / Length)
	end)
	TransformConn = RunService.Stepped:Connect(function()
		for _, Motor in next, Controller.Motor6DTable do
			Motor.Transform = CFrame.new()
		end
	end)

	CurrentlyAnimating[Controller] = {
		Iteration = IterationConn,
		Transform = TransformConn
	}
end

An issue I’ve been experiencing would be the lack of communication on the API page, though. I had to look through the source to find out about the Motor6DTable property of the CCDIKController object. Other than that, I’ve yet to have any annoying issues with this module! :tada:

7 Likes

What would be the proper way to stop a CCDIKController? Even if I stop calling any functions, the arm keeps being animated weird even after I restore the C0 and C1 properties of the Motor6D object.
What’s weirder is that I don’t even have to call any methods for the weird behavior to occur - the moment I create a new CCDIKController object, the animation for the left arm becomes off:


My code is just this:
image
Is this expected behavior? Because even after calling :Destroy() this remains :confused:

I’m assuming the behavior has to do with how you reset the rotation here:
image
But it seems pretty hard to restore the original Motor6D behavior nevertheless

3 Likes

It’s probably due to the C0 and C1 resets I included when creating the CCDIK controller which @nurokoi pointed out since I reset the orientation to CFrame.new() which probably messed up the direction the animations were made in.

You can probably include a method to reset it to original orientation once the CCDIK controller is destroyed.

2 Likes

Yeah, I fixed it by just resetting the C0 and C1 values back to default right after creating the CCDIKController object. Would this cause any unexpected behavior when using the Controller’s methods later on? Because I’m thinking of wrapping the CCDIKController.new method to safely reset the C0 and C1 properties back to default right after creating the controller object.
image

EDIT: Yeah I see what you mean by what nurokoi pointed out now - it seems like I need to nullify all rotation right before calling any CCDIK methods to fix this behavior… Really weird workaround, but it’ll sadly have to do

2 Likes

I got it working image


Already said this but this module is really useful and easy-to-learn; I can’t imagine being able to do the same with a FABRIK-based one…

9 Likes

So, I’ve now ran into an issue where the CCDIK module iterates from seemingly the default resting position of the right arm - is there a way to change this behavior to iterate from the current arm’s position? Currently what I’m doing is lerping an arm onto a target, but if I keep lerping it once it reached that target once, the arm will start from it’s default position and rise quickly to the target position like so:


I can’t instantly iterate the arm after iterating it once because if the target goal moves slightly, the arm’s rotation would quickly snap towards the goal rather than smoothly interpolating towards it. I see properties such as CCDIKController.LerpMode, but I don’t know what they do and messing around with them doesn’t see to do much either.

Is there some solution to this, or do I need to alter the source to get the behavior I’d like? :confused:
There isn’t any method that returns the CFrame that the limb would be at either without actually changing the limb, so I can’t use a binary search to figure out the right step parameter to seamlessly lerp towards the end goal either.

EDIT: It seems like CCDIKIterateUntil fixes most of the problem in regards to random snapping - I don’t lerp after the first time (i.e when the arm reaches for the door), which also addresses my other issue above.
https://cdn.discordapp.com/attachments/785870115463626765/852636743148634122/LqelvIBfrc.mp4

7 Likes

Hey, I am attempting to make a mech similar to yours. Now my problem is not that I cant use the module but I’m unsure on how to make the legs move as if their walking.

This is what iv got. If my explanation isnt clear enough then basically I am trying to replicated the legs movement in your post “Haha mech go stomp”

[Haha Mech go stomp, any feedback?]

This is like my third post sorry if I am doing this thing really wrong lol.
Thanks in advance.

1 Like