Skip to content

Commit ac9d8b0

Browse files
extend anymal-c navigation envs to 1) have obstacle and camera sensor. 2) have new pose command and reset event.
1 parent f7c3e03 commit ac9d8b0

File tree

7 files changed

+627
-68
lines changed

7 files changed

+627
-68
lines changed

source/isaaclab/isaaclab/envs/mdp/commands/__init__.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,12 +8,13 @@
88
from .commands_cfg import (
99
NormalVelocityCommandCfg,
1010
NullCommandCfg,
11+
ObstaclePose2dCommandCfg,
1112
TerrainBasedPose2dCommandCfg,
1213
UniformPose2dCommandCfg,
1314
UniformPoseCommandCfg,
1415
UniformVelocityCommandCfg,
1516
)
1617
from .null_command import NullCommand
17-
from .pose_2d_command import TerrainBasedPose2dCommand, UniformPose2dCommand
18+
from .pose_2d_command import ObstaclePose2dCommand, TerrainBasedPose2dCommand, UniformPose2dCommand
1819
from .pose_command import UniformPoseCommand
1920
from .velocity_command import NormalVelocityCommand, UniformVelocityCommand

source/isaaclab/isaaclab/envs/mdp/commands/commands_cfg.py

Lines changed: 32 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,11 +8,15 @@
88

99
from isaaclab.managers import CommandTermCfg
1010
from isaaclab.markers import VisualizationMarkersCfg
11-
from isaaclab.markers.config import BLUE_ARROW_X_MARKER_CFG, FRAME_MARKER_CFG, GREEN_ARROW_X_MARKER_CFG
11+
from isaaclab.markers.config import (
12+
BLUE_ARROW_X_MARKER_CFG,
13+
FRAME_MARKER_CFG,
14+
GREEN_ARROW_X_MARKER_CFG,
15+
)
1216
from isaaclab.utils import configclass
1317

1418
from .null_command import NullCommand
15-
from .pose_2d_command import TerrainBasedPose2dCommand, UniformPose2dCommand
19+
from .pose_2d_command import ObstaclePose2dCommand, TerrainBasedPose2dCommand, UniformPose2dCommand
1620
from .pose_command import UniformPoseCommand
1721
from .velocity_command import NormalVelocityCommand, UniformVelocityCommand
1822

@@ -172,7 +176,9 @@ class Ranges:
172176
ranges: Ranges = MISSING
173177
"""Ranges for the commands."""
174178

175-
goal_pose_visualizer_cfg: VisualizationMarkersCfg = FRAME_MARKER_CFG.replace(prim_path="/Visuals/Command/goal_pose")
179+
goal_pose_visualizer_cfg: VisualizationMarkersCfg = FRAME_MARKER_CFG.replace(
180+
prim_path="/Visuals/Command/goal_pose"
181+
)
176182
"""The configuration for the goal pose visualization marker. Defaults to FRAME_MARKER_CFG."""
177183

