Quellcode durchsuchen

Changes the way collision listeners work so that a listener is only notified when a collision first occurs and not every frame that the rigid bodies are still colliding.
Updated some comments for increased clarity (in Node, PhysicsGenericConstraint, PhysicsHingeConstraint).
Removed two parameters from PhysicsHingeConstraint::setLimits (one appears to not even be used by Bullet, the other's significance is unclear, so we just use their default values).
Adds the collidesWith function to PhysicsRigidBody to allow querying at any time if two rigid bodies collide with each other.

Chris Culy vor 14 Jahren
Ursprung
Commit
fbe8db69aa

+ 5 - 4
gameplay/src/Node.h

@@ -365,11 +365,12 @@ public:
     PhysicsRigidBody* getPhysicsRigidBody() const;
 
     /**
-     * Assigns a physics rigid body to this node.
+     * Sets (or disables) the physics rigid body for this node.
      * 
      * Note: This is only allowed for nodes that have a model attached to them.
      *
-     * @param type The type of rigid body to set.
+     * @param type The type of rigid body to set; to disable the physics rigid
+     *      body, pass PhysicsRigidBody#SHAPE_NONE.
      * @param mass The mass of the rigid body, in kilograms.
      * @param friction The friction of the rigid body (between 0.0 and 1.0, where 0.0 is
      *      minimal friction and 1.0 is maximal friction).
@@ -378,8 +379,8 @@ public:
      * @param linearDamping The percentage of linear velocity lost per second (between 0.0 and 1.0).
      * @param angularDamping The percentage of angular velocity lost per second (between 0.0 and 1.0).
      */
-    void setPhysicsRigidBody(PhysicsRigidBody::Type type, float mass, float friction = 0.5,
-        float restitution = 0.0, float linearDamping = 0.0, float angularDamping = 0.0);
+    void setPhysicsRigidBody(PhysicsRigidBody::Type type, float mass = 0.0f, float friction = 0.5f,
+        float restitution = 0.0f, float linearDamping = 0.0f, float angularDamping = 0.0f);
 
     /**
      * Returns the bounding box for the Node, in world space.

+ 54 - 4
gameplay/src/PhysicsController.cpp

@@ -195,6 +195,27 @@ void PhysicsController::update(long elapsedTime)
         }
     }
 
+    // All statuses are set with the DIRTY bit before collision processing occurs.
+    // During collision processing, if a collision occurs, the status is 
+    // set to COLLISION and the DIRTY bit is cleared. Then, after collision processing 
+    // is finished, if a given status is still dirty, the COLLISION bit is cleared.
+
+    // Dirty all the collision listeners' collision status caches.
+    for (unsigned int i = 0; i < _bodies.size(); i++)
+    {
+        if (_bodies[i]->_listeners)
+        {
+            for (unsigned int k = 0; k < _bodies[i]->_listeners->size(); k++)
+            {
+                std::map<PhysicsRigidBody::CollisionPair, int>::iterator iter = (*_bodies[i]->_listeners)[k]->_collisionStatus.begin();
+                for (; iter != (*_bodies[i]->_listeners)[k]->_collisionStatus.end(); iter++)
+                {
+                    iter->second |= PhysicsRigidBody::Listener::DIRTY;
+                }
+            }
+        }
+    }
+
     // Go through the physics rigid bodies and update the collision listeners.
     for (unsigned int i = 0; i < _bodies.size(); i++)
     {
@@ -202,10 +223,39 @@ void PhysicsController::update(long elapsedTime)
         {
             for (unsigned int k = 0; k < _bodies[i]->_listeners->size(); k++)
             {
-                if ((*_bodies[i]->_listeners)[k]->_rbB)
-                    Game::getInstance()->getPhysicsController()->_world->contactPairTest((*_bodies[i]->_listeners)[k]->_rbA->_body, (*_bodies[i]->_listeners)[k]->_rbB->_body, *(*_bodies[i]->_listeners)[k]);
-                else
-                    Game::getInstance()->getPhysicsController()->_world->contactTest((*_bodies[i]->_listeners)[k]->_rbA->_body, *(*_bodies[i]->_listeners)[k]);
+                std::map<PhysicsRigidBody::CollisionPair, int>::iterator iter = (*_bodies[i]->_listeners)[k]->_collisionStatus.begin();
+                for (; iter != (*_bodies[i]->_listeners)[k]->_collisionStatus.end(); iter++)
+                {
+                    // If this collision pair was one that was registered for listening, then perform the collision test.
+                    // (In the case where we register for all collisions with a rigid body, there will be a lot
+                    // of collision pairs in the status cache that we did not explicitly register for.)
+                    if ((iter->second & PhysicsRigidBody::Listener::REGISTERED) != 0)
+                    {
+                        if (iter->first._rbB)
+                            Game::getInstance()->getPhysicsController()->_world->contactPairTest(iter->first._rbA->_body, iter->first._rbB->_body, *(*_bodies[i]->_listeners)[k]);
+                        else
+                            Game::getInstance()->getPhysicsController()->_world->contactTest(iter->first._rbA->_body, *(*_bodies[i]->_listeners)[k]);
+                    }
+                }   
+            }
+        }
+    }
+
+    // Go through all the collision listeners and update their collision status caches.
+    for (unsigned int i = 0; i < _bodies.size(); i++)
+    {
+        if (_bodies[i]->_listeners)
+        {
+            for (unsigned int k = 0; k < _bodies[i]->_listeners->size(); k++)
+            {
+                std::map<PhysicsRigidBody::CollisionPair, int>::iterator iter = (*_bodies[i]->_listeners)[k]->_collisionStatus.begin();
+                for (; iter != (*_bodies[i]->_listeners)[k]->_collisionStatus.end(); iter++)
+                {
+                    if ((iter->second & PhysicsRigidBody::Listener::DIRTY) != 0)
+                    {
+                        iter->second &= ~PhysicsRigidBody::Listener::COLLISION;
+                    }
+                }
             }
         }
     }

+ 4 - 4
gameplay/src/PhysicsGenericConstraint.h

@@ -52,18 +52,18 @@ public:
     inline const Vector3& getTranslationOffsetB() const;
 
     /**
-     * Sets the lower angular limits along the constraint's local
+     * Sets the lower angular limits (as Euler angle limits) along the constraint's local
      * X, Y, and Z axes using the values in the given vector.
      * 
-     * @param limits The lower angular limits along the local X, Y, and Z axes.
+     * @param limits The lower angular limits (as Euler angle limits) along the local X, Y, and Z axes.
      */
     inline void setAngularLowerLimit(const Vector3& limits);
 
     /**
-     * Sets the upper angular limits along the constraint's local
+     * Sets the upper angular limits (as Euler angle limits) along the constraint's local
      * X, Y, and Z axes using the values in the given vector.
      * 
-     * @param limits The upper angular limits along the local X, Y, and Z axes.
+     * @param limits The upper angular limits (as Euler angle limits) along the local X, Y, and Z axes.
      */
     inline void setAngularUpperLimit(const Vector3& limits);
     

+ 3 - 2
gameplay/src/PhysicsHingeConstraint.cpp

@@ -9,9 +9,10 @@
 namespace gameplay
 {
 
-void PhysicsHingeConstraint::setLimits(float minAngle, float maxAngle, float softness, float biasFactor, float relaxationFactor)
+void PhysicsHingeConstraint::setLimits(float minAngle, float maxAngle, float bounciness)
 {
-    ((btHingeConstraint*)_constraint)->setLimit(minAngle, maxAngle, softness, biasFactor, relaxationFactor);
+    // Use the defaults for softness (0.9) and biasFactor (0.3).
+    ((btHingeConstraint*)_constraint)->setLimit(minAngle, maxAngle, 0.9f, 0.3f, bounciness);
 }
 
 PhysicsHingeConstraint::PhysicsHingeConstraint(PhysicsRigidBody* a, const Quaternion& rotationOffsetA, const Vector3& translationOffsetA,

+ 6 - 5
gameplay/src/PhysicsHingeConstraint.h

@@ -28,12 +28,13 @@ public:
      * 
      * @param minAngle The minimum angle for the hinge.
      * @param maxAngle The maximum angle for the hinge.
-     * @param softness The softness of the hinge (defaults to 0.9).
-     * @param biasFactor The bias factor for the hinge (defaults to 0.3).
-     * @param relaxationFactor The relaxation factor for the hinge (defaults to 1.0).
+     * @param bounciness The bounciness of the hinge (this is applied as
+     *      a factor to the incoming velocity when a hinge limit is met in
+     *      order to calculate the outgoing velocity-for example, 0.0 corresponds
+     *      to no bounce and 1.0 corresponds to an outgoing velocity that is equal
+     *      in magnitude to the incoming velocity).
      */
-    void setLimits(float minAngle, float maxAngle, float softness = 0.9f, 
-        float biasFactor = 0.3f, float relaxationFactor = 1.0f);
+    void setLimits(float minAngle, float maxAngle, float bounciness = 1.0f);
 
 private:
     /**

+ 48 - 9
gameplay/src/PhysicsRigidBody.cpp

@@ -11,6 +11,10 @@
 namespace gameplay
 {
 
+const int PhysicsRigidBody::Listener::DIRTY = 0x01;
+const int PhysicsRigidBody::Listener::COLLISION = 0x02;
+const int PhysicsRigidBody::Listener::REGISTERED = 0x04;
+
 PhysicsRigidBody::PhysicsRigidBody(Node* node, PhysicsRigidBody::Type type, float mass, 
         float friction, float restitution, float linearDamping, float angularDamping)
         : _shape(NULL), _body(NULL), _node(node), _listeners(NULL), _angularVelocity(NULL),
@@ -98,9 +102,8 @@ void PhysicsRigidBody::addCollisionListener(Listener* listener, PhysicsRigidBody
     if (!_listeners)
         _listeners = new std::vector<Listener*>();
 
-    listener->_rbA = this;
-    if (body)
-        listener->_rbB = body;
+    CollisionPair pair(this, body);
+    listener->_collisionStatus[pair] = PhysicsRigidBody::Listener::REGISTERED;
 
     _listeners->push_back(listener);
 }
@@ -158,6 +161,15 @@ void PhysicsRigidBody::applyTorqueImpulse(const Vector3& torque)
     }
 }
 
+bool PhysicsRigidBody::collidesWith(PhysicsRigidBody* body)
+{
+    static CollidesWithCallback callback;
+
+    callback.result = false;
+    Game::getInstance()->getPhysicsController()->_world->contactPairTest(_body, body->_body, callback);
+    return callback.result;
+}
+
 btRigidBody* PhysicsRigidBody::createBulletRigidBody(btCollisionShape* shape, float mass, Node* node,
     float friction, float restitution, float linearDamping, float angularDamping, const Vector3* centerOfMassOffset)
 {
@@ -196,8 +208,13 @@ void PhysicsRigidBody::removeConstraint(PhysicsConstraint* constraint)
     }
 }
 
-PhysicsRigidBody::Listener::Listener()
-    : _rbA(NULL), _rbB(NULL)
+PhysicsRigidBody::CollisionPair::CollisionPair(PhysicsRigidBody* rbA, PhysicsRigidBody* rbB)
+    : _rbA(rbA), _rbB(rbB)
+{
+    // DUMMY FUNCTION
+}
+
+PhysicsRigidBody::Listener::~Listener()
 {
     // DUMMY FUNCTION
 }
@@ -205,14 +222,36 @@ PhysicsRigidBody::Listener::Listener()
 btScalar PhysicsRigidBody::Listener::addSingleResult(btManifoldPoint& cp, const btCollisionObject* a,
     int partIdA, int indexA, const btCollisionObject* b, int partIdB, int indexB)
 {
+    // Get pointers to the PhysicsRigidBody objects.
     PhysicsRigidBody* rbA = Game::getInstance()->getPhysicsController()->getPhysicsRigidBody(a);
     PhysicsRigidBody* rbB = Game::getInstance()->getPhysicsController()->getPhysicsRigidBody(b);
-
-    if (rbA == _rbA)
-        collisionEvent(rbB, Vector3(cp.getPositionWorldOnA().x(), cp.getPositionWorldOnA().y(), cp.getPositionWorldOnA().z()));
+    
+    // If the given rigid body pair has collided in the past, then
+    // we notify the listener only if the pair was not colliding
+    // during the previous frame. Otherwise, it's a new pair, so notify the listener.
+    CollisionPair pair(rbA, rbB);
+    if (_collisionStatus.count(pair) > 0)
+    {
+        if ((_collisionStatus[pair] & COLLISION) == 0)
+            collisionEvent(pair, Vector3(cp.getPositionWorldOnA().x(), cp.getPositionWorldOnA().y(), cp.getPositionWorldOnA().z()));
+    }
     else
-        collisionEvent(rbA, Vector3(cp.getPositionWorldOnB().x(), cp.getPositionWorldOnB().y(), cp.getPositionWorldOnB().z()));
+    {
+        collisionEvent(pair, Vector3(cp.getPositionWorldOnA().x(), cp.getPositionWorldOnA().y(), cp.getPositionWorldOnA().z()));
+    }
 
+    // Update the collision status cache (we remove the dirty bit
+    // set in the controller's update so that this particular collision pair's
+    // status is not reset to 'no collision' when the controller's update completes).
+    _collisionStatus[pair] &= ~DIRTY;
+    _collisionStatus[pair] |= COLLISION;
+    return 0.0f;
+}
+
+btScalar PhysicsRigidBody::CollidesWithCallback::addSingleResult(btManifoldPoint& cp, const btCollisionObject* a, int partIdA,
+            int indexA, const btCollisionObject* b, int partIdB, int indexB)
+{
+    result = true;
     return 0.0f;
 }
 

+ 57 - 6
gameplay/src/PhysicsRigidBody.h

@@ -42,6 +42,32 @@ public:
         SHAPE_NONE
     };
 
+    /** 
+     * Defines a pair of rigid bodies that collided (or may collide).
+     */
+    class CollisionPair
+    {
+    public:
+        /**
+         * Constructor.
+         */
+        CollisionPair(PhysicsRigidBody* rbA, PhysicsRigidBody* rbB);
+
+        /**
+         * Less than operator (needed for use as a key in std::map).
+         * 
+         * @param cp The collision pair to compare.
+         * @return True if this pair is "less than" the given pair; false otherwise.
+         */
+        bool operator<(const CollisionPair& cp) const;
+
+        /** The first rigid body in the collision. */
+        PhysicsRigidBody* _rbA;
+
+        /** The second rigid body in the collision. */
+        PhysicsRigidBody* _rbB;
+    };
+
     /**
      * Collision listener interface.
      */
@@ -52,17 +78,17 @@ public:
 
     public:
         /**
-         * Constructor.
+         * Destructor.
          */
-        Listener();
+        virtual ~Listener();
 
         /**
          * Handles when a collision occurs for the rigid body where this listener is registered.
          * 
-         * @param body The other rigid body in the collision.
+         * @param collisionPair The two rigid bodies involved in the collision.
          * @param contactPoint The point (in world space) where the collision occurred.
          */
-        virtual void collisionEvent(PhysicsRigidBody* body, const Vector3& contactPoint) = 0;
+        virtual void collisionEvent(const CollisionPair& collisionPair, const Vector3& contactPoint) = 0;
 
         /**
          * Internal function used for Bullet integration (do not use or override).
@@ -71,8 +97,16 @@ public:
             int indexA, const btCollisionObject* b, int partIdB, int indexB);
         
     protected:
-        PhysicsRigidBody* _rbA;
-        PhysicsRigidBody* _rbB;
+        /** Holds the collision status for each pair of rigid bodies. */
+        std::map<CollisionPair, int> _collisionStatus;
+
+    private:
+        // Internal constant.
+        static const int DIRTY;
+        // Internal constant.
+        static const int COLLISION;
+        // Internal constant.
+        static const int REGISTERED;
     };
 
     /**
@@ -113,6 +147,14 @@ public:
      */
     void applyTorqueImpulse(const Vector3& torque);
     
+    /**
+     * Checks if this rigid body collides with the given rigid body.
+     * 
+     * @param body The rigid body to test collision with.
+     * @return True if this rigid body collides with the given rigid body; false otherwise.
+     */
+    bool collidesWith(PhysicsRigidBody* body);
+
     /**
      * Gets the rigid body's angular damping.
      * 
@@ -280,6 +322,15 @@ private:
     // Removes a constraint from this rigid body (used by the constraint destructor).
     void removeConstraint(PhysicsConstraint* constraint);
 
+    // Internal class used to implement the collidesWith(PhysicsRigidBody*) function.
+    struct CollidesWithCallback : public btCollisionWorld::ContactResultCallback
+    {
+        btScalar addSingleResult(btManifoldPoint& cp, const btCollisionObject* a, int partIdA,
+            int indexA, const btCollisionObject* b, int partIdB, int indexB);
+
+        bool result;
+    };
+
     btCollisionShape* _shape;
     btRigidBody* _body;
     Node* _node;

+ 17 - 3
gameplay/src/PhysicsRigidBody.inl

@@ -7,9 +7,6 @@
 namespace gameplay
 {
 
-// TODO: We can't cache these Vector3 values, but should we have member variables
-// that are set each time instead of creating a new Vector3 every time?
-
 inline float PhysicsRigidBody::getAngularDamping() const
 {
     return _body->getAngularDamping();
@@ -129,4 +126,21 @@ inline void PhysicsRigidBody::setRestitution(float restitution)
     _body->setRestitution(restitution);
 }
 
+inline bool PhysicsRigidBody::CollisionPair::operator<(const CollisionPair& cp) const
+{
+    // If the pairs are equal, then return false.
+    if ((_rbA == cp._rbA && _rbB == cp._rbB) || (_rbA == cp._rbB && _rbB == cp._rbA))
+        return false;
+    else
+    {
+        // We choose to compare based on _rbA arbitrarily.
+        if (_rbA < cp._rbA)
+            return true;
+        else if (_rbA == cp._rbA)
+            return _rbB < cp._rbB;
+        else
+            return false;
+    }
+}
+
 }