BGE: Adding a Python collision API. The initial patch was provided by agoose77, with...
authorMitchell Stokes <mogurijin@gmail.com>
Sat, 14 Sep 2013 02:02:58 +0000 (02:02 +0000)
committerMitchell Stokes <mogurijin@gmail.com>
Sat, 14 Sep 2013 02:02:58 +0000 (02:02 +0000)
KX_GameObject now has a collisionCallbacks list which is a list of callables that are called when a collision occurs. The callables will be called with an argument that contains a reference to the other object involved in the collision (i.e., not self).

doc/python_api/rst/bge_types/bge.types.KX_GameObject.rst
source/gameengine/Ketsji/KX_GameObject.cpp
source/gameengine/Ketsji/KX_GameObject.h
source/gameengine/Ketsji/KX_TouchEventManager.cpp

index 3be148556ef36e97231b7f5d855ecb664aa7afab..5d699637eb72594bdc06fe3eaa2c38298f6976b2 100644 (file)
@@ -134,6 +134,12 @@ base class --- :class:`SCA_IObject`
 
       :type: :class:`KX_GameObject` or None
 
+   .. attribute:: collisionCallbacks
+
+      A list of callables to be run when a collision occurs.
+
+      :type: list
+
    .. attribute:: scene
 
       The object's scene. (read-only).
index e0ec4983739794bf3cdbd98f92ee2babbb0ebdc8..e06f7ab66335f08a29bebe3d6fa038a001384edd 100644 (file)
@@ -114,8 +114,10 @@ KX_GameObject::KX_GameObject(
       m_pDupliGroupObject(NULL),
       m_actionManager(NULL),
       m_isDeformable(false)
+
 #ifdef WITH_PYTHON
-    , m_attr_dict(NULL)
+    , m_attr_dict(NULL),
+    m_collisionCallbacks(NULL)
 #endif
 {
        m_ignore_activity_culling = false;
@@ -133,6 +135,20 @@ KX_GameObject::KX_GameObject(
 
 KX_GameObject::~KX_GameObject()
 {
+#ifdef WITH_PYTHON
+       if (m_attr_dict) {
+               PyDict_Clear(m_attr_dict); /* in case of circular refs or other weird cases */
+               /* Py_CLEAR: Py_DECREF's and NULL's */
+               Py_CLEAR(m_attr_dict);
+       }
+       // Unregister collision callbacks
+       // Do this before we start freeing physics information like m_pClient_info
+       if (m_collisionCallbacks){
+               UnregisterCollisionCallbacks();
+               Py_CLEAR(m_collisionCallbacks);
+       }
+#endif // WITH_PYTHON
+
        RemoveMeshes();
 
        // is this delete somewhere ?
@@ -180,13 +196,6 @@ KX_GameObject::~KX_GameObject()
        {
                m_pInstanceObjects->Release();
        }
-#ifdef WITH_PYTHON
-       if (m_attr_dict) {
-               PyDict_Clear(m_attr_dict); /* in case of circular refs or other weird cases */
-               /* Py_CLEAR: Py_DECREF's and NULL's */
-               Py_CLEAR(m_attr_dict);
-       }
-#endif // WITH_PYTHON
 }
 
 KX_GameObject* KX_GameObject::GetClientObject(KX_ClientObjectInfo *info)
@@ -1336,6 +1345,77 @@ const MT_Point3& KX_GameObject::NodeGetLocalPosition() const
 }
 
 
+void KX_GameObject::UnregisterCollisionCallbacks()
+{
+       if (!GetPhysicsController()) {
+               printf("Warning, trying to unregister collision callbacks for object without collisions: %s!\n", GetName().ReadPtr());
+               return;
+       }
+
+       // Unregister from callbacks
+       KX_Scene* scene = GetScene();
+       PHY_IPhysicsEnvironment* pe = scene->GetPhysicsEnvironment();
+       PHY_IPhysicsController* spc = static_cast<PHY_IPhysicsController*> (GetPhysicsController()->GetUserData());
+       // If we are the last to unregister on this physics controller
+       if (pe->removeCollisionCallback(spc)){
+               // If we are a sensor object
+               if (m_pClient_info->isSensor())
+                       // Remove sensor body from physics world
+                       pe->removeSensor(spc);
+       }
+}
+
+void KX_GameObject::RegisterCollisionCallbacks()
+{
+       if (!GetPhysicsController()) {
+               printf("Warning, trying to register collision callbacks for object without collisions: %s!\n", GetName().ReadPtr());
+               return;
+       }
+
+       // Register from callbacks
+       KX_Scene* scene = GetScene();
+       PHY_IPhysicsEnvironment* pe = scene->GetPhysicsEnvironment();
+       PHY_IPhysicsController* spc = static_cast<PHY_IPhysicsController*> (GetPhysicsController()->GetUserData());
+       // If we are the first to register on this physics controller
+       if (pe->requestCollisionCallback(spc)){
+               // If we are a sensor object
+               if (m_pClient_info->isSensor())
+                       // Add sensor body to physics world
+                       pe->addSensor(spc);
+       }
+}
+void KX_GameObject::RunCollisionCallbacks(KX_GameObject *collider)
+{
+       #ifdef WITH_PYTHON
+       Py_ssize_t len;
+       PyObject* collision_callbacks = m_collisionCallbacks;
+
+       if (collision_callbacks && (len=PyList_GET_SIZE(collision_callbacks)))
+       {
+               PyObject* args = Py_BuildValue("(O)", collider->GetProxy()); // save python creating each call
+               PyObject *func;
+               PyObject *ret;
+
+               // 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);
+
+                       if (ret == NULL) {
+                               PyErr_Print();
+                               PyErr_Clear();
+                       }
+                       else {
+                               Py_DECREF(ret);
+                       }
+               }
+
+               Py_DECREF(args);
+       }
+       #endif
+}
+
 /* Suspend/ resume: for the dynamic behavior, there is a simple
  * method. For the residual motion, there is not. I wonder what the
  * correct solution is for Sumo. Remove from the motion-update tree?
@@ -1716,6 +1796,7 @@ PyAttributeDef KX_GameObject::Attributes[] = {
        KX_PYATTRIBUTE_RW_FUNCTION("orientation",KX_GameObject,pyattr_get_worldOrientation,pyattr_set_localOrientation),
        KX_PYATTRIBUTE_RW_FUNCTION("scaling",   KX_GameObject, pyattr_get_worldScaling, pyattr_set_localScaling),
        KX_PYATTRIBUTE_RW_FUNCTION("timeOffset",KX_GameObject, pyattr_get_timeOffset,pyattr_set_timeOffset),
+       KX_PYATTRIBUTE_RW_FUNCTION("collisionCallbacks",                KX_GameObject, pyattr_get_collisionCallbacks,   pyattr_set_collisionCallbacks),
        KX_PYATTRIBUTE_RW_FUNCTION("state",             KX_GameObject, pyattr_get_state,        pyattr_set_state),
        KX_PYATTRIBUTE_RO_FUNCTION("meshes",    KX_GameObject, pyattr_get_meshes),
        KX_PYATTRIBUTE_RW_FUNCTION("localOrientation",KX_GameObject,pyattr_get_localOrientation,pyattr_set_localOrientation),
@@ -2008,6 +2089,51 @@ PyObject *KX_GameObject::pyattr_get_group_members(void *self_v, const KX_PYATTRI
        Py_RETURN_NONE;
 }
 
+PyObject* KX_GameObject::pyattr_get_collisionCallbacks(void *self_v, const KX_PYATTRIBUTE_DEF *attrdef)
+{
+       KX_GameObject* self = static_cast<KX_GameObject*>(self_v);
+
+       // Only objects with a physics controller should have collision callbacks
+       if (!self->GetPhysicsController()) {
+               PyErr_SetString(PyExc_AttributeError, "KX_GameObject.collisionCallbacks: attribute only available for objects with collisions enabled");
+               return NULL;
+       }
+
+       // Return the existing callbacks
+       if (self->m_collisionCallbacks == NULL)
+       {
+               self->m_collisionCallbacks = PyList_New(0);
+               // Subscribe to collision update from KX_TouchManager
+               self->RegisterCollisionCallbacks();
+       }
+       Py_INCREF(self->m_collisionCallbacks);
+       return self->m_collisionCallbacks;
+}
+
+int KX_GameObject::pyattr_set_collisionCallbacks(void *self_v, const KX_PYATTRIBUTE_DEF *attrdef, PyObject *value)
+{
+       KX_GameObject* self = static_cast<KX_GameObject*>(self_v);
+
+       // Only objects with a physics controller should have collision callbacks
+       if (!self->GetPhysicsController()) {
+               PyErr_SetString(PyExc_AttributeError, "KX_GameObject.collisionCallbacks: attribute only available for objects with collisions enabled");
+               return PY_SET_ATTR_FAIL;
+       }
+
+       if (!PyList_CheckExact(value))
+       {
+               PyErr_SetString(PyExc_ValueError, "Expected a list");
+               return PY_SET_ATTR_FAIL;
+       }
+
+       Py_XDECREF(self->m_collisionCallbacks);
+       Py_INCREF(value);
+
+       self->m_collisionCallbacks = value;
+
+       return PY_SET_ATTR_SUCCESS;
+}
+
 PyObject* KX_GameObject::pyattr_get_scene(void *self_v, const KX_PYATTRIBUTE_DEF *attrdef)
 {
        KX_GameObject *self = static_cast<KX_GameObject*>(self_v);
index 5a3b9df74ee32168832da562f9a190df84ed9abd..dde3ff532990678fcf593898e6afaac245b2ce30 100644 (file)
@@ -137,21 +137,22 @@ public:
 
 #ifdef WITH_PYTHON
        // Python attributes that wont convert into CValue
-       // 
+       //
        // there are 2 places attributes can be stored, in the CValue,
        // where attributes are converted into BGE's CValue types
        // these can be used with property actuators
        //
        // For the python API, For types that cannot be converted into CValues (lists, dicts, GameObjects)
        // these will be put into "m_attr_dict", logic bricks cannot access them.
-       // 
+       //
        // rules for setting attributes.
-       // 
+       //
        // * there should NEVER be a CValue and a m_attr_dict attribute with matching names. get/sets make sure of this.
        // * if CValue conversion fails, use a PyObject in "m_attr_dict"
        // * when assigning a value, first see if it can be a CValue, if it can remove the "m_attr_dict" and set the CValue
-       // 
-       PyObject*                                                       m_attr_dict; 
+       //
+       PyObject*                                                       m_attr_dict;
+       PyObject*                                                       m_collisionCallbacks;
 #endif
 
        virtual void    /* This function should be virtual - derived classed override it */
@@ -872,6 +873,9 @@ public:
         * \section Logic bubbling methods.
         */
 
+       void RegisterCollisionCallbacks();
+       void UnregisterCollisionCallbacks();
+       void RunCollisionCallbacks(KX_GameObject *collider);
        /**
         * Stop making progress
         */
@@ -1040,6 +1044,8 @@ public:
        static PyObject*        pyattr_get_attrDict(void *self_v, const KX_PYATTRIBUTE_DEF *attrdef);
        static PyObject*        pyattr_get_obcolor(void *selv_v, const KX_PYATTRIBUTE_DEF *attrdef);
        static int                      pyattr_set_obcolor(void *self_v, const KX_PYATTRIBUTE_DEF *attrdef, PyObject *value);
+       static PyObject*        pyattr_get_collisionCallbacks(void *selv_v, const KX_PYATTRIBUTE_DEF *attrdef);
+       static int                      pyattr_set_collisionCallbacks(void *self_v, const KX_PYATTRIBUTE_DEF *attrdef, PyObject *value);
        
        /* Experimental! */
        static PyObject*        pyattr_get_sensors(void *self_v, const KX_PYATTRIBUTE_DEF *attrdef);
index 96872f4e6fd1d9b3b2329ef485af59ca6e25dc78..fcb09ebfec663d65e10f95e85e0a794aab573afa 100644 (file)
@@ -81,28 +81,42 @@ bool         KX_TouchEventManager::newBroadphaseResponse(void *client_data,
                                                        void *object2,
                                                        const PHY_CollData *coll_data)
 {
-       PHY_IPhysicsController* ctrl = static_cast<PHY_IPhysicsController*>(object1);
-       KX_ClientObjectInfo *info = (ctrl) ? static_cast<KX_ClientObjectInfo*>(ctrl->getNewClientInfo()) : NULL;
+       PHY_IPhysicsController* ctrl1 = static_cast<PHY_IPhysicsController*>(object1);
+       PHY_IPhysicsController* ctrl2 = static_cast<PHY_IPhysicsController*>(object2);
+
+       KX_ClientObjectInfo *info1 = (ctrl1) ? static_cast<KX_ClientObjectInfo*>(ctrl1->getNewClientInfo()) : NULL;
+       KX_ClientObjectInfo *info2 = (ctrl1) ? static_cast<KX_ClientObjectInfo*>(ctrl2->getNewClientInfo()) : NULL;
+
        // This call back should only be called for controllers of Near and Radar sensor
-       if (!info)
+       if (!info1)
                return true;
 
-       switch (info->m_type)
+       // Get KX_GameObjects for callbacks
+       KX_GameObject* gobj1 = info1->m_gameobject;
+       KX_GameObject* gobj2 = (info2) ? info1->m_gameobject : NULL;
+
+       bool has_py_callbacks = false;
+
+       // Consider callbacks for broadphase inclusion if it's a sensor object type
+       if (gobj1 && gobj2)
+               has_py_callbacks = gobj1->m_collisionCallbacks || gobj2->m_collisionCallbacks;
+
+       switch (info1->m_type)
        {
        case KX_ClientObjectInfo::SENSOR:
-               if (info->m_sensors.size() == 1)
+               if (info1->m_sensors.size() == 1)
                {
                        // only one sensor for this type of object
-                       KX_TouchSensor* touchsensor = static_cast<KX_TouchSensor*>(*info->m_sensors.begin());
-                       return touchsensor->BroadPhaseFilterCollision(object1,object2);
+                       KX_TouchSensor* touchsensor = static_cast<KX_TouchSensor*>(*info1->m_sensors.begin());
+                       return touchsensor->BroadPhaseFilterCollision(object1, object2);
                }
                break;
        case KX_ClientObjectInfo::OBSENSOR:
        case KX_ClientObjectInfo::OBACTORSENSOR:
                // this object may have multiple collision sensors, 
                // check is any of them is interested in this object
-               for (std::list<SCA_ISensor*>::iterator it = info->m_sensors.begin();
-                       it != info->m_sensors.end();
+               for (std::list<SCA_ISensor*>::iterator it = info1->m_sensors.begin();
+                       it != info1->m_sensors.end();
                        ++it)
                {
                        if ((*it)->GetSensorType() == SCA_ISensor::ST_TOUCH) 
@@ -112,7 +126,8 @@ bool         KX_TouchEventManager::newBroadphaseResponse(void *client_data,
                                        return true;
                        }
                }
-               return false;
+
+               return has_py_callbacks;
 
        // quiet the compiler
        case KX_ClientObjectInfo::STATIC:
@@ -155,32 +170,43 @@ void KX_TouchEventManager::EndFrame()
 
 void KX_TouchEventManager::NextFrame()
 {
-       if (!m_sensors.Empty())
-       {
                SG_DList::iterator<KX_TouchSensor> it(m_sensors);
                for (it.begin();!it.end();++it)
                        (*it)->SynchronizeTransform();
                
                for (std::set<NewCollision>::iterator cit = m_newCollisions.begin(); cit != m_newCollisions.end(); ++cit)
                {
+                       // Controllers
                        PHY_IPhysicsController* ctrl1 = (*cit).first;
-//                     PHY_IPhysicsController* ctrl2 = (*cit).second;
-//                     KX_GameObject* gameOb1 = ctrl1->getClientInfo();
-//                     KX_GameObject* gameOb1 = ctrl1->getClientInfo();
+                       PHY_IPhysicsController* ctrl2 = (*cit).second;
 
-                       KX_ClientObjectInfo *client_info = static_cast<KX_ClientObjectInfo *>(ctrl1->getNewClientInfo());
+                       // Sensor iterator
                        list<SCA_ISensor*>::iterator sit;
+
+                       // First client info
+                       KX_ClientObjectInfo *client_info = static_cast<KX_ClientObjectInfo*>(ctrl1->getNewClientInfo());
+                       // First gameobject
+                       KX_GameObject *kxObj1 = KX_GameObject::GetClientObject(client_info);
+                       // Invoke sensor response for each object
                        if (client_info) {
                                for ( sit = client_info->m_sensors.begin(); sit != client_info->m_sensors.end(); ++sit) {
-                                       static_cast<KX_TouchSensor*>(*sit)->NewHandleCollision((*cit).first, (*cit).second, NULL);
+                                       static_cast<KX_TouchSensor*>(*sit)->NewHandleCollision(ctrl1, ctrl2, NULL);
                                }
                        }
-                       client_info = static_cast<KX_ClientObjectInfo *>((*cit).second->getNewClientInfo());
+
+                       // Second client info
+                       client_info = static_cast<KX_ClientObjectInfo *>(ctrl2->getNewClientInfo());
+                       // Second gameobject
+                       KX_GameObject *kxObj2 = KX_GameObject::GetClientObject(client_info);
                        if (client_info) {
                                for ( sit = client_info->m_sensors.begin(); sit != client_info->m_sensors.end(); ++sit) {
-                                       static_cast<KX_TouchSensor*>(*sit)->NewHandleCollision((*cit).second, (*cit).first, NULL);
+                                       static_cast<KX_TouchSensor*>(*sit)->NewHandleCollision(ctrl2, ctrl1, NULL);
                                }
                        }
+                       // Run python callbacks
+                       kxObj1->RunCollisionCallbacks(kxObj2);
+                       kxObj2->RunCollisionCallbacks(kxObj1);
+
                }
                        
                m_newCollisions.clear();
@@ -188,4 +214,3 @@ void KX_TouchEventManager::NextFrame()
                for (it.begin();!it.end();++it)
                        (*it)->Activate(m_logicmgr);
        }
-}