jrouwe / JoltPhysics

A multi core friendly rigid body physics and collision detection library. Written in C++. Suitable for games and VR applications. Used by Horizon Forbidden West.
MIT License
6.72k stars 447 forks source link

Soft body contact callbacks #936

Closed jrouwe closed 8 months ago

jrouwe commented 8 months ago

@mihe and @jankrassnigg can you take a look at this one and let me know what you think of the API? The sensors interface is still missing (no more time tonight), but imagine there being a mIsSensor in SoftBodyContactSettings (meaning you will get a callback when aabbs overlap and can use that to turn a contact into a sensor, so the verts won't be checked at that point meaning it is a bit coarse).

mihe commented 8 months ago

I think it looks good!

Although I'm a bit curious as to why SoftBodyContactSettings is passed in as part of the *Validate method rather than the *Added method, like with the existing contact listener.

The sensors interface is still missing (no more time tonight), but imagine there being a mIsSensor in SoftBodyContactSettings (meaning you will get a callback when aabbs overlap and can use that to turn a contact into a sensor, so the verts won't be checked at that point meaning it is a bit coarse).

Just to clarify, is this meant to be the same as ContactSettings::mIsSensor? I was under the impression that it was made largely redundant by the inverse mass/inertia scales.

Is there any chance of getting support for soft bodies colliding with rigid sensors, or would that suffer from the same performance pitfalls as SetCollideKinematicVsNonDynamic?

jrouwe commented 8 months ago

Although I'm a bit curious as to why SoftBodyContactSettings is passed in as part of the Validate method rather than the Added method, like with the existing contact listener.

I don't want to do a virtual call per colliding vertex (too expensive), and the OnSoftBodyContactAdded callback happens after all collisions have been processed and the new velocities have been calculated. This means the only place that I can use to get the collision settings is the OnSoftBodyContactValidate callback. At that point you don't know if there is really going to be a collision or not but I can store the settings and use them when an actual collision takes place.

Note that I have incorporated sensors into the system as well now. This was actually much more simple than I thought it would be and they DO give individual contact points now rather than being a broad phase only check.

Just to clarify, is this meant to be the same as ContactSettings::mIsSensor? I was under the impression that it was made largely redundant by the inverse mass/inertia scales.

In the current system it is not redundant since particles with inv mass scale of zero will still be projected out of the rigid body (I tried removing this but then the whole thing falls over). So there is a little bit of a response of the soft body even if inv mass scale is zero. I may revisit this in the future.

Is there any chance of getting support for soft bodies colliding with rigid sensors, or would that suffer from the same performance pitfalls as SetCollideKinematicVsNonDynamic?

Since the soft body is always dynamic it doesn't suffer from this issue. It will collide with any rigid body (if the collision filter allows it) so will interact with static sensors.

jrouwe commented 8 months ago

Just to clarify, is this meant to be the same as ContactSettings::mIsSensor? I was under the impression that it was made largely redundant by the inverse mass/inertia scales.

Actually, thinking about this some more: No mIsSensor is not the same as setting inv mass scale to zero. If you set the inv mass scale to zero then the bodies will still interact (one will push the other away). Setting one of the two inv mass scales to infinite is also not the solution as any interaction will give that body infinite velocity (because it will have mass 0). Setting mIsSensor = true will disable any interaction (but will detect it).

sonarcloud[bot] commented 8 months ago

Quality Gate Passed Quality Gate passed

Issues
0 New issues

Measures
0 Security Hotspots
0.0% Coverage on New Code
0.0% Duplication on New Code

See analysis details on SonarCloud

jrouwe commented 8 months ago

Ok, I think it's good enough. @mihe let me know if you run into any issues while integrating this in godot-jolt.

jrouwe commented 8 months ago

Made a movie of the results: https://www.youtube.com/watch?v=DmS_8d2bdOw

mihe commented 8 months ago

I'm noticing that when a soft body collides with a kinematic body, and I set mInvMassScale1 to 0, because the soft body isn't meant to be affected by the collision with the kinematic body, but should still register the contacts, then there's still a normal collision between the two, which isn't the case with the regular contact listener.

If you set the inv mass scale to zero then the bodies will still interact (one will push the other away).

Is this what you were referring to when you mentioned the above?

I can sort of solve this by instead setting mIsSensor instead of mInvMassScale1 when colliding with a kinematic body, but I didn't have to rely on mIsSensor with the normal rigid body contact listener, so I'm curious as to why it's necessary with soft bodies.

So what ideally would look something this:

if (should_soft_body_collide_with_other_body && !should_other_body_collide_with_soft_body) {
    contact_settings.mInvMassScale2 = 0.0f;
    contact_settings.mInvInertiaScale2 = 0.0f;
} else if (should_other_body_collide_with_soft_body && !should_soft_body_collide_with_other_body) {
    contact_settings.mInvMassScale1 = 0.0f;
}

Now instead ends up looking something like this:

if (should_soft_body_collide_with_other_body && !should_other_body_collide_with_soft_body) {
    contact_settings.mInvMassScale2 = 0.0f;
    contact_settings.mInvInertiaScale2 = 0.0f;
} else if (should_other_body_collide_with_soft_body && !should_soft_body_collide_with_other_body) {
    if (other_body.IsKinematic()) {
        contact_settings.mIsSensor = true;
    } else {
        contact_settings.mInvMassScale1 = 0.0f;
    }
}

Similarly, you can with the regular contact listener (although I don't actually rely on this) set both bodies inverse mass/inertia to 0 and they'll just go right through eachother, whereas with soft bodies things seem to explode in SoftBodyMotionProperties::UpdateSoftBodyState if you try that.

Also, a sidenote, is there any possibility of getting *Persisted and *Removed callbacks for the manifolds? I failed to realize this earlier, but I'm more or less forced to do the manifold caching on my end now in order to properly emit the correct enter/exit events with areas/sensors.

jrouwe commented 8 months ago

I can sort of solve this by instead setting mIsSensor instead of mInvMassScale1 when colliding with a kinematic body, but I didn't have to rely on mIsSensor with the normal rigid body contact listener, so I'm curious as to why it's necessary with soft bodies.

I will look if I can make this more consistent.

Also, a sidenote, is there any possibility of getting Persisted and Removed callbacks for the manifolds? I failed to realize this earlier, but I'm more or less forced to do the manifold caching on my end now in order to properly emit the correct enter/exit events with areas/sensors.

I can easily add a 'was in contact last frame' bool on a per vertex basis, but this wouldn't tell you which body they collided with in the last frame. Is that enough or do you need the body too?

jrouwe commented 8 months ago

It would basically be this: https://github.com/jrouwe/JoltPhysics/tree/feature/sb_had_collision (warning untested code)

mihe commented 8 months ago

I can easily add a 'was in contact last frame' bool on a per vertex basis, but this wouldn't tell you which body they collided with in the last frame. Is that enough or do you need the body too?

I would presumably need the body too, since you can theoretically overlap with multiple sensors/areas at the same time.

On second thought, I might opt for a different approach with soft bodies and sensors/areas. I was thinking I would rely on the same plumbing as with the rigid bodies, and thereby emit the appropriate area signals, but this is not actually what Godot Physics does, and would probably just lead to confusion given that I would need to associate some kind of (invalid) shape index with the overlap anyway.

I really just need the list of currently overlapping areas for every soft body, so I can probably just wipe the list before every simulation step and populate it again from the contact listener.

As I'm typing this out though I'm realizing that I don't actually have the other body in OnSoftBodyContactAdded, and that it isn't actually present in SoftBodyManifold either, other than through GetContactBodyID. For some reason I was under the impression that there was one manifold per body pair, but I'm realizing now that there's only one manifold for the entire soft body.

The comment and signature of this method would then mean:

https://github.com/jrouwe/JoltPhysics/blob/3178fd65c2e61a6711540402fdde8b202af0e039/Jolt/Physics/SoftBody/SoftBodyManifold.h#L36-L40

... that only a single body (sensor or otherwise) can ever collide with a particular soft body vertex, I guess?

Testing this out seems to prove this right, where I can essentially cut out a hole in a piece of cloth by placing a sensor, assuming I haven't stumbled on some other bug by accident:

https://github.com/jrouwe/JoltPhysics/assets/4884246/1a42c9b6-80c9-4f59-b6d2-1ca7408778f5

jrouwe commented 8 months ago

that only a single body (sensor or otherwise) can ever collide with a particular soft body vertex, I guess?

Yes, there's only 1 body per vertex.

So I'm guessing you won't need this change? (I might submit it anyway as it could be useful and the member falls in the padding currently)

mihe commented 8 months ago

Yes, there's only 1 body per vertex.

Fair enough. I take it that this is a fundamental limitation then?

If so, I suppose the only way of accommodating for multiple sensor/area overlaps would be to refactor areas to use physics queries instead of actual sensors, but I'm not sure it's worth it for what will essentially only be used for wind stuff. Those wind properties probably should have been on the soft body itself (in Godot) to begin with, with the areas providing some sort of override, similar to how they deal with gravity/damping, in which case omitting support for it would be relatively insignificant.

So I'm guessing you won't need this change?

I don't think so, no, but I agree that it seems useful.

jrouwe commented 8 months ago

Fair enough. I take it that this is a fundamental limitation then?

It's more a design decision to keep the simulation fast. But yes, a sensor overlapping a soft body will indeed cause an artifact, I hadn't thought about that yet (mainly because I don't use sensors in combination with soft bodies).

B.t.w. If you're only interested in getting the list of bodies that overlapped with the soft body then I can quite easily produce that list (it's basically in SoftBodyMotionProperties::mCollidingShapes which would need an extra bool to indicate that a vertex actually collided with it).

mihe commented 8 months ago

B.t.w. If you're only interested in getting the list of bodies that overlapped with the soft body then I can quite easily produce that list (it's basically in SoftBodyMotionProperties::mCollidingShapes which would need an extra bool to indicate that a vertex actually collided with it).

Would that allow for all bodies to be correctly reported if you (for example) had multiple sensors perfectly overlapping with eachother and then had the soft body completely enveloped by all those sensors?

jrouwe commented 8 months ago

Would that allow for all bodies to be correctly reported if you (for example) had multiple sensors perfectly overlapping with eachother and then had the soft body completely enveloped by all those sensors?

If I just give the raw list without checking if a vertex actually collided then you would see all sensors, but then an overlap between a sensor and a soft body would be defined as when their bounding boxes overlap and I'm not sure if that is what you'd want (in that case you might as well just keep track of the bodies when OnSoftBodyContactValidate is called, store the sensors in a separate array and return RejectContact so it doesn't stop the soft body from colliding with actual geometry).

If we want to know if vertices actually collide with sensors I would need to re-engineer this so that you can actually collide against multiple bodies per vertex, but I would need to do it in such a way that it doesn't become much more expensive, which is tricky.