how to avoid Ghost Objects with rayTest()

Post Reply
PcChip
Posts: 33
Joined: Sun May 20, 2018 3:09 pm

how to avoid Ghost Objects with rayTest()

Post by PcChip »

Hi,

I've been reading through documentation and forum posts, and am having trouble figuring out how to do this:

some of my units (turrets), have a cylinderShape ghost object around them that I'm using as a trigger to start targeting enemies inside of it

This works perfectly, however now when I do a rayTest() for the user right-clicking on the terrain, instead of the terrain I get the ghost object as the result

Is there a way to just ignore ghost objects in rayTest() ?

FYI I've tried setting the ghost object's collision filter group to "sensorTrigger" but then it started detecting everything (even mass 0) as a hit!

This is how I'm doing it:

Code: Select all

	btCollisionWorld::ClosestRayResultCallback RayCallback(
		btVector3(out_origin.x, out_origin.y, out_origin.z),
		btVector3(out_end.x, out_end.y, out_end.z)
	);

	world->physicsModule->dynamicsWorld->rayTest(
		btVector3(out_origin.x, out_origin.y, out_origin.z),
		btVector3(out_end.x, out_end.y, out_end.z),
		RayCallback
	);

	if (RayCallback.hasHit()) {
		//std::cout << "mesh " << (int)RayCallback.m_collisionObject->getUserPointer();
		mouseRaycastHasHit = true;
		btVector3 hitPointWorld = RayCallback.m_hitPointWorld;
		mouseHitPointWorld = glm::vec3(hitPointWorld.getX(), hitPointWorld.getY(), hitPointWorld.getZ());
		mouseHitObjectUserIndex1 = (int)RayCallback.m_collisionObject->getUserIndex();
		mouseHitObjectUserIndex2 = (int)RayCallback.m_collisionObject->getUserIndex2();

	}
	else {
		mouseRaycastHasHit = false;
	}
thanks for any help you can provide :)
User avatar
drleviathan
Posts: 849
Joined: Tue Sep 30, 2014 6:03 pm
Location: San Francisco

Re: how to avoid Ghost Objects with rayTest()

Post by drleviathan »

You need to also set the collision group and mask correctly on the btCollisionWorld::ClosestRayResultCallback. The default ctor for that struct has these lines in it:

Code: Select all

            m_collisionFilterGroup(btBroadphaseProxy::DefaultFilter),
            m_collisionFilterMask(btBroadphaseProxy::AllFilter),
Those are public data members so you can set them directly. Before submitting the callback to the rayTest() something like this should prevent it from colliding with groupA and groupB:

Code: Select all

            RayCallback.m_collsionFilterMask &= ~(groupA & groupB);
PcChip
Posts: 33
Joined: Sun May 20, 2018 3:09 pm

Re: how to avoid Ghost Objects with rayTest()

Post by PcChip »

Thanks for the help - this makes sense

-old post removed-

Edit: with your help I was able to get rayTest to avoid the trigger ghost object, but now the ghost object is detecting the terrain in its collision manifold! (wasting CPU cycles)

any tips on how I can avoid this?

this is how I'm doing it now when spawning a turret's ghost trigger volume:

Code: Select all

dynamicsWorld->addCollisionObject(myGhostObject, btBroadphaseProxy::SensorTrigger);
when doing the rayTest and trying to avoid ghost objects:

Code: Select all

	RayCallback.m_collisionFilterMask &= ~btBroadphaseProxy::SensorTrigger;


