Using Hydroelastic Contact Models (SDF) for Soft Contact in Newton

Newton’s hydroelastic contact model uses Signed Distance Fields (SDFs) to distribute contact forces over a surface patch, enabling realistic soft-body interactions with smoother force gradients than traditional point-contact methods.

Newton is an open-source physics engine that implements advanced hydroelastic contact models for simulating compliant, surface-based interactions. By representing colliding shapes with Signed Distance Fields (SDFs), Newton’s hydroelastic pipeline distributes forces across a contact patch rather than at discrete points, providing more realistic soft contact behavior for robotics and deformable body simulation.

How Hydroelastic SDF Contact Works in Newton

Unlike point-contact models that resolve collisions at single points, hydroelastic contacts model the intersection volume between two SDF-represented shapes. The HydroelasticSDF class in newton/_src/geometry/sdf_hydroelastic.py (lines 70-78) orchestrates a five-stage pipeline that converts overlapping SDF volumes into distributed contact forces.

The Five-Stage Collision Pipeline

The hydroelastic pipeline processes collisions through distinct computational stages, each implemented as specialized kernels in sdf_hydroelastic.py:

  • Broadphase – An OBB intersection test culls non-overlapping shape pairs using broadphase_collision_pairs_count and broadphase_collision_pairs_scatter (lines 880-910).

  • Octree Refinement – Hierarchical subdivision (8×8×8 → 4×4×4 → 2×2×2 → voxels) locates iso-voxels where the zero-isosurface between two SDFs exists via count_iso_voxels_block and scatter_iso_subblock (lines 1010-1080).

  • Marching Cubes – The pipeline extracts contact-surface triangles from each iso-voxel using mc_iterate_voxel_vertices and related helper kernels (lines 1190-1240).

  • Contact Generation – generate_contacts_kernel (created in _get_generate_contacts_kernel and invoked from _generate_contacts) computes contact centroids, normals, penetration depths, and areas from the triangles.

  • Contact Reduction – Optional binning reduces contacts to a representative set per shape pair using HydroelasticContactReduction (see newton/_src/geometry/contact_reduction_hydroelastic.py).

Configuring Hydroelastic SDF Contacts

HydroelasticSDF.Config Parameters

The HydroelasticSDF.Config dataclass in newton/_src/geometry/sdf_hydroelastic.py (lines 122-150) controls pipeline behavior:

  • reduce_contacts – When True, contacts are reduced via a spatial hashtable; set False for full contact resolution useful for debugging.
  • pre_prune_contacts – Enables fast local-first face compaction that reduces global hashtable traffic.
  • buffer_fraction, buffer_mult_* – Scale pre-allocated buffers for broadphase, octree refinement, and contact storage.
  • output_contact_surface – Generates a ContactSurfaceData object containing triangle vertices for visualization when True.
  • anchor_contact – Adds an anchor contact at the patch centroid to improve moment balance during reduction.
  • margin_contact_area – Small area used for non-penetrating contacts at the contact margin.

Enabling Hydroelastic on Shapes

To use hydroelastic contacts, you must set shape flags and generate SDFs:

  1. Set Shape Flags – Apply ShapeFlags.HYDROELASTIC to shapes using the SDF model:
shape_idx = builder.add_shape_mesh(...)
builder.shape_flags[shape_idx] |= newton.ShapeFlags.HYDROELASTIC
  1. Generate SDFs – Meshes require an associated SDF built with appropriate resolution:
mesh.build_sdf(
    max_resolution=64,
    narrow_band_range=(-gap, gap),
    margin=gap,
)

For scaled meshes, ensure the SDF is scale-baked (rescale to unit scale before building) as demonstrated in newton/examples/robot/example_robot_panda_hydro.py (lines 94-103).

  1. Configure Pipeline – Pass HydroelasticSDF.Config to CollisionPipeline:
hydro_cfg = HydroelasticSDF.Config(
    reduce_contacts=True,
    output_contact_surface=False,
    buffer_fraction=1.0,
)
pipeline = newton.CollisionPipeline(
    model,
    rigid_contact_max=6000,
    sdf_hydroelastic_config=hydro_cfg,
)

Practical Implementation Examples

Minimal Hydroelastic Model Setup

This complete example creates a soft box with hydroelastic contacts:

import newton, warp as wp
from newton.geometry import HydroelasticSDF

# Create a simple box mesh and bake an SDF

box = newton.Mesh.create_box(0.05, 0.05, 0.05)
box.build_sdf(max_resolution=64, narrow_band_range=(-0.01, 0.01), margin=0.01)

builder = newton.ModelBuilder()
shape_cfg = newton.ModelBuilder.ShapeConfig(is_hydroelastic=True, gap=0.01)
builder.default_shape_cfg = shape_cfg

body = builder.add_body()
builder.add_shape_mesh(body=body, mesh=box)

model = builder.finalize(device=wp.get_device())
hydro_cfg = HydroelasticSDF.Config(
    reduce_contacts=False,
    output_contact_surface=True,
)
pipeline = newton.CollisionPipeline(
    model,
    sdf_hydroelastic_config=hydro_cfg,
    rigid_contact_max=2000,
)

# Run one collision step

state = model.state()
contacts = pipeline.contacts()
pipeline.collide(state, contacts)

# Visualise contact surface (if using Newton viewer)

if pipeline.hydroelastic_sdf:
    surface = pipeline.hydroelastic_sdf.get_contact_surface()
    print("Contact triangles:", surface.max_num_face_contacts)

Enabling Hydroelastic Contacts on URDF Robots

Selectively enable soft contacts on specific URDF links (e.g., robot finger pads):

import newton, warp as wp
from newton.geometry import HydroelasticSDF
import copy

# Base shape config for all URDF parts (hydroelastic disabled by default)

base_cfg = newton.ModelBuilder.ShapeConfig(
    kh=1e11,
    sdf_max_resolution=64,
    is_hydroelastic=False,
    sdf_narrow_band_range=(-0.01, 0.01),
    gap=0.01,
)

builder = newton.ModelBuilder()
builder.default_shape_cfg = base_cfg

# Import a robot URDF; visual meshes become colliders

builder.add_urdf(
    newton.utils.download_asset("franka_emika_panda") / "urdf/fr3_franka_hand.urdf",
    parse_visuals_as_colliders=True,
)

# Enable hydroelastic only on the finger pads

finger_shape_cfg = copy.deepcopy(base_cfg)
finger_shape_cfg.is_hydroelastic = True

for idx, body_idx in enumerate(builder.shape_body):
    if builder.shape_type[idx] == newton.GeoType.MESH:
        # Assume we know which shapes correspond to the pads

        if body_idx in {left_finger_idx, right_finger_idx}:
            mesh = builder.shape_source[idx]
            mesh.build_sdf(max_resolution=64, narrow_band_range=(-0.01, 0.01), margin=0.01)
            builder.shape_flags[idx] |= newton.ShapeFlags.HYDROELASTIC

model = builder.finalize(device=wp.get_device())
pipeline = newton.CollisionPipeline(
    model,
    sdf_hydroelastic_config=HydroelasticSDF.Config(
        reduce_contacts=True,
        anchor_contact=True,
    ),
)

Running a Full Simulation with Soft Contacts

Execute a complete simulation loop using the hydroelastic pipeline:

import newton, warp as wp
from newton.geometry import HydroelasticSDF

# Build scene (see build_stacked_cubes_scene in tests for full details)

model, solver, state0, state1, ctrl, pipeline, _, _ = build_stacked_cubes_scene(
    device=wp.get_device(),
    solver_fn=lambda m: newton.solvers.SolverXPBD(m, iterations=10),
    shape_type=newton.tests.ShapeType.PRIMITIVE,
    reduce_contacts=True,
    sdf_hydroelastic_config=HydroelasticSDF.Config(
        output_contact_surface=False,
        reduce_contacts=True,
        anchor_contact=True,
    ),
)

contacts = pipeline.contacts()
for step in range(300):
    pipeline.collide(state0, contacts)
    solver.step(state0, state1, ctrl, contacts, 1.0 / 60.0)
    state0, state1 = state1, state0
    # Optional: visualise or log contact forces here

Visualizing Hydroelastic Contact Surfaces

When debugging or analyzing soft contacts, you can extract the actual contact surface geometry. Setting output_contact_surface=True in the configuration generates a ContactSurfaceData object containing the triangle vertices of the contact patch.

The viewer extracts these vertex buffers and renders them as wireframes (see viewer/_src/viewer/viewer.py line ~530). Here is how to capture and visualize the contact surface:

