diff options
author | Sybren A. Stüvel <sybren@stuvel.eu> | 2015-02-05 11:39:53 +0300 |
---|---|---|
committer | Sybren A. Stüvel <sybren@stuvel.eu> | 2015-02-08 17:52:13 +0300 |
commit | dd65a44c9a192d62f7661090682ee0dc99fb0491 (patch) | |
tree | 38a4a150113aca9c09de3851052597ee0f8bdfb9 /source | |
parent | 51b645a655eb41c5b73d8487ee7df9ba99fc692b (diff) |
BGE physics: When colliding, report first contact point to Python
This patch adds two parameters to the functions in the
collisionCallbacks list. The callback function should thus be like
this:
```
def on_colliding(other, point, normal):
print("Colliding with %s at %s with normal %s" % (other, point, normal))
game_ob.collisionCallbacks.append(on_colliding)
```
The `point` parameter will contain the collision point in world
coordinates on the current object, and the `normal` contains the
surface normal at the collision point.
The callback functions are checked for the number of arguments
`co_argcount`. The new `point` and `normal` arguments are only passed
when `co_argcount > 1` or when `co_argcount` cannot be determined.
Reviewers: brita_, campbellbarton
Subscribers: sergey, sybren, agoose77
Projects: #game_physics
Differential Revision: https://developer.blender.org/D926
Diffstat (limited to 'source')
-rw-r--r-- | source/gameengine/Ketsji/KX_GameObject.cpp | 46 | ||||
-rw-r--r-- | source/gameengine/Ketsji/KX_GameObject.h | 2 | ||||
-rw-r--r-- | source/gameengine/Ketsji/KX_TouchEventManager.cpp | 24 | ||||
-rw-r--r-- | source/gameengine/Ketsji/KX_TouchEventManager.h | 24 | ||||
-rw-r--r-- | source/gameengine/Physics/Bullet/CcdPhysicsEnvironment.cpp | 18 |
5 files changed, 104 insertions, 10 deletions
diff --git a/source/gameengine/Ketsji/KX_GameObject.cpp b/source/gameengine/Ketsji/KX_GameObject.cpp index e8b68d20e84..6d4b5564e19 100644 --- a/source/gameengine/Ketsji/KX_GameObject.cpp +++ b/source/gameengine/Ketsji/KX_GameObject.cpp @@ -77,6 +77,8 @@ typedef unsigned long uint_ptr; #include "BL_Action.h" #include "PyObjectPlus.h" /* python stuff */ +#include "BLI_utildefines.h" +#include "python_utildefines.h" // This file defines relationships between parents and children // in the game engine. @@ -1498,7 +1500,7 @@ void KX_GameObject::RegisterCollisionCallbacks() pe->AddSensor(spc); } } -void KX_GameObject::RunCollisionCallbacks(KX_GameObject *collider) +void KX_GameObject::RunCollisionCallbacks(KX_GameObject *collider, const MT_Vector3 &point, const MT_Vector3 &normal) { #ifdef WITH_PYTHON Py_ssize_t len; @@ -1506,15 +1508,50 @@ void KX_GameObject::RunCollisionCallbacks(KX_GameObject *collider) if (collision_callbacks && (len=PyList_GET_SIZE(collision_callbacks))) { - PyObject* args = Py_BuildValue("(O)", collider->GetProxy()); // save python creating each call + // Argument tuples are created lazily, only when they are needed. + PyObject *args_3 = NULL; + PyObject *args_1 = NULL; // Only for compatibility with pre-2.74 callbacks that take 1 argument. + PyObject *func; PyObject *ret; + int co_argcount; // Iterate the list and run the callbacks for (Py_ssize_t pos=0; pos < len; pos++) { func = PyList_GET_ITEM(collision_callbacks, pos); - ret = PyObject_Call(func, args, NULL); + + // Get the number of arguments, supporting functions, methods and generic callables. + if (PyMethod_Check(func)) { + // Take away the 'self' argument for methods. + co_argcount = ((PyCodeObject *)PyFunction_GET_CODE(PyMethod_GET_FUNCTION(func)))->co_argcount - 1; + } else if (PyFunction_Check(func)) { + co_argcount = ((PyCodeObject *)PyFunction_GET_CODE(func))->co_argcount; + } else { + // We'll just assume the callable takes the correct number of arguments. + co_argcount = 3; + } + + // Check whether the function expects the colliding object only, + // or also the point and normal. + if (co_argcount <= 1) { + // One argument, or *args (which gives co_argcount == 0) + if (args_1 == NULL) { + args_1 = PyTuple_New(1); + PyTuple_SET_ITEMS(args_1, collider->GetProxy()); + } + ret = PyObject_Call(func, args_1, NULL); + } else { + // More than one argument, assume we can give point & normal. + if (args_3 == NULL) { + args_3 = PyTuple_New(3); + PyTuple_SET_ITEMS(args_3, + collider->GetProxy(), + PyObjectFrom(point), + PyObjectFrom(normal)); + } + ret = PyObject_Call(func, args_3, NULL); + } if (ret == NULL) { PyErr_Print(); @@ -1525,7 +1562,8 @@ void KX_GameObject::RunCollisionCallbacks(KX_GameObject *collider) } } - Py_DECREF(args); + if (args_3) Py_DECREF(args_3); + if (args_1) Py_DECREF(args_1); } #endif } diff --git a/source/gameengine/Ketsji/KX_GameObject.h b/source/gameengine/Ketsji/KX_GameObject.h index 4538679303f..5800cbc6834 100644 --- a/source/gameengine/Ketsji/KX_GameObject.h +++ b/source/gameengine/Ketsji/KX_GameObject.h @@ -919,7 +919,7 @@ public: void RegisterCollisionCallbacks(); void UnregisterCollisionCallbacks(); - void RunCollisionCallbacks(KX_GameObject *collider); + void RunCollisionCallbacks(KX_GameObject *collider, const MT_Vector3 &point, const MT_Vector3 &normal); /** * Stop making progress */ diff --git a/source/gameengine/Ketsji/KX_TouchEventManager.cpp b/source/gameengine/Ketsji/KX_TouchEventManager.cpp index 7ec379eec26..eb774960d41 100644 --- a/source/gameengine/Ketsji/KX_TouchEventManager.cpp +++ b/source/gameengine/Ketsji/KX_TouchEventManager.cpp @@ -60,7 +60,7 @@ bool KX_TouchEventManager::NewHandleCollision(void* object1, void* object2, cons PHY_IPhysicsController* obj1 = static_cast<PHY_IPhysicsController*>(object1); PHY_IPhysicsController* obj2 = static_cast<PHY_IPhysicsController*>(object2); - m_newCollisions.insert(std::pair<PHY_IPhysicsController*, PHY_IPhysicsController*>(obj1, obj2)); + m_newCollisions.insert(NewCollision(obj1, obj2, coll_data)); return false; } @@ -209,9 +209,11 @@ void KX_TouchEventManager::NextFrame() } } // Run python callbacks - kxObj1->RunCollisionCallbacks(kxObj2); - kxObj2->RunCollisionCallbacks(kxObj1); + PHY_CollData *colldata = cit->colldata; + kxObj1->RunCollisionCallbacks(kxObj2, colldata->m_point1, colldata->m_normal); + kxObj2->RunCollisionCallbacks(kxObj1, colldata->m_point2, -colldata->m_normal); + delete cit->colldata; } m_newCollisions.clear(); @@ -219,3 +221,19 @@ void KX_TouchEventManager::NextFrame() for (it.begin();!it.end();++it) (*it)->Activate(m_logicmgr); } + + +KX_TouchEventManager::NewCollision::NewCollision(PHY_IPhysicsController *first, + PHY_IPhysicsController *second, + const PHY_CollData *colldata) + : first(first), second(second), colldata(new PHY_CollData(*colldata)) +{} + +KX_TouchEventManager::NewCollision::NewCollision(const NewCollision &to_copy) + : first(to_copy.first), second(to_copy.second), colldata(to_copy.colldata) +{} + +bool KX_TouchEventManager::NewCollision::operator<(const NewCollision &other) const +{ + return first < other.first || second < other.second || colldata < other.colldata; +} diff --git a/source/gameengine/Ketsji/KX_TouchEventManager.h b/source/gameengine/Ketsji/KX_TouchEventManager.h index bd4903c1545..d9c6fdad307 100644 --- a/source/gameengine/Ketsji/KX_TouchEventManager.h +++ b/source/gameengine/Ketsji/KX_TouchEventManager.h @@ -45,7 +45,29 @@ class PHY_IPhysicsEnvironment; class KX_TouchEventManager : public SCA_EventManager { - typedef std::pair<PHY_IPhysicsController*, PHY_IPhysicsController*> NewCollision; + /** + * Contains two colliding objects and the first contact point. + */ + class NewCollision { + public: + PHY_IPhysicsController *first; + PHY_IPhysicsController *second; + PHY_CollData *colldata; + + /** + * Creates a copy of the given PHY_CollData; freeing that copy should be done by the owner of + * the NewCollision object. + * + * This allows us to efficiently store NewCollision objects in a std::set without creating more + * copies of colldata, as the NewCollision copy constructor reuses the pointer and doesn't clone + * it again. */ + NewCollision(PHY_IPhysicsController *first, + PHY_IPhysicsController *second, + const PHY_CollData *colldata); + NewCollision(const NewCollision &to_copy); + bool operator<(const NewCollision &other) const; + }; + PHY_IPhysicsEnvironment* m_physEnv; std::set<NewCollision> m_newCollisions; diff --git a/source/gameengine/Physics/Bullet/CcdPhysicsEnvironment.cpp b/source/gameengine/Physics/Bullet/CcdPhysicsEnvironment.cpp index bcdc6f471ec..c43a3f782a7 100644 --- a/source/gameengine/Physics/Bullet/CcdPhysicsEnvironment.cpp +++ b/source/gameengine/Physics/Bullet/CcdPhysicsEnvironment.cpp @@ -2261,6 +2261,7 @@ void CcdPhysicsEnvironment::CallbackTriggers() int numManifolds = dispatcher->getNumManifolds(); for (int i=0;i<numManifolds;i++) { + bool colliding_ctrl0 = true; btPersistentManifold* manifold = dispatcher->getManifoldByIndexInternal(i); int numContacts = manifold->getNumContacts(); if (!numContacts) continue; @@ -2289,12 +2290,27 @@ void CcdPhysicsEnvironment::CallbackTriggers() if (iter == m_triggerControllers.end()) { iter = m_triggerControllers.find(ctrl1); + colliding_ctrl0 = false; } if (iter != m_triggerControllers.end()) { + static PHY_CollData coll_data; + const btManifoldPoint &cp = manifold->getContactPoint(0); + + /* Make sure that "point1" is always on the object we report on, and + * "point2" on the other object. Also ensure the normal is oriented + * correctly. */ + btVector3 point1 = colliding_ctrl0 ? cp.m_positionWorldOnA : cp.m_positionWorldOnB; + btVector3 point2 = colliding_ctrl0 ? cp.m_positionWorldOnB : cp.m_positionWorldOnA; + btVector3 normal = colliding_ctrl0 ? -cp.m_normalWorldOnB : cp.m_normalWorldOnB; + + coll_data.m_point1 = MT_Vector3(point1.m_floats); + coll_data.m_point2 = MT_Vector3(point2.m_floats); + coll_data.m_normal = MT_Vector3(normal.m_floats); + m_triggerCallbacks[PHY_OBJECT_RESPONSE](m_triggerCallbacksUserPtrs[PHY_OBJECT_RESPONSE], - ctrl0,ctrl1,0); + ctrl0, ctrl1, &coll_data); } // Bullet does not refresh the manifold contact point for object without contact response // may need to remove this when a newer Bullet version is integrated |