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

> Explore Newton's hydroelastic contact models using SDF for realistic soft-body interactions. This advanced method provides smoother force gradients than traditional point contact, improving simulation fidelity.

- Repository: [Newton Physics/newton](https://github.com/newton-physics/newton)
- Tags: how-to-guide
- Published: 2026-03-19

---

**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`](https://github.com/newton-physics/newton/blob/main/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`](https://github.com/newton-physics/newton/blob/main/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`](https://github.com/newton-physics/newton/blob/main/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`](https://github.com/newton-physics/newton/blob/main/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:

```python
shape_idx = builder.add_shape_mesh(...)
builder.shape_flags[shape_idx] |= newton.ShapeFlags.HYDROELASTIC

```

2. **Generate SDFs** – Meshes require an associated SDF built with appropriate resolution:

```python
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`](https://github.com/newton-physics/newton/blob/main/newton/examples/robot/example_robot_panda_hydro.py) (lines 94-103).

3. **Configure Pipeline** – Pass `HydroelasticSDF.Config` to `CollisionPipeline`:

```python
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:

```python
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):

```python
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:

```python
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`](https://github.com/newton-physics/newton/blob/main/viewer/_src/viewer/viewer.py) line ~530). Here is how to capture and visualize the contact surface:

```python
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`](https://github.com/newton-physics/newton/blob/main/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`](https://github.com/newton-physics/newton/blob/main/newton/_src/geometry/contact_reduction_hydroelastic.py)** – Implements `HydroelasticContactReduction` for spatial hashing and contact patch simplification.

- **[`newton/examples/robot/example_robot_panda_hydro.py`](https://github.com/newton-physics/newton/blob/main/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`](https://github.com/newton-physics/newton/blob/main/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`](https://github.com/newton-physics/newton/blob/main/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`](https://github.com/newton-physics/newton/blob/main/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.