unilab.terrains

Procedural terrain generation.

Ported from mjlab (https://github.com/mjlab/mjlab). The terrain generator builds a grid of difficulty-graded sub-terrains and writes a merged heightfield PNG at cold path. Backend materializers consume that output to build backend-specific scene models.

class unilab.terrains.FlatPatchSamplingCfg[source]

Bases: object

Configuration for sampling flat patches on a heightfield surface.

Parameters:
num_patches: int = 10

Number of flat patches to sample per sub-terrain.

patch_radius: float = 0.5

Radius of the circular footprint used to test flatness, in meters.

max_height_diff: float = 0.05

Maximum allowed height variation within the patch footprint, in meters.

x_range: tuple[float, float] = (-1000000.0, 1000000.0)

Allowed range of x coordinates for sampled patches, in meters.

y_range: tuple[float, float] = (-1000000.0, 1000000.0)

Allowed range of y coordinates for sampled patches, in meters.

z_range: tuple[float, float] = (-1000000.0, 1000000.0)

Allowed range of z coordinates (world height) for sampled patches, in meters.

grid_resolution: float | None = None

Resolution of the grid used for flat-patch detection, in meters. When None (default), the terrain’s own horizontal_scale is used. Set to a smaller value (e.g. 0.025) for finer boundary precision at the cost of a larger intermediate grid.

__init__(num_patches=10, patch_radius=0.5, max_height_diff=0.05, x_range=(-1000000.0, 1000000.0), y_range=(-1000000.0, 1000000.0), z_range=(-1000000.0, 1000000.0), grid_resolution=None)
Parameters:
class unilab.terrains.GeneratedTerrain[source]

Bases: object

Merged terrain heightfield ready to be exported as a single PNG asset.

Parameters:
heights_yx: ndarray

rows=y, cols=x.

Type:

World-space surface heights in image convention

horizontal_scale: float
z_min: float
z_max: float
base_thickness: float
terrain_origins: ndarray
property size: tuple[float, float]
property height_extent: float
property hfield_size: tuple[float, float, float, float]
property geom_pos: tuple[float, float, float]
to_uint16()[source]
Return type:

ndarray

surface_sampler()[source]
Return type:

HeightfieldSurfaceSampler

write_png(path)[source]

Write the merged hfield as a 16-bit grayscale PNG.

Parameters:

path (Path)

Return type:

None

hfield_size_xml()[source]
Return type:

str

geom_pos_xml()[source]
Return type:

str

__init__(heights_yx, horizontal_scale, z_min, z_max, base_thickness, terrain_origins)
Parameters:
class unilab.terrains.HfFlatTerrainCfg[source]

Bases: SubTerrainCfg

A flat heightfield terrain (all-zero noise array).

Parameters:
  • proportion (float)

  • size (tuple[float, float])

  • flat_patch_sampling (dict[str, FlatPatchSamplingCfg] | None)

  • horizontal_scale (float)

  • vertical_scale (float)

  • base_thickness_ratio (float)

__init__(proportion=1.0, size=(10.0, 10.0), flat_patch_sampling=None, *, horizontal_scale=0.05, vertical_scale=0.005, base_thickness_ratio=0.0)
Parameters:
  • proportion (float)

  • size (tuple[float, float])

  • flat_patch_sampling (dict[str, FlatPatchSamplingCfg] | None)

  • horizontal_scale (float)

  • vertical_scale (float)

  • base_thickness_ratio (float)

horizontal_scale: float = 0.05

Heightfield grid resolution. Overwritten by TerrainGenerator.

vertical_scale: float = 0.005

Heightfield height resolution. Overwritten by TerrainGenerator.

base_thickness_ratio: float = 0.0

Ratio of the heightfield base thickness to its surface height. The helper enforces a minimum thickness so a literal zero is fine here.

function(difficulty, rng)[source]

Generate backend-agnostic terrain data.

Return type:

TerrainOutput

Returns:

TerrainOutput containing spawn origin and heightfield data.

Parameters:
class unilab.terrains.HfInvertedPyramidStairsTerrainCfg[source]

Bases: HfPyramidStairsTerrainCfg

A pit-style pyramid stairs terrain encoded as a heightfield.

Inverts HfPyramidStairsTerrainCfg: outer ring sits at world z=0, rings descend toward a central platform at the bottom. With holes=True the diagonal corners are even deeper than the platform.

Parameters:
  • proportion (float)

  • size (tuple[float, float])

  • flat_patch_sampling (dict[str, FlatPatchSamplingCfg] | None)

  • step_height_range (tuple[float, float])

  • step_width (float)

  • platform_width (float)

  • border_width (float)

  • holes (bool)

  • pit_depth (float)

  • horizontal_scale (float)

  • vertical_scale (float)

  • base_thickness_ratio (float)

__init__(proportion=1.0, size=(10.0, 10.0), flat_patch_sampling=None, *, step_height_range, step_width, platform_width=1.0, border_width=0.0, holes=False, pit_depth=5.0, horizontal_scale=0.05, vertical_scale=0.005, base_thickness_ratio=1.0)
Parameters:
  • proportion (float)

  • size (tuple[float, float])

  • flat_patch_sampling (dict[str, FlatPatchSamplingCfg] | None)

  • step_height_range (tuple[float, float])

  • step_width (float)

  • platform_width (float)

  • border_width (float)

  • holes (bool)

  • pit_depth (float)

  • horizontal_scale (float)

  • vertical_scale (float)

  • base_thickness_ratio (float)

function(difficulty, rng)[source]

Generate backend-agnostic terrain data.

Return type:

TerrainOutput

Returns:

TerrainOutput containing spawn origin and heightfield data.

Parameters:
class unilab.terrains.HfPyramidSlopedTerrainCfg[source]

Bases: SubTerrainCfg

HfPyramidSlopedTerrainCfg(proportion: ‘float’ = 1.0, size: ‘tuple[float, float]’ = (10.0, 10.0), flat_patch_sampling: ‘dict[str, FlatPatchSamplingCfg] | None’ = None, *, slope_range: ‘tuple[float, float]’, platform_width: ‘float’ = 1.0, inverted: ‘bool’ = False, border_width: ‘float’ = 0.0, horizontal_scale: ‘float’ = 0.1, vertical_scale: ‘float’ = 0.005, base_thickness_ratio: ‘float’ = 1.0)

Parameters:
  • proportion (float)

  • size (tuple[float, float])

  • flat_patch_sampling (dict[str, FlatPatchSamplingCfg] | None)

  • slope_range (tuple[float, float])

  • platform_width (float)

  • inverted (bool)

  • border_width (float)

  • horizontal_scale (float)

  • vertical_scale (float)

  • base_thickness_ratio (float)

slope_range: tuple[float, float]

Range of slope gradients (rise / run), interpolated by difficulty.

platform_width: float = 1.0

Side length of the flat square platform at the terrain center, in meters.

inverted: bool = False

If True, the pyramid is inverted so the platform is at the bottom.

border_width: float = 0.0

Width of the flat border around the terrain edges, in meters. Must be >= horizontal_scale if non-zero.

horizontal_scale: float = 0.1

Heightfield grid resolution along x and y, in meters per cell.

vertical_scale: float = 0.005

Heightfield height resolution, in meters per integer unit of the noise array.

base_thickness_ratio: float = 1.0

Ratio of the heightfield base thickness to its maximum surface height.

function(difficulty, rng)[source]

Generate backend-agnostic terrain data.

Return type:

TerrainOutput

Returns:

TerrainOutput containing spawn origin and heightfield data.

Parameters:
__init__(proportion=1.0, size=(10.0, 10.0), flat_patch_sampling=None, *, slope_range, platform_width=1.0, inverted=False, border_width=0.0, horizontal_scale=0.1, vertical_scale=0.005, base_thickness_ratio=1.0)
Parameters:
  • proportion (float)

  • size (tuple[float, float])

  • flat_patch_sampling (dict[str, FlatPatchSamplingCfg] | None)

  • slope_range (tuple[float, float])

  • platform_width (float)

  • inverted (bool)

  • border_width (float)

  • horizontal_scale (float)

  • vertical_scale (float)

  • base_thickness_ratio (float)

class unilab.terrains.HfPyramidStairsTerrainCfg[source]

Bases: SubTerrainCfg

A pyramid stairs terrain encoded as a heightfield.

Concentric square rings from the outside in form a staircase climbing toward a central platform. With holes=True the four diagonal corners of each ring are carved out to a deep pit; agents falling into the pit reach a terminating depth instead of an infinite void.

Parameters:
  • proportion (float)

  • size (tuple[float, float])

  • flat_patch_sampling (dict[str, FlatPatchSamplingCfg] | None)

  • step_height_range (tuple[float, float])

  • step_width (float)

  • platform_width (float)

  • border_width (float)

  • holes (bool)

  • pit_depth (float)

  • horizontal_scale (float)

  • vertical_scale (float)

  • base_thickness_ratio (float)

__init__(proportion=1.0, size=(10.0, 10.0), flat_patch_sampling=None, *, step_height_range, step_width, platform_width=1.0, border_width=0.0, holes=False, pit_depth=5.0, horizontal_scale=0.05, vertical_scale=0.005, base_thickness_ratio=1.0)
Parameters:
  • proportion (float)

  • size (tuple[float, float])

  • flat_patch_sampling (dict[str, FlatPatchSamplingCfg] | None)

  • step_height_range (tuple[float, float])

  • step_width (float)

  • platform_width (float)

  • border_width (float)

  • holes (bool)

  • pit_depth (float)

  • horizontal_scale (float)

  • vertical_scale (float)

  • base_thickness_ratio (float)

step_height_range: tuple[float, float]

Min and max step height, in meters. Interpolated by difficulty.

step_width: float

Depth (run) of each step, in meters. Must be a multiple of horizontal_scale.

platform_width: float = 1.0

Side length of the flat square platform at the top of the staircase, in meters.

border_width: float = 0.0

Width of the flat outer border around the staircase, in meters.

holes: bool = False

If True, carve deep pits at the diagonal corners of each step ring.

pit_depth: float = 5.0

Depth of holes-mode pits below the lowest stair, in meters.

horizontal_scale: float = 0.05

Heightfield grid resolution. Overwritten by TerrainGenerator.

vertical_scale: float = 0.005

Heightfield height resolution. Overwritten by TerrainGenerator.

base_thickness_ratio: float = 1.0

Ratio of the heightfield base thickness to its surface height.

function(difficulty, rng)[source]

Generate backend-agnostic terrain data.

Return type:

TerrainOutput

Returns:

TerrainOutput containing spawn origin and heightfield data.

Parameters:
class unilab.terrains.HfRandomUniformTerrainCfg[source]

Bases: SubTerrainCfg

HfRandomUniformTerrainCfg(proportion: ‘float’ = 1.0, size: ‘tuple[float, float]’ = (10.0, 10.0), flat_patch_sampling: ‘dict[str, FlatPatchSamplingCfg] | None’ = None, *, noise_range: ‘tuple[float, float]’, noise_step: ‘float’ = 0.005, downsampled_scale: ‘float | None’ = None, horizontal_scale: ‘float’ = 0.1, vertical_scale: ‘float’ = 0.005, base_thickness_ratio: ‘float’ = 1.0, border_width: ‘float’ = 0.0)

Parameters:
  • proportion (float)

  • size (tuple[float, float])

  • flat_patch_sampling (dict[str, FlatPatchSamplingCfg] | None)

  • noise_range (tuple[float, float])

  • noise_step (float)

  • downsampled_scale (float | None)

  • horizontal_scale (float)

  • vertical_scale (float)

  • base_thickness_ratio (float)

  • border_width (float)

noise_range: tuple[float, float]

Min and max height noise, in meters.

noise_step: float = 0.005

Height quantization step, in meters. Sampled heights are multiples of this value within noise_range.

downsampled_scale: float | None = None

Spacing between randomly sampled height points before interpolation, in meters. If None, uses horizontal_scale. Must be >= horizontal_scale.

horizontal_scale: float = 0.1

Heightfield grid resolution along x and y, in meters per cell.

vertical_scale: float = 0.005

Heightfield height resolution, in meters per integer unit of the noise array.

base_thickness_ratio: float = 1.0

Ratio of the heightfield base thickness to its maximum surface height.

border_width: float = 0.0

Width of the flat border around the terrain edges, in meters. Must be >= horizontal_scale if non-zero.

function(difficulty, rng)[source]

Generate backend-agnostic terrain data.

Return type:

TerrainOutput

Returns:

TerrainOutput containing spawn origin and heightfield data.

Parameters:
__init__(proportion=1.0, size=(10.0, 10.0), flat_patch_sampling=None, *, noise_range, noise_step=0.005, downsampled_scale=None, horizontal_scale=0.1, vertical_scale=0.005, base_thickness_ratio=1.0, border_width=0.0)
Parameters:
  • proportion (float)

  • size (tuple[float, float])

  • flat_patch_sampling (dict[str, FlatPatchSamplingCfg] | None)

  • noise_range (tuple[float, float])

  • noise_step (float)

  • downsampled_scale (float | None)

  • horizontal_scale (float)

  • vertical_scale (float)

  • base_thickness_ratio (float)

  • border_width (float)

class unilab.terrains.HfWaveTerrainCfg[source]

Bases: SubTerrainCfg

HfWaveTerrainCfg(proportion: ‘float’ = 1.0, size: ‘tuple[float, float]’ = (10.0, 10.0), flat_patch_sampling: ‘dict[str, FlatPatchSamplingCfg] | None’ = None, *, amplitude_range: ‘tuple[float, float]’, num_waves: ‘int’ = 1, horizontal_scale: ‘float’ = 0.1, vertical_scale: ‘float’ = 0.005, base_thickness_ratio: ‘float’ = 0.25, border_width: ‘float’ = 0.0)

Parameters:
  • proportion (float)

  • size (tuple[float, float])

  • flat_patch_sampling (dict[str, FlatPatchSamplingCfg] | None)

  • amplitude_range (tuple[float, float])

  • num_waves (int)

  • horizontal_scale (float)

  • vertical_scale (float)

  • base_thickness_ratio (float)

  • border_width (float)

amplitude_range: tuple[float, float]

Min and max wave amplitude, in meters. Interpolated by difficulty.

num_waves: int = 1

Number of complete wave cycles along the terrain length.

horizontal_scale: float = 0.1

Heightfield grid resolution along x and y, in meters per cell.

vertical_scale: float = 0.005

Heightfield height resolution, in meters per integer unit of the noise array.

base_thickness_ratio: float = 0.25

Ratio of the heightfield base thickness to its maximum surface height.

__init__(proportion=1.0, size=(10.0, 10.0), flat_patch_sampling=None, *, amplitude_range, num_waves=1, horizontal_scale=0.1, vertical_scale=0.005, base_thickness_ratio=0.25, border_width=0.0)
Parameters:
  • proportion (float)

  • size (tuple[float, float])

  • flat_patch_sampling (dict[str, FlatPatchSamplingCfg] | None)

  • amplitude_range (tuple[float, float])

  • num_waves (int)

  • horizontal_scale (float)

  • vertical_scale (float)

  • base_thickness_ratio (float)

  • border_width (float)

border_width: float = 0.0

Width of the flat border around the terrain edges, in meters. Must be >= horizontal_scale if non-zero.

function(difficulty, rng)[source]

Generate backend-agnostic terrain data.

Return type:

TerrainOutput

Returns:

TerrainOutput containing spawn origin and heightfield data.

Parameters:
class unilab.terrains.SubTerrainCfg[source]

Bases: ABC

SubTerrainCfg(proportion: ‘float’ = 1.0, size: ‘tuple[float, float]’ = (10.0, 10.0), flat_patch_sampling: ‘dict[str, FlatPatchSamplingCfg] | None’ = None)

Parameters:
proportion: float = 1.0

Robot spawning weight for this terrain type.

In curriculum mode, controls how many robots are spawned on this terrain’s column relative to other terrain types. Each terrain type always gets exactly one column; proportion only affects spawning distribution.

In random mode, controls the sampling probability for each patch.

size: tuple[float, float] = (10.0, 10.0)

Width and length of the terrain patch, in meters.

flat_patch_sampling: dict[str, FlatPatchSamplingCfg] | None = None

Named flat-patch sampling configurations, or None to disable.

abstract function(difficulty, rng)[source]

Generate backend-agnostic terrain data.

Return type:

TerrainOutput

Returns:

TerrainOutput containing spawn origin and heightfield data.

Parameters:
__init__(proportion=1.0, size=(10.0, 10.0), flat_patch_sampling=None)
Parameters:
class unilab.terrains.TerrainGenerator[source]

Bases: object

Generates procedural terrain grids with configurable difficulty.

Creates a grid of terrain patches where each patch can be a different terrain type. Supports two modes:

  • Random mode (curriculum=False): Every patch independently samples a

    terrain type weighted by proportions. Results in random variety across all patches.

  • Curriculum mode (curriculum=True): Each terrain type gets exactly one column

    (the generator uses len(sub_terrains) columns regardless of num_cols). Difficulty increases along rows. The proportion field controls robot spawning distribution, not column count.

Terrain types are weighted by proportion and their geometry is generated based on a difficulty value in the configured range. The grid is centered at the world origin. A border can be added around the entire grid along with optional overhead lighting.

Parameters:
__init__(cfg, device='cpu')[source]
Parameters:
generate()[source]

Generate the full terrain as one backend-agnostic merged hfield.

Return type:

GeneratedTerrain

write_png(path)[source]
Parameters:

path (Path)

Return type:

GeneratedTerrain

class unilab.terrains.TerrainGeneratorCfg[source]

Bases: object

TerrainGeneratorCfg(*, seed: ‘int | None’ = None, curriculum: ‘bool’ = False, size: ‘tuple[float, float]’, horizontal_scale: ‘float’ = 0.05, vertical_scale: ‘float’ = 0.005, border_width: ‘float’ = 0.0, num_rows: ‘int’ = 1, num_cols: ‘int’ = 1, sub_terrains: ‘dict[str, SubTerrainCfg]’ = <factory>, difficulty_range: ‘tuple[float, float]’ = (0.0, 1.0), add_lights: ‘bool’ = False)

Parameters:
seed: int | None = None

Random seed for terrain generation. None uses a random seed.

curriculum: bool = False

Controls terrain allocation mode:

  • curriculum=True: Each terrain type gets exactly ONE column. The generator uses

    len(sub_terrains) columns regardless of num_cols. Difficulty increases along rows. The proportion field controls how many robots are spawned per column, not column count.

  • curriculum=False: Every patch is randomly sampled from all terrain types.

    Proportions control sampling probability. Use this for random variety.

size: tuple[float, float]

Width and length of each sub-terrain patch, in meters. Both components must be integer multiples of horizontal_scale.

horizontal_scale: float = 0.05

Heightfield grid resolution along x and y, in meters per cell. Shared by every sub-terrain (overwritten in TerrainGenerator __init__). All length-like sub-terrain parameters (step_width, platform_width, border_width, etc.) must be integer multiples of this value.

__init__(*, seed=None, curriculum=False, size, horizontal_scale=0.05, vertical_scale=0.005, border_width=0.0, num_rows=1, num_cols=1, sub_terrains=<factory>, difficulty_range=(0.0, 1.0), add_lights=False)
Parameters:
vertical_scale: float = 0.005

Heightfield height resolution, in meters per integer unit of the noise array. Shared by every sub-terrain (overwritten in TerrainGenerator __init__).

border_width: float = 0.0

Width of the flat border around the entire terrain grid, in meters. Must be an integer multiple of horizontal_scale if non-zero. The border is a flat hfield slab whose top surface is flush with the inner-terrain floor at z=0; it is NOT a wall.

num_rows: int = 1

Number of sub-terrain rows in the grid. Represents difficulty levels in curriculum mode. Note: Environments are randomly assigned to rows, so multiple envs can share the same patch.

num_cols: int = 1

Number of sub-terrain columns in the grid.

In curriculum mode the generator ignores this value and uses one column per terrain type (len(sub_terrains)). In random mode it is used as-is.

sub_terrains: dict[str, SubTerrainCfg]

Named sub-terrain configurations to populate the grid.

difficulty_range: tuple[float, float] = (0.0, 1.0)

Min and max difficulty values used when generating sub-terrains.

add_lights: bool = False

If True, adds a directional light above the terrain grid.

class unilab.terrains.TerrainHeightField[source]

Bases: object

Backend-agnostic heightfield data for one sub-terrain patch.

Parameters:
noise: ndarray

Quantized height units before normalization.

size: tuple[float, float]

Patch size as (x, y) in meters.

horizontal_scale: float
vertical_scale: float
elevation_min: int
elevation_max: int
max_physical_height: float
base_thickness: float
z_offset: float
physical_heights_xy()[source]

Return world-space surface heights as an (x, y) matrix.

Return type:

ndarray

normalized_elevation()[source]

Return normalized hfield values in [0, 1].

Return type:

ndarray

__init__(noise, size, horizontal_scale, vertical_scale, elevation_min, elevation_max, max_physical_height, base_thickness, z_offset)
Parameters:
class unilab.terrains.TerrainOutput[source]

Bases: object

TerrainOutput(origin: ‘np.ndarray’, heightfield: ‘TerrainHeightField’, flat_patches: ‘dict[str, np.ndarray] | None’ = None)

Parameters:
origin: ndarray

Spawn origin position (x, y, z) in the sub-terrain’s local frame.

heightfield: TerrainHeightField

Backend-agnostic heightfield data.

flat_patches: dict[str, ndarray] | None = None

Named sets of flat patch positions, each an (N, 3) array. None if not configured.

__init__(origin, heightfield, flat_patches=None)
Parameters:
unilab.terrains.flat(**overrides)[source]
Parameters:

overrides (Any)

Return type:

HfFlatTerrainCfg

unilab.terrains.hf_pyramid_slope(**overrides)[source]
Parameters:

overrides (Any)

Return type:

HfPyramidSlopedTerrainCfg

unilab.terrains.hf_pyramid_slope_inv(**overrides)[source]
Parameters:

overrides (Any)

Return type:

HfPyramidSlopedTerrainCfg

unilab.terrains.pyramid_stairs(**overrides)[source]
Parameters:

overrides (Any)

Return type:

HfPyramidStairsTerrainCfg

unilab.terrains.pyramid_stairs_inv(**overrides)[source]
Parameters:

overrides (Any)

Return type:

HfInvertedPyramidStairsTerrainCfg

unilab.terrains.random_rough(**overrides)[source]
Parameters:

overrides (Any)

Return type:

HfRandomUniformTerrainCfg

unilab.terrains.terrain_preset(fn)[source]

Register a terrain preset into ALL_TERRAIN_PRESETS.

Parameters:

fn (TypeVar(_F, bound= Callable[..., SubTerrainCfg]))

Return type:

TypeVar(_F, bound= Callable[..., SubTerrainCfg])

unilab.terrains.wave_terrain(**overrides)[source]
Parameters:

overrides (Any)

Return type:

HfWaveTerrainCfg

Modules

config

Terrain configuration presets and named terrain sets.

heightfield_terrains

Terrains composed of heightfields.

terrain_generator

utils

Utility functions for terrain generation.