Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Objective
Closes #150.
Advanced contact scenarios often require filtering or modifying contacts with custom logic. Use cases include:
Physics engines typically handle this with hooks or callbacks that are called during specific parts of the simulation loop. For example:
b2CustomFilterFcn()
andb2PreSolveFcn
(see docs)PhysicsHooks
trait withfilter_contact_pair
/filter_intersection_pair
andmodify_solver_contacts
(see docs)ContactListener
withOnContactValidate
,OnContactAdded
,OnContactPersisted
, andOnContactRemoved
(see docs)Currently, we just have a
PostProcessCollisions
schedule where users can freely add systems to operate on collision data before constraints are generated. However:PostProcessCollisions
is generally not a good approach for performance, and it is not enough for our needs, even if it is highly flexible. We need proper collision hooks that are called as part of the simulation loop.Solution
Add a
CollisionHooks
trait for types implementingReadOnlySystemParam
, with afilter_pairs
method for filtering broad phase pairs, and amodify_contacts
method for modifying and filtering contacts computed by the narrow phase.The system parameter allows ECS access in hooks. Only read-only access is allowed, because contact modification hooks may run in parallel, but deferred changes are supported through
Commands
passed to the hooks.An example implementation to support interaction groups and one-way platforms might look like this:
The hooks can then be added to the app using
PhysicsPlugins::with_collision_hooks
:Note
The hooks are passed to the
BroadPhasePlugin
andNarrowPhasePlugin
with generics. An app can only have one set of hooks defined.Where are the generics on
PhysicsPlugins
then?bevy_rapier
requires them onRapierPhysicsPlugin
, forcing people to specify generics even if hooks aren't used, likeRapierPhysicsPlugin::<()>::default()
(see dimforge/bevy_rapier#501).Given that this is the first thing users do with the engine, I wanted to avoid forcing unnecessary generics. I'm using a subtle trick to get around them;
PhysicsPlugins
has no generics, but there is a separatePhysicsPluginsWithHooks
wrapper with a similar API that is returned bywith_collision_hooks
. This abstraction is largely transparent to users, and gets around unnecessary generics in the public API.It is rare to want hooks to run for every single collision pair. Thus, hooks are only called for collisions where at least one entity has the new
ActiveCollisionHooks
component with the corresponding flags set. By default, no hooks are called.Comparison With
bevy_rapier
The design of the collision hooks is partially inspired by
bevy_rapier
, but with what I think is a slightly friendlier and more flexible API. Some core differences:raw
property you need to access. It provides read-only access to some internal Rapier data (using Nalgebra types) and a contact manifold, and write-access to a contact normal and "solver contacts". There seems to be no way to queue commands or otherwise perform changes to the ECS, only read-only access.Commands
, while the contact modification hook provides mutable access to theContacts
(not necessarily just one manifold) between a contact pair, and toCommands
. Read-only data about the involved entities can be queried with the ECS.Personally, I think
bevy_rapier
's hooks introduce a bit too much complexity and new APIs for Bevy users; there are "context views", contact manifolds, solver contacts, a bunch of internal Rapier structures, and everything uses Nalgebra types. I tried to keep it more simple, with the same contact types people already use when accessing theCollisions
resource, while supporting read-only ECS access using the system parameter and deferred changes usingCommands
. No weird context views or Nalgebra types.Rapier provides solver contacts, while my implementation provides raw narrow phase contact data. Both have their trade-offs, but using raw contact data introduces less new concepts, and it allows earlier termination, since the data for solver contacts doesn't need to be computed (though our implementation there is somewhat different from Rapier anyway).
Currently, my implementation runs hooks per collision pair (
Contacts
), not per manifold (ContactManifold
). This provides some more data and allows entire collision pairs to be ignored at once if desired. I'm not 100% sure which is preferable though; many other engines seem to have contact modification be per-manifold. There is a possibility that we change this at some point.Other Changes
one_way_platform_2d
example to use collision hooks, and overall improved the example code.AabbIntervalFlags
instead of several booleans for AABB intervals.BroadPhasePlugin
,NarrowPhasePlugin
, and manyNarrowPhase
methods now take generics forCollisionHooks
.Follow-Up Work
I have several follow-up PRs in progress:
I expect them to be ready in that order. For 3-4, I would like #564 first.
It is also highly likely that we will deprecate the
PostProcessCollisions
schedule in favor of these hooks.Migration Guide
For custom contact filtering and modification logic, it is now recommended to define
CollisionHooks
instead of manually accessing and modifyingCollisions
in thePostProcessCollisions
schedule.The
BroadPhasePlugin
,NarrowPhasePlugin
, and manyNarrowPhase
methods now take generics forCollisionHooks
.