Is it possible to perform collision detection against a single object

Post Reply
nickak2003
Posts: 17
Joined: Thu Mar 04, 2010 10:35 pm

Is it possible to perform collision detection against a single object

Post by nickak2003 »

I want to do my unit sight detection with the library, I think. So, I was thinking to have a collision object, the units sight area, and then if I could just do collision against that object to build a list of sighted objects, is this possible, or is there a better method?
User avatar
drleviathan
Posts: 849
Joined: Tue Sep 30, 2014 6:03 pm
Location: San Francisco

Re: Is it possible to perform collision detection against a single object

Post by drleviathan »

You have a scene of objects and you want to figure out which objects are visible from some point of view, excluding objects that are occluded?

Or do you want to figure out the full list of objects that overlap the "sight area" including objects that might be "occluded" inside other objects?

If the former then there are better render/GPU methods than using Bullet collisions.

If the latter then there may be better render/GPU methods if the "view" is frustum shaped, otherwise Bullet's collision system might be a good way to go.

Assuming Bullet is the way to go and not knowing any more about what you really want... I would suggest a strategy: derive a custom class from btPairCachingGhostObject to quickly get the subset of world objects that overlap your "view" shape.
nickak2003
Posts: 17
Joined: Thu Mar 04, 2010 10:35 pm

Re: Is it possible to perform collision detection against a single object

Post by nickak2003 »

So my game is top down and sort of rts-ish. A unit has a vision in which it decides to engage an enemy unit. I thought that doing a bullet-collision-shape for sight made sense to gather a list of enemies in range. Is this true?
User avatar
drleviathan
Posts: 849
Joined: Tue Sep 30, 2014 6:03 pm
Location: San Francisco

Re: Is it possible to perform collision detection against a single object

Post by drleviathan »

The btGhostObject is a btCollisionObject which doesn't actually collide with anything but keeps a list of overlapping objects. By default its overlap shape is its AABB but you can derive from that class and override some of its methods to provide different overlap criteria. Each Unit could drag around its own btGhostObject that represents its "view" in the world: it could "see" overlapping objects.

Note: it is possible to use collision group/mask feature when adding objects to the DynamicsWorld to ensure that GhostObjects don't "see" other GhostObjects.

Note: I incorrectly suggested a btPairCachingGhostObject earlier in the discussion. I realize now that was a bad suggestion. The btPairCachingGhostObject tracks the set of overlapping objects but also the overlaps between elements of that set. It can be used for faster collision queries or ray tests against a sub-set of objects rather than against ALL the objects in the world. This extra functionality would not be useful in your case.
nickak2003
Posts: 17
Joined: Thu Mar 04, 2010 10:35 pm

Re: Is it possible to perform collision detection against a single object

Post by nickak2003 »

So I have implemented a btGhostObject in my collision world, but It seems to be getting sent to the general collision detection handler. When you said it doesn't collide, were you referring to in a dynamics world? Or am I perhaps doing something wrong?
User avatar
drleviathan
Posts: 849
Joined: Tue Sep 30, 2014 6:03 pm
Location: San Francisco

Re: Is it possible to perform collision detection against a single object

Post by drleviathan »

There is more than one way to do it, but here an outline of how it might work:

You would derive a new class from btGhostObject. Let's call it UnitVisionSensor. Below is a simple example of what the UnitVisionSensor class might look like. I've made some assumptions about your GameObject API such as:

(1) The btCollisionObject::m_userPointer has been used to store a back pointer to your GameObject.

(2) The GameObject has a getRadius() method for easy sphere-spehre collision tests with the UnitVisionSensor's sphere.

(3) When you create a UnitVisionSensor you remember to call setRadius() on it so it knows its vision radius. This radius should be compatible with (e.g. inside) the local AABB of whatever btCollisionShape that you used for the UnitVisionSensor.

Code: Select all

#include <btGhostObject>

class UnitVisionSensor : public btGhostObject {
    UnitVisionSensor() : btGhostObject() {}
    void setRadius(btScalar radius) { m_radius = radius; }
    void updateVisibleThings();
    void removeOverlappingObjectInternal(btBroadphaseProxy* otherProxy, btDispatcher* dispatcher, btBroadphaseProxy* thisProxy=0) override;
private:
    std::set<GameObject*> m_visibleThings;
};  

void UnitVisionSensor::updateVisibleThings() {
    for (int32_t i = 0; i < m_overlappingObjects.size(); ++i) {
        btCollisionObject object = m_overlappingObjects[i];
        void* userPointer = object->getUserPointer();
        if (userPointer) {
            GameObject* gameObject = static_cast<GameObject*>(userPointer);
            btVector3 position = gameObject->getPosition();
            btScalar distance = (getPosition() - position).length();
            std::set<GameObject*>::iterator itr = m_visibleThings.find(gameObject);
            if (distance < m_radius + gameObject->getRadius()) {
                if (itr == m_visibleThings.end()) {
                    m_visibleThings.insert(gameObject);
                }   
            } else {
                if (itr != m_visibleThings.end()) {
                    m_visibleThings.erase(itr);
                }   
            }   
        }
    }   
}

