diff --git a/doc/python_api/rst/bge_types/bge.types.KX_GameObject.rst b/doc/python_api/rst/bge_types/bge.types.KX_GameObject.rst index 3be148556ef..5d699637eb7 100644 --- a/doc/python_api/rst/bge_types/bge.types.KX_GameObject.rst +++ b/doc/python_api/rst/bge_types/bge.types.KX_GameObject.rst @@ -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). diff --git a/source/gameengine/Ketsji/KX_GameObject.cpp b/source/gameengine/Ketsji/KX_GameObject.cpp index e0ec4983739..e06f7ab6633 100644 --- a/source/gameengine/Ketsji/KX_GameObject.cpp +++ b/source/gameengine/Ketsji/KX_GameObject.cpp @@ -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 (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 (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(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(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(self_v); diff --git a/source/gameengine/Ketsji/KX_GameObject.h b/source/gameengine/Ketsji/KX_GameObject.h index 5a3b9df74ee..dde3ff53299 100644 --- a/source/gameengine/Ketsji/KX_GameObject.h +++ b/source/gameengine/Ketsji/KX_GameObject.h @@ -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); diff --git a/source/gameengine/Ketsji/KX_TouchEventManager.cpp b/source/gameengine/Ketsji/KX_TouchEventManager.cpp index 96872f4e6fd..fcb09ebfec6 100644 --- a/source/gameengine/Ketsji/KX_TouchEventManager.cpp +++ b/source/gameengine/Ketsji/KX_TouchEventManager.cpp @@ -81,28 +81,42 @@ bool KX_TouchEventManager::newBroadphaseResponse(void *client_data, void *object2, const PHY_CollData *coll_data) { - PHY_IPhysicsController* ctrl = static_cast(object1); - KX_ClientObjectInfo *info = (ctrl) ? static_cast(ctrl->getNewClientInfo()) : NULL; + PHY_IPhysicsController* ctrl1 = static_cast(object1); + PHY_IPhysicsController* ctrl2 = static_cast(object2); + + KX_ClientObjectInfo *info1 = (ctrl1) ? static_cast(ctrl1->getNewClientInfo()) : NULL; + KX_ClientObjectInfo *info2 = (ctrl1) ? static_cast(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(*info->m_sensors.begin()); - return touchsensor->BroadPhaseFilterCollision(object1,object2); + KX_TouchSensor* touchsensor = static_cast(*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::iterator it = info->m_sensors.begin(); - it != info->m_sensors.end(); + for (std::list::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 it(m_sensors); for (it.begin();!it.end();++it) (*it)->SynchronizeTransform(); for (std::set::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(ctrl1->getNewClientInfo()); + // Sensor iterator list::iterator sit; + + // First client info + KX_ClientObjectInfo *client_info = static_cast(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(*sit)->NewHandleCollision((*cit).first, (*cit).second, NULL); + static_cast(*sit)->NewHandleCollision(ctrl1, ctrl2, NULL); } } - client_info = static_cast((*cit).second->getNewClientInfo()); + + // Second client info + client_info = static_cast(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(*sit)->NewHandleCollision((*cit).second, (*cit).first, NULL); + static_cast(*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); } -}