ammo.js icon indicating copy to clipboard operation
ammo.js copied to clipboard

gContactProcessedCallback & gContactDestroyedCallback

Open ghost opened this issue 10 years ago • 27 comments

I have been searching for the last couple days, and can't seem to find out how to tap into these two Bullet callback functions.

I am trying to create some 'collisionBegin / collisionEnd' and 'triggerEnter / triggerLeave' type functions, and so far I have only been able to get the initial contact to work using the dispatcher manifolds.

Is there any way to determine if two objects, which were previously colliding, is no longer colliding? From what it looks like, I should be able to set a global 'ContactProcessedCallback' to detect when two objects collide, and a global 'ContactDestroyedCallback' for when the separate, but there is really no information on how to do this with Ammo.

If there is no way to do it with these callbacks, I would really be interested in a 'work around' on how to do it otherwise.

ghost avatar Aug 16 '14 18:08 ghost

I would also be interested in this functionality.

rhulha avatar Jan 13 '15 17:01 rhulha

I would also like to see this feature or some documentation how to do it!

Since you are asking for a workaround, maybe my simple trigger is interessting for you. There is no End-Overlapping-detection Im just resetting with setTimeout(). Maybe this could be done with a second trigger object surrounding the actual trigger. Live example Weapon pickup, ammo refill (please dont judge my code :hamster: )

if ( manifold.getBody0() === fps.body.a && manifold.getBody1() === trigger1.a ) {

    if ( onTrigger === false ) {

        sound3.play();
        onTrigger = true;
        magazine++;

        setTimeout(function(){ onTrigger = false; }, 1000 );
    }
}

weiserhei avatar Jan 20 '15 15:01 weiserhei

What's wrong with using btCollisionWorld::contactPairTest?

Anyway, I don't think it's possible to use these callbacks in Ammo as there's no way (that I know of) to export global variables to js. You could add some code to bullet though, do your processing in c++ and only export the results. Here's how I managed to (crudely) use gContactAddedCallback in my branch:

https://github.com/Kuang11/ammo.js/commit/d8478a9a261cb7ede563ab8771ca7cd2710111cc

honzasusek avatar Jan 20 '15 15:01 honzasusek

My current workaround: Flag all JS collision objects as separated. Check Ammo for current collisions. If they are still connected, flag them as not separated. If a new collision is detected, create a JS collision object for those two bodies, call the 'collision begin' function on the objects, passing in the JS collision. Any collisions which are still flagged as separated, call their 'collision end' function and delete the JS collision object.

It is kind of like a 'stay connected' ping in netoworking.

It works ok, but fails in situations like if a ball is rolling on a ground, there are constant collision begin / ends.

I haven't looked at contactPairTest, will look into it now. Perhaps I could use that after the initial collision, to determine if they are still colliding.

ghost avatar Jan 20 '15 19:01 ghost

I don't suppose there is a way to show how to use the webidl bindings approach to override/write custom callbacks, since the "customizeVTable stuff is tricky and deprecated". Is there an example I am overlooking?

ghost avatar Jan 22 '15 19:01 ghost

See

http://kripken.github.io/emscripten-site/docs/porting/connecting_cpp_and_javascript/WebIDL-Binder.html#sub-classing-c-base-classes-in-javascript-jsimplementation

Example (js in same dir) https://github.com/kripken/emscripten/blob/master/tests/webidl/test.idl#L38

kripken avatar Jan 28 '15 01:01 kripken

Awesome, I can see how to do it now, but what about global scope functions, which aren't part of a class? Is it the 'GlobalObject' class? We would just add an interface for GlobalObject and add gContactProcessed / DestroyedCallback as functions? I'm talking about something like thecplusplusguy does here: http://youtu.be/YweNArzAHs4?t=7m40s

ghost avatar Jan 30 '15 21:01 ghost

Not sure what is the relevant part of the video - is there a text code sample of this?

kripken avatar Jan 30 '15 21:01 kripken

I guess we're supposed to be able to create a ContactAddedCallback function, something like:

