# How Box3D Handles Height Field Collision for Terrain in Games

> Discover how Box3D handles height field collision for terrain. Learn about on-demand triangle generation and mesh-contact pipeline reuse for efficient game collision detection.

- Repository: [Erin Catto/box3d](https://github.com/erincatto/box3d)
- Tags: how-to-guide
- Published: 2026-08-01

---

**Box3D treats terrain as a compressed grid of height samples, generating collision triangles on-demand during narrow-phase queries while reusing the engine's mesh-contact pipeline for static geometry.**

Box3D, the 3D physics engine maintained by Erin Catto, implements **height field collision** through a specialized static shape that balances memory efficiency with robust collision detection. Instead of storing explicit triangles, the engine keeps terrain data in a compact grid structure and defers geometry instantiation until the narrow-phase demands it. This approach allows game developers to create expansive terrains that interact reliably with dynamic rigid bodies without the memory overhead of full triangle meshes.

## Height Field Data Structure and Compression

The foundation of Box3D’s terrain system is the `b3HeightFieldData` structure defined in [`src/height_field.c`](https://github.com/erincatto/box3d/blob/main/src/height_field.c). This container stores terrain as a regular grid with the following key properties:

- **Grid dimensions**: `rowCount` and `columnCount` define the resolution, while `scaleX` and `scaleZ` specify the horizontal spacing between samples.
- **Compressed storage**: Heights are stored as integers in the `compressedHeights` array and reconstructed to floating-point values using `minHeight` and `heightScale` offsets.
- **Optional attributes**: Per-cell material IDs and flag bits enable surface-specific properties such as friction and collision filtering.

This compression scheme drastically reduces memory footprint for large worlds compared to raw floating-point vertex buffers.

## Shape Registration and Static Body Constraints

Height fields can only be attached to **static bodies**, enforced by validation logic in [`src/shape.c`](https://github.com/erincatto/box3d/blob/main/src/shape.c) (lines 276–278). The creation flow proceeds as follows:

1. Define a `b3HeightFieldDef` structure populated with grid dimensions, scale factors, and the compressed height array.
2. Call `b3CreateHeightFieldShape`, which instantiates a `b3Shape` with `type` set to `b3_heightShape` and stores a pointer to the `b3HeightFieldData`.

Attempting to attach this shape to a dynamic or kinematic body triggers an assertion failure, ensuring terrain remains immovable in the simulation.

## Broad-Phase Optimization

For efficient culling, Box3D computes an axis-aligned bounding box on-the-fly via `b3ComputeHeightFieldAABB`. This function is invoked from `b3ShapeAABB` in [`src/shape.c`](https://github.com/erincatto/box3d/blob/main/src/shape.c) (lines 574–576) and returns an AABB that encloses the entire grid from the minimum to maximum reconstructed heights. The broad-phase uses this bounding volume to quickly eliminate non-intersecting pairs before expensive narrow-phase work begins.

## Narrow-Phase Triangle Generation

When a query reaches the narrow-phase, Box3D avoids building a permanent triangle mesh. Instead, it employs lazy evaluation through two key functions in [`src/mesh.c`](https://github.com/erincatto/box3d/blob/main/src/mesh.c):

- **`b3QueryHeightField`** (lines 73–79): Enumerates grid cells whose bounding boxes intersect the query region.
- **`b3GetHeightFieldTriangle`** (lines 591–596): Generates the two triangles for a specific cell on-demand using the reconstructed heights.

These transient triangles are fed into the generic mesh-contact code in [`src/mesh_contact.c`](https://github.com/erincatto/box3d/blob/main/src/mesh_contact.c), which calculates contact points, normals, and penetration depths using the same algorithms applied to static triangle meshes. This design keeps memory usage constant regardless of query complexity.

## Collision Query Implementations

Box3D supports three primary query types against height fields, each optimized to walk the grid efficiently:

### Ray Casting

The `b3RayCastHeightField` function in [`src/height_field.c`](https://github.com/erincatto/box3d/blob/main/src/height_field.c) (lines 594–608) performs a 2-D Digital Differential Analyzer (DDA) traversal through the grid. It identifies intersected cells and tests the ray against the two constituent triangles, returning the nearest hit fraction and surface normal.

### Shape Casting and Overlap Testing

- **`b3ShapeCastHeightField`** (lines 856–862 in [`src/shape.c`](https://github.com/erincatto/box3d/blob/main/src/shape.c)): Sweeps a convex shape through the terrain by sampling cells along the sweep path and testing against generated triangles.
- **`b3OverlapHeightField`** (lines 891–894 in [`src/shape.c`](https://github.com/erincatto/box3d/blob/main/src/shape.c)): Performs broad-phase-aware overlap tests by querying cells intersecting the probe AABB and checking each triangle for intersection.

## Contact Resolution and Surface Properties

Once the narrow-phase identifies contacts, the solver in [`src/contact_solver.c`](https://github.com/erincatto/box3d/blob/main/src/contact_solver.c) processes them identically to standard mesh collisions, applying impulse resolution, friction, and restitution. For per-cell material variation, Box3D provides `b3GetHeightFieldMaterial` (accessed in [`src/shape.c`](https://github.com/erincatto/box3d/blob/main/src/shape.c) line 2390), allowing distinct surface properties (e.g., low friction for ice, high friction for rock) based on the cell index.

## Practical Implementation Example

The following example demonstrates creating a 10×10 terrain, attaching it to a static body, and performing a vertical ray-cast:

```c
/* 1. Define the height field */
int rowCount = 10, columnCount = 10;
float heights[100] = { /* ... height samples ... */ };

b3HeightFieldDef hfDef = {0};
hfDef.rowCount = rowCount;
hfDef.columnCount = columnCount;
hfDef.heights = heights;
hfDef.minHeight = 0.0f;
hfDef.heightScale = 0.2f;  // Each integer step = 0.2 meters
hfDef.scaleX = 1.0f;
hfDef.scaleZ = 1.0f;

/* 2. Create static body with height field shape */
b3BodyId ground = b3CreateBody(world, &(b3BodyDef){ .type = b3_staticBody });
b3ShapeId terrain = b3CreateHeightFieldShape(ground,
                                              &(b3ShapeDef){ .friction = 0.6f },
                                              &hfDef);

/* 3. Ray-cast against the terrain */
b3RayCastInput input = {
    .origin = {0.0f, 10.0f, 0.0f},
    .direction = {0.0f, -1.0f, 0.0f},
    .maxFraction = 1.0f
};
b3CastOutput output = b3RayCastHeightField(&hfDef, &input);

if (output.hit) {
    printf("Hit at fraction %f, normal (%.2f, %.2f, %.2f)\n",
           output.fraction,
           output.normal.x, output.normal.y, output.normal.z);
}

```

## Summary

- **Compact storage**: Box3D stores terrain in `b3HeightFieldData` using compressed integers rather than full vertex buffers, minimizing memory for large worlds.
- **Static-only restriction**: Height field shapes are strictly validated for static bodies in [`src/shape.c`](https://github.com/erincatto/box3d/blob/main/src/shape.c), ensuring immovable terrain.
- **On-demand geometry**: The engine generates collision triangles lazily via `b3GetHeightFieldTriangle` only when queried, avoiding preprocessing overhead.
- **Unified pipeline**: Height field collisions flow through the same [`src/mesh_contact.c`](https://github.com/erincatto/box3d/blob/main/src/mesh_contact.c) narrow-phase and contact solver used for static meshes, ensuring consistent physics behavior.
- **Material support**: Per-cell surface properties are accessible via `b3GetHeightFieldMaterial`, enabling diverse terrain interactions.

## Frequently Asked Questions

### Can height fields be attached to dynamic bodies in Box3D?

No. The shape creation logic in [`src/shape.c`](https://github.com/erincatto/box3d/blob/main/src/shape.c) explicitly validates that height fields are attached only to static bodies. Attempting to use them with dynamic or kinematic bodies will trigger an assertion error, as the engine assumes terrain remains fixed in world space.

### How does Box3D minimize memory usage for large terrains?

Box3D compresses height data into integer arrays using `minHeight` and `heightScale` parameters for reconstruction. Additionally, it avoids storing explicit triangle meshes; instead, `b3QueryHeightField` generates triangles on-the-fly during collision queries, keeping memory overhead proportional to grid resolution rather than triangle count.

### What collision queries are supported against height fields?

Box3D supports **ray casts** via `b3RayCastHeightField`, **shape casts** via `b3ShapeCastHeightField`, and **overlap tests** via `b3OverlapHeightField`. All three use efficient grid-traversal algorithms (DDA for rays) to test only relevant cells rather than the entire terrain.

### How are different terrain materials handled in collision response?

Each cell can store a material ID within the `b3HeightFieldData` structure. During contact generation, `b3GetHeightFieldMaterial` (referenced in [`src/shape.c`](https://github.com/erincatto/box3d/blob/main/src/shape.c)) retrieves the specific material for the colliding triangle, allowing per-cell customization of friction and restitution properties (e.g., slippery ice versus rough gravel).