|
| 1 | +# Terrains |
| 2 | + |
| 3 | +`genelab.terrains` is a thin GeneLab-shaped wrapper over Genesis's native |
| 4 | +`gs.morphs.Terrain` morph. Sub-terrain configs map onto Genesis's built-in subterrain |
| 5 | +string types, `TerrainGenerator` assembles them into a 2D grid, and `TerrainImporter` |
| 6 | +spawns the terrain into the scene plus tracks per-env curriculum state. |
| 7 | + |
| 8 | +## Why a dedicated layer |
| 9 | + |
| 10 | +Genesis ships the height-field terrain primitives, but its native API uses pydantic |
| 11 | +options keyed by string names (`"pyramid_stairs_terrain"`, `"random_uniform_terrain"`). |
| 12 | +A GeneLab cfg layer keeps the surface dataclass-shaped (matching the rest of the project), |
| 13 | +gives every sub-terrain a typed Python class with explicit fields, and tracks per-env |
| 14 | +spawn / level state so curriculum terms can promote and demote without touching the |
| 15 | +Genesis handle directly. |
| 16 | + |
| 17 | +## Three shipped sub-terrains |
| 18 | + |
| 19 | +| Class | Genesis type | Parameters | |
| 20 | +|---|---|---| |
| 21 | +| `FlatPatchCfg` | `flat_terrain` | none | |
| 22 | +| `PyramidStairsCfg` | `pyramid_stairs_terrain` | `step_width`, `step_height` | |
| 23 | +| `RandomRoughCfg` | `random_uniform_terrain` | `min_height`, `max_height`, `step`, `downsampled_scale` | |
| 24 | + |
| 25 | +`PyramidStairsCfg(step_height=-0.1)` builds concentric descending steps; positive |
| 26 | +`step_height` would invert the pyramid. `RandomRoughCfg` samples bumps at |
| 27 | +`downsampled_scale` then upsamples, so the apparent feature size is independent of |
| 28 | +`horizontal_scale`. Three more Genesis types (`sloped`, `wave`, `stepping_stones`) are |
| 29 | +not wrapped yet — extending `SubTerrainCfg` to expose them is a 20-line task. |
| 30 | + |
| 31 | +## Composing a terrain grid |
| 32 | + |
| 33 | +```python |
| 34 | +from genelab.terrains import ( |
| 35 | + FlatPatchCfg, |
| 36 | + PyramidStairsCfg, |
| 37 | + RandomRoughCfg, |
| 38 | + TerrainGeneratorCfg, |
| 39 | +) |
| 40 | +from genelab.configs import InteractiveSceneCfg |
| 41 | + |
| 42 | +scene = InteractiveSceneCfg( |
| 43 | + env_spacing=(0.0, 0.0), |
| 44 | + terrain=TerrainGeneratorCfg( |
| 45 | + num_rows=4, |
| 46 | + num_cols=4, |
| 47 | + subterrain_size=(8.0, 8.0), |
| 48 | + horizontal_scale=0.1, |
| 49 | + vertical_scale=0.005, |
| 50 | + sub_terrains={ |
| 51 | + "flat": FlatPatchCfg(proportion=1.0), |
| 52 | + "stairs": PyramidStairsCfg(step_width=0.5, step_height=-0.08, proportion=2.0), |
| 53 | + "rough": RandomRoughCfg(min_height=-0.05, max_height=0.05, proportion=1.0), |
| 54 | + }, |
| 55 | + curriculum=True, |
| 56 | + seed=0, |
| 57 | + ), |
| 58 | +) |
| 59 | +``` |
| 60 | + |
| 61 | +`sub_terrains` is a `dict[str, SubTerrainCfg]`; the key is the local cfg label, the |
| 62 | +value carries the Genesis parameters. `layout` defaults to `None` (random tiling by |
| 63 | +`proportion`); pass a `tuple[tuple[str, ...], ...]` of cfg keys to pin the grid |
| 64 | +deterministically. |
| 65 | + |
| 66 | +!!! warning "One parameter set per Genesis type" |
| 67 | + Genesis keys `subterrain_parameters` by the *type string*, not per cell. Two |
| 68 | + `PyramidStairsCfg` instances with different `step_width` collapse to a single |
| 69 | + parameter set. Use distinct keys for distinct geometries, or rely on |
| 70 | + `randomize=True` on the importer for in-type variation. |
| 71 | + |
| 72 | +## Ray-cast sensor integration |
| 73 | + |
| 74 | +When `InteractiveSceneCfg.terrain` is set, `InteractiveScene.build()` spawns |
| 75 | +`gs.morphs.Terrain` instead of the default plane and exposes the importer as |
| 76 | +`scene.terrain`. `RayCastSensor` checks for it and, when present, bilinearly samples |
| 77 | +`terrain.heightfield_tensor` at each ray's world `(x, y)` to compute hit elevation. |
| 78 | +`TerrainHeightSensor` is a thin wrapper around the same path, so the per-link height |
| 79 | +scan output reflects the true terrain instead of a constant plane. |
| 80 | + |
| 81 | +Vertical and near-vertical rays match the underlying height field to within |
| 82 | +`vertical_scale`. Oblique rays use the elevation at the ray-start `(x, y)` — accurate |
| 83 | +for height-scan grids but approximate for BVH-style queries; override |
| 84 | +`RayCastSensor._intersect_world_rays` for those. |
| 85 | + |
| 86 | +## Curriculum: `terrain_levels_vel` |
| 87 | + |
| 88 | +```python |
| 89 | +from genelab.managers import CurriculumTermCfg |
| 90 | +from genelab.mdp import terrain_levels_vel |
| 91 | + |
| 92 | +curriculum_cfg = { |
| 93 | + "terrain_levels": CurriculumTermCfg( |
| 94 | + func=terrain_levels_vel, |
| 95 | + params={"distance_threshold": 2.0, "demote_ratio": 0.5}, |
| 96 | + ), |
| 97 | +} |
| 98 | +``` |
| 99 | + |
| 100 | +Each reset, the term compares every env's walked distance (XY norm of `root_pos - |
| 101 | +spawn_pos`) against `distance_threshold`. Envs that walked further than the threshold |
| 102 | +move up one row of the difficulty grid (capped at `num_rows - 1`); envs that walked |
| 103 | +less than `distance_threshold * demote_ratio` move down (floored at 0). The curriculum |
| 104 | +then writes the new spawn pose into the sim via `Articulation.write_root_state`, so |
| 105 | +the next episode starts on the new sub-terrain. The manager logs the mean level under |
| 106 | +`Curriculum/terrain_levels`. |
| 107 | + |
| 108 | +## Failure modes worth knowing |
| 109 | + |
| 110 | +* **Empty `sub_terrains`** — `TerrainGenerator.__init__` raises immediately; the |
| 111 | + generator has nothing to tile. |
| 112 | +* **`layout` shape mismatch** — `ValueError` lists the actual vs declared shape. |
| 113 | +* **`layout` references unknown key** — `ValueError` lists the missing keys. |
| 114 | +* **Per-env state access before build** — `terrain_levels` / `terrain_cols` / |
| 115 | + `spawn_pos` raise `RuntimeError` until `init_per_env_state` runs; |
| 116 | + `InteractiveScene.build` calls it automatically. |
| 117 | +* **`heightfield` access before spawn** — `RuntimeError`; spawn first, then read. |
| 118 | + |
| 119 | +## See also |
| 120 | + |
| 121 | +- [Configs](configs.md) |
| 122 | +- [Sensors](sensors.md) |
| 123 | +- [Actuators](actuators.md) |
0 commit comments