however the manifold is full of hits with the terrain =(
User avatar
drleviathan
Posts: 849
Joined: Tue Sep 30, 2014 6:03 pm
Location: San Francisco

Re: how to avoid Ghost Objects with rayTest()

Post by drleviathan »

To make the ghost not detect the terrain you need to set the collision group and mask correctly. The simplest case would be: terrain is in a single group (GroupA) and the ghost is also in a single group (GroupB), and you would clear the GroupA bit on the ghost's mask. As long as one of the objects' masks is missing the bit of the other then the broadphase won't track overlaps between the two objects.

You don't have to use the default groups defined in Bullet:

Code: Select all

            DefaultFilter = 1,
            StaticFilter = 2,
            KinematicFilter = 4,
            DebrisFilter = 8,
            SensorTrigger = 16,
            CharacterFilter = 32,
You can ignore these and define your own.
PcChip
Posts: 33
Joined: Sun May 20, 2018 3:09 pm

Re: how to avoid Ghost Objects with rayTest()

Post by PcChip »

okay thanks, I'll try to figure this out

two questions:

- does this mean I now have to set collision groups on everything? never used them before and was hoping for an easy way to do what I want without them. Why is this only happening now that I set the ghost object to be a sensortrigger?

- is there some guide that explains the difference between groups, filters, masks, and flags? and a tutorial on how to use all of them?

thanks again for your help
User avatar
drleviathan
Posts: 849
Joined: Tue Sep 30, 2014 6:03 pm
Location: San Francisco

Re: how to avoid Ghost Objects with rayTest()

Post by drleviathan »

You only need collision groups if you're doing fancy stuff. Using ghosts for radar sensors and ray-picking for object selection would count as "fancy stuff".

However, there is another reason to use groups: collision optimizations. The groups+masks are checked on each new overlap in the broadphase. If the two AABB proxies should not collide according to their groups+masks then the broadphase never reports the overlapping pair to the narrowphase. In short: an early cheap check can avoid more expensive later checks.

I don't know why this is happening now. I have insufficient info to answer that question.

I don't know of a good tutorial but I'm sure there is one somewhere in these forums. Understanding of groups and masks didn't click for me right away, but now it seems simple enough. I would summarize it with this fact: two overlapping broadphase proxies are filtered out (don't collide) if: !(maskA & groupB) || !(maskB & groupA). That is, if either (group & mask) combination evaluates to zero, then the broadphase proxies don't "touch".

One way to go for your game be to define the following groups and masks:

Code: Select all

    terrainGroup = 0x01
    unitGroup = 0x02
    rayGroup = 0x04
    ghostGroup = 0x08

    terrainMask = unitGroup
    unitMask = unitGroup | terrainGroup
    clickableUnitMask = unitGroup | terrainGroup | rayGroup
    rayMask = rayGroup
    ghostMask = unitGroup
Terrain would have terrainGroup + terrainMask. If you want the terrain to be clickable then add the rayGroup bit to its mask.

Units would have unitGroup and either unitMask or clickableUnitMask, depending on if you should be able to click on it or not.

Rays would have rayGroup and rayMask. They only collide against things in their own group (e.g. any units set as "clickable").

Ghosts would have ghostGroup and ghostMask.
PcChip
Posts: 33
Joined: Sun May 20, 2018 3:09 pm

Re: how to avoid Ghost Objects with rayTest()

Post by PcChip »

thanks for the mini-tutorial, it's hard to find good understandable info on this

I think I've managed to sort it out for now but I will certainly add your example into the comments of my code for later use (I know I'll be back to it soon) - as a point of reference this is my simple little game: https://www.youtube.com/watch?v=8YLuHKeE10g


It appears to be working as expected now when I do the following:

Terrain Rigid Body:

Code: Select all

dynamicsWorld->addRigidBody(myRigidBody, btBroadphaseProxy::StaticFilter, ~btBroadphaseProxy::SensorTrigger);


Turret Ghost Object (radar/sensor)

Code: Select all

dynamicsWorld->addCollisionObject(myGhostObject, btBroadphaseProxy::SensorTrigger, ~(btBroadphaseProxy::SensorTrigger | btBroadphaseProxy::StaticFilter));


it also appears to be running smoother as well, less CPU wasted on narrowphase I'm hoping

Thanks again!
(and hopefully someone down the line can use this example when trying to learn groups)
PcChip
Posts: 33
Joined: Sun May 20, 2018 3:09 pm

Re: how to avoid Ghost Objects with rayTest()

Post by PcChip »

update:

