Physics: Ammo.js contactTest

Hi folks :smile:

I’m working on an overlap test with Ammo.js, but I can’t get it to work in a PlayCanvas context. I’m trying to write a function that returns an array with all Entities touching or inside a given sphere.

I’ve tried reading the engine source code, to try and understand Ammo.castObject and Ammo.wrapPointer, but I’m a bit stumped. Seems PlayCanvas stores an entity property in its collision objects, I just can’t find them. Perhaps you guys can help me? :sweat_smile:

This is what I have so far:

const position = new pc.Vec3(10, 0, 0);
const radius = 5;

const ammoVector3$1 = new Ammo.btVector3();
const ammoTransform$1 = new Ammo.btTransform();

ammoVector3$1.setValue(position.x, position.y, position.z);
ammoTransform$1.setOrigin(ammoVector3$1);

const sphereShape = new Ammo.btSphereShape(radius);
const sphereBody = new Ammo.btRigidBody(new Ammo.btRigidBodyConstructionInfo(0, null, sphereShape));

sphereBody.setWorldTransform(ammoTransform$1);

const resultCallback = new Ammo.ConcreteContactResultCallback();
resultCallback.addSingleResult = (cp, colObj0, partId0, index0, colObj1, partId1, index1) => {
    // Should I do something like this?...
    const body0 = Ammo.wrapPointer(colObj0, Ammo.btCollisionObject);
    const pcEntity = body0.entity; // undefined :(

    // Or this..?
    const rb0 = Ammo.castObject(body0, Ammo.btRigidBody);
    const pcEntity = body0.entity; // = undefined, too
};

// this = the app's RigidBodyComponentSystem
this.dynamicsWorld.contactTest(sphereBody, resultCallback);

Thanks

Hi @ra_rasmus,

Not sure if that’s of any help, but for each rigidbody component, there is a body property that holds an Ammo point (to the actual Ammo body):

entity.rigidbody.body;

From what I understand you want to do the opposite, from a body pointer find which entity is referencing that?

@LeXXik do you have any idea?

2 Likes

Agh, hmm, I can’t recall immediately from the top of my head, but I believe you are supposed to cast the collision object to a rigidbody to get the body, which we store in the rigidbody component. Something like:

const body = Ammo.castObject(colObj0, Ammo.btRigidBody);

Edit:
I will check it out later in the evening, if the answer is not found till then.

3 Likes

Thanks for the replies, guys! :smile:

@Leonidas Precisely, it’s the reverse I’m after.

@LeXXik Yeah, that’s also along what I was thinking. It’d be great if you took a gander. :face_with_monocle: Tried your suggestion with the following:

const body0 = Ammo.castObject(colObj0, Ammo.btRigidBody);
console.log(body0, body0.entity);

Just getting this. I believe it’s some WASM gibberish?

image

I cracked it! :partying_face:

Phew, the Bullet documentation is hard to read for my feeble brain.
https://pybullet.org/Bullet/BulletFull/structbtCollisionWorld_1_1ContactResultCallback.html

Apparently the collision objects are wrapped in Ammo.btCollisionObjectWrappers, so you have to dig out the Rigidbody.

A lot of trial and error got me to here:

resultCallback.addSingleResult = (cp, colObj0Wrap, partId0, index0, colObj1Wrap, partId1, index1) => {
    const wrap0 = Ammo.wrapPointer(colObj0Wrap, Ammo.btCollisionObjectWrapper) as Ammo.btCollisionObjectWrapper;
    const obj0 = wrap0.getCollisionObject();
    const body0 = Ammo.castObject(obj0, Ammo.btRigidBody);
    console.log(wrap0, obj0, body0, body0.entity);

    const wrap1 = Ammo.wrapPointer(colObj1Wrap, Ammo.btCollisionObjectWrapper) as Ammo.btCollisionObjectWrapper;
    const obj1 = wrap1.getCollisionObject();
    const body1 = Ammo.castObject(obj1, Ammo.btRigidBody);
    console.log(wrap1, obj1, body1, body1.entity);
};

Which produces the following in the console:

I have successfully detected the Player and Collision (the floor) entities. Groovy!

Every 2nd entry must be the temporary sphereBody that I add to actually perform the contactTest with. See how its pointer ID is the same in both collision cases.

Awesome, I’m unstuck! :smile:

Thanks for the help!

3 Likes

Wow, that’s deep, many thanks for sharing!

1 Like

For good measure, I found another way of doing what I wanted. Just thought I’d share:

pc.RigidBodyComponentSystem.prototype.overlapSphere = function(position: pc.Vec3, radius: number, out: pc.Entity[], collisionFilterMask: CollisionFilters = CollisionFilters.AllFilter): number {
	vector3$1.setValue(position.x, position.y, position.z);
	transform$1.setIdentity();
	transform$1.setOrigin(vector3$1);

	const sphereShape = new Ammo.btSphereShape(radius);
	const ghostObject = new Ammo.btPairCachingGhostObject();
	ghostObject.setCollisionShape(sphereShape);
	ghostObject.setWorldTransform(transform$1);
	ghostObject.setCollisionFlags(CollisionFlags.CF_NO_CONTACT_RESPONSE);

	(this.dynamicsWorld as Ammo.btDynamicsWorld).addCollisionObject(ghostObject, CollisionFilters.AllFilter, collisionFilterMask);

	const numOverlaps = Math.min(ghostObject.getNumOverlappingObjects(), out.length);
	let outI = 0;

	for (let i = 0; i < numOverlaps; i++) {
		const obj = ghostObject.getOverlappingObject(i);
		const body = Ammo.castObject(obj, Ammo.btRigidBody);

		if (body && body.entity) {
			out[outI++] = body.entity;
		}
	}

	(this.dynamicsWorld as Ammo.btDynamicsWorld).removeCollisionObject(ghostObject);

	Ammo.destroy(ghostObject);
	Ammo.destroy(sphereShape);

	return outI;
};

btPairCachingGhostObject is from what I can gather, specifically made for this situation - checking for overlaps.

I had attempted a couple of times before to no avail, but in the end it was because I needed to jam this bit into PlayCanvas’ underlying initialization of Ammo.

const dynamicsWorld = pc.Application.getApplication().systems.rigidbody.dynamicsWorld;	
dynamicsWorld.getBroadphase().getOverlappingPairCache().setInternalGhostPairCallback(new Ammo.btGhostPairCallback());

It requires somekind of internal callback setup. Otherwise the overlap test doesn’t register any bodies.

Maybe once I’m through implementing all these Ammo helper functions, I’ll post them somewhere for people to use. I personally need a bit more than just the two given raycast functions. :slight_smile:

2 Likes