Constraints between kinematic and dynamic rigid bodies

gjaegy
Posts: 178
Joined: Fri Apr 18, 2008 2:20 pm

Constraints between kinematic and dynamic rigid bodies

Post by gjaegy »

Hi,

I'm facing an issue with a chain of dynamic rigid bodies, linked together with 6DOFSpring2 constraints, and linked to a kinematic rigid body using the same constraint type. I've spent days trying to solve it, unsuccessfully, so I thought I would ask for some help :/

When the dynamic object (the helicopter) is not moving, everything looks perfect:
kinematic_static.jpg
However, as soon as the kinematic object moves, the chain seems to "detach" from the kinematic object, and seems to travel further from what one would expect (1 or 2 meters too far). The constraint seems to relax quite heavily (while the constraints used to link dynamic rigid bodies look fine):
kinematic_moving.jpg
I've tried to change the mass of all objects (1:1 ratio, other ratio, 0 for kinematic, non-zero for kinematic, other solver, smaller time step), however, whatever I've tried, the issue is still present. The faster the helicopter moves, the bigger the "gap".

According to the debug draw, the constraints seems to be located at a fairly right position, but the rigid bodies are not. They seem to move faster forwards than the kinematic object itself (i.e. the offset is always in the movement direction).

Any idea how I could reduce that gap ? Thanks a lot !
You do not have the required permissions to view the files attached to this post.
User avatar
drleviathan
Posts: 849
Joined: Tue Sep 30, 2014 6:03 pm
Location: San Francisco

Re: Constraints between kinematic and dynamic rigid bodies

Post by drleviathan »

The problem is: for each substep constraints are solved prior to moving kinematic objects. If you examine the implementation of btDiscreteDynamicsWorld::stepSimulation() you'll see for each substep: btDiscreteDynamicsWorld::internalSingleStepSimulation() is called before btDiscreteDhnaicsWorld::synchronizeMotionStates(). The constraints are solved in the former, kinematic objects are moved in the latter.

A solution is definitely NOT to swap the order of those two calls. The MotionStates of dynamic objects need to be synchronized AFTER they move.

I can think of two solutions to try:

(1) Hack your bullet library like so: add btDiscreteDynamicsWorld::synchronizeKinematicMotionsStates() which works as named. Call it before btDiscreteDynamicsWorld::internalSingleStepSimulation(). You probably don't even need to modify btDiscreteDhnaicsWorld::synchronizeMotionStates() since any movement it would apply to the kinematic rigid body would just be redundant, however if you have A LOT of kinematic objects moving around then you would have an opportunity to optimize out the redundant work. BTW for the record (e.g. for future audience who might not know)... in whatever MotionState you're using to move your kinematic objects be sure to also set their velocities to agree with whatever is their changing transform --> for more correct collision results when dynamic objects hit them.

(2) Write a custom Action to dynamically offset the constraint's kinematic-object-relative transform each substep. In other words: derive from btActionInterface and implement Action::updateAction() to measure the future world-frame position of the parent kinematic object, measure the difference, transform that into the correct local-frame, and then adjust the constraints corresponding offset transform accordingly. For each kinematic object that has a constraint hanging off of it: instantiate your Action, give it enough context to get its work done (e.g pointers to the relevant kinematic object and constraint) and add it to the world. This method would require you to get the math right, which is always tricky.
gjaegy
Posts: 178
Joined: Fri Apr 18, 2008 2:20 pm

Re: Constraints between kinematic and dynamic rigid bodies

Post by gjaegy »

Hi drleviathan,

Thanks a lot for your answer, I appreciate it.

Actually, I had to deliver so managed to find a solution by basically getting rid of the constraint between the kinematic object and the first (top) chain element. I'm now updating the position/orientation of the first chain manually, in addition to the angular and linear velocities, according to the kinematic object position/orientation and velocities, after each physics update step. Which might basically lead to the same results as your suggestions (mathematically), I guess.

This solutions works well, at least, it produces a nice and smooth simulation, even with a standard 16.6ms time step (which is great).

I will however consider your two options since they look a bit cleaner. It now makes things clearer and explain the effects I was seeing. I didn't see that btDiscreteDynamicsWorld::synchronizeKinematicMotionsStates() call in the code and miss that one completely.

Option 1 sounds the easiest/quickest to implement indeed, however, I was wondering what are the consequences of adding/moving that btDiscreteDynamicsWorld::synchronizeKinematicMotionsStates() call prior to stepping the simulation (by consequences, I mean difference with simulation results, not tiny performance hit from the added call) ?

Thanks again for taking the time to answer !

Cheers,
Greg
User avatar
drleviathan
Posts: 849
Joined: Tue Sep 30, 2014 6:03 pm
Location: San Francisco

Re: Constraints between kinematic and dynamic rigid bodies

Post by drleviathan »

...what are the consequences of adding/moving that btDiscreteDynamicsWorld::synchronizeKinematicMotionsStates() call prior to stepping the simulation... ?
It isn't that we would be calling btDiscreteDynamicsWorld::synchronizeKinematicMotionsStates() before stepping the simulation. Rather, we would just be swapping the order of sub-parts that happen during the step. The kinematic advance would happen prior to solving constraints, integrating dynamic objects forward, and execution of Actions. Kinematic objects are usually not governed by the simulation itself, but by motion dictated from outside logic (e.g. by whatever the KinematicMotionState is doing) so it doesn't really matter to them whether they are updated first or second. Dynamic objects, on the other hand, can be moved by collisions with kinematic which means... in the modified system dynamic objects will use the current substep's notion of kinematic transform+velocity rather than those from the previous substep. I can't think of likely ways this would break a game simulation however perhaps I lack imagination.

An example of a kinematic object that cares about simulation state would be something like the btKinematicCharacterController, which can "bump" into obstacles in the world. However, its collision logic happens as part of an Action rather than a MotionState, so I would expect it to work as normal.
gjaegy
Posts: 178
Joined: Fri Apr 18, 2008 2:20 pm

Re: Constraints between kinematic and dynamic rigid bodies

Post by gjaegy »

It isn't that we would be calling btDiscreteDynamicsWorld::synchronizeKinematicMotionsStates() before stepping the simulation. Rather, we would just be swapping the order of sub-parts that happen during the step.
I got that point, didn't express myself very well.

So, shouldn't that swapped order be the default implementation actually ?
User avatar
drleviathan
Posts: 849
Joined: Tue Sep 30, 2014 6:03 pm
Location: San Francisco

Re: Constraints between kinematic and dynamic rigid bodies

Post by drleviathan »

So, shouldn't that swapped order be the default implementation actually ?
Yes, I would think so, since it would fix the problem you were seeing while introducing minimal undesired behavior changes. However, I doubt Erwin would accept such a change if a PR was created. There is already a large backlog of submitted PRs (count=117 as I type this), many of which look good. He probably doesn't have enough time/resources to test the proposed changes. Perhaps the pressure will build until someone with the time and inclination decides to fork Bullet and manage a modern version.
gjaegy
Posts: 178
Joined: Fri Apr 18, 2008 2:20 pm

Re: Constraints between kinematic and dynamic rigid bodies

Post by gjaegy »

I see... :/

I just had a look at the code. Actually, the current implementation of btDiscreteDynamicsWorld::synchronizeSingleMotionState() starts with the following line:
if (body->getMotionState() && !body->isStaticOrKinematicObject())
That means, kinematic objects are not updated in that call at all in the current implementation.

That seems to be done in btDiscreteDynamicsWorld::saveKinematicState(), which is currently already called before the internalSingleStepSimulation() call.
User avatar
drleviathan
Posts: 849
Joined: Tue Sep 30, 2014 6:03 pm
Location: San Francisco

Re: Constraints between kinematic and dynamic rigid bodies

Post by drleviathan »

Oh hey, you're right! And btDiscreteDynamicsWorld::saveKinematicState() is called once per step rather than per substep. This means idea (1) would NOT solve your original problem, and also reintroduces the original mystery: Why would the first constraint relax so much?

I understand you tried different masses for the kinematic helicopter and the problem persisted, but was the problem also invariant? That is, changing the mass ratio didn't grow or shrink the gap? If the constraint solver thinks the helicopter should "respond" to the tension on it (but it doesn't because it is kinematic) then I could see how the constraint would relax too much, but if this were the case then I would expect increasing the relative mass of the helicopter would inform the constraint solver to reduce the gap, but I assume you fiddled with the mass ratios and would have noticed if this were happening.

I'm stumped, but at least you have a workaround, which looks good to me BTW. It sounds like an explicit enforcement of a particular "fixed" constraint and would help the constraint solver reach a more correct solution for the rest of them.
gjaegy
Posts: 178
Joined: Fri Apr 18, 2008 2:20 pm

Re: Constraints between kinematic and dynamic rigid bodies

Post by gjaegy »

I've played with the masses a lot, and no, this didn't really fixed the instability/wobbling issue (but I guess it had an impact on the gap).

So I'll keep my existing solution for now ;)