Height Fields (HField)#

Height Fields (HField) are an efficient geometry type for representing terrain surfaces. They store elevation information through 2D grid data, providing realistic physical collision responses for robot simulation, terrain following, and surface interaction.

Height Field Features#

Height fields offer the following advantages:

  • Efficient Storage: Uses regular grids to store elevation data with low memory footprint

  • Fast Collision Detection: Grid-based spatial partitioning algorithms provide efficient collision queries

  • Realistic Terrain Simulation: Supports complex terrain features such as hillsides, ravines, plains, etc.

  • Large-scale Scenes: Suitable for representing large terrain areas, commonly used in outdoor robot simulation

MJCF Configuration#

In MJCF files, height fields are defined using the <hfield> tag, supporting three data source methods:

1. Inline Elevation Data#

Directly specify elevation data matrix in XML:

<asset>
    <hfield name="terrain1"
            nrow="15"
            ncol="15"
            elevation="0.0 0.43 0.78 0.97 ..."
            size="5 5 2 0.1"/>
</asset>

2. PNG Image Files#

Use PNG images as elevation data source (recommended for visual terrain):

<asset>
    <hfield name="png_terrain"
            file="terrain.png"
            content_type="image/png"
            size="10 10 3 0.2"/>
</asset>

Notes:

  • PNG images are automatically converted to grayscale

  • White pixels correspond to high elevation, black pixels to low elevation

  • Intensity values are used as elevation data and normalized to [0, 1] range

3. Custom Binary Files#

Use MuJoCo custom binary format:

<asset>
    <hfield name="binary_terrain"
            file="terrain.hfield"
            content_type="image/vnd.mujoco.hfield"
            size="8 8 2.5 0.15"/>
</asset>

Binary file format:

  • File size: 4 ร— (2 + nrow ร— ncol) bytes

  • Structure:

    • int32 nrow: Number of rows

    • int32 ncol: Number of columns

    • float32 data[nrowร—ncol]: Elevation data (row-major order)

MJCF Attribute Description#

Attribute

Type

Default

Description

name

string

optional

Height field name for reference. If omitted and file name is specified, uses filename (without path and extension)

content_type

string

optional

Currently ignored by MotrixSim

file

string

optional

External file path. .png files are converted to grayscale, intensity values used as elevation data; other formats treated as custom binary format

nrow

integer

โ€œ0โ€

Number of rows in elevation data matrix. Default 0 means load from file and automatically infer matrix size

ncol

integer

โ€œ0โ€

Number of columns in elevation data matrix

elevation

float array (nrowร—ncol)

optional

Inline elevation data matrix. Data automatically normalized to [0, 1] range. Defaults to 0 if not provided

size

float array (4 elements)

required

Physical dimensions: [radius_x, radius_y, elevation_z, base_z]

Detailed size Attribute Description#

The size attribute contains four floats: [radius_x, radius_y, elevation_z, base_z], with each parameter meaning:

  • radius_x: Radius in X direction (half-width). Total width of height field on X axis is 2 ร— radius_x

  • radius_y: Radius in Y direction (half-length). Total length of height field on Y axis is 2 ร— radius_y

  • elevation_z: Maximum elevation. This value scales normalized [0-1] elevation data, so lowest point is at Z=0, highest point at Z=elevation_z

  • base_z: Base depth. Currently ignored by MotrixSim

Important Notes:

  • Height fields are centered in the local coordinate system of the referencing geometry

  • Elevation direction is +Z direction

  • Unlike planes, height fields are treated as unions of regular geometry, with no concept of โ€œbelow height fieldโ€ - geometry is either inside or outside the height field

  • Therefore, base parts must have non-zero thickness to avoid penetration issues

Example:

<!-- Create a 10ร—10 unit height field with max elevation 2 units and base thickness 0.1 units -->
<hfield name="terrain" size="5 5 2 0.1" nrow="50" ncol="50"/>

Collision Detection Features#

Height field collision detection has the following important characteristics:

Collision Model:

  • Height fields are treated as unions of triangular prisms

  • First select potentially colliding sub-grid prisms based on geometry bounding boxes

  • Then use general convex colliders for precise collision calculation

Supported Collision Types:

  • โœ… Height field with sphere, capsule, cylinder, cube, polyhedron

  • โŒ Height field with plane collision (not supported)

  • โŒ Height field with other height field collision (not supported)

For more detailed hfield field descriptions, please refer to the MJCF official documentation.

Geometry Reference#

Use height fields in <worldbody>:

<worldbody>
    <geom name="terrain" type="hfield" hfield="terrain1" material="ground_material"/>
    <geom pos="10 0 0" name="terrain2" type="hfield" hfield="file_terrain" material="ground_material"/>
</worldbody>

Main Interfaces#

In MotrixSim, you can access HField objects through the following methods:

HField Object Properties#

After obtaining an HField object, you can access the following properties:

hfield = model.get_hfield("terrain1")

# Basic properties
name = hfield.name          # Height field name
nrow = hfield.nrow          # Number of grid rows
ncol = hfield.ncol          # Number of grid columns
bound = hfield.bound        # Bounding box [-x, -y, 0, x, y, z]

# Elevation data
height_matrix = hfield.height_matrix  # Complete elevation matrix (nrow ร— ncol)

# Query elevation at specific point
height = hfield.get(row=5, col=10)    # Get elevation at specified row and column

Usage Examples#

Basic Height Field Operations#

# Get number of height fields
num_hfields = model.num_hfields
print(f"Scene contains {num_hfields} height fields")

# Get height field by name
hfield1 = model.get_hfield("terrain1")
print(f"Height field name: {hfield1.name}")
print(f"Grid size: {hfield1.nrow} ร— {hfield1.ncol}")
print(f"Bounding box: {hfield1.bound}")

