Box2D Forums

It is currently Sat May 18, 2013 4:01 am

All times are UTC - 8 hours [ DST ]




Post new topic Reply to topic  [ 6 posts ] 
Author Message
PostPosted: Sun Nov 27, 2011 10:09 am 
Offline

Joined: Sun Nov 27, 2011 9:43 am
Posts: 3
So I was looking for a way to get certain dynamic bodies to push others out of the way but not the other way around. Essentially like the way kinematic bodies work, but I want it to be dynamic. So let me explain why I want to do this. I am working on a 2d platform puzzle game where the character can move around in a manner similar to the way Mario moves. Now I have some boulders (big round heavy rocks) which the character should not be able to move just by pushing into them. He has to move them in other ways (e.g. set off an explosion next to it, etc..). So my character is a dynamic body and so is the boulder. I could almost achieve what I want by setting the mass of the boulder much higher than the character. But not quite because the character can still move it if he is insistent enough. I want it to be truly immovable from the character's point of view. So I don't think Box2D supports this out of the box, so I've made some changes to support it. I've hooked into the PreSolve callback and done something like this:

Code:
void CGameLayer::PreSolve(b2Contact* contact, const b2Manifold* oldManifold)
{
   b2Fixture* pFixtureA = contact->GetFixtureA();
   b2Fixture* pFixtureB = contact->GetFixtureB();
   b2Body* pBodyA = pFixtureA->GetBody();
   b2Body* pBodyB = pFixtureB->GetBody();
   if (pBodyA->GetType() == b2_dynamicBody && pBodyB->GetType() == b2_dynamicBody)
   {
      CAnimatedObject* pAnimatedObjectA = (CAnimatedObject*)pBodyA->GetUserData();
      CAnimatedObject* pAnimatedObjectB = (CAnimatedObject*)pBodyB->GetUserData();
      if (pAnimatedObjectA->GetObjectType() == eOT_Character && pAnimatedObjectB->GetObjectType() == eOT_Boulder)
      {
         contact->SetBodyBKinematic(true);
      }
      else if(pAnimatedObjectA->GetObjectType() == eOT_Boulder && pAnimatedObjectB->GetObjectType() == eOT_Character)
      {
         contact->SetBodyAKinematic(true);
      }
   }
}


This works similar to disabling contacts, note the "SetBodyAKinematic" & "SetBodyBKinematic" functions are new and added by myself. It effectively makes one of the bodies have infinite mass as far as this contact is concerned. I've attached the Box2D patch below which implements these new functions. I thought they might be useful for others and may be worth trying to get into the official Box2D code (not sure what the procedure for submitting patches is).

Code:
diff --git "a/C:\\Users\\Martin\\AppData\\Local\\Temp\\b2CF66D.tmp\\b2ContactSolver-8088ef-left.h" "b/C:\\Users\\Martin\\AppData\\Local\\Temp\\b2CF66C.tmp\\b2ContactSolver-cb91b7-right.h"
index 5064702..1415b6f 100644
--- "a/C:\\Users\\Martin\\AppData\\Local\\Temp\\b2CF66D.tmp\\b2ContactSolver-8088ef-left.h"
+++ "b/C:\\Users\\Martin\\AppData\\Local\\Temp\\b2CF66C.tmp\\b2ContactSolver-cb91b7-right.h"
@@ -49,6 +49,10 @@ struct b2ContactConstraint
    b2Mat22 K;
    b2Body* bodyA;
    b2Body* bodyB;
+   float32 invMassA;
+   float32 invIA;
+   float32 invMassB;
+   float32 invIB;;
    b2Manifold::Type type;
    float32 radius;
    float32 friction;


