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
This commit is contained in:
parent
51b645a655
commit
dd65a44c9a
|
@ -157,9 +157,46 @@ base class --- :class:`SCA_IObject`
|
|||
|
||||
.. attribute:: collisionCallbacks
|
||||
|
||||
A list of callables to be run when a collision occurs.
|
||||
A list of functions to be called when a collision occurs.
|
||||
|
||||
:type: list
|
||||
:type: list of functions and/or methods
|
||||
|
||||
Callbacks should either accept one argument `(object)`, or three
|
||||
arguments `(object, point, normal)`. For simplicity, per
|
||||
colliding object only the first collision point is reported.
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
# Function form
|
||||
def callback_three(object, point, normal):
|
||||
print('Hit by %r at %s with normal %s' % (object.name, point, normal))
|
||||
|
||||
def callback_one(object):
|
||||
print('Hit by %r' % object.name)
|
||||
|
||||
def register_callback(controller):
|
||||
controller.owner.collisionCallbacks.append(callback_three)
|
||||
controller.owner.collisionCallbacks.append(callback_one)
|
||||
|
||||
|
||||
# Method form
|
||||
class YourGameEntity(bge.types.KX_GameObject):
|
||||
def __init__(self, old_owner):
|
||||
self.collisionCallbacks.append(self.on_collision_three)
|
||||
self.collisionCallbacks.append(self.on_collision_one)
|
||||
|
||||
def on_collision_three(self, object, point, normal):
|
||||
print('Hit by %r at %s with normal %s' % (object.name, point, normal))
|
||||
|
||||
def on_collision_one(self, object):
|
||||
print('Hit by %r' % object.name)
|
||||
|
||||
.. note::
|
||||
For backward compatibility, a callback with variable number of
|
||||
arguments (using `*args`) will be passed only the `object`
|
||||
argument. Only when there is more than one fixed argument (not
|
||||
counting `self` for methods) will the three-argument form be
|
||||
used.
|
||||
|
||||
.. attribute:: scene
|
||||
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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
|
||||
*/
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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
|
||||
|
|
Loading…
Reference in New Issue