Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

IK solver example rig? #41

Open
Silverlan opened this issue Mar 30, 2023 · 13 comments
Open

IK solver example rig? #41

Silverlan opened this issue Mar 30, 2023 · 13 comments

Comments

@Silverlan
Copy link

Hello!

I'm very interested in this library for the IK solver. I've checked bepuphysics v2, but it doesn't appear to include it, which is why I'm asking here.
I know there's an ik demo included with the library, but it's a very basic one. I'm curious to see what the set up looks like for a more complex humanoid IK rig, specifically like the one used in this video: https://youtu.be/lG3uKYQTVj4
I've tried replicating the behavior shown in the video, but haven't had much luck.

Is that model +rig available for download somewhere? If so, is there some way I can see how it was set up, i.e. with what kind of joints, limits, controls and parameters so I can try replicating it programmatically?

Thanks in advance!

@RossNordby
Copy link
Member

The rig from that video is hiding somewhere in here but it might be a pain to find.

IK rigs can benefit from fewer constraints, since it gives the solver more paths to search for valid solutions and usually the solver won't be tasked with correcting poses that are wildly out of position. In that sense, IK rigs can actually be simpler than rigs for ragdoll dynamics.

Notably, internally bepuik is just a repurposed dynamic simulation. The interface it is wrapped with is what makes it behave like an IK solver. You could do the same thing pretty easily in bepuphysics2, too, and it would would be faster and more stable.

@Silverlan
Copy link
Author

Notably, internally bepuik is just a repurposed dynamic simulation. The interface it is wrapped with is what makes it behave like an IK solver. You could do the same thing pretty easily in bepuphysics2, too, and it would would be faster and more stable.

I've already tried creating my own IK solver a while back and ran into some problems I wasn't able to solve (mainly related to constraints), which is why I'm here. There's plenty of other IK solutions out there, but most of them don't support constraints/limits, IK trees, handles or are lacking something else.
This one has everything I need, I just need some kind of reference to work with and the rig from the video would be perfect for that.

The rig from that video is hiding somewhere in here but it might be a pain to find.

I believe I found the model with the rig over here: https://code.google.com/archive/p/bepuik/downloads ("bepuik_autorig_example.blend").
Although I'm not entirely sure it's the correct one, since I can't seem to be able to move any of the controls in pose mode (the transform manipulator doesn't appear):
https://user-images.githubusercontent.com/3609960/229157031-08c619cb-64d9-4292-8767-cf49e2744529.mp4

I'm not that familiar with Blender though, maybe I'm just doing something wrong. Is there some way to export the ik rig in a text-based format, or some way to inspect the parameters in Blender?

@RossNordby
Copy link
Member

Dunno! I didn't write the blender plugin, just the solver bits.

My guess is the easiest path is starting from the InverseKinematicsTestDemo.BuildActionFigure. It's not as complicated as the one from the video, but the demo does have all the same concepts.

@Silverlan
Copy link
Author

I've been making some progress, but I'm having some trouble understanding how the swing limit works if the two axes are not the same.

Here's a very simple ball-socket constraint with a swing limit that limits the angle to 10 degrees (with the same axisA and axisB):

Bone bodyBone = new Bone(body.Position, Quaternion.CreateFromAxisAngle(new Vector3(0, 0, 1), MathHelper.PiOver2), 0.5f, 1.0f);
Bone headBone = new Bone(head.Position, Quaternion.CreateFromAxisAngle(new Vector3(0, 0, 1), MathHelper.PiOver2), 0.5f, 1.0f);

bones.Add(new BoneRelationship(bodyBone, body));
bones.Add(new BoneRelationship(headBone, head));

joints.Add(new IKBallSocketJoint(bodyBone, headBone, headBodyBallSocketAnchor));
joints.Add(new IKSwingLimit(bodyBone, headBone, new Vector3(-1.0f,0.0f,0.0f), new Vector3(-1.0f, 0.0f, 0.0f), DegreeToRadian(10.0f)));

joints.Last().Rigidity = 999.0f;
BEPUphysicsDemos.2023-04-24.16-37-58-358.mp4

So far so good. This is the same example, except axisB of the swing limit is slightly angled relative to axisA:

Bone bodyBone = new Bone(body.Position, Quaternion.CreateFromAxisAngle(new Vector3(0, 0, 1), MathHelper.PiOver2), 0.5f, 1.0f);
Bone headBone = new Bone(head.Position, Quaternion.CreateFromAxisAngle(new Vector3(0, 0, 1), MathHelper.PiOver2), 0.5f, 1.0f);

bones.Add(new BoneRelationship(bodyBone, body));
bones.Add(new BoneRelationship(headBone, head));

joints.Add(new IKBallSocketJoint(bodyBone, headBone, headBodyBallSocketAnchor));
joints.Add(new IKSwingLimit(bodyBone, headBone, new Vector3(-1.0f, 0.0f, 0.0f), Vector3.Normalize(new Vector3(-1.0f, 0.0f, 0.2f)), DegreeToRadian(10.0f)));

joints.Last().Rigidity = 999.0f;
BEPUphysicsDemos.2023-04-24.16-38-40-710.mp4

This is probably correct behavior, but I can't quite visualize what's going on here in my head. I would like to add some debug visualization aid, but I'm not quite sure what that would look like. If it's not too much to ask, do you have any tips for me?

There are a few cases like this in the action figure demo, and I'm trying to understand how this configuration works exactly. For instance:

//Right leg
joints.Add(new IKBallSocketJoint(bodyBone, upperRightLegBone, bodyUpperRightLegBallSocketAnchor));
joints.Add(new IKSwingLimit(bodyBone, upperRightLegBone, Vector3.Normalize(new Vector3(.3f, -1, .6f)), Vector3.Down, MathHelper.Pi * 0.6f));

@RossNordby
Copy link
Member

In the end, the swing limit (both the dynamic version and IKSwingLimit) try to keep the angle between the axis attached to A and the axis attached to B within the specified angle.

So, for the hip joint, because I didn't want equal range of motion in all directions but rather more front/out, I set the axis attached to A (bodyBone) to point more in the front/out direction.

Imagine a cone centered on the axis attached to A that keeps the axis attached to B within it.

@Silverlan
Copy link
Author

Silverlan commented May 2, 2023

Thank you, I ended up creating a debug visualization overlay for the IK solver in my program to help visualize what's going on. In general it works just fine, but there are a few cases where the behavior is not what I would expect:
https://user-images.githubusercontent.com/3609960/235695071-bf907054-d943-45a5-9094-11d6ee0fa664.mp4

In this case there is a simple IK chain with 5 bones: Hip, UpperLeg, LowerLeg, Foot, Toe.
The Hip bone is locked and the Toe bone has a state control for moving it around. There are 4 joint sets to hold it all together:

  1. BallSocketJoint +SwingLimit +TwistLimit between Hip and UpperLeg (Anchor is UpperLeg position)
  2. BallSocketJoint +RevoluteJoint +SwingLimit between UpperLeg and LowerLeg (Anchor is LowerLeg position)
  3. BallSocketJoint +SwingLimit +Twist Limit between LowerLeg and Foot (Anchor is Foot position)
  4. BallSocketJoint +AngularJoint between Foot and Toe (this should act like a fixed constraint)

pragma_y9RMi7uYql
The two cones represent the allowed swingspan for the BallSocketJoints (1. and 3.). The circle sector represents the swing span for the RevoluteJoint (2.). The light blue lines represent axisB in each case.

In the first pose in the video, everything works as it should:
mpv_1VPSpAN6gz
The blue lines are all near the edge of the allowed swing span for each joint, so it can't move any further in those directions.

Unfortunately in the second pose it kind of falls apart:
mpv_Nmtd105vWw
You can see that the revolute joint still has room for movement so it should be possible to move the toe much closer to the target position, however the solver refuses to move any further in that direction and the simulation becomes extremely unstable.
The number of solver iterations is set to 100, which should be sufficient I think.

It also becomes unstable in other configurations, though I'm not sure why:
https://user-images.githubusercontent.com/3609960/235672214-45ff4afb-e796-4d69-b1e5-bf0a326650d4.mp4

Any ideas what could cause this? I'm not sure what to look for.

@RossNordby
Copy link
Member

however the solver refuses to move any further in that direction

If I had to guess, the control is attempting to maintain an orientation which is incompatible with bending the leg more. Controlling only the linear degrees of freedom would test this.

simulation becomes extremely unstable.

I'd bet it's related to the twist limit. Twist related constraints have a singularity when the twist axes line up in reverse. v1 and bepuik didn't handle that singularity super duper gracefully, especially when combined with how it handles spring stiffness tuning.

Disabling the twist limits would test this. To avoid it, swing limits pushing the configuration away from the twist limit singularity would help.

The number of solver iterations is set to 100, which should be sufficient I think.

Is that all three iteration counts?

  1. VelocitySubiterationCount
  2. ControlIterationCount
  3. FixerIterationCount

Increasing the ControlIterationCount in combination with reducing the TimeStepDuration should be the most powerful stabilizer if I remember correctly. With lots of control/fixer iterations, you typically don't need tons of velocity subiterations, but bumping them all up to impractical levels can sometimes tell you if the simulation is fundamentally unstable due to configuration or if it's just a merely harder configuration.

Probably not actually necessary if other issues are resolved, though.

@Silverlan
Copy link
Author

Thank you for the tips, after changing the iteration values (and time step), I haven't seen any instability anymore:

m_solver->SetTimeStepDuration(0.01f);
m_solver->ControlIterationCount = 100;
m_solver->FixerIterationCount = 100;
m_solver->VelocitySubiterationCount = 100;

It is now way too slow for a real-time simulation, but I should be able to tweak the values to find a good balance through experimentation.

Unfortunately the issue where it refuses to exhaust the joint limits still remains. After some testing I've been able to narrow it down, it looks like the culprit is actually the state control:
https://github.com/bepu/bepuphysics1/assets/3609960/d1cbcc31-5443-4261-8dec-7e96a9b07493
(Please excuse the low framerate, it's due to the high iteration counts.)

In this example there are the following constraints:

  1. BallSocketJoint +RevoluteJoint +SwingLimit between Hips and UpperLeg
  2. BallSocketJoint +RevoluteJoint +SwingLimit between UpperLeg and LowerLeg
  3. BallSocketJoint +AngularJoint between LowerLeg and Foot
  4. BallSocketJoint +AngularJoint between Foot and Toe

I.e. the toe and foot are fixed, and the lower and upper leg bones each have a hinge constraint. No twist limits involved.
The drag/state control is assigned to the toe.

The drag control in the first half of the video behaves how you'd expect, but I don't understand the behavior of the state control. The description for it says:

Constrains an individual bone in an attempt to reach some position and orientation goal.

As I understand it, it should just try to solve the IK in such a way that it keeps the bone's original rotation relative to its parent(?). But to me it seems like it should be able to do so, while still being able to get much closer to the effector position than it does:
firefox_JlyXecrczA

Am I misunderstanding the state control?

As an aside, how different is the constraint system in bepuphysics2 compared to this ik system? Would it be possible to 'merge' the improvements into the ik system, without having to do large-scale rewrites and without having to take a deep dive into the intricacies of IK, or are the two too fundamentally different?

@RossNordby
Copy link
Member

As I understand it, it should just try to solve the IK in such a way that it keeps the bone's original rotation relative to its parent(?).

Ah, there's the issue: it's absolute orientation, not parent relative orientation. "Controls" operate on only one target bone- if you want to maintain the relationship between two bones, you'd probably want the IKAngularJoint.

As an aside, how different is the constraint system in bepuphysics2 compared to this ik system? Would it be possible to 'merge' the improvements into the ik system, without having to do large-scale rewrites and without having to take a deep dive into the intricacies of IK, or are the two too fundamentally different?

They're similar in that they're both implemented with conceptually similar solvers, but it's quite different in terms of how you interact with the solver and there is no shared code. A "merge" would realistically be just "make a version of a bepuik-like API that wraps bepuphysics2." Which isn't impossible, for what it's worth- it'd probably fit in a bepuphysics2 demo. (I may do that at some point, but definitely not within a week.)

@Silverlan
Copy link
Author

I'm going to stick with the current implementation for now, but there's one more thing I'd like to know:

I would like to replicate this behavior using bepuik:
https://github.com/bepu/bepuphysics1/assets/3609960/00423468-c38c-4be0-a151-a18207071f17

In this example, there is one control for the hand bone, and one for the elbow. The elbow control is more of a "suggestion" though, the solver always prioritizes trying to reach the control position for the hand, so moving the elbow control has no effect on the hand.

I tried a similar setup with bepuik:
https://github.com/bepu/bepuphysics1/assets/3609960/a9182bf7-9e69-4b5a-aad6-61b9e5a7a564

It's just a few ball socket joints, a drag control for the elbow and a state control for the hand (no limits).
Since both controls are treated equally, moving the elbow control also affects the position of the hand, because it tries to reach both positions at the same time.

Is it possible to change the behavior to mimic that of the first video?
I could probably create two solvers, the first with only the hand control, and the second with only the elbow control (and the hand bone locked), but maybe there's a more elegant (and less computationally expensive) solution?

@RossNordby
Copy link
Member

RossNordby commented May 30, 2023

You can set bone.Pinned = true to make it impossible for constraints to change the pose of a bone. A more forgiving version would be to modify relative strengths of controls.

The blender bepuik integration also did things like doing IK simulation down the bone hierarchy to the deepest defined control, while treating bones beyond that point as 'peripheral' and handled with simple FK. Plus some configuration options to change the behavior as needed. You can do things like that without needing multiple solvers running simultaneously; you can just add/remove on demand.

@Silverlan
Copy link
Author

The behavior you get from pinning is what I'm looking for, but of course that only works as long as the target position is actually reachable:
https://github.com/bepu/bepuphysics1/assets/3609960/35c8467b-51ca-473b-85be-ae134bc6c626

A more forgiving version would be to modify relative strengths of controls.
I didn't see a strength property for controls, would that be the MaximumForce? I played around with that, but it didn't seem to have any effect, and looking at the source code, it doesn't actually appear to be used anywhere in the ik system. ( https://github.com/search?q=repo%3Abepu%2Fbepuphysics1+MaximumForce++path%3A%2F%5EBEPUik%5C%2F%2F&type=code )

@RossNordby
Copy link
Member

RossNordby commented May 31, 2023

of course that only works as long as the target position is actually reachable:

If the concern is the freakout behavior, decrease the IKSolver.TimeStepDuration and, if necessary, increase the IKSolver.ControlIterationCount and/or IKSolver.FixerIterationCount or decrease the rigidity of the involved constraints.

The freakout is caused by the simulation not converging to a solution. Shortening the time step duration is the strongest stabilizer for that kind of failure, while reducing rigidity (where acceptable for the use case) simply makes the simulation easier to solve and more likely to converge.

If you're concerned about the pin being movable beyond the limit of the character's reach, that's more fundamental; pins are inviolable. The state control would be necessary to avoid that (along with whatever tuning to the simulation is necessary to reach the desired stability).

I didn't see a strength property for controls,

DragControl.LinearMotor.Rigidity, or StateControl.(Linear/Angular)Motor.Rigidity.

it doesn't actually appear to be used anywhere in the ik system.

Getting/setting the maximum force is an abstract property that is used to change the control's internal constraint values. For example: https://github.com/bepu/bepuphysics1/blob/master/BEPUik/DragControl.cs#L65

Also, if solver.AutoscaleControlImpulses is on, control maximum force values will be auto-configured by the solver, so your changes will be overwritten.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants