To implement advanced behaviors like one-way platforms or custom sensor logic, use physics hooks to filter collision and intersection pairs.
Filter Contact Pairs
useFilterContactPair controls how collisions are processed. The callback must return:
rapier.SolverFlags.COMPUTE_IMPULSE (1): Process the collision normally.rapier.SolverFlags.EMPTY (0): Ignore the collision.null: Let other hooks or default behavior decide.
Note: The first hook that returns a non-null value wins.
Filter Intersection Pairs
useFilterIntersectionPair controls sensor intersections. The callback must return:
true: Allow the intersection.false: Block the intersection.
Note: The first hook that returns false blocks the intersection.
Critical Constraint: Avoid Aliasing Errors
You cannot access rigid body properties (e.g., translation(), linvel()) directly inside these hooks because they run during the physics step. You must cache the required state in useBeforePhysicsStep and access the cached values within the filter hooks.
Implementation Requirements
You must explicitly enable the hooks on the collider using collider.setActiveHooks(rapier.ActiveHooks.FILTER_CONTACT_PAIRS) or FILTER_INTERSECTION_PAIRS.
import {
useRapier,
useBeforePhysicsStep,
useFilterContactPair
} from "@react-three/rapier";
const OneWayPlatform = () => {
const platformRef = useRef<RapierRigidBody>(null);
const ballRef = useRef<RapierRigidBody>(null);
const colliderRef = useRef<RapierCollider>(null);
const bodyStateCache = useRef(new Map());
const { rapier } = useRapier();
// 1. Cache state BEFORE the step to avoid Rust aliasing errors
useBeforePhysicsStep(() => {
if (platformRef.current && ballRef.current) {
bodyStateCache.current.set(ballRef.current.handle, {
position: ballRef.current.translation(),
velocity: ballRef.current.linvel()
});
}
});
// 2. Use cached data to filter collisions
useFilterContactPair((collider1, collider2, body1, body2) => {
const ballState = bodyStateCache.current.get(body1);
if (!ballState) return null;
if (ballState.velocity.y < 0 && ballState.position.y > 0) {
return rapier.SolverFlags.COMPUTE_IMPULSE;
}
return rapier.SolverFlags.EMPTY;
});
// 3. Enable hooks on the collider
useEffect(() => {
colliderRef.current?.setActiveHooks(rapier.ActiveHooks.FILTER_CONTACT_PAIRS);
}, []);
return (
<>
<RigidBody ref={platformRef} type="fixed">
<CuboidCollider ref={colliderRef} args={[5, 0.1, 5]} />
</RigidBody>
<RigidBody ref={ballRef} position={[0, 3, 0]}>
<CuboidCollider args={[1, 1, 1]} />
</RigidBody>
</>
);
};