I'm using MatterJs for a physics based game and have not found a solution for the problem of preventing bodies being force-dragged by the mouse through other bodies. If you drag a body into another body, the body being dragged can force itself into and through the other body. I'm looking for a reliable way to prevent them from intersecting. You can observe this effect in any MatterJS demo by selecting a body with the mouse, and trying to force it through another body. Here is a typical example:
https://brm.io/matter-js/demo/#staticFriction
Unfortunately this breaks any games or simulations depending on drag-and-drop. I have attempted numerous solutions, such as breaking the mouse constraint when a collision occurs, or reducing constraint stiffness, but nothing which works reliably.
Any suggestions welcome!
I think that the best answer here is would be a significant overhaul to the
Matter.Resolver
module to implement predictive avoidance of physical conflicts between any bodies. Anything short of that is guaranteed to fail under certain circumstances. That being said here are two "solutions" which, in reality, are just partial solutions. They are outlined below.Solution 1 (Update)
This solution has several advantages:
The idea behind this approach is to resolve the paradox of what happens "when an unstoppable force meets an immovable object" by rendering the force stoppable. This is enabled by the
Matter.Event
beforeUpdate
, which allows the absolute velocity and impulse (or ratherpositionImpulse
, which isn't really physical impulse) in each direction to be constrained to within user-defined bounds.In the example I am restricting the
velocity
andpositionImpulse
inx
andy
to a maximum magnitude of25.0
. The result is shown belowAs you can see, it is possible to be quite violent in dragging the bodies and they will not pass through one another. This is what sets this approach apart from others: most other potential solutions fail when the user is sufficiently violent with their dragging.
The only shortcoming I have encountered with this method is that it is possible to use a non-static body to hit another non-static body hard enough to give it sufficient velocity to the point where the
Resolver
module will fail to detect the collision and allow the second body to pass through other bodies. (In the static friction example the required velocity is around50.0
, I've only managed to do this successfully one time, and consequently I do not have an animation depicting it).Solution 2
This is an additional solution, fair warning though: it is not straightforward.
In broad terms the way this works is to check if the body being dragged,
dragBody
, has collided with a static body and if the mouse has since moved too far withoutdragBody
following. If it detects that the separation between the mouse anddragBody
has become too large it removes theMatter.js
mouse.mousemove
event listener frommouse.element
and replaces it with a different mousemove function,mousemove()
. This function checks if the mouse has returned to within a given proximity of the center of the body. Unfortunately I couldn't get the built-inMatter.Mouse._getRelativeMousePosition()
method to work properly so I had to include it directly (someone more knowledgeable than me in Javascript will have to figure that one out). Finally, if amouseup
event is detected it switches back to the normalmousemove
listener.After applying the event listener switching scheme the bodies now behave more like this
I have tested this fairly thoroughly, but I can't guarantee it will work in every case. It also bears noting that the
mouseup
event is not detected unless the mouse is within the canvas when it occurs - but this is true for any Matter.jsmouseup
detection so I didn't try to fix that.If the velocity is sufficiently large,
Resolver
will fail to detect any collision, and since it lacks predictive prevention of this flavor of physical conflict, will allow the body to pass through, as shown here.This can be resolved by combining with Solution 1.
One last note here, it is possible to apply this to only certain interactions (e.g. those between a static and a non-static body). Doing so is accomplished by changing
to (for e.g. static bodies)
Failed solutions
In case any future users come across this question and find both solutions insufficient for their use case, here are some of the solutions I attempted which did not work. A guide of sorts for what not to do.
mouse.mouseup
directly: object deleted immediately.mouse.mouseup
viaEvent.trigger(mouseConstraint, 'mouseup', {mouse: mouse})
: overridden byEngine.update
, behavior unchanged.Matter.Body.setStatic(body, false)
orbody.isStatic = false
).(0,0)
viasetForce
when approaching conflict: object can still pass through, would need to be implemented inResolver
to actually work.mouse.element
to a different canvas viasetElement()
or by mutatingmouse.element
directly: object deleted immediately.collisionStart
: inconsistent collision detection still permits pass through with this methodThis seems to be related to issue 672 on their GitHub page which seems to suggest that this occurs due to a lack of Continuous Collision Detection (CCD).
An attempt to remedy this has been made and the code for it can be found here but the issue is still open so it looks like you might need to edit the engine to build CCD into it yourself.
I would have managed the feature in another way:
To control collision when dragged you need to utilize collision filter and events.
Create bodies with default collision filter mask
0x0001
. Add catchstartdrag
andenddrag
events and set different body collision filter category to temporarily avoid collisions.