Skip to content

feat: implement _update_terrain_curriculum and fix _get_env_origins for heightfield/trimesh#84

Open
dcol91863 wants to merge 1 commit intounitreerobotics:mainfrom
dcol91863:feat/terrain-curriculum
Open

feat: implement _update_terrain_curriculum and fix _get_env_origins for heightfield/trimesh#84
dcol91863 wants to merge 1 commit intounitreerobotics:mainfrom
dcol91863:feat/terrain-curriculum

Conversation

@dcol91863
Copy link

Summary

Fixes #81

The _update_terrain_curriculum(env_ids) method was referenced in the docstring of reset_idx but was never implemented. With cfg.terrain.curriculum = True and a heightfield/trimesh terrain, the curriculum was silently a no-op — all robots stayed on the same terrain level forever.

This PR implements the full terrain curriculum loop in a single file (legged_gym/envs/base/legged_robot.py).


Changes

_get_env_origins() — heightfield/trimesh branch

Previously the method always created a flat grid and left custom_origins = False, so robots were never actually placed on the heightfield platforms. Now:

  • Sets custom_origins = True for heightfield and trimesh mesh types.
  • Converts terrain.env_origins (numpy array, shape [num_rows, num_cols, 3]) to a GPU tensor self.terrain_origins for fast indexed lookups.
  • Assigns each environment a random terrain type (column index).
  • Initialises each environment to a random level (row index) in [0, max_init_terrain_level].
  • Populates self.env_origins from the lookup table immediately.

_update_terrain_curriculum(env_ids) — new method

Called at the start of each reset_idx when cfg.terrain.curriculum is True:

  1. Computes how far each resetting robot moved from its terrain-platform centre (XY distance).
  2. Promotes robots that covered > half a platform length (env_length / 2) to the next harder level.
  3. Demotes robots that fell short by one level.
  4. Clamps all levels to [0, num_rows - 1].
  5. Refreshes self.env_origins[env_ids] from the terrain_origins lookup table so that _reset_root_states places the robot on the correct platform.

The method is a no-op until self.init_done is set, preserving the initial level assignment from _get_env_origins.

reset_idx() — wired up

  • Calls _update_terrain_curriculum(env_ids) before robot repositioning when cfg.terrain.curriculum is True.
  • Logs extras["episode"]["terrain_level"] (mean across all envs) for training curve monitoring.

Test plan

  • Set cfg.terrain.mesh_type = 'heightfield' and cfg.terrain.curriculum = True, train a G1/H1 policy — confirm robots spawn on terrain platforms and terrain_level in tensorboard increases over training.
  • Set cfg.terrain.mesh_type = 'plane' — confirm flat-grid path is unchanged.
  • Set cfg.terrain.curriculum = False with heightfield — confirm _update_terrain_curriculum is never called and robots still spawn correctly.

…or heightfield/trimesh

Closes unitreerobotics#81

The `_update_terrain_curriculum(env_ids)` method was referenced in the
docstring of `reset_idx` but was never implemented, making terrain
curriculum training a no-op even when `cfg.terrain.curriculum = True`.

Changes
-------

legged_gym/envs/base/legged_robot.py:

1. _get_env_origins() — heightfield / trimesh branch (new)
   Previously the method only created a flat grid regardless of terrain
   type, so `custom_origins` was always False and robots were never
   placed on the actual heightfield platforms.  Now:
   - Sets `custom_origins = True` for heightfield and trimesh terrains.
   - Converts `terrain.env_origins` (numpy, rows=levels, cols=types) to
     a GPU tensor `self.terrain_origins` for fast index lookups.
   - Assigns each environment a random terrain type (column).
   - Initialises each environment to a random level in
     [0, max_init_terrain_level].
   - Sets `self.env_origins` from the lookup table.

2. _update_terrain_curriculum(env_ids) — new method
   Promotes robots that traversed more than half a platform length
   (env_length / 2) during the episode to the next harder level, and
   demotes robots that fell short by one level.  Levels are clamped to
   [0, num_rows - 1].  env_origins is refreshed for the reset envs.
   The method is a no-op on the very first call (before init_done) so
   the initial level assignment from _get_env_origins is preserved.

3. reset_idx() — wired up
   Added call to `_update_terrain_curriculum(env_ids)` when
   `cfg.terrain.curriculum` is True (placed before robot repositioning
   so the updated origin is used immediately).  Also logs the mean
   terrain level to `extras["episode"]["terrain_level"]` for monitoring.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

No curriculum learning implementation

2 participants