function callbackFunction (manifoldPoint, bodyA, idA, indexA, bodyB, idB, indexB){ // do something with manifoldPoint, bodies, etc here. }

Then to use this callback function, we need to set it to a global variable:

gContactAddedCallback = callbackFunction;

So if one object hits another, it will call that callback function, this way we don't need to iterate over all the contact manifolds and do it manually. For example, if objectA has its CF_CUSTOM_MATERIAL_CALLBACK flag set, and it collides with another object, the 'callbackFunction' will be called when those two objects collide.

Supposedly there are two more callbacks:ContactDestroyedCallback and ContactProcessedCallback.

http://www.bulletphysics.org/Bullet/phpBB3/viewtopic.php?p=7417&f=&t=#p7417

I'm just not sure how to add this to the IDL.

ghost avatar Jan 30 '15 23:01 ghost

Are you saying bullet has a global variable that you need to set? That seems like an odd API for them to use...

The way to support this here is probably to add a static method to some class,

  class btBulletSomething {
    static setCallback(.. callbackFunction) {
      gContactAddedCallback = callbackFunction;
    }
  }

then add that method in idl.

kripken avatar Jan 30 '15 23:01 kripken

can anyone share implemented gContactProcessedCallback ?

praleks avatar Dec 07 '15 15:12 praleks

was this solved? Is there a way/example of using gContactProcessedCallback ?

reliableJARED avatar Dec 07 '16 18:12 reliableJARED

Hey everyone. I just wanted to share a pretty solid solution I have. I can't send a pull request, because you need to make a small change in the Bullet code. This info assumes you have a working build environment, and can recompile Ammo.js from the Bullet sources.

In Bullet, in the file btDiscreteDynamicsWorld.h add

void setContactAddedCallback(unsigned long callbackFunction) {
      gContactAddedCallback = (ContactAddedCallback)callbackFunction;
}
void setContactProcessedCallback(unsigned long callbackFunction) {
      gContactProcessedCallback = (ContactProcessedCallback)callbackFunction;
}
void setContactDestroyedCallback(unsigned long callbackFunction) {
      gContactDestroyedCallback = (ContactDestroyedCallback)callbackFunction;
}

In Ammo, in the ammo.idl file add

interface btDiscreteDynamicsWorld {
  void setContactAddedCallback(long funcpointer);
  void setContactProcessedCallback(long funcpointer);
  void setContactDestroyedCallback(long funcpointer);
...
}

In make.py, add -s RESERVED_FUNCTION_POINTERS=20 to the array emcc_args. In my project, that's on line 29, but might have changed since I forked.

Go ahead and build Ammo.

Finally, there are a few hoops to jump through in the JS. Note that you'll have to manually cast the incoming pointers to the proper type. I've made an example here for gContactProcessedCallback, and you can probably figure out the others from the Bullet docs.


function collisionCallbackFunc( cp,colObj0,colObj1)
{
            colObj0 = Ammo.wrapPointer(colObj0, Ammo.btRigidBody);
            colObj1 = Ammo.wrapPointer(colObj1, Ammo.btRigidBody);
            cp = Ammo.wrapPointer(cp, Ammo.btManifoldPoint);
            //trigger your events. 
}

var collisionCallbackPointer = Ammo.Runtime.addFunction( collisionCallbackFunc);
var dynamicsWorld = new Ammo.btDiscreteDynamicsWorld(...);
dynamicsWorld.setContactProcessedCallback(collisionCallbackPointer);

Finally, don't forget to set CF_CUSTOM_MATERIAL_CALLBACK on all bodies. We have hard coded this like so, wherever we set the flags

this.body.setCollisionFlags(this.collisionFlags | CF_CUSTOM_MATERIAL_CALLBACK);

FYI the value of CF_CUSTOM_MATERIAL_CALLBACK is 8.

This solution is working great at Virtulous. Before, the only way to get consistent collision notifications was to test the manifolds after each simulation step. If there were more than a single substep, you could end up missing contacts that were created then destroyed within a single step. So, we had to choose between physics accuracy and getting the callbacks.

Virtulous avatar Feb 28 '17 21:02 Virtulous

I wanted to reach out to determine if this functionality has been added to the latest AmmoJS build before going through the previously mentioned process. Does anyone know if this has been added?

InteractiveTimmy avatar Apr 13 '18 01:04 InteractiveTimmy

glad I'm not the only one interested in that feature :+1:.

I tried recently to apply what was said by @Virtulous and failed to make it work.

I believe I did every steps in the right order, multiple unsuccessful attempts so far. my final issue is that dynamicsWorld.setContactProcessedCallback stay undefined, even if I'm able to find it in the ammo.js generated.

I'd gladly contribute to the repo if I ever get it to work (spoiler: I would need assistance to do that).

in the meantime, if anyone have a clue why it might fail on my side so far? I believe it should work :|

GuillaumeBouchetEpitech avatar Apr 29 '18 22:04 GuillaumeBouchetEpitech

Hey, I'm the Virtulous guy. Sorry that the process did not work. I tried to document as carefully as possible, but it was a few years ago and I can't say if the same steps work with current builds. If you email me, I can give you my compiled version of the engine with this feature enabled..

rchadwic avatar May 22 '18 14:05 rchadwic

Hey, cheers for the answer :).

tbh I ended up making it works a while ago (mid-May or after).

The problem was the emscripten sdk installed on my machine, slightly outdated, there was a few bugs present with this old version that would not make it work at all if I remember correctly (I think it was addFunction that misbehaved).