hydro_cfg = HydroelasticSDF.Config(
    output_contact_surface=True,
    reduce_contacts=False,
)
pipeline = newton.CollisionPipeline(model, sdf_hydroelastic_config=hydro_cfg)

# After a collision step:

surface = pipeline.hydroelastic_sdf.get_contact_surface()
if surface:
    viewer.add_mesh(
        vertices=surface.contact_surface_point,
        faces=surface.face_contact_count,  # each 3 vertices form a triangle

        color=(0.2, 0.7, 1.0, 0.5),
    )

Key Source Files and References

Understanding the hydroelastic implementation requires familiarity with these specific source locations:

  • newton/_src/geometry/sdf_hydroelastic.py – Core hydroelastic implementation containing the HydroelasticSDF class (lines 70-78), the Config dataclass (lines 122-150), and all pipeline kernels including broadphase_collision_pairs_count, count_iso_voxels_block, and mc_iterate_voxel_vertices.

  • newton/_src/geometry/contact_reduction_hydroelastic.py – Implements HydroelasticContactReduction for spatial hashing and contact patch simplification.

  • newton/examples/robot/example_robot_panda_hydro.py – End-to-end example demonstrating SDF generation for scaled meshes (lines 94-103), shape flag configuration, and collision pipeline setup for a Franka Panda arm.

  • newton/tests/test_hydroelastic.py – Comprehensive test suite featuring build_stacked_cubes_scene (lines 82-115) for validating hydroelastic contacts against positional and rotational error thresholds using both MuJoCo and XPBD solvers.

Summary

  • Hydroelastic contact models in Newton use SDFs to represent colliding geometry, enabling distributed force calculation across contact patches rather than discrete points.

  • The five-stage pipeline (Broadphase → Octree Refinement → Marching Cubes → Contact Generation → Contact Reduction) processes collisions through specialized kernels in sdf_hydroelastic.py.

  • Configuration via HydroelasticSDF.Config controls contact reduction, buffer sizing, and surface output, while shape flags (ShapeFlags.HYDROELASTIC) and mesh.build_sdf() enable the model on specific geometries.

  • Practical implementation follows the pattern: model construction → SDF baking → flagging → pipeline configuration → simulation, with full examples available in the test suite and robot examples.

Frequently Asked Questions

What is the difference between hydroelastic contacts and standard rigid contacts in Newton?

Standard rigid contacts use point-based collision detection where forces are applied at discrete contact points, often resulting in jittery or unstable behavior with soft materials. Hydroelastic contacts model the actual intersection volume between shapes using Signed Distance Fields, distributing forces across the entire contact patch. This produces smoother force gradients and more realistic compliance, particularly important for grasping and soft-body simulation.

How do I generate an SDF for my mesh to use with hydroelastic contacts?

You must call build_sdf() on your mesh object before finalizing the model, specifying the resolution and narrow band range appropriate for your object scale. For example: mesh.build_sdf(max_resolution=64, narrow_band_range=(-0.01, 0.01), margin=0.01). If your mesh is scaled non-uniformly, you should scale-bake it first (rescale to unit scale before building the SDF) as demonstrated in newton/examples/robot/example_robot_panda_hydro.py lines 94-103.

Why are my hydroelastic contacts not generating any contact forces?

First, verify that you have set the ShapeFlags.HYDROELASTIC flag on your shape: builder.shape_flags[shape_idx] |= newton.ShapeFlags.HYDROELASTIC. Second, ensure you passed a valid HydroelasticSDF.Config to the CollisionPipeline via the sdf_hydroelastic_config parameter. Third, confirm that your SDF was built with sufficient resolution and that the narrow_band_range covers the expected penetration depths. Without proper SDF generation or configuration flags, the pipeline will not process hydroelastic collisions.

Can I visualize the actual contact surface patches in Newton?

Yes, set output_contact_surface=True in your HydroelasticSDF.Config. After a collision step, access the surface data via pipeline.hydroelastic_sdf.get_contact_surface(), which returns a ContactSurfaceData object containing contact_surface_point vertices and face_contact_count indices. You can render these triangles in Newton’s viewer using viewer.add_mesh() with the extracted vertices and faces, typically drawn as wireframes to visualize the contact patch geometry.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →