diff --git a/README.md b/README.md index 73fdcd2..679df56 100644 --- a/README.md +++ b/README.md @@ -159,7 +159,6 @@ xml_path: "{ROOT_DIR}/resources/go2/your-custom-scene.xml" ## Differences from `go2_rl_gym` -- Different terrain composition - Different tracking reward formulation (fixed sigma vs. dynamic sigma) - Lack domain_rand: randomize_motor_strength diff --git a/source/robot_lab/robot_lab/tasks/go2/env_cfg.py b/source/robot_lab/robot_lab/tasks/go2/env_cfg.py index 3ed2a4f..50ed99d 100644 --- a/source/robot_lab/robot_lab/tasks/go2/env_cfg.py +++ b/source/robot_lab/robot_lab/tasks/go2/env_cfg.py @@ -15,10 +15,10 @@ from isaaclab.terrains import TerrainImporterCfg from isaaclab.utils import configclass from isaaclab.utils.assets import ISAAC_NUCLEUS_DIR, ISAACLAB_NUCLEUS_DIR from isaaclab.utils.noise import AdditiveUniformNoiseCfg as Unoise -import isaaclab.terrains as terrain_gen import robot_lab.tasks.go2.mdp as mdp from robot_lab.assets.unitree import GO2_CFG_ROBOTLAB, GO2_CFG_UNITREE +from robot_lab.tasks.go2.mdp.terrain import DEFAULT_TERRAIN_CFG JOINT_NAMES = [ "FL_hip_joint", "FL_thigh_joint", "FL_calf_joint", @@ -35,47 +35,7 @@ BASE_HEIGHT_TARGET = 0.38 # base height target for rewards, set as an attribute # Terrain definition ## -TERRAIN_CFG = terrain_gen.TerrainGeneratorCfg( - size=(8.0, 8.0), - border_width=25.0, - num_rows=10, - num_cols=20, - horizontal_scale=0.1, - vertical_scale=0.005, - slope_threshold=0.75, - use_cache=False, - sub_terrains={ - "pyramid_stairs": terrain_gen.MeshPyramidStairsTerrainCfg( - proportion=0.15, - step_height_range=(0.05, 0.25), - step_width=0.3, - platform_width=3.0, - border_width=1.0, - holes=False, - ), - "pyramid_stairs_inv": terrain_gen.MeshInvertedPyramidStairsTerrainCfg( - proportion=0.20, - step_height_range=(0.05, 0.25), - step_width=0.3, - platform_width=3.0, - border_width=1.0, - holes=False, - ), - "boxes": terrain_gen.MeshRandomGridTerrainCfg( - proportion=0.15, grid_width=0.45, grid_height_range=(0.025, 0.1), platform_width=2.0 - ), - "random_rough": terrain_gen.HfRandomUniformTerrainCfg( - proportion=0.15, noise_range=(0.01, 0.06), noise_step=0.01, border_width=0.25 - ), - "flat": terrain_gen.MeshPlaneTerrainCfg(proportion=0.15), - "hf_pyramid_slope": terrain_gen.HfPyramidSlopedTerrainCfg( - proportion=0.1, slope_range=(0.0, 0.5), platform_width=2.0, border_width=0.25 - ), - "hf_pyramid_slope_inv": terrain_gen.HfInvertedPyramidSlopedTerrainCfg( - proportion=0.1, slope_range=(0.0, 0.5), platform_width=2.0, border_width=0.25 - ), - }, -) +TERRAIN_CFG = DEFAULT_TERRAIN_CFG ## @@ -517,8 +477,8 @@ class Go2EnvCfg(ManagerBasedRLEnvCfg): # Physics material settings from subclass self.sim.physics_material = self.scene.terrain.physics_material - self.sim.physx.gpu_max_rigid_patch_count = 10 * 2**15 - self.sim.physx.gpu_collision_stack_size = int(64 * 1024 * 1024) # 128 MB + self.sim.physx.gpu_max_rigid_patch_count = 512 * 1024 + self.sim.physx.gpu_collision_stack_size = int(128 * 1024 * 1024) # 128 MB self.sim.physx.enable_external_forces_every_iteration = True # Update sensor periods diff --git a/source/robot_lab/robot_lab/tasks/go2/mdp/commands.py b/source/robot_lab/robot_lab/tasks/go2/mdp/commands.py index 5f52e92..c8fd04c 100644 --- a/source/robot_lab/robot_lab/tasks/go2/mdp/commands.py +++ b/source/robot_lab/robot_lab/tasks/go2/mdp/commands.py @@ -373,23 +373,42 @@ class Go2RLGymCommandCfg(CommandTermCfg): 'ang_vel_yaw': [-2.0, 2.0], # min max [rad/s] }] """List for command range curriculums at specific training iterations""" + # terrain_max_command_ranges: dict[str, dict] = { + # 'random_rough': + # {'lin_vel_x': [-1.5, 1.5], 'lin_vel_y': [-1.0, 1.0], 'ang_vel_yaw': [-1.5, 1.5]}, + # 'hf_pyramid_slope': + # {'lin_vel_x': [-1.5, 1.5], 'lin_vel_y': [-1.0, 1.0], 'ang_vel_yaw': [-1.5, 1.5]}, + # 'hf_pyramid_slope_inv': + # {'lin_vel_x': [-1.5, 1.5], 'lin_vel_y': [-1.0, 1.0], 'ang_vel_yaw': [-1.5, 1.5]}, + # 'pyramid_stairs': + # {'lin_vel_x': [-1.0, 1.0], 'lin_vel_y': [-1.0, 1.0], 'ang_vel_yaw': [-1.5, 1.5]}, + # 'pyramid_stairs_inv': + # {'lin_vel_x': [-1.0, 1.0], 'lin_vel_y': [-1.0, 1.0], 'ang_vel_yaw': [-1.5, 1.5]}, + # 'boxes': + # {'lin_vel_x': [-1.0, 1.0], 'lin_vel_y': [-1.0, 1.0], 'ang_vel_yaw': [-1.5, 1.5]}, + # 'flat': + # {'lin_vel_x': [-2.0, 2.0], 'lin_vel_y': [-1.0, 1.0], 'ang_vel_yaw': [-2.0, 2.0]}, + # } terrain_max_command_ranges: dict[str, dict] = { - 'random_rough': + 'wave': {'lin_vel_x': [-1.5, 1.5], 'lin_vel_y': [-1.0, 1.0], 'ang_vel_yaw': [-1.5, 1.5]}, - 'hf_pyramid_slope': + 'slope': {'lin_vel_x': [-1.5, 1.5], 'lin_vel_y': [-1.0, 1.0], 'ang_vel_yaw': [-1.5, 1.5]}, - 'hf_pyramid_slope_inv': + 'rough_slope': {'lin_vel_x': [-1.5, 1.5], 'lin_vel_y': [-1.0, 1.0], 'ang_vel_yaw': [-1.5, 1.5]}, - 'pyramid_stairs': + 'stairs_up': {'lin_vel_x': [-1.0, 1.0], 'lin_vel_y': [-1.0, 1.0], 'ang_vel_yaw': [-1.5, 1.5]}, - 'pyramid_stairs_inv': + 'stairs_down': {'lin_vel_x': [-1.0, 1.0], 'lin_vel_y': [-1.0, 1.0], 'ang_vel_yaw': [-1.5, 1.5]}, - 'boxes': + 'obstacles': {'lin_vel_x': [-1.0, 1.0], 'lin_vel_y': [-1.0, 1.0], 'ang_vel_yaw': [-1.5, 1.5]}, + # 'stepping_stones': + # {'lin_vel_x': [-1.0, 1.0], 'lin_vel_y': [-1.0, 1.0], 'ang_vel_yaw': [-1.5, 1.5]}, + # 'gap': + # {'lin_vel_x': [-1.0, 1.0], 'lin_vel_y': [-1.0, 1.0], 'ang_vel_yaw': [-1.5, 1.5]}, 'flat': {'lin_vel_x': [-2.0, 2.0], 'lin_vel_y': [-1.0, 1.0], 'ang_vel_yaw': [-2.0, 2.0]}, } - resampling_time: float = 5.0 resampling_time_range: tuple[float, float] = (5.0, 5.0) """Time before command are changed [s]""" diff --git a/source/robot_lab/robot_lab/tasks/go2/mdp/terrain.py b/source/robot_lab/robot_lab/tasks/go2/mdp/terrain.py new file mode 100644 index 0000000..c428d21 --- /dev/null +++ b/source/robot_lab/robot_lab/tasks/go2/mdp/terrain.py @@ -0,0 +1,265 @@ +# Copyright (c) 2024-2025 Ziqi Fan +# SPDX-License-Identifier: Apache-2.0 + +"""Terrain presets for the Go2 task. + +This module keeps two terrain configurations side by side: + +1. ``ORIGINAL_TERRAIN_CFG``: + The original RobotLab terrain setup that existed before Gym-alignment changes. + This is the default selection used by ``env_cfg.py`` so existing training behavior + is not changed. + +2. ``GYM_ALIGNED_TERRAIN_CFG``: + A Gym-oriented terrain setup derived from: + - ``legged_gym/envs/go2/go2_config.py`` + - ``legged_gym/utils/terrain.py`` + and interpreted with ``IS_HARD = True``. + +Only the terrain generator presets live here. The environment can choose either +configuration without modifying the rest of the task code. +""" + +from __future__ import annotations + +import numpy as np + +import isaaclab.terrains as terrain_gen +from isaaclab.utils import configclass +from isaaclab.terrains.height_field import hf_terrains +from isaaclab.terrains.height_field.utils import height_field_to_mesh + + +# ----------------------------------------------------------------------------- +# Original RobotLab terrain setup +# ----------------------------------------------------------------------------- + +ORIGINAL_TERRAIN_CFG = terrain_gen.TerrainGeneratorCfg( + size=(8.0, 8.0), + border_width=25.0, + num_rows=10, + num_cols=20, + horizontal_scale=0.1, + vertical_scale=0.005, + slope_threshold=0.75, + use_cache=False, + sub_terrains={ + "pyramid_stairs": terrain_gen.MeshPyramidStairsTerrainCfg( + proportion=0.15, + step_height_range=(0.05, 0.25), + step_width=0.3, + platform_width=3.0, + border_width=1.0, + holes=False, + ), + "pyramid_stairs_inv": terrain_gen.MeshInvertedPyramidStairsTerrainCfg( + proportion=0.20, + step_height_range=(0.05, 0.25), + step_width=0.3, + platform_width=3.0, + border_width=1.0, + holes=False, + ), + "boxes": terrain_gen.MeshRandomGridTerrainCfg( + proportion=0.15, + grid_width=0.45, + grid_height_range=(0.025, 0.1), + platform_width=2.0, + ), + "random_rough": terrain_gen.HfRandomUniformTerrainCfg( + proportion=0.15, + noise_range=(0.01, 0.06), + noise_step=0.01, + border_width=0.25, + ), + "flat": terrain_gen.MeshPlaneTerrainCfg(proportion=0.15), + "hf_pyramid_slope": terrain_gen.HfPyramidSlopedTerrainCfg( + proportion=0.1, + slope_range=(0.0, 0.5), + platform_width=2.0, + border_width=0.25, + ), + "hf_pyramid_slope_inv": terrain_gen.HfInvertedPyramidSlopedTerrainCfg( + proportion=0.1, + slope_range=(0.0, 0.5), + platform_width=2.0, + border_width=0.25, + ), + }, +) + + +# ----------------------------------------------------------------------------- +# Gym-aligned terrain setup +# ----------------------------------------------------------------------------- + + +def _gym_signed_pyramid_slope_height_field( + difficulty: float, + cfg: terrain_gen.HfPyramidSlopedTerrainCfg, + random_inverted: bool, +) -> np.ndarray: + """Generate a Gym-style pyramid slope with optional random sign.""" + slope = cfg.slope_range[0] + difficulty * (cfg.slope_range[1] - cfg.slope_range[0]) + if random_inverted and np.random.rand() < 0.5: + slope = -slope + + width_pixels = int(cfg.size[0] / cfg.horizontal_scale) + length_pixels = int(cfg.size[1] / cfg.horizontal_scale) + height_max = int(slope * cfg.size[0] / 2 / cfg.vertical_scale) + center_x = int(width_pixels / 2) + center_y = int(length_pixels / 2) + + x = np.arange(0, width_pixels) + y = np.arange(0, length_pixels) + xx, yy = np.meshgrid(x, y, sparse=True) + xx = (center_x - np.abs(center_x - xx)) / center_x + yy = (center_y - np.abs(center_y - yy)) / center_y + xx = xx.reshape(width_pixels, 1) + yy = yy.reshape(1, length_pixels) + + hf_raw = height_max * xx * yy + + platform_width = int(cfg.platform_width / cfg.horizontal_scale / 2) + x_pf = width_pixels // 2 - platform_width + y_pf = length_pixels // 2 - platform_width + z_pf = hf_raw[x_pf, y_pf] + hf_raw = np.clip(hf_raw, min(0, z_pf), max(0, z_pf)) + + return np.rint(hf_raw).astype(np.int16) + + +@height_field_to_mesh +def gym_wave_terrain(difficulty: float, cfg) -> np.ndarray: + """Gym-style wave terrain: wave plus random uniform roughness.""" + wave = hf_terrains.wave_terrain.__wrapped__(difficulty, cfg) + rough = hf_terrains.random_uniform_terrain.__wrapped__(difficulty, cfg) + return np.rint(wave + rough).astype(np.int16) + + +@height_field_to_mesh +def gym_slope_terrain(difficulty: float, cfg) -> np.ndarray: + """Gym-style slope terrain with 50/50 positive and negative slopes.""" + return _gym_signed_pyramid_slope_height_field(difficulty, cfg, random_inverted=True) + + +@height_field_to_mesh +def gym_rough_slope_terrain(difficulty: float, cfg) -> np.ndarray: + """Gym-style rough slope terrain: slope plus random uniform roughness.""" + slope = _gym_signed_pyramid_slope_height_field(difficulty, cfg, random_inverted=False) + rough = hf_terrains.random_uniform_terrain.__wrapped__(difficulty, cfg) + return np.rint(slope + rough).astype(np.int16) + + +@configclass +class GymWaveTerrainCfg(terrain_gen.HfTerrainBaseCfg): + function = gym_wave_terrain + amplitude_range: tuple[float, float] = (0.1, 0.28) + num_waves: int = 5 + noise_range: tuple[float, float] = (-0.05, 0.05) + noise_step: float = 0.005 + downsampled_scale: float = 0.2 + + +@configclass +class GymSlopeTerrainCfg(terrain_gen.HfTerrainBaseCfg): + function = gym_slope_terrain + slope_range: tuple[float, float] = (0.1, 0.568) + platform_width: float = 3.0 + + +@configclass +class GymRoughSlopeTerrainCfg(terrain_gen.HfTerrainBaseCfg): + function = gym_rough_slope_terrain + slope_range: tuple[float, float] = (0.1, 0.568) + platform_width: float = 3.0 + noise_range: tuple[float, float] = (-0.05, 0.05) + noise_step: float = 0.005 + downsampled_scale: float = 0.2 + + +GYM_ALIGNED_TERRAIN_CFG = terrain_gen.TerrainGeneratorCfg( + size=(8.0, 8.0), + border_width=25.0, + num_rows=10, + num_cols=20, + horizontal_scale=0.1, + vertical_scale=0.005, + slope_threshold=0.75, + use_cache=False, + sub_terrains={ + "wave": GymWaveTerrainCfg( + proportion=0.05, + border_width=0.0, + horizontal_scale=0.1, + vertical_scale=0.005, + slope_threshold=0.75, + ), + "slope": GymSlopeTerrainCfg( + proportion=0.20, + border_width=0.0, + horizontal_scale=0.1, + vertical_scale=0.005, + slope_threshold=0.75, + ), + "rough_slope": GymRoughSlopeTerrainCfg( + proportion=0.05, + border_width=0.0, + horizontal_scale=0.1, + vertical_scale=0.005, + slope_threshold=0.75, + ), + "stairs_up": terrain_gen.HfInvertedPyramidStairsTerrainCfg( + proportion=0.25, + step_height_range=(0.05, 0.257), + step_width=0.31, + platform_width=3.0, + border_width=0.0, + horizontal_scale=0.1, + vertical_scale=0.005, + slope_threshold=0.75, + ), + "stairs_down": terrain_gen.HfPyramidStairsTerrainCfg( + proportion=0.10, + step_height_range=(0.05, 0.257), + step_width=0.31, + platform_width=3.0, + border_width=0.0, + horizontal_scale=0.1, + vertical_scale=0.005, + slope_threshold=0.75, + ), + "obstacles": terrain_gen.HfDiscreteObstaclesTerrainCfg( + proportion=0.20, + obstacle_width_range=(1.0, 2.0), + obstacle_height_range=(0.05, 0.275), + num_obstacles=20, + platform_width=3.0, + border_width=0.0, + horizontal_scale=0.1, + vertical_scale=0.005, + slope_threshold=0.75, + ), + # "stepping_stones": terrain_gen.HfSteppingStonesTerrainCfg( + # proportion=0.0, + # stone_height_max=0.0, + # stone_width_range=(0.075, 1.575), + # stone_distance_range=(0.05, 0.10), + # holes_depth=-10.0, + # platform_width=4.0, + # border_width=0.0, + # horizontal_scale=0.1, + # vertical_scale=0.005, + # slope_threshold=0.75, + # ), + # "gap": terrain_gen.MeshGapTerrainCfg( + # proportion=0.0, + # gap_width_range=(0.0, 0.9), + # platform_width=3.0, + # ), + "flat": terrain_gen.MeshPlaneTerrainCfg(proportion=0.15), + }, +) + + +DEFAULT_TERRAIN_CFG = GYM_ALIGNED_TERRAIN_CFG