Anyway, I updated the emscripten sdk and also had to refresh a bit how to proceed with the advices you kindly shared in here, I think the whole method to achieve this changed quite a bit with the latest version I decided to go for.

I believe to have noted the steps to achieve this somewhere.

Should anyone else be struggling for this I can always try to: -> dig it up, make it work again and share the steps here. -> then I should ideally repeat it with the latest emscripten sdk released and share the result as well.

I'll do my best to help if it's needed. I also won't lie about the limited amount of free time I can spend on this (life's too short).

GuillaumeBouchetEpitech avatar Jun 19 '18 21:06 GuillaumeBouchetEpitech

I've implemented the fix and I have ContactAddedCallback working, but ContactDestroyedCallback never calls. I've tried to figure out any possibility, but I'm unable to. It's being done the same way as ContactAdded everywhere, so I don't see any reason why it shouldn't work unless there's some other issue. The code I'm using is below:

            function contactDestroyedCallback(cp_ptr, bodyA_ptr, bodyB_ptr) {
			const bodyA = Ammo.wrapPointer(bodyA_ptr, Ammo.btRigidBody);
			const bodyB = Ammo.wrapPointer(bodyB_ptr, Ammo.btRigidBody);
			const cp = Ammo.wrapPointer(cp_ptr, Ammo.btManifoldPoint);

			console.log('Collision end', bodyA, bodyB, cp);
		}

		const contactDestroyedCallback_ptr = Ammo.addFunction(contactDestroyedCallback);
		this.world.setContactDestroyedCallback(contactDestroyedCallback_ptr);

@GuillaumeBouchetEpitech did you manage to get this callback working? If so, how/what am I doing wrong?

MrSonicMaster avatar Oct 01 '18 22:10 MrSonicMaster

@MrSonicMaster It worked for me.

I think I see where your problem might be, I solved in C++ and dispatched event in JS from it.

Let me try to explain:

by default bullet physic in C++ possess 3 global callbacks:

  • gContactAddedCallback
  • gContactProcessedCallback
  • gContactDestroyedCallback

gContactAddedCallback:

  • it's apparently a bad idea to use it, so you can safely ignore it, I did and it worked anyway, just saying.
  • this callback is called early during a collision process, it might not collide yet or ever... useless to me.
  • this callback is also said to be called too many times which can impact the performances...

gContactProcessedCallback

  • this callback is the one you normally need to tell if a collision "begin" or is "updated"
  • it does mean that this callback will be called for both the "begin" and the "updated" part

gContactDestroyedCallback

  • this callback is the one you need to tell if a collision "end"
  • it will never be called if the "contactPoint.m_userPersistentData" is NULL
  • as far as I can remember, it will only provide value of the "contactPoint.m_userPersistentData"

When gContactProcessedCallback is called: 1: check if the callback a "begin" or an "updated" one 2: to do so check the value of "contactPoint.m_userPersistentData" 3: BEGIN COLLISION -- 1: if "contactPoint.m_userPersistentData"is NULL then it's a "begin" collision -- 2: you must set the value (of m_userPersistentData) to something useful -- 3: the value should be something you can use later to recognise the collision -- 4: ideally the value would also be stored in a container to look it up (I used a "Map"). -- 5: beyond that I just dispatched a JS event "beginContact" to the related bodies 4: UPDATED COLLISION -- 1: if "contactPoint.m_userPersistentData"is NOT NULL then it's an "updated" -- 2: you must use the value (of m_userPersistentData) to identify which collision is concerned -- 3: this callback is called often so I had to check if the position and normal changed a bit ---- 1: the position is held in "contactPoint.m_positionWorldOnB" ---- 2: the normal is held in "contactPoint.m_normalWorldOnB" -- 4: beyond that I just dispatched a JS event "updateContact" to the related bodies

When gContactDestroyedCallback is called: 1: you must use the value (of m_userPersistentData) to identify which collision is concerned 2: here it's a good idea to clear the collision from the container to avoid a memory leak (the "Map") 3: beyond that I just dispatched a JS event "endContact" to the related bodies

It might help you for now, I will try to see if I can share some code at a later date, I might be able to make a contribution to ammo.js as well if it's not a problem, will see.

GuillaumeBouchetEpitech avatar Oct 02 '18 21:10 GuillaumeBouchetEpitech

Hello! Thanks for the response. I've added m_userPersistentData to the webidl bindings and was able to get destroy to correctly be called by calling point.set_m_userPersistentData(). That being said, the value of m_userPersistentData is always 0 the collision processed callbacks. I'm unable to detect a 'begin' vs an 'update.' Any ideas why that might be and how I might go about solving this?

MrSonicMaster avatar Oct 02 '18 22:10 MrSonicMaster

