# Working with Heightfield Terrain Collisions in Newton Simulations

> Master heightfield terrain collisions in Newton simulations. Learn how the Heightfield class uses GPU-accelerated SDF sampling for accurate contact detection with dynamic bodies.

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

---

**Heightfield terrain collisions in Newton simulations are handled by the `Heightfield` class, which normalizes elevation data to the [0, 1] range and uses GPU-accelerated signed-distance field (SDF) sampling for accurate contact detection between terrain and dynamic bodies.**

Newton Physics represents terrain-like static surfaces through the **Heightfield** abstraction, providing an efficient way to simulate complex landscapes without explicit mesh generation. This article explains how to create, configure, and optimize heightfield terrain collisions in Newton simulations using the actual source implementation from the `newton-physics/newton` repository.

## Understanding the Heightfield Abstraction in Newton

The core implementation resides in [`newton/_src/geometry/types.py`](https://github.com/newton-physics/newton/blob/main/newton/_src/geometry/types.py), where the `Heightfield` class encapsulates 2-D grid elevation data. Internally, Newton normalizes all heightfield data to the range **[0, 1]** regardless of input scale, then maps these normalized values to world-space heights using the user-provided `min_z` and `max_z` parameters.

Key characteristics of the Newton heightfield implementation include:

- **Automatic normalization**: Input arrays are reshaped to `(nrow, ncol)` and scaled to [0, 1] based on their intrinsic min/max values (`Heightfield.__init__` lines 15-25).
- **Static geometry**: Heightfields are always immovable with `mass = 0`, zero inertia matrix, and `has_inertia = False` (lines 38-41).
- **Geometric footprint**: Half-extents `hx` and `hy` define the XY bounding box as `[-hx, +hx] × [-hy, +hy]`.

## Creating and Configuring Heightfield Terrain

To instantiate terrain, pass a NumPy array of elevation values to the `Heightfield` constructor along with grid dimensions and spatial extents. The following example from [`newton/examples/basic/example_basic_heightfield.py`](https://github.com/newton-physics/newton/blob/main/newton/examples/basic/example_basic_heightfield.py) generates a sinusoidal landscape:

```python
import numpy as np
import warp as wp
import newton

# Build a sinusoidal heightfield

nrow, ncol = 50, 50
hx, hy = 5.0, 5.0
x = np.linspace(-hx, hx, ncol)
y = np.linspace(-hy, hy, nrow)
xx, yy = np.meshgrid(x, y)
elevation = np.sin(xx) * np.cos(yy) * 0.5

hfield = newton.Heightfield(
    data=elevation, 
    nrow=nrow, 
    ncol=ncol, 
    hx=hx, 
    hy=hy
)

builder = newton.ModelBuilder()
builder.add_shape_heightfield(heightfield=hfield)

```

The constructor automatically derives `min_z` and `max_z` from the data if omitted, or you may specify them explicitly to control the world-space vertical range independently of the source array's scale.

## Heightfield Collision Detection Architecture

Newton employs a hybrid collision strategy for heightfields, combining signed-distance field (SDF) queries for fast broad-phase rejection with exact triangle extraction for narrow-phase contact solving. The implementation in [`newton/_src/utils/heightfield.py`](https://github.com/newton-physics/newton/blob/main/newton/_src/utils/heightfield.py) provides the GPU kernels that drive this pipeline.

### SDF Sampling Kernels

For rapid distance queries, Newton uses `sample_sdf_heightfield` (lines 171-187), which computes the signed distance from a query point to the terrain surface. Positive values indicate points above the terrain, while negative values indicate penetration. The companion function `sample_sdf_grad_heightfield` (lines 190-212) additionally returns the surface gradient, yielding the contact normal required for impulse resolution.

### Triangle Extraction for Narrow-Phase

When a convex shape intersects the heightfield's bounding region, `heightfield_vs_convex_midphase` (lines 310-362) performs a broad-phase grid traversal to emit candidate triangle indices. The narrow-phase solver then invokes `get_triangle_shape_from_heightfield` (lines 215-286) to extract the specific terrain triangle as a `GenericShapeData` object, enabling standard GJK/MPR contact algorithms to resolve the collision.

## Integrating Heightfields into Simulation Models

To incorporate terrain into a simulation, use the `ModelBuilder` API, which handles buffer management and GPU memory allocation automatically. The workflow follows three stages: construction, finalization, and execution.

First, add the heightfield to your builder using `add_shape_heightfield`:

```python
builder = newton.ModelBuilder()
builder.add_shape_heightfield(heightfield=hfield)

```

This method stores a compact `HeightfieldData` struct (defined in [`newton/_src/utils/heightfield.py`](https://github.com/newton-physics/newton/blob/main/newton/_src/utils/heightfield.py) lines 77-90) and appends the elevation data to a concatenated GPU buffer.

Next, add dynamic bodies and finalize the model:

```python

# Add a sphere above the terrain

sphere = builder.add_body(
    xform=wp.transform(p=wp.vec3(0.0, 0.0, 0.5), q=wp.quat_identity())
)
builder.add_shape_sphere(body=sphere, radius=0.1)

model = builder.finalize()

```

During finalization, `Heightfield.finalize()` (lines 61-74) allocates the Warp array on the target device and records the buffer offset in `HeightfieldData`.

The following test snippet from [`newton/tests/test_heightfield.py`](https://github.com/newton-physics/newton/blob/main/newton/tests/test_heightfield.py) verifies that a sphere resting on a flat heightfield does not fall through:

```python
builder = newton.ModelBuilder()
hfield = newton.Heightfield(
    data=np.zeros((10, 10), dtype=np.float32),
    nrow=10, ncol=10, hx=5.0, hy=5.0,
    min_z=0.0, max_z=1.0
)
builder.add_shape_heightfield(heightfield=hfield)

sphere = builder.add_body(xform=wp.transform((0,0,0.5), wp.quat_identity()))
builder.add_shape_sphere(body=sphere, radius=0.1)

model = builder.finalize()
solver = newton.solvers.SolverMuJoCo(model)

state_in, state_out = model.state(), model.state()
control = model.control()
dt = 1.0/240.0

# Step for a few hundred frames

for _ in range(200):
    solver.step(state_in, state_out, control, None, dt)
    state_in, state_out = state_out, state_in

final_z = float(state_in.body_q.numpy()[sphere, 2])
assert final_z > -0.1   # sphere stays above the terrain

```

## Advanced: Direct SDF Queries for Custom Forces

For simulations requiring custom soft-contact models or sensor queries, you can access the heightfield SDF directly without invoking the full narrow-phase pipeline. The `sample_sdf_grad_heightfield` function in [`newton/_src/utils/heightfield.py`](https://github.com/newton-physics/newton/blob/main/newton/_src/utils/heightfield.py) returns both the signed distance and surface normal for any world-space point.

```python

# Assume hfd is a HeightfieldData struct and elev is the concatenated elevation buffer

pos = wp.vec3(1.0, -0.5, 0.2)
dist, normal = newton._src.utils.heightfield.sample_sdf_grad_heightfield(hfd, elev, pos)

# dist: signed distance (positive above terrain, negative inside)

# normal: surface gradient for impulse direction

```

This low-level access enables implementation of specialized effects such as granular terrain scarring, tire deformation, or heightfield-based fluid coupling while leveraging Newton's optimized GPU kernels.

## Summary

- **Heightfield terrain collisions in Newton simulations** rely on the `Heightfield` class in [`newton/_src/geometry/types.py`](https://github.com/newton-physics/newton/blob/main/newton/_src/geometry/types.py), which normalizes elevation data to [0, 1] and scales it to world-space using `min_z` and `max_z`.
- Heightfields are strictly static geometry (zero mass and inertia) defined by half-extents `hx`, `hy` that bound the XY footprint.
- Collision detection uses a hybrid approach: GPU-accelerated SDF sampling (`sample_sdf_heightfield`, `sample_sdf_grad_heightfield`) for broad-phase queries, and exact triangle extraction (`get_triangle_shape_from_heightfield`) for narrow-phase GJK/MPR contact solving.
- Integration requires adding the heightfield to a `ModelBuilder` via `add_shape_heightfield`, which manages the `HeightfieldData` struct and GPU buffer concatenation automatically.
- Advanced users can query the SDF directly for custom contact models or sensor logic without invoking the full collision pipeline.

## Frequently Asked Questions

### How does Newton normalize heightfield elevation data?

Newton automatically rescales any input 2-D array to the range **[0, 1]** during `Heightfield` initialization ([`newton/_src/geometry/types.py`](https://github.com/newton-physics/newton/blob/main/newton/_src/geometry/types.py) lines 15-25). The class computes the intrinsic minimum and maximum of the source data, then stores normalized values internally. When the simulation runs, these normalized heights are mapped to world-space coordinates using the user-provided `min_z` and `max_z` parameters, allowing you to define terrain extents independently of the source data scale.

### Can heightfields be used with dynamic bodies in Newton?

Yes, but **heightfields themselves are always static**. According to the source code in [`newton/_src/geometry/types.py`](https://github.com/newton-physics/newton/blob/main/newton/_src/geometry/types.py) (lines 38-41), heightfields have `mass = 0`, zero inertia matrices, and `has_inertia = False`. They serve as immovable terrain surfaces that dynamic bodies—such as spheres, boxes, or arbitrary convex shapes—can collide against. The collision pipeline in [`newton/_src/utils/heightfield.py`](https://github.com/newton-physics/newton/blob/main/newton/_src/utils/heightfield.py) handles interactions between moving bodies and the static heightfield grid automatically.

### What collision algorithms does Newton use for heightfield terrain?

Newton employs a **hybrid collision strategy** optimized for GPU execution. For broad-phase queries, the engine uses signed-distance field (SDF) sampling via `sample_sdf_heightfield` and `sample_sdf_grad_heightfield` (lines 171-212 in [`newton/_src/utils/heightfield.py`](https://github.com/newton-physics/newton/blob/main/newton/_src/utils/heightfield.py)) to quickly determine penetration depth and contact normals. For narrow-phase resolution, the system extracts exact terrain triangles using `get_triangle_shape_from_heightfield` (lines 215-286) and `heightfield_vs_convex_midphase` (lines 310-362), feeding these into standard GJK/MPR contact algorithms for precise collision response.

### How do I prevent objects from falling through heightfield terrain?

Ensure that your **dynamic bodies are initialized above the terrain surface** and that the heightfield's `min_z` and `max_z` parameters correctly map to your world's vertical extents. As demonstrated in [`newton/tests/test_heightfield.py`](https://github.com/newton-physics/newton/blob/main/newton/tests/test_heightfield.py), you should add the heightfield to a `ModelBuilder` using `add_shape_heightfield`, then finalize the model to allocate GPU buffers. The collision pipeline automatically queries the heightfield SDF during each simulation step; if objects still fall through, verify that your solver time step (`dt`) is sufficiently small (typically 1/240s or less) to capture contact events accurately.