178184
current_pose_visualizer_cfg: VisualizationMarkersCfg = FRAME_MARKER_CFG.replace(
@@ -228,6 +234,29 @@ class Ranges:
228234
goal_pose_visualizer_cfg.markers["arrow"].scale = (0.2, 0.2, 0.8)
229235

230236

237+
@configclass
238+
class ObstaclePose2dCommandCfg(UniformPose2dCommandCfg):
239+
"""Configuration for the obstacle-based 2D-pose command generator."""
240+
241+
class_type: type = ObstaclePose2dCommand
242+
243+
object_name: str = MISSING
244+
"""Name of the obstacle object in the environment."""
245+
246+
@configclass
247+
class Ranges:
248+
"""Uniform distribution ranges for the position commands."""
249+
250+
heading: tuple[float, float] = MISSING
251+
"""Heading range for the position commands (in rad).
252+
253+
Used only if :attr:`simple_heading` is False.
254+
"""
255+
256+
ranges: Ranges = MISSING
257+
"""Distribution ranges for the sampled commands."""
258+
259+
231260
@configclass
232261
class TerrainBasedPose2dCommandCfg(UniformPose2dCommandCfg):
233262
"""Configuration for the terrain-based position command generator."""

source/isaaclab/isaaclab/envs/mdp/commands/pose_2d_command.py

Lines changed: 80 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -81,8 +81,12 @@ def command(self) -> torch.Tensor:
8181

8282
def _update_metrics(self):
8383
# logs data
84-
self.metrics["error_pos_2d"] = torch.norm(self.pos_command_w[:, :2] - self.robot.data.root_pos_w[:, :2], dim=1)
85-
self.metrics["error_heading"] = torch.abs(wrap_to_pi(self.heading_command_w - self.robot.data.heading_w))
84+
self.metrics["error_pos_2d"] = torch.norm(
85+
self.pos_command_w[:, :2] - self.robot.data.root_pos_w[:, :2], dim=1
86+
)
87+
self.metrics["error_heading"] = torch.abs(
88+
wrap_to_pi(self.heading_command_w - self.robot.data.heading_w)
89+
)
8690

8791
def _resample_command(self, env_ids: Sequence[int]):
8892
# obtain env origins for the environments
@@ -102,7 +106,9 @@ def _resample_command(self, env_ids: Sequence[int]):
102106
# compute errors to find the closest direction to the current heading
103107
# this is done to avoid the discontinuity at the -pi/pi boundary
104108
curr_to_target = wrap_to_pi(target_direction - self.robot.data.heading_w[env_ids]).abs()
105-
curr_to_flipped_target = wrap_to_pi(flipped_target_direction - self.robot.data.heading_w[env_ids]).abs()
109+
curr_to_flipped_target = wrap_to_pi(
110+
flipped_target_direction - self.robot.data.heading_w[env_ids]
111+
).abs()
106112

107113
# set the heading command to the closest direction
108114
self.heading_command_w[env_ids] = torch.where(
@@ -117,7 +123,9 @@ def _resample_command(self, env_ids: Sequence[int]):
117123
def _update_command(self):
118124
"""Re-target the position command to the current root state."""
119125
target_vec = self.pos_command_w - self.robot.data.root_pos_w[:, :3]
120-
self.pos_command_b[:] = quat_rotate_inverse(yaw_quat(self.robot.data.root_quat_w), target_vec)
126+
self.pos_command_b[:] = quat_rotate_inverse(
127+
yaw_quat(self.robot.data.root_quat_w), target_vec
128+
)
121129
self.heading_command_b[:] = wrap_to_pi(self.heading_command_w - self.robot.data.heading_w)
122130

123131
def _set_debug_vis_impl(self, debug_vis: bool):
@@ -143,6 +151,68 @@ def _debug_vis_callback(self, event):
143151
)
144152

145153

154+
class ObstaclePose2dCommand(UniformPose2dCommand):
155+
"""Command generator that generates pose commands based on the obstacle.
156+
157+
This command generator determines the command position based on the position of the obstacle.
158+
The heading commands are either set to point towards the target or are sampled uniformly.
159+
This can be configured through the :attr:`Pose2dCommandCfg.simple_heading` parameter in
160+
the configuration.
161+
"""
162+
163+
cfg: ObstaclePose2dCommandCfg
164+
"""Configuration for the command generator."""
165+
166+
def __init__(self, cfg: ObstaclePose2dCommandCfg, env: ManagerBasedEnv):
167+
"""Initialize the command generator class.
168+
169+
Args:
170+
cfg: The configuration parameters for the command generator.
171+
env: The environment object.
172+
"""
173+
# initialize the base class
174+
super().__init__(cfg, env)
175+
176+
# obtain the obstacle object
177+
self.object: RigidObject = env.scene[cfg.object_name]
178+
179+
def _resample_command(self, env_ids: Sequence[int]):
180+
# obtain env origins for the environments
181+
self.pos_command_w[env_ids] = self._env.scene.env_origins[env_ids]
182+
r = torch.empty(len(env_ids), device=self.device)
183+
# offset the position command by the current root position
184+
self.pos_command_w[env_ids, 0] = (
185+
2 * self.object.data.root_pos_w[env_ids, 0] - self.robot.data.root_pos_w[env_ids, 0]
186+
)
187+
self.pos_command_w[env_ids, 1] = (
188+
2 * self.object.data.root_pos_w[env_ids, 1] - self.robot.data.root_pos_w[env_ids, 1]
189+
)
190+
self.pos_command_w[env_ids, 2] = self.robot.data.default_root_state[env_ids, 2]
191+
192+
if self.cfg.simple_heading:
193+
# set heading command to point towards target
194+
target_vec = self.pos_command_w[env_ids] - self.robot.data.root_pos_w[env_ids]
195+
target_direction = torch.atan2(target_vec[:, 1], target_vec[:, 0])
196+
flipped_target_direction = wrap_to_pi(target_direction + torch.pi)
197+
198+
# compute errors to find the closest direction to the current heading
199+
# this is done to avoid the discontinuity at the -pi/pi boundary
200+
curr_to_target = wrap_to_pi(target_direction - self.robot.data.heading_w[env_ids]).abs()
201+
curr_to_flipped_target = wrap_to_pi(
202+
flipped_target_direction - self.robot.data.heading_w[env_ids]
203+
).abs()
204+
205+
# set the heading command to the closest direction
206+
self.heading_command_w[env_ids] = torch.where(
207+
curr_to_target < curr_to_flipped_target,
208+
target_direction,
209+
flipped_target_direction,
210+
)
211+
else:
212+
# random heading command
213+
self.heading_command_w[env_ids] = r.uniform_(*self.cfg.ranges.heading)
214+
215+
146216
class TerrainBasedPose2dCommand(UniformPose2dCommand):
147217
"""Command generator that generates pose commands based on the terrain.
148218
@@ -173,7 +243,9 @@ def __init__(self, cfg: TerrainBasedPose2dCommandCfg, env: ManagerBasedEnv):
173243

174244
def _resample_command(self, env_ids: Sequence[int]):
175245
# sample new position targets from the terrain
176-
ids = torch.randint(0, self.valid_targets.shape[2], size=(len(env_ids),), device=self.device)
246+
ids = torch.randint(
247+
0, self.valid_targets.shape[2], size=(len(env_ids),), device=self.device
248+
)
177249
self.pos_command_w[env_ids] = self.valid_targets[
178250
self.terrain.terrain_levels[env_ids], self.terrain.terrain_types[env_ids], ids
179251
]
@@ -189,7 +261,9 @@ def _resample_command(self, env_ids: Sequence[int]):
189261
# compute errors to find the closest direction to the current heading
190262
# this is done to avoid the discontinuity at the -pi/pi boundary
191263
curr_to_target = wrap_to_pi(target_direction - self.robot.data.heading_w[env_ids]).abs()
192-
curr_to_flipped_target = wrap_to_pi(flipped_target_direction - self.robot.data.heading_w[env_ids]).abs()
264+
curr_to_flipped_target = wrap_to_pi(
265+
flipped_target_direction - self.robot.data.heading_w[env_ids]
266+
).abs()
193267

194268
# set the heading command to the closest direction
195269
self.heading_command_w[env_ids] = torch.where(

0 commit comments

Comments
 (0)