• Runtimes
  • Spine + Physics (box2d) - with more accurate collisions

Hi All,

There's been a few posts about similar questions, but none completely address my question:
How to combine spine animations with box2d while allowing the physical model to affect the animation AND allowing the animation to affect the physical model.

Most of the ideas I've seen describe the solution in which the animation affects the physical model, but not the other way around. This sample code is such an example: spine box2d: https://github.com/EsotericSoftware/spi ... Box2D.java
Video example: https://www.youtube.com/watch?v=YvEjbQO ... t=1h12m13s

This works great, but if the boy ran into a wall, there would be no automatic way to relay that back to the animation to prevent it from going through that wall.

So to deal with this, I'm considering one of the following solutions:

  • (My current favorite solution) Create a body for each attachment mesh and connect each body with a revolute joint. Designate one of the bodies (e.g. the torso) as the body that controls the world position of the animation - this body will not be updated to match the animation. The rest of the bodies are updated (by adjusting their joints) to match the spine animation. In the case of ragdoll, all spine-to-physics updates are turned off and instead physics-to-spine updates are used. Cons: seems complicated.

  • Create fixtures for each attachment. Problem with this is that box2d apparently doesn't like it when you move fixtures around and at the very least it's inefficient to recreate them on every frame.

  • Create fixtures for all possible positions of the animation and then activate/deactivate them on each frame accordingly. Seems like it would be too inaccurate.

  • Write my own physics using spine meshes as the object bounds. - I don't have the time =(

Would love some feedback, suggestions, ideas,
Thanks

P.S. Here are some other forum posts for reference:
Unity:
viewtopic.php?f=7&t=2470&p=11991&hilit=box2d#p11991
spine box2d: https://github.com/EsotericSoftware/spi ... Box2D.java
cocos2d-x specific:
viewtopic.php?f=11&t=467&p=1890&hilit=+box2d#p1890
viewtopic.php?f=11&t=870&p=4220&hilit=+box2d#p4220
attachment loader, rotation angles, sample code:
viewtopic.php?f=11&t=1215&p=5805&hilit=+box2d#p5805
alignment issues: viewtopic.php?f=11&t=2040&p=10218&hilit=box2d#p10218
more example of separate skeletons:
http://www.gamedev.net/topic/624738-con ... -matrices/

Related Discussions
...
3 months later

Hi, great summary of fusion between Box2D physics and spine animation. I see you wrote that 2 months ago, did you find something new or check any of your solutions? I am very interested in this subject as well.

Hi dagondev,

I ended up going with the first solution I mention above. Also, I discussed it more here: viewtopic.php?f=7&t=3090
I'm still learning as I go and trying to improve the solution, so I'd love to hear about other peoples' approaches. The solution I have now works well, but can be unstable (physically wobbly) at times.

20 days later

Hey Sheado,
I was reading your posts and saw that you had it working just not perfect. Would you be willing to share the code as to how you achieved this so I and others can see what you did and hopefully improve it?
Thank you.

7 days later

Option 1 is the way to go. Note the spine-unity runtime does this.

Sorry for the laggy replies,
@Nate - Thanks for confirming that option 1 is the way to go. One question - have you experienced stability issues as the physical bodies try to keep up with the animations?
@rpascal - I'm not really in a position to share code at the moment. It's not really generic enough to use in any scenario I think. If you like though, I can share specific snippets if there's anything you're stuck on - creating fixtures, creating joints, and rotating joints during gameplay time.

Sure, there are lots of ways it can be set up and lots of ways it can freak out. You might check out how Mitch sets thing up in Unity:
viewtopic.php?f=3&t=3318
He has a cape example somewhere where the cape from the "hero-mesh" project is controlled by physics.

Cool,
I'll check it out.
Thanks!

If you could share with me how you created the the fixtures and joints that would be amazing. If you could also share how you did the rotations that would also be amazing. Even if it is not really generic for every scenario I believe it will help a lot just being able to see how you did it.
Thank you!

Okay,
Warning - this is messy and unoptimized, but it could help you get started. Also, being that it's disorganized code, i'll be cutting out a lot of stuff that's specific to my game. I'm not the best with words, so hopefully my explanations below are not too convoluted. 🙂

