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