Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CONTRIBUTORS.md
Original file line number Diff line number Diff line change
Expand Up @@ -145,6 +145,7 @@ Guidelines for modifications:
* Xavier Nal
* Xinjie Yao
* Xinpeng Liu
* Xin Xu
* Yang Jin
* Yanzi Zhu
* Yijie Guo
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# Copyright (c) 2022-2025, The Isaac Lab Project Developers (https://github.com/isaac-sim/IsaacLab/blob/main/CONTRIBUTORS.md).
# Copyright (c) 2022-2026, The Isaac Lab Project Developers (https://github.com/isaac-sim/IsaacLab/blob/main/CONTRIBUTORS.md).
# All rights reserved.
#
# SPDX-License-Identifier: BSD-3-Clause
Expand All @@ -11,7 +11,7 @@
import scipy.interpolate as interpolate
from typing import TYPE_CHECKING

from .utils import height_field_to_mesh
from .utils import height_field_to_mesh, height_field_to_mesh_v2

if TYPE_CHECKING:
from . import hf_terrains_cfg
Expand Down Expand Up @@ -79,7 +79,7 @@ def random_uniform_terrain(difficulty: float, cfg: hf_terrains_cfg.HfRandomUnifo
return np.rint(z_upsampled).astype(np.int16)


@height_field_to_mesh
@height_field_to_mesh_v2(terrain_origin_judge_width=1.0)
def pyramid_sloped_terrain(difficulty: float, cfg: hf_terrains_cfg.HfPyramidSlopedTerrainCfg) -> np.ndarray:
"""Generate a terrain with a truncated pyramid structure.

Expand Down
55 changes: 54 additions & 1 deletion source/isaaclab/isaaclab/terrains/height_field/utils.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# Copyright (c) 2022-2025, The Isaac Lab Project Developers (https://github.com/isaac-sim/IsaacLab/blob/main/CONTRIBUTORS.md).
# Copyright (c) 2022-2026, The Isaac Lab Project Developers (https://github.com/isaac-sim/IsaacLab/blob/main/CONTRIBUTORS.md).
# All rights reserved.
#
# SPDX-License-Identifier: BSD-3-Clause
Expand Down Expand Up @@ -76,6 +76,59 @@ def wrapper(difficulty: float, cfg: HfTerrainBaseCfg):
return wrapper


def height_field_to_mesh_v2(terrain_origin_judge_width: float = 2.0):
"""
the terrain origin is computed as the max in the square area around the center of the terrain,
the square width is `terrain_origin_judge_width`
"""

def decorator(func):
@functools.wraps(func)
def wrapper(difficulty: float, cfg: HfTerrainBaseCfg):
# check valid border width
if cfg.border_width > 0 and cfg.border_width < cfg.horizontal_scale:
raise ValueError(
f"The border width ({cfg.border_width}) must be greater than or equal to the"
f" horizontal scale ({cfg.horizontal_scale})."
)
# allocate buffer for height field (with border)
width_pixels = int(cfg.size[0] / cfg.horizontal_scale) + 1
length_pixels = int(cfg.size[1] / cfg.horizontal_scale) + 1
border_pixels = int(cfg.border_width / cfg.horizontal_scale) + 1
heights = np.zeros((width_pixels, length_pixels), dtype=np.int16)
# override size of the terrain to account for the border
sub_terrain_size = [width_pixels - 2 * border_pixels, length_pixels - 2 * border_pixels]
sub_terrain_size = [dim * cfg.horizontal_scale for dim in sub_terrain_size]
# update the config
terrain_size = copy.deepcopy(cfg.size)
cfg.size = tuple(sub_terrain_size)
# generate the height field
z_gen = func(difficulty, cfg)
# handle the border for the terrain
heights[border_pixels:-border_pixels, border_pixels:-border_pixels] = z_gen
# set terrain size back to config
cfg.size = terrain_size

# convert to trimesh
vertices, triangles = convert_height_field_to_mesh(
heights, cfg.horizontal_scale, cfg.vertical_scale, cfg.slope_threshold
)
mesh = trimesh.Trimesh(vertices=vertices, faces=triangles)
# compute origin
x1 = int((cfg.size[0] * 0.5 - terrain_origin_judge_width / 2.0) / cfg.horizontal_scale)
x2 = int((cfg.size[0] * 0.5 + terrain_origin_judge_width / 2.0) / cfg.horizontal_scale)
y1 = int((cfg.size[1] * 0.5 - terrain_origin_judge_width / 2.0) / cfg.horizontal_scale)
y2 = int((cfg.size[1] * 0.5 + terrain_origin_judge_width / 2.0) / cfg.horizontal_scale)
origin_z = np.max(heights[x1:x2, y1:y2]) * cfg.vertical_scale
origin = np.array([0.5 * cfg.size[0], 0.5 * cfg.size[1], origin_z])
# return mesh and origin
return [mesh], origin

return wrapper

return decorator


def convert_height_field_to_mesh(
height_field: np.ndarray, horizontal_scale: float, vertical_scale: float, slope_threshold: float | None = None
) -> tuple[np.ndarray, np.ndarray]:
Expand Down