above wasn't working so I gave it another shot using your suggestion + the tutorial on bulletphysics.org

this is what I'm doing:

Code: Select all

enum collisionGroups // http://www.bulletphysics.org/mediawiki-1.5.8/index.php/Collision_Filtering
{
	COL_NOTHING   =     0,  //<Collide with nothing
	COL_GROUND    = BIT(0), //<Collide with ground
	COL_BUILDINGS = BIT(1), //<Collide with buildings
	COL_GHOSTS    = BIT(2), //<Collide with ghost objects
	COL_UNITS     = BIT(3), //<Collide with units
	COL_RAYCASTS  = BIT(4)  //<Collide with Raycasts
};

enum collisionMasks
{
	terrainCollidesWith   = COL_UNITS | COL_RAYCASTS,
	buildingsCollidesWith = COL_UNITS | COL_RAYCASTS,
	ghostsCollidesWith    = COL_UNITS,
	unitsCollidesWith     = COL_GHOSTS | COL_GROUND | COL_BUILDINGS | COL_RAYCASTS | COL_UNITS,
	rayCastCollidesWith   = COL_GROUND | COL_UNITS  | COL_BUILDINGS
};
now strangely everything is working perfectly except the ghost objects - they're not registering the units in their manifold, and they ARE registering other ghost objects... hmmm

Edit: Back answering my own question for future people

The problem was that when coming up with the "detection" code using ghost objects, I found that the ghost object was always in body0, and the unit detected was always in body1, so I built the detection code around that idea. The reality is that objects added to the simulation world after the ghost object were in body1, however objects added into the simulation *before* the ghost were coming up in body0

this is my detection code that my turrets use now:

Code: Select all

bool managerBase::findAnyTarget(btPairCachingGhostObject* myGhostObject, int &index1, int &index2)
{
	
	btManifoldArray manifoldArray;
	btBroadphasePairArray& pairArray = myGhostObject->getOverlappingPairCache()->getOverlappingPairArray();
	int numPairs = pairArray.size();

	for (int k = 0; k < numPairs; ++k)
	{
		manifoldArray.clear();
		const btBroadphasePair& pair = pairArray[k];
		btBroadphasePair* collisionPair = world->physicsModule->dynamicsWorld->getPairCache()->findPair(pair.m_pProxy0, pair.m_pProxy1);
		if (!collisionPair) continue;
		if (collisionPair->m_algorithm)
			collisionPair->m_algorithm->getAllContactManifolds(manifoldArray);

		for (int j = 0; j < manifoldArray.size(); j++)
		{
			btPersistentManifold* manifold = manifoldArray[j];
			for (int p = 0; p < manifold->getNumContacts(); ++p)
			{
				const btManifoldPoint& pt = manifold->getContactPoint(p);

				if (pt.getDistance()<0.f)
				{
					// Object is inside ghost's shape at this manifold point
					// process shape overlap here
					int test1 = manifold->getBody1()->getUserIndex();
					int test2 = manifold->getBody1()->getUserIndex2();

					int test3 = manifold->getBody0()->getUserIndex();
					int test4 = manifold->getBody0()->getUserIndex2();
					if (test1 >= 0 && test2 >= 0) //found something that's not ground or ghost
					{
						index1 = test1;
						index2 = test2;
						//std::cout << "Target found: " << test1 << ":" << test2 << "\n";
						return true;
					}
					else if (test3 >= 0 && test4 >= 0)//found something that's not ground or ghost
					{
						index1 = test3;
						index2 = test4;
						//std::cout << "Target found: " << test3 << ":" << test4 << "\n";
						return true;
					}

					else
					{
						//std::cout << "invalid Target found: myGhostObject: " << myGhostObject << " body0: " << test3 << ":" << test4 << " body1: " << test1 << ":" << test2 << "\n";
					}

				}
				else
				{
					// Object is in ghost's aabb but not inside its shape at this manifold point
					// process aabb overlap here
				}


			}
		}
	}
	return false; //didn't find anything

	
}
Post Reply