Collision Filters
There are three types of collision filters: world.DynamicTree.Filter, world.BroadPhaseFilter, and world.NarrowPhaseFilter.
Dynamic tree filter
The world.DynamicTree.Filter
public Func<IDynamicTreeProxy, IDynamicTreeProxy, bool> Filter { get; set; }
is the earliest filter applied during world.Step and is set by default to World.DefaultDynamicTreeFilter:
public static bool DefaultDynamicTreeFilter(IDynamicTreeProxy proxyA, IDynamicTreeProxy proxyB)
{
if (proxyA is RigidBodyShape rbsA && proxyB is RigidBodyShape rbsB)
{
return rbsA.RigidBody != rbsB.RigidBody;
}
return true;
}
This filters out collisions between shapes that belong to the same body. The dynamic tree will ignore these collisions, and no potential pairs will be created.
For soft bodies, another collision filter is typically used (defined in SoftBodies.DynamicTreeCollisionFilter.Filter), which also filters out collisions between shapes belonging to the same soft body.
Broad phase filter
By default world.BroadPhaseFilter
public IBroadPhaseFilter? BroadPhaseFilter { get; set; }
is null. It is used to filter out collisions that passed broad phase collision detection—that is, after the DynamicTree has added the collision to the PotentialPair hash set.
This can be useful if custom collision proxies are added to world.DynamicTree.
Since the world only knows how to handle collisions between RigidBodyShapes, a filter must handle the detected collision (implement custom collision response code and filter out the collision) such that no InvalidCollisionTypeException is thrown.
The soft body implementation is based on this kind of filter (see SoftBodies.BroadPhaseCollisionFilter).
Example: Collision groups
Collision groups can be implemented using a broad phase filter. In this example, there are two teams: team blue and team red. A filter that disregards all collisions between team members of different colors:
public class TeamFilter : IBroadPhaseFilter
{
public class TeamMember { }
public static TeamMember TeamRed = new();
public static TeamMember TeamBlue = new();
public bool Filter(Shape shapeA, Shape shapeB)
{
if (shapeA.RigidBody.Tag is not TeamMember || shapeB.RigidBody.Tag is not TeamMember)
{
// Handle collision normally if at least one body is not a member of any team
return true;
}
// There is no collision between team red and team blue.
return shapeA.RigidBody.Tag == shapeB.RigidBody.Tag;
}
}
The TeamFilter class can then be instantiated and assigned to world.BroadPhaseFilter:
world.BroadPhaseFilter = new TeamFilter();
...
bodyA.Tag = TeamFilter.TeamBlue;
bodyB.Tag = TeamFilter.TeamRed;
bodyC.Tag = TeamFilter.TeamRed;
Narrow phase filter
The world.NarrowPhaseFilter
public INarrowPhaseFilter? NarrowPhaseFilter { get; set; }
operates similarly. However, this callback is called after narrow phase collision detection, meaning detailed collision information (such as normal, penetration depth, and collision points) is available at this stage. The filter can not only exclude collisions but also modify collision information.
The default narrow phase collision filter is assigned to an instance of TriangleEdgeCollisionFilter, which filters out so-called 'internal edges' for TriangleShapes.
These internal edges typically cause collision artifacts when rigid bodies slide over the edges of connected triangles forming static geometry.
This problem is also known as 'ghost collisions'.