Collision#
SAP Warp treats collision generation as a separate runtime stage. The collision
pipeline reads body poses and shape metadata, writes a bounded
SapContacts buffer, and leaves contact response
to SolverSAP.
This collision front end is based on Newton’s collision code, including its broad-phase/narrow-phase organization and shape metadata. SAP Warp adapts that code path to emit SAP-owned rigid contact buffers while preserving compatibility with Newton-authored assets and runtime behavior.
Hydroelastic contact support is in development; the documented runtime path on
this page is rigid contact generation into
SapContacts.
Pipeline Stages#
collide() runs these stages:
Clear the output contact buffer.
Compute world-space shape AABBs from body poses, shape transforms, margins, and gaps.
Run the selected broad phase to produce candidate shape pairs.
Run narrow phase algorithms for candidate pairs.
Write rigid contacts into
SapContactsuntilrigid_contact_maxis reached.
The solver consumes contact shape ids, body-frame contact points, normals, margins, and optional material attributes. It does not query meshes or SDFs directly.
Broad Phase Modes#
SapCollisionPipeline supports three broad
phase modes:
explicitUses precomputed shape pairs from the model. This is the default path for loaded scenes that provide
shape_contact_pairs.sapSweep-and-prune broad phase. Use this when dynamic pair discovery is needed and
model.shape_worldis available.nxnAll-pairs broad phase filtered by world and shape flags. This is useful for debugging small scenes.
The constructor also accepts expert broad-phase and narrow-phase instances, but both must be supplied together.
Supported Shape Data#
The runtime shape type enum includes planes, heightfields, spheres, capsules, ellipsoids, cylinders, boxes, triangle meshes, cones, and convex meshes. Scene files expose the common inline authoring names:
box, sphere, capsule, cylinder, cone, ellipsoid, mesh
USD, URDF, and MJCF import paths map external geometry into the same collision model arrays.
Margins, Gaps, and Materials#
Each shape can carry:
marginGeometric contact-surface offset. Margin expands the effective collision envelope and is subtracted from the signed gap passed to the SAP solve.
gapCandidate-generation band. Builder
rigid_gapsupplies the default when a shape does not specify one. Gap widens the range in which the collision pipeline emits potential contacts; it does not by itself decide the final contact force.ke,tau,muStiffness, explicitly specified contact dissipation time scale, and friction coefficient passed into the SAP contact solve after pairwise combination.
tauis material data, not a value derived fromke/kd.collision_groupand flagsBroad-phase and narrow-phase participation controls.
The distinction between margin and gap is important for SAP. During
broad phase, each shape AABB is expanded by the sum of its margin and gap:
During narrow phase, a pair of shapes a and b can generate a rigid
contact candidate when the margin-adjusted separation is inside the pair gap:
The contact Jacobian stage then stores the margin-adjusted signed gap
where \(x_0\) and \(x_1\) are the witness points and \(n\) is the contact normal. A larger margin changes \(\phi_0\) and therefore changes the effective contact surface seen by the solver.
The gap term instead acts as an anticipation band. It allows the collision pipeline to hand SAP a contact candidate even when \(\phi_0\) is still slightly positive:
SAP then decides the contact impulse/force inside the velocity-level objective;
collision detection only supplies candidates and material data. This is useful
for stability because the solver can see near-future contacts before visible
penetration occurs, reducing contact popping and missed contacts at larger
timesteps or higher relative velocities. The tradeoff is capacity: increasing
gap may increase the number of candidates, so rigid_contact_max must be
large enough for the broader band.
Capacity#
rigid_contact_max bounds the flat contact buffer owned by the collision
pipeline. Scene files set simulation.max_rigid_contact as a per-world cap
for benchmark runs; with replicated worlds, benchmark.py passes
simulation.max_rigid_contact * num_worlds to
SapCollisionPipeline. When generated contacts
exceed capacity, extra contacts are dropped and the solver exposes the most
recent truncated count through
solver.last_truncated_contact_count.
Common Pattern#
collision = SapCollisionPipeline(scene.collision_model, rigid_contact_max=256)
contacts = collision.contacts()
state_0.clear_forces()
collision.collide(sap_collision_state_from_state(state_0), contacts)
solver.step(state_0, state_1, control, contacts, dt)
Run collision once per solver step unless the scene is known to have fixed contact topology and the caller intentionally reuses a contact set.