Skip to content

Commit 1a37500

Browse files
committed
rough terrain support
1 parent df77fe7 commit 1a37500

5 files changed

Lines changed: 152 additions & 71 deletions

File tree

source/isaaclab/isaaclab/envs/mdp/__init__.pyi

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -137,6 +137,8 @@ __all__ = [
137137
"joint_pos_out_of_manual_limit",
138138
"joint_vel_out_of_limit",
139139
"joint_vel_out_of_manual_limit",
140+
"body_lin_vel_out_of_limit",
141+
"body_ang_vel_out_of_limit",
140142
"root_height_below_minimum",
141143
"time_out",
142144
]
@@ -285,6 +287,8 @@ from .terminations import (
285287
joint_pos_out_of_manual_limit,
286288
joint_vel_out_of_limit,
287289
joint_vel_out_of_manual_limit,
290+
body_lin_vel_out_of_limit,
291+
body_ang_vel_out_of_limit,
288292
root_height_below_minimum,
289293
time_out,
290294
)

source/isaaclab/isaaclab/envs/mdp/terminations.py

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -150,6 +150,41 @@ def joint_effort_out_of_limit(
150150
return torch.any(out_of_limits, dim=1)
151151

152152

153+
"""
154+
Body velocity terminations.
155+
"""
156+
157+
158+
def body_lin_vel_out_of_limit(
159+
env: ManagerBasedRLEnv,
160+
max_velocity: float,
161+
asset_cfg: SceneEntityCfg = SceneEntityCfg("robot"),
162+
) -> torch.Tensor:
163+
"""Terminate when any body's linear velocity magnitude exceeds the limit [m/s].
164+
165+
Also terminates on NaN velocities, which indicate a solver singularity.
166+
"""
167+
asset: Articulation = env.scene[asset_cfg.name]
168+
body_vel = wp.to_torch(asset.data.body_lin_vel_w)[:, asset_cfg.body_ids]
169+
speed = torch.linalg.norm(body_vel, dim=-1)
170+
return torch.any((speed > max_velocity) | torch.isnan(speed), dim=1)
171+
172+
173+
def body_ang_vel_out_of_limit(
174+
env: ManagerBasedRLEnv,
175+
max_velocity: float,
176+
asset_cfg: SceneEntityCfg = SceneEntityCfg("robot"),
177+
) -> torch.Tensor:
178+
"""Terminate when any body's angular velocity magnitude exceeds the limit [rad/s].
179+
180+
Also terminates on NaN velocities, which indicate a solver singularity.
181+
"""
182+
asset: Articulation = env.scene[asset_cfg.name]
183+
body_vel = wp.to_torch(asset.data.body_ang_vel_w)[:, asset_cfg.body_ids]
184+
speed = torch.linalg.norm(body_vel, dim=-1)
185+
return torch.any((speed > max_velocity) | torch.isnan(speed), dim=1)
186+
187+
153188
"""
154189
Contact sensor.
155190
"""

source/isaaclab/isaaclab/terrains/terrain_generator.py

Lines changed: 42 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,6 @@
1818
from isaaclab.utils.timer import Timer
1919
from isaaclab.utils.warp import convert_to_warp_mesh
2020

21-
from .trimesh.utils import make_border
2221
from .utils import color_meshes_by_height, find_flat_patches
2322

2423
if TYPE_CHECKING:
@@ -270,25 +269,48 @@ def _generate_curriculum_terrains(self):
270269
"""
271270

272271
def _add_terrain_border(self):
273-
"""Add a surrounding border over all the sub-terrains into the terrain meshes."""
274-
# border parameters
275-
border_size = (
276-
self.cfg.num_rows * self.cfg.size[0] + 2 * self.cfg.border_width,
277-
self.cfg.num_cols * self.cfg.size[1] + 2 * self.cfg.border_width,
278-
)
279-
inner_size = (self.cfg.num_rows * self.cfg.size[0], self.cfg.num_cols * self.cfg.size[1])
280-
border_center = (
281-
self.cfg.num_rows * self.cfg.size[0] / 2,
282-
self.cfg.num_cols * self.cfg.size[1] / 2,
283-
-self.cfg.border_height / 2,
284-
)
285-
# border mesh
286-
border_meshes = make_border(border_size, inner_size, height=abs(self.cfg.border_height), position=border_center)
287-
border = trimesh.util.concatenate(border_meshes)
288-
# update the faces to have minimal triangles
289-
selector = ~(np.asarray(border.triangles)[:, :, 2] < -0.1).any(1)
290-
border.update_faces(selector)
291-
# add the border to the list of meshes
272+
"""Add a surrounding border over all the sub-terrains into the terrain meshes.
273+
274+
The border is built as a subdivided grid mesh (matching ``horizontal_scale``)
275+
rather than large box primitives. Large triangles on the border can cause
276+
collision detection failures in some physics backends (e.g. Newton/MuJoCo).
277+
"""
278+
bw = self.cfg.border_width
279+
if bw <= 0:
280+
return
281+
282+
inner_w = self.cfg.num_rows * self.cfg.size[0]
283+
inner_l = self.cfg.num_cols * self.cfg.size[1]
284+
hs = self.cfg.horizontal_scale
285+
286+
def _make_grid_strip(x0: float, y0: float, width: float, length: float) -> trimesh.Trimesh:
287+
"""Create a flat subdivided mesh strip at z=0 with grid spacing ``hs``."""
288+
nx = max(int(np.ceil(width / hs)), 1) + 1
289+
ny = max(int(np.ceil(length / hs)), 1) + 1
290+
xs = np.linspace(x0, x0 + width, nx)
291+
ys = np.linspace(y0, y0 + length, ny)
292+
xx, yy = np.meshgrid(xs, ys, indexing="ij")
293+
vertices = np.zeros((nx * ny, 3))
294+
vertices[:, 0] = xx.flatten()
295+
vertices[:, 1] = yy.flatten()
296+
faces = []
297+
for i in range(nx - 1):
298+
for j in range(ny - 1):
299+
v0 = i * ny + j
300+
v1 = v0 + 1
301+
v2 = (i + 1) * ny + j
302+
v3 = v2 + 1
303+
faces.append([v0, v2, v1])
304+
faces.append([v1, v2, v3])
305+
return trimesh.Trimesh(vertices=vertices, faces=np.array(faces, dtype=np.uint32))
306+
307+
strips = [
308+
_make_grid_strip(-bw, -bw, inner_w + 2 * bw, bw), # bottom
309+
_make_grid_strip(-bw, inner_l, inner_w + 2 * bw, bw), # top
310+
_make_grid_strip(-bw, 0.0, bw, inner_l), # left
311+
_make_grid_strip(inner_w, 0.0, bw, inner_l), # right
312+
]
313+
border = trimesh.util.concatenate(strips)
292314
self.terrain_meshes.append(border)
293315

294316
def _add_sub_terrain(

source/isaaclab_tasks/isaaclab_tasks/manager_based/locomotion/velocity/config/anymal_d/rough_env_cfg.py

Lines changed: 21 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -3,13 +3,13 @@
33
#
44
# SPDX-License-Identifier: BSD-3-Clause
55

6+
from isaaclab_newton.physics import MJWarpSolverCfg, NewtonCfg, NewtonCollisionPipelineCfg
7+
from isaaclab_physx.physics import PhysxCfg
8+
9+
from isaaclab.sim import SimulationCfg
610
from isaaclab.utils import configclass
711

8-
from isaaclab_tasks.manager_based.locomotion.velocity.velocity_env_cfg import (
9-
EventsCfg,
10-
LocomotionVelocityRoughEnvCfg,
11-
StartupEventsCfg,
12-
)
12+
from isaaclab_tasks.manager_based.locomotion.velocity.velocity_env_cfg import LocomotionVelocityRoughEnvCfg
1313
from isaaclab_tasks.utils import PresetCfg
1414

1515
##
@@ -19,20 +19,27 @@
1919

2020

2121
@configclass
22-
class AnymalDPhysxEventsCfg(EventsCfg, StartupEventsCfg):
23-
pass
24-
25-
26-
@configclass
27-
class AnymalDEventsCfg(PresetCfg):
28-
default = AnymalDPhysxEventsCfg()
29-
newton = EventsCfg()
22+
class RoughPhysicsCfg(PresetCfg):
23+
default = PhysxCfg(gpu_max_rigid_patch_count=10 * 2**15)
24+
newton = NewtonCfg(
25+
solver_cfg=MJWarpSolverCfg(
26+
njmax=200,
27+
nconmax=100,
28+
cone="pyramidal",
29+
impratio=1.0,
30+
integrator="implicitfast",
31+
use_mujoco_contacts=False,
32+
),
33+
collision_cfg=NewtonCollisionPipelineCfg(max_triangle_pairs=2_500_000),
34+
num_substeps=1,
35+
debug_mode=False,
36+
)
3037
physx = default
3138

3239

3340
@configclass
3441
class AnymalDRoughEnvCfg(LocomotionVelocityRoughEnvCfg):
35-
events: AnymalDEventsCfg = AnymalDEventsCfg()
42+
sim: SimulationCfg = SimulationCfg(physics=RoughPhysicsCfg())
3643

3744
def __post_init__(self):
3845
# post init of parent

source/isaaclab_tasks/isaaclab_tasks/manager_based/locomotion/velocity/velocity_env_cfg.py

Lines changed: 50 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@
2727
from isaaclab.utils.noise import UniformNoiseCfg as Unoise
2828

2929
import isaaclab_tasks.manager_based.locomotion.velocity.mdp as mdp
30-
from isaaclab_tasks.utils import PresetCfg
30+
from isaaclab_tasks.utils import PresetCfg, preset
3131

3232
##
3333
# Pre-defined configs
@@ -161,6 +161,50 @@ def __post_init__(self):
161161
class EventsCfg:
162162
"""Configuration for events."""
163163

164+
# startup
165+
physics_material = EventTerm(
166+
func=mdp.randomize_rigid_body_material,
167+
mode="startup",
168+
params={
169+
"asset_cfg": SceneEntityCfg("robot", body_names=".*"),
170+
"static_friction_range": (0.8, 0.8),
171+
"dynamic_friction_range": (0.6, 0.6),
172+
"restitution_range": (0.0, 0.0),
173+
"num_buckets": 64,
174+
},
175+
)
176+
177+
add_base_mass = EventTerm(
178+
func=mdp.randomize_rigid_body_mass,
179+
mode="startup",
180+
params={
181+
"asset_cfg": SceneEntityCfg("robot", body_names="base"),
182+
"mass_distribution_params": (-5.0, 5.0),
183+
"operation": "add",
184+
},
185+
)
186+
187+
base_com = preset(
188+
default=EventTerm(
189+
func=mdp.randomize_rigid_body_com,
190+
mode="startup",
191+
params={
192+
"asset_cfg": SceneEntityCfg("robot", body_names="base"),
193+
"com_range": {"x": (-0.05, 0.05), "y": (-0.05, 0.05), "z": (-0.01, 0.01)},
194+
},
195+
),
196+
newton=None,
197+
)
198+
199+
collider_offsets = EventTerm(
200+
func=mdp.randomize_rigid_body_collider_offsets,
201+
mode="startup",
202+
params={
203+
"asset_cfg": SceneEntityCfg("robot", body_names=".*"),
204+
"contact_offset_distribution_params": (0.01, 0.01),
205+
},
206+
)
207+
164208
# reset
165209
base_external_force_torque = EventTerm(
166210
func=mdp.apply_external_force_torque,
@@ -206,41 +250,6 @@ class EventsCfg:
206250
)
207251

208252

209-
@configclass
210-
class StartupEventsCfg:
211-
# startup
212-
physics_material = EventTerm(
213-
func=mdp.randomize_rigid_body_material,
214-
mode="startup",
215-
params={
216-
"asset_cfg": SceneEntityCfg("robot", body_names=".*"),
217-
"static_friction_range": (0.8, 0.8),
218-
"dynamic_friction_range": (0.6, 0.6),
219-
"restitution_range": (0.0, 0.0),
220-
"num_buckets": 64,
221-
},
222-
)
223-
224-
add_base_mass = EventTerm(
225-
func=mdp.randomize_rigid_body_mass,
226-
mode="startup",
227-
params={
228-
"asset_cfg": SceneEntityCfg("robot", body_names="base"),
229-
"mass_distribution_params": (-5.0, 5.0),
230-
"operation": "add",
231-
},
232-
)
233-
234-
base_com = EventTerm(
235-
func=mdp.randomize_rigid_body_com,
236-
mode="startup",
237-
params={
238-
"asset_cfg": SceneEntityCfg("robot", body_names="base"),
239-
"com_range": {"x": (-0.05, 0.05), "y": (-0.05, 0.05), "z": (-0.01, 0.01)},
240-
},
241-
)
242-
243-
244253
@configclass
245254
class RewardsCfg:
246255
"""Reward terms for the MDP."""
@@ -286,6 +295,10 @@ class TerminationsCfg:
286295
func=mdp.illegal_contact,
287296
params={"sensor_cfg": SceneEntityCfg("contact_forces", body_names="base"), "threshold": 1.0},
288297
)
298+
body_lin_vel = DoneTerm(
299+
func=mdp.body_lin_vel_out_of_limit,
300+
params={"max_velocity": 20.0, "asset_cfg": SceneEntityCfg("robot", body_names=".*")},
301+
)
289302

290303

291304
@configclass
@@ -313,7 +326,7 @@ class LocomotionVelocityRoughEnvCfg(ManagerBasedRLEnvCfg):
313326
# MDP settings
314327
rewards: RewardsCfg = RewardsCfg()
315328
terminations: TerminationsCfg = TerminationsCfg()
316-
events: EventsCfg = MISSING
329+
events: EventsCfg = EventsCfg()
317330
curriculum: CurriculumCfg = CurriculumCfg()
318331

319332
def __post_init__(self):

0 commit comments

Comments
 (0)