CCDIKController - Alternate inverse kinematics method for Motor6D rigs

Hello there, I’m @dthecoolest and this is my third open-source project which I made to contribute back to the community.

Credit


One of the main reason this project is open-source, couldn’t have done it without them:

@JohnathonSelstad - Blogger who posted the tutorial of Psuedo-Code of CCDIK check out the inverse kinematics on his website on GitHub it’s very cool.

@EgoMoose - The rotation between two vectors trick really come in useful and helped when translating Unity code to lua code. Roblox pls add CFrame.fromToRotation like unity Quaternion.FromToRotation.

Quenty - Maids class for general cleaning

Sleitnick - VectorUtil

Showcase


Leg does work

The notable feature: being able to handle hinge constraints:

Vs Fabrik:

Send Help

Assets


The module script:

https://www.roblox.com/library/6204935490/CCDInverseKinematicsController

Github repository with the place file go check it out:

What is it?


After being disappointed with FABRIK for being unable to handle hinge constraints and the lack of easy documentation to implement constraints other than EgoMooses google doc method I searched online for a new method and I found @JohnathonSelstad blog post on it which explicitly says that this new algorithm works for hinges and the blog post is amazingly well made so yeah I had to try it out.

How does it work?


Uhh just look at the website made by @JohnathonSelstad, he posted the pseudocode of the CCDIK algorithm and it’s interactive as well, go drag that robot arm on his website powered through javascript. Plus there’s also a lot of cool algorithms like orthonormalization on there check it out.

Otherwise, this image should illustrate the rotation happening with each joint which rotates from the direction towards the end effector towards the goal position.

ccd1-1

How do I use it?


Documentation and tutorial is now available!:

datlass.github.io/rbx-ccdik/

Attribution


The project is MIT licensed feel free to fork and make edits to it. The model provided in GitHub is usable but well extremely resembles a mech in another video game so for maximum safety use your own rigs to avoid copyright.

Future of the project


Bug fixes.

166 Likes

Alright, It’s time to test this baby out on my Tripod. I am looking forward for what is to come.
EDIT I been messing around with your Mech, and I have notice some major improvements to the CCDIK.
Noticed Improvements:

  • slower to move gives of a realistic movement.
    *Other Parts Correct themselves.
    *Joint Movement is more Restrictive of angles.
3 Likes

New update!

Featuring twist constraints for ball socket constraints, foot to floor orientation system, Constant speed lerping, and documentation/tutorial example on the website:

Twist constraints:

Foot to floor orientation:

15 Likes

Amazing module, keep up the good work

2 Likes

Yeah it’s a bug, thanks for pointing it out and it should be fixed in the latest GitHub version and toolbox, test place is updated with the R6 Dummy point model.

The issue was caused by the R6 rig being wack where the Motor6D Orientation of both the C0 and C1 has been rotated by 90 degrees for some reason.

Normally, the CCDIK controller expects the Motor6D to have an orientation of (0,0,0) as it’s how it’s normally made in RigEdit and the R15 rig but it’s not.

This consequently messed up the CFrame calculations somehow by leaving an offset of 90 degrees which caused it to rotate on and on and never reach the goal. Now it’s fixed by the constructor resetting the CFrame C0 and C1 orientation.

So yeah check it out, hope it works now.

3 Likes

Hmm, make sure to create an end effector at the tip of the arm in order to ensure the

Motor6D world joint position to tip of the arm position, vector

is parallel with the:

Motor6D world joint position to goal position, vector

which is how the CCD inverse kinematics algorithm works.

Also the place file in the GitHub should illustrate how its done, I recently added a dummy with an attachment named “EndEffector” manually to debug this current issue.

I’ll add a function later to enable a debug mode with tween service to illustrate how the algorithm works, rotating each joint one by one.

2 Likes

I was looking for a way to incorporate IK and found this method. Definitely gunna try it out in the near future. Great work making it happen, it really doesn’t look easy!

1 Like

Any idea why I am getting this sporadic behavior? The gif doesn’t really show it because it doesn’t capture at full framerate, but the arm is flashing between at least two positions each frame.

Without constraints, it’s not sporadically flashing every other frame, but it is wandering continuously and it never settles on a solution:

Also, the two links on the homepage of your github documentation are broken.

3 Likes

Nope, no idea except for this one which is very similar to the one posted by @nurokoi.

The issue was with the rig and how the motor6D were setup which I still IDK the exact maths behind it.

Hmm, I’ll see if I can release a debug mode in order to see if the rotations are being applied in the correct direction.

Looks like it’s coming from a conflict against animations running on the rig.

I added a test character to the example file with the default animate script. With the animate script disabled, it works fine, but with the animate script running an idle animation on the rig, it shows the sporadic behavior. TestPlace.rbxlx (1.1 MB)

Any easy ways to get around this?

3 Likes

Thanks a lot for identifying the problem clearly.

So far the fix I was able to come up with was to just remove the effects of the animations by setting the Transform property to CFrame.new() and running it within .Stepped of Runservice instead of heartbeat.

--within the CCDIK controller rotateJointFromTo and rotateJointFromToWithLerp
--Functions
	motor6DJoint.Transform = CFrame.new()--Just remove it
	motor6DJoint.C0 = goalC0CFrame

I think it’s a pretty dirty solution as well it doesn’t allow for animations to be blended with the IK, but it should work for now.

TestPlaceCCDIKRemoveAnimations.rbxlx (1.1 MB)

2 Likes

Awesome! One revision to this solution though, you need to put the transform resets in CCDIKIterateOnce and CCDIKIterateUntil, BEFORE the goal magnitude check. Otherwise the transform does not reset on frames where there is no calculation because the goal is already near the end effector, and in this case the jitteriness returns.

4 Likes

Got some pretty good results!

https://i.imgur.com/Fgz7D97.mp4

If anyone is curious and needs some tips, here’s an rbxm showing how I set up the constraints for the arms. I also modified the ballsocket hip constraints from the example, because the bending backwards limit was too far. This setup seems to work pretty well, and you don’t need to set up any extra attachments outside of what the R15 rig is already using, which is nice. Character.rbxm (15.3 KB)

36 Likes

I just happened to be looking at this right now for using it on arms and a boat steering wheel and was getting the strange spinning issue alongside unnatural arm rotations.
Thank you very much my dude :sunglasses:

3 Likes

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