So first...
Creating Fixtures and Joints
In order to avoid creating a fixture for every slot, we decide ahead of time which slots we want to map over to the physics world. This is good for performance optimization and for avoiding very mesh-based animation slots (such as wings or cloth).
Another important note - we designate one slot as the central slot. This will match up as the body part that we primarily will steer around and to which we would apply any necessary rotation forces for rotating the entire character. In this case our central slot is named "bodBounds."
Also, make sure to do all this after you've added the skeleton to the scene and placed it in its setup pose.

vector<string> slots = // place the slot names that you want to map to box2d
map<string,b2Body*> boneBodyMap; // to temporarily keep track of slot-to-body mappings
b2Body* bodyCenter = NULL;
struct imgInfo{...} // this is a class that holds the skeleton's bodies and joints for easy access later
for( string name : slots )
{
    spSlot* slot = node->findSlot(name.data());

if( slot )
{
       spSlot* slot = node->findSlot(name.data());

        if( slot )
        {
            
            float worldVertices[1000];
            float minX = FLT_MAX, minY = FLT_MAX, maxX = -FLT_MAX, maxY = -FLT_MAX;

            if (slot->attachment)
            {
                // Create body
                b2BodyDef bodyDef;
                bodyDef.type = b2_dynamicBody;
                bodyDef.position = b2Vec2(position.x,position.y);
                bodyDef.gravityScale = 0;
                bodyDef.bullet = isBullet;
                bodyDef.fixedRotation = isFixedRotation;
                b2Body* body = m_world->CreateBody(&bodyDef);
                body->SetUserData(imgInfo);
                imgInfo->bodies.push_back(body);
                boneBodyMap.insert(pair<string,b2Body*>(string(slot->bone->data->name), body));
                
                int verticesCount = 0;
                switch( slot->attachment->type )
                {
/* ... removed code... */
                        case SP_ATTACHMENT_SKINNED_MESH:
                        {
                            spSkinnedMeshAttachment* mesh = (spSkinnedMeshAttachment*)slot->attachment;
                            spSkinnedMeshAttachment_computeWorldVertices(mesh, slot, worldVertices);
                            verticesCount = mesh->uvsCount;
                            break;
                        }
                        case SP_ATTACHMENT_REGION:
                        {
                            if( verticesCount == 0 )
                            {
                                spRegionAttachment* attachment = (spRegionAttachment*)slot->attachment;
                                spRegionAttachment_computeWorldVertices( attachment, slot->bone, worldVertices );
                                verticesCount = 8;
                            }
                            // NOTE: pass-through here
                        }
                        case SP_ATTACHMENT_BOUNDING_BOX:
                            if( verticesCount == 0 )
                            {
                                spBoundingBoxAttachment* attachment = (spBoundingBoxAttachment*)slot->attachment;
                                spBoundingBoxAttachment_computeWorldVertices(attachment, slot->bone, worldVertices);
                                verticesCount = attachment->verticesCount;
                            }
                            // NOTE: pass-through here
                        case SP_ATTACHMENT_MESH:
                            if( verticesCount == 0 )
                            {
                                spMeshAttachment* mesh = (spMeshAttachment*)slot->attachment;
                                spMeshAttachment_computeWorldVertices(mesh, slot, worldVertices);
                                verticesCount = mesh->verticesCount;
                            }
                            
b2Vec2 shapePoints[8]; b2Vec2 previousStart, previousEnd; // box 2d doesn't like tons of vertices - so use if( verticesCount > 16 ) { /* we PolyPartition library for this - but there's a lot of room for optimization */ } else { // create the fixture using the world vertices b2FixtureDef fixtureDef; b2PolygonShape polygonShape; int iP = 0; for (int ii = 0; ii < verticesCount; ) { shapePoints[iP].x = worldVertices[ii]*node->getScaleX(); ++ii; shapePoints[iP].y = worldVertices[ii]*node->getScaleY(); ++ii; ++iP; } polygonShape.Set( shapePoints, verticesCount/2 ); fixtureDef.shape = &polygonShape; fixtureDef.density = density; fixtureDef.friction = friction; fixtureDef.restitution = restitution; fixtureDef.filter.categoryBits = PHYSICS_FILTER_CATEGORY_ENEMY; fixtureDef.filter.maskBits = PHYSICS_FILTER_MASK_ENEMY; setFilterBits(fixtureDef, aiDefinition->type); b2Fixture *fixture = body->CreateFixture(&fixtureDef); } break; } // sub-bodies get a low damping value so the whole character doesn't freak out when animating body->SetAngularDamping(0.1); body->SetLinearDamping(0.1); // locate our central body and store it for use later if( strcmp(slot->data->name,"bodBounds") == 0 ) { bodyCenter = body; } } } } //// Now to build the joints // TODO - optimize - a second pass like this is not necessary slots.erase(slots.begin()); // remove the central slot for( string name : slots ) { spSlot* slot = node->findSlot(name.data()); if( slot ) { b2Body* body = boneBodyMap.at(string(slot->bone->data->name)); b2Body* parentBody = boneBodyMap.at(string(slot->bone->parent->data->name)); b2RevoluteJointDef jointDef; jointDef.Initialize(body, parentBody, b2Vec2(position.x+slot->bone->worldX*node->getScaleX(), position.y+slot->bone->worldY*node->getScaleY())); jointDef.collideConnected = false; jointDef.enableMotor = true; jointDef.maxMotorTorque = 10000.0f; b2RevoluteJoint* joint = (b2RevoluteJoint*)m_world->CreateJoint(&jointDef); joint->SetUserData(slot); imgInfo->joints.push_back(joint); imgInfo->initialJointAngles.push_back(slot->bone->rotation); } } } /** removed code */ // Now assign damping values you want for the central body bodyCenter->SetAngularDamping(angularDamping); bodyCenter->SetLinearDamping(linearDamping); bodyCenter->SetUserData(imgInfo);
// adjust for current rotation for( b2Body* b : imgInfo->bodies ) { // note: negative rotation b->SetTransform(bodyCenter->GetPosition(), -CC_DEGREES_TO_RADIANS(node->getRotation())); }

Now you should have something like in the image below. This is a debug view of one of our bad guys. Outlines in blue are spine slot bounding boxes. In beige are the box2d fixtures. Note that we did not map all the body parts - just the torso, neck, and head.

Updating Animations - Rotations and Central Body Movement
At every frame, update the physics side to keep in sync with the animation:

vector<b2Joint*>& joints = imgInfo->joints;
vector<float>& initialJointAngles = imgInfo->initialJointAngles;
Node* node = imgInfo->sprite;
b2Body* body = imgInfo->body;
spSlot* bodySlot = imgInfo->bodySlot;

////////////// synchronize joints with animation bones
for( int i = 0; i < joints.size(); ++i )
{
    b2Joint* j = joints.at(i);
    b2RevoluteJoint* joint = (b2RevoluteJoint*)j;
    spSlot* slot = (spSlot*)joint->GetUserData();
    float32 angleError = joint->GetJointAngle() + slot->bone->rotation/180.0f*M_PI - initialJointAngles.at(i)/180.0f*M_PI;
    if( node->getScaleY() < 0 )
        angleError = joint->GetJointAngle() - slot->bone->rotation/180.0f*M_PI + initialJointAngles.at(i)/180.0f*M_PI;
    
if( angleError > M_PI*2 || angleError < M_PI*-2 ) { // TODO - optimize while (angleError <= -M_PI) angleError += M_PI*2.0f; while (angleError > M_PI) angleError -= M_PI*2.0f; } // angular movement joint->SetMaxMotorTorque(10000000); joint->SetMotorSpeed(angleError*-FPS_SPINE_ANIMATIONS); } ////////////// synchronize bod movement spBone* bone = bodySlot->bone; float nodeAngle = imgInfo->sprite->getRotation()/180.0f*M_PI; float xx = bone->worldX*imgInfo->sprite->getScaleX() - imgInfo->previousX; float yy = bone->worldY*imgInfo->sprite->getScaleY() - imgInfo->previousY; float x = xx*sinf(nodeAngle) + yy*sinf(nodeAngle); float y = xx*cosf(nodeAngle) + yy*cosf(nodeAngle); imgInfo->previousX = bone->worldX*imgInfo->sprite->getScaleX(); imgInfo->previousY = bone->worldY*imgInfo->sprite->getScaleY(); // velocity to move the calculated distance in one frame float dx = x*FPS_SPINE_ANIMATIONS; float dy = y*FPS_SPINE_ANIMATIONS; b2Vec2 v = body->GetLinearVelocity(); // don't exceed desired velocity if( dx > 0 && v.x >= dx ) dx = 0; else if( dx < 0 && v.x <= dx ) dx = 0; if( dy > 0 && v.y >= dy ) dy = 0; else if( dy < 0 && v.y <= dy ) dy = 0; float mass = body->GetMass(); float impulseX = mass*dx; float impulseY = mass*dy; b2Vec2 linearImpulse(impulseX,impulseY); // linear movement body->ApplyLinearImpulse(linearImpulse, body->GetWorldCenter(), true);

Ragdoll
Once your animated character needs to ragdoll, stop updating the physics side. Let the physics take over and essentially apply its rotations to the animation instead.

Notes and stuff...
So this works well for us, but some characters' animations cause major stability problems. Synchronizing the central body is the cause of most of these issues. We like keeping that in though because it allows us to do things like synchronized hovering (synchronized with the wings of a character for example).

Okay, so that's all I have for now. I hope that this is remotely useful. If you have ideas for improvements, please let me know.
If time permits, I'll try to do this again with some video examples or something..
And while I'm at it, some self promotion :p
The game we're working on (almost at alpha): A Quiver of Crows - http://www.aquiverofcrows.com/

a month later

Hey...we're trying to follow your example, and we're getting close. One question: where do you get that FPS_SPINE_ANIMATIONS value from?

hey @mpmumau,
That's a const float I set to 30. If your game doesn't run at 30 FPS, you need to use a separate FPS value for the spine animations.

Cool, thanks man, yeah that's what I figured. We also have this weird offset problem...did you perhaps have anything like this?

Image removed due to the lack of support for HTTPS. | Show Anyway

No problem,
Glad to see others tackling this problem. Looks pretty cool what you got there btw..

I did have some offset issues while I was coding, but I don't remember the specific problem I had. It looks like your offset is off on your central body - maybe it's due to how you're using spMeshAttachment_computeWorldVertices? Maybe you're using the wrong slot as a reference point? Maybe your project setup in spine is different than mine (for example our root bone is centered in the central body).

Thanks man, both for the advice and the compliment! This is for sure the first problem that has us tearing out our hair on this game, haha. We'd be even further behind without your example to follow. It very well may be something to do with the root bone's location; I think I'll try experimenting with that to see if it cures the problem. When we figure out what's going wrong, I'll post up some tips for posterity...here's to likely days more of tweaking on this thing, haha! Gotta love Box2d!! (Which is clearly the bigger problem...Spine is awesome, and we love it! 🙂 Just wish we could directly set the rotation angles of the Box2d joints without relying on the motor 😛

hehe, yeah this problem has driven me crazy too (and still does sometimes) =]
good luck!

5 months later

sheado, I tried your code; but when I see the values of shapePoints; everything has the same value
see my screenshot.
I used bounding box with 4 vertices.
still get error.

if (slotH)
{
   if (slotH->attachment)
   {
      if (slotH->attachment->type == SP_ATTACHMENT_MESH)
      {
         mesh = (spMeshAttachment*)slotH->attachment;
         spMeshAttachment_computeWorldVertices(mesh, slotH, worldVertices);
         verticesCount = mesh->verticesCount;
         log("Active: SP_ATTACHMENT_MESH, %d", mesh->verticesCount);
      }
      else if (slotH->attachment->type == SP_ATTACHMENT_BOUNDING_BOX)
      {
         boundBox = (spBoundingBoxAttachment*)slotH->attachment;
         spBoundingBoxAttachment_computeWorldVertices(boundBox, slotH->bone, worldVertices);
         verticesCount = boundBox->verticesCount;
         log("Active: SP_ATTACHMENT_BOUNDING_BOX, %d", boundBox->verticesCount);
      }

  b2Vec2 gravity;
  gravity.Set(0.0f, -10.0f);
  worldPhysics = new b2World(gravity);
  worldPhysics->SetContinuousPhysics(false);

  b2Body *boxBody;
  b2BodyDef boxBodyDef;
  b2FixtureDef boxFixture;
  b2PolygonShape shapeConcave;

  boxBodyDef.type = b2BodyType(b2_staticBody);
  boxBodyDef.gravityScale = 0;
  boxBodyDef.bullet = false;
  boxBodyDef.fixedRotation = true;

  boxBody = worldPhysics->CreateBody(&boxBodyDef);

  b2Vec2 shapePoints[8] = {};
  
  // create the fixture using the world vertices;
  int32 iP = 0;
  for (int32 ii = 0; ii < verticesCount;)
  {
     shapePoints[iP].x = worldVertices[ii] * skelNodeA->getScaleX();
     ++ii;
     shapePoints[iP].y = worldVertices[ii] * skelNodeA->getScaleY();
     ++ii;
     ++iP;      

     log("Vertices count: %d, shapePoints: %d", ii, iP);
     if (ii == verticesCount)
     {
        log("After setting all vertices! Try set a polygonShape!");
        shapeConcave.Set(shapePoints, 8);
     }
  }

  boxFixture.shape = &shapeConcave;
  boxFixture.density = 10;
  boxFixture.friction = 0.8;
  boxFixture.restitution = 0.6;

  boxBody->CreateFixture(&boxFixture);
  boxBody->SetGravityScale(10);
  boxBody->SetAngularDamping(0.1);
  boxBody->SetLinearDamping(0.1);

   }
}

It was error when creating the shapeConcave.

shapeConcave.Set(shapePoints, 8);

I just notice that sheado never use these variables:

float minX = FLT_MAX, minY = FLT_MAX, maxX = -FLT_MAX, maxY = -FLT_MAX;

some users besides sheado use another approach: helg, milos1290.

  1. http://esotericsoftware.com/forum/Cocos2d-x-3-3-How-to-make-bone-to-follow-box2d-body-3952?

I am really confusing, which one I should trust in this forum.
and one more thing, the java code of I_Box2D.java seem very different with
spine library attached in cocos2d-x-3 above.
Is this forum alive?
Any admin who can guide cocos2d-x user?
Really, should we get a dead end like this?
I intend to buy your Spine Pro, but if the guides is nothing to read.
Then I'd not buy.
I'd try Creature.
hope they would give me free example and its library.

5 months later

Hey Sheado, Mpmumau,

I'd love to get this working for ground based characters where the body moves as one. In case you guys have time:

  • When let's say a foot hits a static object like a wall, won't box2D make an adjustment during the collision only for the foot?
  • Assuming you only update the skeleton position based on central body forces, how would you relay the above back to the skeleton?
  • Is there a way to lock the joints so that the body moves as one, and reacts to collisions as such?

Cheers!
Adrian

Hi Adrian,

Good questions - I wish I had time to implement a solution for each.

* When let's say a foot hits a static object like a wall, won't box2D make an adjustment during the collision only for the foot?

If you rig the box2d representation of your character with joints, then the forces do transfer from the foot to other connected body parts. Ideally, when a collision happens, you'd be able to detect it and have it override the animation - essentially flipping things around causing the physics to drive the animation. I haven't had time to implement this, but at least I've been able to gain the benefit of the characters responding to overall physical forces due to the joint-based setup.

* Assuming you only update the skeleton position based on central body forces, how would you relay the above back to the skeleton?

This part I haven't had time to implement. Theoretically you'd detect the collision and then instead of having the animation set the angle of the joints, you'd have the joints set the angles of the bones.

* Is there a way to lock the joints so that the body moves as one, and reacts to collisions as such?

Not sure what you mean here - but if you go with a joint based setup you get to have your character apply forces on physical objects, while still allowing physical objects to apply forces back onto the character.

:sun: