R15 IK Foot Placement

beautiful

8 Likes

Oh boy. Kodran here (I made that third link you put), please if anyone wants to learn don’t bother looking through that code. I wrote it a few years ago and it’s absolute crap; there’s literally a comment that says this:
“–too lazy to explain this”

Not even entirely convinced it works in all cases lol.

That being said, WhoBloxedWho gave a very comprehensive answer which should be more than enough to get you started :+1:

1 Like

Okay, so I’m starting to understand this, except I don’t really get what you mean by “modeling foot and hand movement”? Am I supposed to create an invisible target part for each foot in my character and then animate it so the IK feet know where to move? Or am I not understanding this correctly?

2 Likes

You can think of it as a part, but you’re really only need a position. So if you wanted to model a foot based off an oval, you could do something like:

-- calculate where the floor is
local hipCF = lowerTorso.CFrame * leftHip.C0
local floorCF = hipCF * CFrame.new(0, -2, 0) -- raycasting for this value is probably better

-- you can use cosine and sine to model circles,
-- if you clamp the height of the circle to never go below 0
-- you end up making a half circle shape,
-- stretching the length (in this case Z) gives us an oval shape
local offsetFromGround = Vector3.new(
	0, 
	math.max(0, math.cos(time() * math.pi * walkSpeed ) * 3),
	math.sin(time() * math.pi * walkSpeed) * 3
)

-- use this value for a TargetPosition for your IK
local footGoal = floorCF * offsetFromGround

The example isn’t an ideal shape for the path of a foot as you’re walking relative to the torso, but it’s a start. It’s well worth doing some research and figuring out how you want it to look.

Being able to visualize these shapes really helps too, and I’d absolutely recommend playing with it in studio or some kinda graphing program and just trying to move a point/part back and forth via math until you get some form of motion you really like.

9 Likes

What is lowerC0 in “elbowMotor.C0 = lowerC0 * cfAngles(elbowAngle, 0, 0)”?

Also I’ve done everything you’ve said except the “lowerC0” bit and the legs are kinda getting detached :smiley:

https://i.gyazo.com/f47d16eb427266024526dce4fff8e81c.gif

2 Likes

Correct me if I’m wrong, @WhoBloxedWho, but wouldn’t you have to disable the default Roblox animation when doing this. That may be why it’s messing up for you @vsnry

I have made it so the leg movement in the default running animation is disabled so I’m pretty sure that’s not what’s detaching the legs

Edit:

As you can see it still does it after I disabled the animation :confused:

https://i.gyazo.com/54a81a0f7ad72ab76b1330a6afe7f2b8.gif

I tried swapping shoulderCF with TARGET_POS and it stopped it, but the leg was turned upside down and behaved weirdly

image

2 Likes

Guys, are you seriously trying to get IK to work without disconnecting the character’s leg motors?
I feel I should remind you that a client can move their character’s parts any way they want, and it’ll replicate.

What are you talking about?

And no Motor6D’s don’t replicate.


@WhoBloxedWho so I’m trying to work with your code, but it doesn’t seem to work properly for some reason :confused: can you check it out and see what’s wrong? Or am I the one doing it wrong?

https://i.gyazo.com/48f3e05dca64dd566b04a95892ee3f8f.gif

Could you DM me a code snippet or the rbxl of that place?

There’s a way to make character leg stay in place, by using roblox animation. Looping it in normal position.

Motor6D doesn’t need to be disconnected anymore. Just need to be moved.

You can also just set a motors .Transform CFrame to an empty CFrame every runService.Stepped. It’s something that should be done client side, so it’s not a big deal as you should also be doing procedural animation client side.

2 Likes

What I’m talking about is that there does not need to be any physical justification of how a character is moving their limbs.
Motor6Ds are a pain to work with, but you can disconnect them and move the character’s legs by setting CFrames clientside, and the server will happily replicate that.

Ok, so I’ve figured out how to do most of it and it works pretty well, but now I can’t figure out how to make the foot properly adjust itself so the leg looks like it’s actually sitting ontop of something. I tried cross product, but it didn’t really work. Any ideas?


image

2 Likes

You can shoot a ray downward. The third variable findpartonray returns is the surface normal

Yes, but how do I compensate for the leg rotation? The foot is already rotated by how much the lowerleg is rotated so if I were to rotate it to the surface normal it still wouldn’t be right.

Oh, hmm…
I’ve never been good with IK, sorry…

I wouldn’t use the surface normal since this is prone to glitches if your foot is over multiple surfaces. I’d fire a downwards ray at the toes position, then just get the angle between the heel & wherever that hits.

2 Likes

In case anyone gets stuck where I did, here’s some more info from WhoBloxedWho

4 Likes

I have been messing with this for a while, but I can’t seem to get it to work…
https://gyazo.com/d53e7776b913948b62ba056fac56e80c

local fromAxisAngle = CFrame.fromAxisAngle
local acos = math.acos
local max = math.max
local min = math.min
local cfNew = CFrame.new
local forwardV3 = Vector3.new(0, 0, -1)
local pi = math.pi
local cfAngles = CFrame.Angles

wait(2)

local upperTorso = workspace.ViewModel.UpperTorso
local shoulderMotor = workspace.ViewModel.RightUpperArm.RightShoulder
local elbowMotor = workspace.ViewModel.RightLowerArm.RightElbow
local lowerC0 = elbowMotor.C0

-- OriginCF is the CFrame of the shoulder, direction is important
-- TargetPos is the Vector3 position we're reaching to
-- l1 and l2 are both numbers that represent the length of the segments of the arm
local function solveIK(originCF, targetPos, l1, l2)	
	-- put our position into local space in regards to the originCF
	local localized = originCF:pointToObjectSpace(targetPos)
	
	-- this breaks if OriginCF and targetPos are at the same position
	local localizedUnit = localized.unit
	
	-- the distance to from originCF.p to targetPos
	local l3 = localized.magnitude
	
	-- build an axis of rotation for rolling the arm
	-- forwardV3 is Vector3.new(0, 0, -1)
	local axis = forwardV3:Cross(localizedUnit)
	
	-- find the angle to rotate our plane at
	local angle = acos(-localizedUnit.Z)
	
	-- create a rotated plane to base the angles off of
	local planeCF = originCF * fromAxisAngle(axis, angle)
	
	-- L3 is between the length of l1 and l2
	-- return an offset plane so that one part reaches the goal and "folded" angles
	if l3 < max(l2, l1) - min(l2, l1) then
		return planeCF * cfNew(0, 0,  max(l2, l1) - min(l2, l1) - l3), -pi/2, pi
		
	-- l3 is greater than both arm lengths
	-- return an offset plane so the arm reaches its goal and flat angles
	elseif l3 > l1 + l2 then
		return planeCF * cfNew(0, 0, l1 + l2 - l3), pi/2, 0
		
	-- the lengths check out, do the law of cosines math and offset the plane to reach the targetPos
	-- return the offset plane, and the 2 angles for our "arm"
	else
		local a1 = -acos((-(l2 * l2) + (l1 * l1) + (l3 * l3)) / (2 * l1 * l3))
		local a2 = acos(((l2  * l2) - (l1 * l1) + (l3 * l3)) / (2 * l2 * l3))

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


-- simple demo for how its called

local TARGET_POS = Vector3.new(0, 3, 0)
local SHOULDER_C0_VALUE = CFrame.new(1, 0.4, 0)

game:GetService("RunService").RenderStepped:Connect(function()
	--local shoulderCF = upperTorso.CFrame * shoulderMotor.C0
	local shoulderCF = shoulderMotor.Part0.CFrame
	local plane, shoulderAngle, elbowAngle = solveIK(shoulderCF, workspace.Front.Position, shoulderMotor.Part0.Size.Y, shoulderMotor.Part1.Size.Y)

	shoulderMotor.C0 = upperTorso.CFrame:toObjectSpace(plane) * cfAngles(shoulderAngle, 0, 0)
	elbowMotor.C0 = lowerC0 * cfAngles(elbowAngle, 0, 0)
end)
4 Likes