# Get height field by index
hfield2 = model.get_hfield(1)
print(f"Second height field name: {hfield2.name}")

# Get complete height matrix
height_matrix = hfield1.height_matrix
print(f"Height matrix shape: {height_matrix.shape}")

# Query specific height value
sample_height = hfield1.get(row=7, col=7)
print(f"Center point height: {sample_height:.3f}")

Elevation Data Analysis#

# Height data analysis
heights = hfield1.height_matrix

# Calculate statistics
min_height = np.min(heights)
max_height = np.max(heights)
mean_height = np.mean(heights)
std_height = np.std(heights)

print("\nHeight statistics:")
print(f"  Min height: {min_height:.3f}")
print(f"  Max height: {max_height:.3f}")
print(f"  Mean height: {mean_height:.3f}")
print(f"  Std deviation: {std_height:.3f}")

# Find specific elevation ranges
high_points = np.where(heights > 0.5)
print(f"\nPoints with height > 0.5: {len(high_points[0])}")

# Calculate terrain slope (simple approximation)
if heights.shape[0] > 1 and heights.shape[1] > 1:
    # Calculate elevation differences between adjacent points
    grad_y = np.gradient(heights, axis=0)
    grad_x = np.gradient(heights, axis=1)
    gradient_magnitude = np.sqrt(grad_x**2 + grad_y**2)

    max_gradient = np.max(gradient_magnitude)
    print(f"Max slope gradient: {max_gradient:.3f}")

Complete Simulation Example#

# Copyright (C) 2020-2025 Motphys Technology Co., Ltd. All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#     http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
# ==============================================================================


import numpy as np

from motrixsim import SceneData, load_model, run, step
from motrixsim.render import RenderApp


def demonstrate_hfield_api(model):
    """Demonstrate height field API usage"""
    print("=== Height Field API Demo ===")

    # tag::basic_access
    # Get number of height fields
    num_hfields = model.num_hfields
    print(f"Scene contains {num_hfields} height fields")

    # Get height field by name
    hfield1 = model.get_hfield("terrain1")
    print(f"Height field name: {hfield1.name}")
    print(f"Grid size: {hfield1.nrow} ร— {hfield1.ncol}")
    print(f"Bounding box: {hfield1.bound}")

    # Get height field by index
    hfield2 = model.get_hfield(1)
    print(f"Second height field name: {hfield2.name}")

    # Get complete height matrix
    height_matrix = hfield1.height_matrix
    print(f"Height matrix shape: {height_matrix.shape}")

    # Query specific height value
    sample_height = hfield1.get(row=7, col=7)
    print(f"Center point height: {sample_height:.3f}")
    # end::basic_access

    # tag::height_analysis
    # Height data analysis
    heights = hfield1.height_matrix

    # Calculate statistics
    min_height = np.min(heights)
    max_height = np.max(heights)
    mean_height = np.mean(heights)
    std_height = np.std(heights)

    print("\nHeight statistics:")
    print(f"  Min height: {min_height:.3f}")
    print(f"  Max height: {max_height:.3f}")
    print(f"  Mean height: {mean_height:.3f}")
    print(f"  Std deviation: {std_height:.3f}")

    # Find specific elevation ranges
    high_points = np.where(heights > 0.5)
    print(f"\nPoints with height > 0.5: {len(high_points[0])}")

    # Calculate terrain slope (simple approximation)
    if heights.shape[0] > 1 and heights.shape[1] > 1:
        # Calculate elevation differences between adjacent points
        grad_y = np.gradient(heights, axis=0)
        grad_x = np.gradient(heights, axis=1)
        gradient_magnitude = np.sqrt(grad_x**2 + grad_y**2)

        max_gradient = np.max(gradient_magnitude)
        print(f"Max slope gradient: {max_gradient:.3f}")
    # end::height_analysis

    print("\n" + "=" * 50 + "\n")


def main():
    # Create render window for visualization
    with RenderApp() as render:
        # The scene description file
        path = "examples/assets/hfield.xml"
        # Load the scene model
        model = load_model(path)

        # Demonstrate height field API
        demonstrate_hfield_api(model)

        # Create the render instance of the model
        render.launch(model)
        # Create the physics data of the model
        data = SceneData(model)

        # Add object to demonstrate collision with height field
        # Add a test object below existing sphere
        print("Starting simulation demo...")
        print("Spheres will collide with height field and roll along terrain")
        print("Use mouse to control view:")
        print("  - Left click drag: Rotate view")
        print("  - Right click drag: Pan view")

        run.render_loop(model.options.timestep, 60, lambda: step(model, data), lambda: render.sync(data))


if __name__ == "__main__":
    main()

Run the height field simulation example:

uv run examples/hfield.py

File Format#

MotrixSim supports binary height field file format (.hfield):

File Structure#

  • Header: First 8 bytes

    • nrow (int32): Number of grid rows

    • ncol (int32): Number of grid columns

  • Data section: Remaining bytes

    • Elevation data array (float32), length nrow ร— ncol

Generating Height Field Files#

You can create custom height field files using the following method:

import numpy as np

def create_hfield_file(filename, height_data):
    """Create binary height field file"""
    nrow, ncol = height_data.shape

    with open(filename, 'wb') as f:
        # Write header information
        f.write(np.array([nrow, ncol], dtype=np.int32).tobytes())
        # Write elevation data
        f.write(height_data.astype(np.float32).tobytes())

# Example: Create simple terrain
x = np.linspace(-5, 5, 50)
y = np.linspace(-5, 5, 50)
X, Y = np.meshgrid(x, y)
Z = 0.5 * np.sin(X) * np.cos(Y)  # Sine wave terrain

create_hfield_file("custom_terrain.hfield", Z)

API Reference#

For more HField-related APIs, please refer to: