Data driven character simulation

Show what you made with Bullet Physics SDK: Games, Demos, Integrations with a graphics engine, modeler or any other application
Post Reply
User avatar
drleviathan
Posts: 849
Joined: Tue Sep 30, 2014 6:03 pm
Location: San Francisco

Data driven character simulation

Post by drleviathan »

I've been evaluating the Bullet physics engine for use in a virtual world project (highfidelity.io). In particular I wanted to find out if Bullet could simulate an articulated character. To that end I started with the 2.82 Ragdoll demo and added a custom internalTickCallback() that pulls the collection of RigidBody's together.

The algorithm was inspired by the Dynamo Data Driven Character project from Williams College: http://graphics.cs.williams.edu/papers/DynamoVGS06/

Specifically it uses verlet relaxation to transfer momentum between neighboring bones. The target rotations are achieved by slerping the RigidBody transforms directly, rather than applying torques (this causes a few side effects such as sudden penetrations and possibly tunneling). The character is kept upright using the "zero inverse inertia" hack on the root bone (the character cannot be knocked over).

I've posted a short video here: https://www.youtube.com/watch?v=nxGvnoQZdko

The source code can be downloaded from here: https://docs.google.com/a/highfidelity. ... zk2TkpwMFk

If you want to play with it just unpack the tarfile into the normal RagdollDemo directory. You need to add a new constraint type called NULL_CONSTRAINT_TYPE .../src/BulletDynamics/ConstraintSolver/btTypedConstraint.h and rebuild the physics engine and Demos.

The character can be steered using the 'awsd' keys which I hijacked from their normal Demo-wide behavior:

s = stop walk animation
w = start walk animation
a = turn left
d = turn right

Edit: added more instructions on how to successfully build.
Last edited by drleviathan on Tue Nov 04, 2014 12:00 am, edited 1 time in total.
StabInTheDark
Posts: 29
Joined: Sat May 18, 2013 1:36 am
Location: NY
Contact:

Re: Data driven character simulation

Post by StabInTheDark »

I tried to rebuild the demo using your code and ran into a little problem.
Could you tell me where you defined "NULL_CONSTRAINT_TYPE"?

I am impressed with the results you are getting in your video.
Are you planning on adding any other data driven motions besides walking?
Would you mind if I copied your method for my project?

I would like to add it to the Ragdoll I have working in Dark Basic Professional(DirectX).
http://www.youtube.com/watch?v=N93j9ZoGbok
User avatar
drleviathan
Posts: 849
Joined: Tue Sep 30, 2014 6:03 pm
Location: San Francisco

Re: Data driven character simulation

Post by drleviathan »

@StabInTheDark, oh right I forgot that I had to hack the Bullet source code for NULL_CONSTRAINT_TYPE. I just added it near the end of the enumerated types in .../src/BulletDynamics/ConstraintSolver/btTypedConstraint.h, right above MAX_CONSTRAINT_TYPE. The reason I added that custom constraint was because I couldn't get the collision filtering to work after following the directions on the wiki. Since I had witnessed the pairwise collision filtering working for constraints I decided to add a custom no-op constraint to get the pairwise filtering as the primary feature, rather than the side-effect.

I also forgot to mention: I hacked the Demo code to render the MultiSphereShape differently -- to render a line of spheres whenever the shape only had two spheres. I used the MultiSphere shape for everything so I could avoid calculating an extra transform for capsules, and also so that the bones would collide like tapered capsules.

When I watched my video again I realized that I had left the gravity at a low value -- the character is walking in a 3 m/sec^2 gravitational field. I doubled it up to 6 m/sec^2 but then the character had trouble achieving its target animation, which is probably why I had left the gravity so low. So I decreased the BONE_ROTATION_TIMESCALE down to 0.025 sec and then it was able to walk again.

I'm a little dissatisfied with how I had to slave the bone rotations -- basically I slerp them toward their target rotation. Perhaps I'll revisit this problem in a later version. What I'd really like to do is get a character simulated using the Featherstone algorithms, but I think this might be good enough for a start.

Generating the target animation state is outside the scope of what I'm actually aiming for, so the walk animation was purely for fun and demonstration -- I don't have any plans on adding more animations. The walk was achieved by adding hand-tuned rotation oscillators to select joints. I make the avatar stop by simply attenuating the amplitude of all oscillators to zero over some time period.

Yes, the code is free to use for any purpose whatsoever, basically under the same license of the Demo code itself since that is what I started with. The only other bit I pulled in was a bit of code from the Bullet wiki.
StabInTheDark
Posts: 29
Joined: Sat May 18, 2013 1:36 am
Location: NY
Contact:

Re: Data driven character simulation

Post by StabInTheDark »

Thanks for your quick reply, I got the demo running once I added the NULL_CONSTRAINT_TYPE.
I also forgot to mention: I hacked the Demo code to render the MultiSphereShape differently -- to render a line of spheres whenever the shape only had two spheres. I used the MultiSphere shape for everything so I could avoid calculating an extra transform for capsules, and also so that the bones would collide like tapered capsules
What did you change for the multiSphereShape? My demo is not rendering the character the same as in your video.

Here is a link to a project on Locomotion of a character using Bullet.
It has the source code link on the page. I plan on incorporating this into my project,
unfortunately I have not found the time too. Maybe it can help you with your goal.

http://animation.comp.nus.edu.sg/locotest.html
User avatar
drleviathan
Posts: 849
Joined: Tue Sep 30, 2014 6:03 pm
Location: San Francisco

Re: Data driven character simulation

Post by drleviathan »

I dug around and rediscovered where I had hacked the Demo draw code. Replace the relevant section of code in .../Demos/OpenGL/GL_ShapeDrawer.cpp with this block:

Code: Select all

            case MULTI_SPHERE_SHAPE_PROXYTYPE:                                                                      
              {                                                                                                       
                  const btMultiSphereShape* multiSphereShape = static_cast<const btMultiSphereShape*>(shape);         
                                                                                                                      
                  btTransform childTransform;                                                                         
                  childTransform.setIdentity();                                                                       
                                                                                                                      
                                                                                                                      
                  int numSpheres = multiSphereShape->getSphereCount();
                  for (int i = multiSphereShape->getSphereCount()-1; i>=0;i--)                                        
                  {                                                                                                   
                      btSphereShape sc(multiSphereShape->getSphereRadius(i));                                         
                      childTransform.setOrigin(multiSphereShape->getSpherePosition(i));                               
                      ATTRIBUTE_ALIGNED16(btScalar) childMat[16];                                                     
                      childTransform.getOpenGLMatrix(childMat);                                                       
                      drawOpenGL(childMat,&sc,color,debugMode,worldBoundsMin,worldBoundsMax);                         
                  }                                                                                                   
                  if (numSpheres == 2)
                  {
                      // we'll draw a line of spheres from one end to the other
                      // but first we compute the number of spheres we want...
                      float startRadius = multiSphereShape->getSphereRadius(0);
                      float endRadius = multiSphereShape->getSphereRadius(1);
                      float averageRadius = 0.5f * (startRadius + endRadius);
                      const float SOMETHING_SMALL = 0.001f;
                      if (averageRadius > SOMETHING_SMALL) {
                          btVector3 startPosition = multiSphereShape->getSpherePosition(0);
                          btVector3 endPosition = multiSphereShape->getSpherePosition(1);
                          float distance = (startPosition - endPosition).length();
                          int numRenderedSpheres = (int)(2.0f * distance / averageRadius);
                          if (numRenderedSpheres > 2) {
                              float dx = distance / (float)(numRenderedSpheres - 1);
                              float dr = (endRadius - startRadius) / (float)(numRenderedSpheres - 1);
                              btVector3 direction = (endPosition - startPosition) / distance;
                              for (int i = 1; i < numRenderedSpheres - 1; ++i) {
                                  float radius = startRadius + (float)i * dr;
                                  btVector3 pos = startPosition + ((float)i * dx) * direction;
                                  btSphereShape sc(radius);
                                  childTransform.setOrigin(pos);
                                  ATTRIBUTE_ALIGNED16(btScalar) childMat[16];
                                  childTransform.getOpenGLMatrix(childMat);
                                  drawOpenGL(childMat,&sc,color,debugMode,worldBoundsMin,worldBoundsMax);
                              }
                          }
                      }
                  }
  
                  break;
              }
User avatar
drleviathan
Posts: 849
Joined: Tue Sep 30, 2014 6:03 pm
Location: San Francisco

Re: Data driven character simulation

Post by drleviathan »

Thanks for the link to the LocoTest project and paper.

My method doesn't use normal constraints between the character bones. Instead it uses verlet relaxation and other methods to push the bones around and maintain the constraints.

In the beginning I had tried using btFixedConstraints between the character bones since this was the simpler approach. Unfortunately the simulation was unstable for the number of joints I wanted and timesteps I was taking (although I note that it was stable when applied to the original ragdolls in the demo (11 pieces, 10 joints)). Meanwhile, even in the stable zone the constraints would tend to push the pivot points slightly apart, which was something I wanted to minimize.

After seeing the tiny timesteps that they used in LocoTest it occurs to me that I didn't fully explore the the small timestep limit of the regular constraints, nor have I tested how my system performs at such small steps. I should do that.
Post Reply