diff --git "a/C:\\Users\\Martin\\AppData\\Local\\Temp\\b2CF65B.tmp\\b2ContactSolver-8088ef-left.cpp" "b/C:\\Users\\Martin\\AppData\\Local\\Temp\\b2CF65A.tmp\\b2ContactSolver-cb91b7-right.cpp"
index 7928b62..143379b 100644
--- "a/C:\\Users\\Martin\\AppData\\Local\\Temp\\b2CF65B.tmp\\b2ContactSolver-8088ef-left.cpp"
+++ "b/C:\\Users\\Martin\\AppData\\Local\\Temp\\b2CF65A.tmp\\b2ContactSolver-cb91b7-right.cpp"
@@ -63,6 +63,10 @@ b2ContactSolver::b2ContactSolver(b2Contact** contacts, int32 contactCount,
       b2ContactConstraint* cc = m_constraints + i;
       cc->bodyA = bodyA;
       cc->bodyB = bodyB;
+      cc->invMassA = bodyA->m_invMass;
+      cc->invIA = bodyA->m_invI;
+      cc->invMassB = bodyB->m_invMass;
+      cc->invIB = bodyB->m_invI;
       cc->manifold = manifold;
       cc->normal = worldManifold.normal;
       cc->pointCount = manifold->pointCount;
@@ -73,6 +77,17 @@ b2ContactSolver::b2ContactSolver(b2Contact** contacts, int32 contactCount,
       cc->radius = radiusA + radiusB;
       cc->type = manifold->type;
 
+      if (contact->m_flags & b2Contact::e_bodyAKinematic)
+      {
+         cc->invMassA = 0.0f;
+         cc->invIA = 0.0f;
+      }
+      if (contact->m_flags & b2Contact::e_bodyBKinematic)
+      {
+         cc->invMassB = 0.0f;
+         cc->invIB = 0.0f;
+      }
+
       for (int32 j = 0; j < cc->pointCount; ++j)
       {
          b2ManifoldPoint* cp = manifold->points + j;
@@ -91,7 +106,7 @@ b2ContactSolver::b2ContactSolver(b2Contact** contacts, int32 contactCount,
          rnA *= rnA;
          rnB *= rnB;
 
-         float32 kNormal = bodyA->m_invMass + bodyB->m_invMass + bodyA->m_invI * rnA + bodyB->m_invI * rnB;
+         float32 kNormal = cc->invMassA + cc->invMassB + cc->invIA * rnA + cc->invIB * rnB;
 
          b2Assert(kNormal > b2_epsilon);
          ccp->normalMass = 1.0f / kNormal;
@@ -103,7 +118,7 @@ b2ContactSolver::b2ContactSolver(b2Contact** contacts, int32 contactCount,
          rtA *= rtA;
          rtB *= rtB;
 
-         float32 kTangent = bodyA->m_invMass + bodyB->m_invMass + bodyA->m_invI * rtA + bodyB->m_invI * rtB;
+         float32 kTangent = cc->invMassA + cc->invMassB + cc->invIA * rtA + cc->invIB * rtB;
 
          b2Assert(kTangent > b2_epsilon);
          ccp->tangentMass = 1.0f /  kTangent;
@@ -123,10 +138,10 @@ b2ContactSolver::b2ContactSolver(b2Contact** contacts, int32 contactCount,
          b2ContactConstraintPoint* ccp1 = cc->points + 0;
          b2ContactConstraintPoint* ccp2 = cc->points + 1;
          
-         float32 invMassA = bodyA->m_invMass;
-         float32 invIA = bodyA->m_invI;
-         float32 invMassB = bodyB->m_invMass;
-         float32 invIB = bodyB->m_invI;
+         float32 invMassA = cc->invMassA;
+         float32 invIA = cc->invIA;
+         float32 invMassB = cc->invMassB;
+         float32 invIB = cc->invIB;
 
          float32 rn1A = b2Cross(ccp1->rA, cc->normal);
          float32 rn1B = b2Cross(ccp1->rB, cc->normal);
@@ -170,10 +185,10 @@ void b2ContactSolver::WarmStart()
 
       b2Body* bodyA = c->bodyA;
       b2Body* bodyB = c->bodyB;
-      float32 invMassA = bodyA->m_invMass;
-      float32 invIA = bodyA->m_invI;
-      float32 invMassB = bodyB->m_invMass;
-      float32 invIB = bodyB->m_invI;
+      float32 invMassA = c->invMassA;
+      float32 invIA = c->invIA;
+      float32 invMassB = c->invMassB;
+      float32 invIB = c->invIB;
       b2Vec2 normal = c->normal;
       b2Vec2 tangent = b2Cross(normal, 1.0f);
 
@@ -200,10 +215,10 @@ void b2ContactSolver::SolveVelocityConstraints()
       float32 wB = bodyB->m_angularVelocity;
       b2Vec2 vA = bodyA->m_linearVelocity;
       b2Vec2 vB = bodyB->m_linearVelocity;
-      float32 invMassA = bodyA->m_invMass;
-      float32 invIA = bodyA->m_invI;
-      float32 invMassB = bodyB->m_invMass;
-      float32 invIB = bodyB->m_invI;
+      float32 invMassA = c->invMassA;
+      float32 invIA = c->invIA;
+      float32 invMassB = c->invMassB;
+      float32 invIB = c->invIB;
       b2Vec2 normal = c->normal;
       b2Vec2 tangent = b2Cross(normal, 1.0f);
       float32 friction = c->friction;
@@ -573,10 +588,10 @@ bool b2ContactSolver::SolvePositionConstraints(float32 baumgarte)
       b2Body* bodyA = c->bodyA;
       b2Body* bodyB = c->bodyB;
 
-      float32 invMassA = bodyA->m_mass * bodyA->m_invMass;
-      float32 invIA = bodyA->m_mass * bodyA->m_invI;
-      float32 invMassB = bodyB->m_mass * bodyB->m_invMass;
-      float32 invIB = bodyB->m_mass * bodyB->m_invI;
+      float32 invMassA = bodyA->m_mass * c->invMassA;
+      float32 invIA = bodyA->m_mass * c->invIA;
+      float32 invMassB = bodyB->m_mass * c->invMassB;
+      float32 invIB = bodyB->m_mass * c->invIB;
 
       // Solve normal constraints
       for (int32 j = 0; j < c->pointCount; ++j)


diff --git "a/C:\\Users\\Martin\\AppData\\Local\\Temp\\b2CF63A.tmp\\b2Contact-8088ef-left.h" "b/C:\\Users\\Martin\\AppData\\Local\\Temp\\b2CF639.tmp\\b2Contact-cb91b7-right.h"
index 14e6120..e26f685 100644
--- "a/C:\\Users\\Martin\\AppData\\Local\\Temp\\b2CF63A.tmp\\b2Contact-8088ef-left.h"
+++ "b/C:\\Users\\Martin\\AppData\\Local\\Temp\\b2CF639.tmp\\b2Contact-cb91b7-right.h"
@@ -82,6 +82,12 @@ public:
    /// Has this contact been disabled?
    bool IsEnabled() const;
 
+   /// Set one of the bodies in this contact to be kinematic pushing
+   /// the other out of the way. This can be used inside the pre-solve
+   /// contact listener.
+   void SetBodyAKinematic(bool flag);
+   void SetBodyBKinematic(bool flag);
+
    /// Get the next contact in the world's contact list.
    b2Contact* GetNext();
    const b2Contact* GetNext() const;
@@ -122,6 +128,12 @@ protected:
       // This bullet contact had a TOI event
       e_bulletHitFlag      = 0x0010,
 
+      // BodyA can be made kinematic for this contact
+      e_bodyAKinematic   = 0x0020,
+
+      // BodyB can be made kinematic for this contact
+      e_bodyBKinematic   = 0x0040,
+
    };
 
    /// Flag this contact for filtering. Filtering will occur the next time step.
@@ -204,6 +216,30 @@ inline bool b2Contact::IsTouching() const
    return (m_flags & e_touchingFlag) == e_touchingFlag;
 }
 
+inline void b2Contact::SetBodyAKinematic(bool flag)
+{
+   if (flag)
+   {
+      m_flags |= e_bodyAKinematic;
+   }
+   else
+   {
+      m_flags &= ~e_bodyAKinematic;
+   }
+}
+
+inline void b2Contact::SetBodyBKinematic(bool flag)
+{
+   if (flag)
+   {
+      m_flags |= e_bodyBKinematic;
+   }
+   else
+   {
+      m_flags &= ~e_bodyBKinematic;
+   }
+}
+
 inline b2Contact* b2Contact::GetNext()
 {
    return m_next;
@@ -239,4 +275,4 @@ inline void b2Contact::FlagForFiltering()
    m_flags |= e_filterFlag;
 }
 
-#endif
+#endif
\ No newline at end of file



Top
 Profile  
 
PostPosted: Sun Nov 27, 2011 8:59 pm 
Offline

Joined: Tue Jun 24, 2008 8:25 pm
Posts: 1515
Location: Tokyo
This sounds kinda similar to the problem where two players should be able to move around but not push each other around. Although I have not tried it I was thinking that if each player had a kinematic body with identical fixtures which followed it around, and the collision filter of this kinematic was set so that it only collided with the dynamic body of other players, it might work.
In your case, the boulder would have a kinematic of the same shape, and any other body which should not be able to move it (eg. your player) would have its collision filter set to collide with that kinematic.
Hmm... I wonder if the kinematic would need to be made a little bigger than the dynamic it shadows...

I'm guessing your method would cover this case too.


Top
 Profile  
 
PostPosted: Mon Nov 28, 2011 12:10 pm 
Offline

Joined: Sun Nov 27, 2011 9:43 am
Posts: 3
Yes similar problem indeed. Your solution sounds like it just might work, although I wouldn't want to do that because it means doubling up on the number of bodies which can't be good for performance. I think my patch to Box2D might be workable for this problem as well. You can't make both bodies kinematic because otherwise they would pass through each other (neither body could push the other). But you could figure out which body is pushing into the other from the PreSolve callback and make the other kinematic. Something like this (pseudo code coming, this won't work as-is):

Code:
void CGameLayer::PreSolve(b2Contact* contact, const b2Manifold* oldManifold)
{
   b2Fixture* pFixtureA = contact->GetFixtureA();
   b2Fixture* pFixtureB = contact->GetFixtureB();
   b2Body* pBodyA = pFixtureA->GetBody();
   b2Body* pBodyB = pFixtureB->GetBody();
   if (pBodyA->GetType() == b2_dynamicBody && pBodyB->GetType() == b2_dynamicBody)
   {
      // Find out which body is pushing which the most
      float pushSpeedA = pBodyA->GetVelocity().dotProduct(contact.normal);
      float pushSpeedB = pBodyB->GetVelocity().dotProduct(contact.normal);
      // Make the body which is pushing the least kinematic
      if (pushSpeedA  > pushSpeedB )
      {
         contact->SetBodyAKinematic(false);
         contact->SetBodyBKinematic(true);
      }
      else
      {
         contact->SetBodyAKinematic(true);
         contact->SetBodyBKinematic(false);
      }
   }
}


Top
 Profile  
 
PostPosted: Mon Nov 28, 2011 10:41 pm 
Offline

Joined: Tue Jun 24, 2008 8:25 pm
Posts: 1515
Location: Tokyo
Seems to me that could sometimes make the player kinematic and the boulder dynamic...?
Also you would probably want to use the relative velocity of the two bodies rather than just the global velocity.


Top
 Profile  
 
PostPosted: Tue Nov 29, 2011 3:39 am 
Offline

Joined: Tue Jun 24, 2008 8:25 pm
Posts: 1515
Location: Tokyo
fwiw here is the idea I was talking about. In theory I think it's ok but there may be problems in practice because the players can get completely squashed between the static ground and the boulder. This might be ok if they were to die in that case but if they are supposed to somehow stay alive and moving then it could be a problem.
The small circles are all mutually 'non-bumpable', and the large circle can bump the small ones around but not vice-versa.


Attachments:
iforce2d_mutualNonbumping.h [4.1 KiB]
Downloaded 27 times
Top
 Profile  
 
PostPosted: Tue Nov 29, 2011 11:37 am 
Offline

Joined: Sun Nov 27, 2011 9:43 am
Posts: 3
>>Seems to me that could sometimes make the player kinematic and the boulder dynamic...?

Yes sorry I was referring to the case you described about "the problem where two players should be able to move around but not push each other around". For the case of the player vs boulder then the original code I posted works fine.

>>Also you would probably want to use the relative velocity of the two bodies rather than just the global velocity.

Quite possibly yes, I haven't worked out the details as it's not a problem for the game I working on.

>>fwiw here is the idea I was talking about. In theory I think it's ok but there may be problems in practice because the players can get completely squashed between the static ground and the boulder. This might be ok if they were to die in that case but if they are supposed to somehow stay alive and moving then it could be a problem.

Looks good, my solution also suffers from the problem of the player being able to get trapped between a rock and a hard place. But as you suggest I plan to solve it by killing the player when he gets crushed.


Top
 Profile  
 
Display posts from previous:  Sort by  
Post new topic Reply to topic  [ 6 posts ] 

All times are UTC - 8 hours [ DST ]


Who is online

Users browsing this forum: No registered users and 4 guests


You cannot post new topics in this forum
You cannot reply to topics in this forum
You cannot edit your posts in this forum
You cannot delete your posts in this forum
You cannot post attachments in this forum

Search for:
Powered by phpBB® Forum Software © phpBB Group