void UnitVisionSensor::removeOverlappingObjectInternal(btBroadphaseProxy* otherProxy, btDispatcher* dispatcher, btBroadphaseProxy* thisProxy) {
    btCollisionObject* object = static_cast<btColliionObject*>(otherProxy->m_clientObject);
    if (object && object->getUserPointer()) {
        GameObject* gameObject = static_cast<GameObject*>(object->getUserPointer());
        std::set<GameObject*>::iterator itr = m_visibleThings.find(gameObject);
        if (iter != m_visibleThings.end()) {
            m_visibleThings.erase(itr);
        }
    }
    btGhostObject::removeOverlappingObjectInternal(otherProxy, dispatcher, thisProxy);
}
Disclaimer: that code is completely theoretical and untested. It may have bugs. Dunno if it even compiles.

Each Unit that can "see" would have a UnitVisionSensor, which would be added to the World and its position updated whenever the Unit moves (note: you would need to write the code that makes the UnitVisionSensor follow its Unit).

After the World->stepSimulation() you would iterate over all of the Units that can see and call: UnitVisionSensor::updateVisibleThings() for each sensor. After that: each UnitVisionSensor would have a correct list of things that it "sees".

Note: the override of removeOverlappingObjectInternal() is necessary since otherwise you would end up with dangling pointers in m_visibleThings.

Note: when using btGhostObject's you also need to add a single btGhostPairCallback to the World in order to "enable" the btGhostObject handling code. Something like this:

Code: Select all

btGhostPairCallgack* ghostPairCallback = new btGhostPairCallback();
dynamicsWorld->getPairCache()->setInternalGhostPairCallback(ghostPairCallback);
// Note: don't forget to keep a copy of ghostPairCallback and delete it on game destruction
nickak2003
Posts: 17
Joined: Thu Mar 04, 2010 10:35 pm

Re: Is it possible to perform collision detection against a single object

Post by nickak2003 »

Thanks for helping me.
Ok so here was the question from my earlier post explained better I hope:
I am using a collision world, not dynamics.
I call,

Code: Select all

	Physics::CollisionCallback callback = std::bind(&Playground::collisionCallback, this, std::placeholders::_1, std::placeholders::_2);
	mPhysics.setCollisionCallback(callback);
	mPhysics.performCollisionDetection();
perfromCollisionDetection looks like this:

Code: Select all

	void performCollisionDetection() {
		mWorld->performDiscreteCollisionDetection();

		int numManifolds = mWorld->getDispatcher()->getNumManifolds();
		//For each contact manifold
		for (int i = 0; i < numManifolds; i++) {
			btPersistentManifold* contactManifold = mWorld->getDispatcher()->getManifoldByIndexInternal(i);
			btCollisionObject* obA = const_cast<btCollisionObject*>(contactManifold->getBody0());
			btCollisionObject* obB = const_cast<btCollisionObject*>(contactManifold->getBody1());
			contactManifold->refreshContactPoints(obA->getWorldTransform(), obB->getWorldTransform());
			int numContacts = contactManifold->getNumContacts();
			//For each contact point in that manifold
			if (numContacts > 0) {
				if (mCollisionCallback)
					mCollisionCallback(obA, obB);
				//	OutputDebugStringA("Collision!!!");
			}
		}
	}
Now what I am saying is that, performCollisionDetection grabs and sends ghostObjects to the callback. I see in the code you posted above and in other code I have found on the net, that ghostobjects are generally looped through like so:

Code: Select all

  for (int32_t i = 0; i < m_overlappingObjects.size(); ++i) {
        btCollisionObject object = m_overlappingObjects[i];
I am new to bullet, is my performCollisionDetection function correct? How could I change things such that I would interact with ghostobjects the way you do rather than through collisionCallback? Yet, I would still like collision callback to work with generic Collision Objects. Maybe it doesnt matter if its looped or callback, either seems like it would work. What are your suggestions?
User avatar
drleviathan
Posts: 849
Joined: Tue Sep 30, 2014 6:03 pm
Location: San Francisco

Re: Is it possible to perform collision detection against a single object

Post by drleviathan »

I think it would go something like this:

Code: Select all

// when you create your btCollisionWorld you want to also register a btGhostPairCallback
// this effectively "enables" ghost collision behavior
mWorld = new btCollisionWorld(dispatcher, broadphasePairCache, collisionConfiguration);
mGhostPairCallback = new btGhostPairCallback();
mWorld->getPairCache()->setInternalGhostPairCallback(mGhostPairCallback)

...

// define some collision groups and their corresponding masks
int UNIT_COLLISION_GROUP = 0x01 << 0;
int GHOST_COLLISION_GROUP = 0x01 << 1;
int UNIT_COLLISION_MASK = UNIT_COLLISION_GROUP | GHOST_COLLISION_GROUP; // collides with Units and ghosts
int GHOST_COLLISION_MASK = UNIT_COLLISION_GROUP; // only collides with Units, not other Ghosts

...

// whenever you add a Unit to the world you also add its Ghost
mWorld->addCollisionObject(unit->getBody(), UNIT_COLLISION_GROUP, UNIT_COLLISION_MASK);
mWorld->addCollisionObject(unit->getGhost(), GHOST_COLLISION_GROUP, GHOST_COLLISION_MASK);

...

// after calling mWorld->performDiscreteCollisionDetection() the Ghost's lists of overlapping pairs should be up to date
Post Reply