It mght depend on how you are seeting the value of m_userPersistentData. I'm not sure I can help a lot on this predictable (frustrating) issue as I made it work in C++. Meaning that in my case: => the value of m_userPersistentData was a C++ pointer and not a Js reference => the pointer was (void*) was litteraly a casted integer and I just used the value as an collisionID => then I setup a std::map to store a custom structure holding the collsion data as value and the collisionID as key => from here I just call the Js callback (your addFunction part)

NOTES: the difficult part here might very well be that gContactProcessedCallback is a global callback from bullet so you can't use attributes from a class unless those are static attributes, meaning the std::map I mentionned need to be either: a globale, a locale variable, a (private?) static attribute; All fr the sake of it being accessible from the inside of the callbacks, same for the addFunction pointers, those need to be accessible from inside the callback as it's where you may want to use them.

hopefully it help.... if not feel free to ask.

GuillaumeBouchetEpitech avatar Oct 03 '18 09:10 GuillaumeBouchetEpitech

I simply added attribute any m_userPersistentData; to the WebIDL bindings for the btManifoldPoint interface. I was then able to use the auto-generated set_m_userPersistentData() and get_m_userPersistentData() functions in the javascript gContactProcessedCallback. Using the set_m_userPersistentData in the contactprocessed callback, I was able to receive gContactDestroyed callbacks, with the only argument being the value of m_userPersistentData that I set. All in all, that part seemed to work except for the fact that m_userPersistentData was always 0 in the ContactProcessed callback. I just decided to create a map in my Javascript code and

  • on contact processed, use body userIndex value and store collision data to map
  • if already in map, return; (it's an update, not an add)
  • on contact destroyed, remove from map and retrieve collision data.

This solution seems to work for my purposes. Thanks for the assistance.

MrSonicMaster avatar Oct 03 '18 20:10 MrSonicMaster

I'm experiencing the same issue described here with triangle edges when using a btBvhTriangleMeshShape. The fix described there requires use of gContactAddedCallback to modify the contact normal. I'm having issues with the 2 collision objects. Only one of the pointers is resolved to the correct object, the other one is not. Code is as follows:

function contactAddedCallback(cp_ptr, colObj0_ptr, partId0, index0, colObj1_ptr, partId1, index1) {
			const colObj0 = Ammo.wrapPointer(colObj0_ptr, Ammo.btCollisionObject);
			const colObj1 = Ammo.wrapPointer(colObj1_ptr, Ammo.btCollisionObject);
			const point = Ammo.wrapPointer(cp_ptr, Ammo.btManifoldPoint);

			console.log(cp_ptr, colObj0_ptr, partId0, index0, colObj1_ptr, partId1, index1);

			this.fixCollision(point, colObj0);
			this.fixCollision(point, colObj1);

			return true;
		}

		const contactAddedCallback_ptr = Ammo.addFunction(contactAddedCallback.bind(this));
		this.world.setContactAddedCallback(contactAddedCallback_ptr);

in fixCollision, I'm doing the logs console.log(shape, shape.getShapeType()); and it logs the correct shape and shape type for the 1st one, but a null pointer shape for the 2nd one. The shape type for the 2nd one should be 21. Any ideas why?

I also logged the pointers for each and nothing looked out of the ordinary to me - 5271572 28216 1121641784 1111238624 28416 0 14811

MrSonicMaster avatar Oct 03 '18 23:10 MrSonicMaster

Glad it worked for you as well then.

On this latest issue of yours, I'm afraid it's beyond my current knowledges. I can try to help if you provide a simple example that help reproduce it but it would mean sharing (part of) your code. I'd welcome any update on your progress on the matter othwerwise ;).

GuillaumeBouchetEpitech avatar Oct 04 '18 08:10 GuillaumeBouchetEpitech

Ah! Maybe I am not experiencing your latest issue as I might use a different version of bullet as well and probably not the one shipped with ammo.js.

That's what I used last time I needed bullet's latest source code

git clone https://github.com/bulletphysics/bullet3
cd ./bullet3/
git checkout tags/2.87

GuillaumeBouchetEpitech avatar Oct 04 '18 10:10 GuillaumeBouchetEpitech

I assume you updated the webidl bindings yourself as the bindings shipped with this repsitory don't work with bullet3 yet. I'll update them when I get the time, I've been busy working on more important parts of my project before I get to fixing the triangle edge issue I talked about. Too bad I've got no way of knowing if bullet3 will fix my issue or not.

MrSonicMaster avatar Oct 07 '18 00:10 MrSonicMaster

I do not wrap bullet but use it with C++ and got some kind of custom wrapper above it (some form of ammojs duplication that expose a few bullet "hidden" and "challenging" features). It's still not ready to be shown but looks promising (as far as I can tell anyway).

I'm happy to help but you will need to provide a working source code that reproduce your issue ;).

GuillaumeBouchetEpitech avatar Oct 08 '18 09:10 GuillaumeBouchetEpitech