Skip to content

Commit fa19b40

Browse files
committed
Updated navigation tasks to support Newton + minor bug fixes
Minor fix Implemented obstacle generation and newton camera more efficiently Minor fix Minor fix
1 parent d324a58 commit fa19b40

7 files changed

Lines changed: 320 additions & 162 deletions

File tree

‎source/isaaclab_contrib/isaaclab_contrib/controllers/lee_controller_base.py‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -55,10 +55,10 @@ def __init__(self, cfg: LeeControllerBaseCfg, asset: Multirotor, num_envs: int,
5555
root_quat_exp = root_quat_w.unsqueeze(1).expand(num_envs, self.robot.num_bodies, 4)
5656
body_link_pos_delta = body_link_pos_w - root_pos_w.unsqueeze(1)
5757

58-
body_masses = self._to_torch(self.robot.root_view.get_masses())
58+
body_masses = self._to_torch(self.robot.data.body_mass)
5959
body_inv_mass_local = torch.where(body_masses > 0, 1.0 / body_masses, torch.zeros_like(body_masses))
6060
self.mass, self.robot_inertia, _ = aggregate_inertia_about_robot_com(
61-
self._to_torch(self.robot.root_view.get_inertias()),
61+
self._to_torch(self.robot.data.body_inertia),
6262
body_inv_mass_local,
6363
body_com_pos_b,
6464
body_com_quat_b,

‎source/isaaclab_contrib/test/controllers/test_drone_geometric_controllers.py‎

Lines changed: 3 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -36,27 +36,13 @@
3636
from isaaclab_contrib.controllers.lee_velocity_control_cfg import LeeVelControllerCfg
3737

3838

39-
class _DummyRootView:
40-
"""Stub articulation view with ``get_masses`` and ``get_inertias`` for controller tests."""
41-
42-
def __init__(self, num_envs: int, num_bodies: int, device: torch.device):
43-
inertia_flat = torch.eye(3, device=device).reshape(9)
44-
self._inertias = inertia_flat.unsqueeze(0).unsqueeze(0).expand(num_envs, num_bodies, 9).clone()
45-
self._masses = torch.ones((num_envs, num_bodies), device=device)
46-
47-
def get_inertias(self) -> torch.Tensor:
48-
return self._inertias
49-
50-
def get_masses(self) -> torch.Tensor:
51-
return self._masses
52-
53-
5439
class _DummyRobot:
5540
"""Minimal multirotor stub exposing the attributes used by the controllers."""
5641

5742
def __init__(self, num_envs: int, num_bodies: int, device: torch.device):
5843
self.num_bodies = num_bodies
5944
quat_id = torch.tensor([0.0, 0.0, 0.0, 1.0], device=device)
45+
inertia_flat = torch.eye(3, device=device).reshape(9)
6046
self.data = types.SimpleNamespace(
6147
root_link_quat_w=quat_id.repeat(num_envs, 1),
6248
root_quat_w=quat_id.repeat(num_envs, 1),
@@ -67,8 +53,9 @@ def __init__(self, num_envs: int, num_bodies: int, device: torch.device):
6753
body_link_quat_w=quat_id.repeat(num_envs, num_bodies, 1),
6854
body_com_pos_b=torch.zeros((num_envs, num_bodies, 3), device=device),
6955
body_com_quat_b=quat_id.repeat(num_envs, num_bodies, 1),
56+
body_mass=torch.ones((num_envs, num_bodies), device=device),
57+
body_inertia=inertia_flat.unsqueeze(0).unsqueeze(0).expand(num_envs, num_bodies, 9).clone(),
7058
)
71-
self.root_view = _DummyRootView(num_envs, num_bodies, device)
7259

7360

7461
class _DummySimCfg:

‎source/isaaclab_tasks/isaaclab_tasks/manager_based/drone_arl/mdp/events.py‎

Lines changed: 76 additions & 81 deletions
Original file line numberDiff line numberDiff line change
@@ -40,8 +40,8 @@ def reset_obstacles_with_individual_ranges(
4040
4141
Walls are positioned at fixed locations based on their configuration ratios. Obstacles
4242
are randomly placed within their designated zones, with the number of active obstacles
43-
determined by the curriculum difficulty level. Inactive obstacles are moved far below
44-
the scene (-1000m in Z) to effectively remove them from the environment.
43+
determined by the curriculum difficulty level. Inactive obstacles are parked at distinct
44+
locations far below the scene to avoid overlapping collision geometry.
4545
4646
The curriculum scaling works as:
4747
num_obstacles = min + (difficulty / max_difficulty) * (max - min)
@@ -70,9 +70,9 @@ def reset_obstacles_with_individual_ranges(
7070
"""
7171
obstacles: RigidObjectCollection = env.scene[asset_cfg.name]
7272

73-
num_objects = obstacles.num_objects
73+
num_objects = obstacles.num_bodies
7474
num_envs = len(env_ids)
75-
object_names = obstacles.object_names
75+
object_names = obstacles.body_names
7676

7777
# Get difficulty levels per environment
7878
if use_curriculum:
@@ -101,89 +101,84 @@ def reset_obstacles_with_individual_ranges(
101101
wall_names = list(wall_configs.keys())
102102
obstacle_types = list(obstacle_configs.values())
103103
env_size_t = torch.tensor(env_size, device=env.device)
104-
105-
# place walls
106-
for wall_name, wall_cfg in wall_configs.items():
107-
if wall_name in object_names:
108-
wall_idx = object_names.index(wall_name)
109-
110-
min_ratio = torch.tensor(wall_cfg.center_ratio_min, device=env.device)
111-
max_ratio = torch.tensor(wall_cfg.center_ratio_max, device=env.device)
112-
113-
if torch.allclose(min_ratio, max_ratio):
114-
center_ratios = min_ratio.unsqueeze(0).repeat(num_envs, 1)
115-
else:
116-
ratios = torch.rand(num_envs, 3, device=env.device)
117-
center_ratios = ratios * (max_ratio - min_ratio) + min_ratio
118-
119-
positions = (center_ratios - 0.5) * env_size_t
120-
positions[:, 2] += ground_offset
121-
positions += env.scene.env_origins[env_ids]
122-
123-
all_poses[:, wall_idx, 0:3] = positions
124-
all_poses[:, wall_idx, 3:7] = torch.tensor([1.0, 0.0, 0.0, 0.0], device=env.device).repeat(num_envs, 1)
104+
identity_quat = torch.tensor([0.0, 0.0, 0.0, 1.0], device=env.device)
105+
all_poses[..., 3:7] = identity_quat
106+
107+
# Place walls
108+
wall_entries = [(object_names.index(name), cfg) for name, cfg in wall_configs.items() if name in object_names]
109+
if wall_entries:
110+
wall_indices = [entry[0] for entry in wall_entries]
111+
wall_min_ratios = torch.tensor(
112+
[entry[1].center_ratio_min for entry in wall_entries], dtype=torch.float32, device=env.device
113+
)
114+
wall_max_ratios = torch.tensor(
115+
[entry[1].center_ratio_max for entry in wall_entries], dtype=torch.float32, device=env.device
116+
)
117+
wall_center_ratios = wall_min_ratios.unsqueeze(0).expand(num_envs, -1, -1).clone()
118+
variable_wall_indices = [
119+
i
120+
for i, (_, wall_cfg) in enumerate(wall_entries)
121+
if wall_cfg.center_ratio_min != wall_cfg.center_ratio_max
122+
]
123+
if variable_wall_indices:
124+
wall_ratios = torch.rand(num_envs, len(variable_wall_indices), 3, device=env.device)
125+
wall_center_ratios[:, variable_wall_indices] = (
126+
wall_ratios
127+
* (wall_max_ratios[variable_wall_indices] - wall_min_ratios[variable_wall_indices])
128+
+ wall_min_ratios[variable_wall_indices]
129+
)
130+
wall_positions = (wall_center_ratios - 0.5) * env_size_t
131+
wall_positions[..., 2] += ground_offset
132+
wall_positions += env.scene.env_origins[env_ids].unsqueeze(1)
133+
all_poses[:, wall_indices, 0:3] = wall_positions
125134

126135
# Get obstacle indices
127136
obstacle_indices = [idx for idx, name in enumerate(object_names) if name not in wall_names]
128137

129138
if len(obstacle_indices) == 0:
130-
obstacles.write_object_pose_to_sim(all_poses, env_ids=env_ids)
131-
obstacles.write_object_velocity_to_sim(all_velocities, env_ids=env_ids)
139+
obstacles.write_body_pose_to_sim_index(body_poses=all_poses, env_ids=env_ids)
140+
obstacles.write_body_com_velocity_to_sim_index(body_velocities=all_velocities, env_ids=env_ids)
132141
return
133142

134-
# Determine which obstacles are active per env
135-
active_masks = torch.zeros(num_envs, len(obstacle_indices), dtype=torch.bool, device=env.device)
136-
for env_idx in range(num_envs):
137-
num_active = obstacles_per_env[env_idx].item()
138-
perm = torch.randperm(len(obstacle_indices), device=env.device)[:num_active]
139-
active_masks[env_idx, perm] = True
140-
141-
# place obstacles
142-
for obj_list_idx in range(len(obstacle_indices)):
143-
obj_idx = obstacle_indices[obj_list_idx]
144-
145-
# Which envs need this obstacle?
146-
envs_need_obstacle = active_masks[:, obj_list_idx]
147-
148-
if not envs_need_obstacle.any():
149-
# Move all to -1000
150-
all_poses[:, obj_idx, 0:3] = env.scene.env_origins[env_ids] + torch.tensor(
151-
[0.0, 0.0, -1000.0], device=env.device
152-
)
153-
all_poses[:, obj_idx, 3:7] = torch.tensor([1.0, 0.0, 0.0, 0.0], device=env.device)
154-
continue
155-
156-
# Get obstacle config
157-
config_idx = obj_list_idx % len(obstacle_types)
158-
obs_cfg = obstacle_types[config_idx]
159-
160-
min_ratio = torch.tensor(obs_cfg.center_ratio_min, device=env.device)
161-
max_ratio = torch.tensor(obs_cfg.center_ratio_max, device=env.device)
162-
163-
# sample object positions
164-
num_active_envs = envs_need_obstacle.sum().item()
165-
ratios = torch.rand(num_active_envs, 3, device=env.device)
166-
positions = (ratios * (max_ratio - min_ratio) + min_ratio - 0.5) * env_size_t
167-
positions[:, 2] += ground_offset
168-
169-
# Add env origins
170-
active_env_indices = torch.where(envs_need_obstacle)[0]
171-
positions += env.scene.env_origins[env_ids[active_env_indices]]
172-
173-
# Generate quaternions
174-
quats = math_utils.random_orientation(num_envs, device=env.device)
175-
176-
# Write poses
177-
all_poses[envs_need_obstacle, obj_idx, 0:3] = positions
178-
all_poses[envs_need_obstacle, obj_idx, 3:7] = quats[envs_need_obstacle]
179-
180-
# Move inactive obstacles far away
181-
inactive = ~envs_need_obstacle
182-
all_poses[inactive, obj_idx, 0:3] = env.scene.env_origins[env_ids[inactive]] + torch.tensor(
183-
[0.0, 0.0, -1000.0], device=env.device
184-
)
185-
all_poses[inactive, obj_idx, 3:7] = torch.tensor([1.0, 0.0, 0.0, 0.0], device=env.device)
143+
num_obstacles = len(obstacle_indices)
144+
145+
# Select the requested number of unique obstacles per environment
146+
random_order = torch.argsort(torch.rand(num_envs, num_obstacles, device=env.device), dim=1)
147+
active_by_rank = torch.arange(num_obstacles, device=env.device).unsqueeze(0) < obstacles_per_env.unsqueeze(1)
148+
active_masks = torch.zeros(num_envs, num_obstacles, dtype=torch.bool, device=env.device)
149+
active_masks.scatter_(1, random_order, active_by_rank)
150+
151+
# Sample every obstacle in one operation.
152+
obstacle_min_ratios = torch.tensor(
153+
[obstacle_types[i % len(obstacle_types)].center_ratio_min for i in range(num_obstacles)],
154+
dtype=torch.float32,
155+
device=env.device,
156+
)
157+
obstacle_max_ratios = torch.tensor(
158+
[obstacle_types[i % len(obstacle_types)].center_ratio_max for i in range(num_obstacles)],
159+
dtype=torch.float32,
160+
device=env.device,
161+
)
162+
obstacle_ratios = torch.rand(num_envs, num_obstacles, 3, device=env.device)
163+
obstacle_positions = (
164+
obstacle_ratios * (obstacle_max_ratios - obstacle_min_ratios) + obstacle_min_ratios - 0.5
165+
) * env_size_t
166+
obstacle_positions[..., 2] += ground_offset
167+
obstacle_positions += env.scene.env_origins[env_ids].unsqueeze(1)
168+
169+
# Inactive samples are discarded
170+
inactive_positions = env.scene.env_origins[env_ids].unsqueeze(1).expand(-1, num_obstacles, -1).clone()
171+
inactive_positions[..., 2] += -1000.0 - 5.0 * torch.arange(num_obstacles, device=env.device)
172+
obstacle_positions = torch.where(active_masks.unsqueeze(-1), obstacle_positions, inactive_positions)
173+
174+
obstacle_quats = math_utils.random_orientation(num_envs * num_obstacles, device=env.device).view(
175+
num_envs, num_obstacles, 4
176+
)
177+
obstacle_quats = torch.where(active_masks.unsqueeze(-1), obstacle_quats, identity_quat)
178+
179+
all_poses[:, obstacle_indices, 0:3] = obstacle_positions
180+
all_poses[:, obstacle_indices, 3:7] = obstacle_quats
186181

187182
# Write to sim
188-
obstacles.write_object_pose_to_sim(all_poses, env_ids=env_ids)
189-
obstacles.write_object_velocity_to_sim(all_velocities, env_ids=env_ids)
183+
obstacles.write_body_pose_to_sim_index(body_poses=all_poses, env_ids=env_ids)
184+
obstacles.write_body_com_velocity_to_sim_index(body_velocities=all_velocities, env_ids=env_ids)

‎source/isaaclab_tasks/isaaclab_tasks/manager_based/drone_arl/navigation/config/arl_robot_1/navigation_env_cfg.py‎

Lines changed: 87 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,12 @@
77
import math
88
from dataclasses import MISSING
99

10+
from isaaclab_newton.physics import (
11+
MJWarpSolverCfg,
12+
NewtonCfg,
13+
NewtonCollisionPipelineCfg,
14+
NewtonShapeCfg,
15+
)
1016
from isaaclab_physx.physics import PhysxCfg
1117

1218
import isaaclab.sim as sim_utils
@@ -44,6 +50,7 @@
4450
distance_to_goal_exp_curriculum,
4551
velocity_to_goal_reward_curriculum,
4652
)
53+
from isaaclab_tasks.utils import PresetCfg
4754

4855
logging.getLogger("isaaclab.sensors.ray_caster.multi_mesh_ray_caster").setLevel(logging.WARNING)
4956

@@ -56,6 +63,81 @@
5663
)
5764

5865

66+
##
67+
# Physics presets
68+
##
69+
70+
71+
@configclass
72+
class NavigationPhysicsCfg(PresetCfg):
73+
"""Physics backends supported by the ARL navigation task."""
74+
75+
default = PhysxCfg(gpu_max_rigid_patch_count=2**21)
76+
newton_mjwarp = NewtonCfg(
77+
solver_cfg=MJWarpSolverCfg(
78+
njmax=768,
79+
nconmax=128,
80+
cone="elliptic",
81+
impratio=100,
82+
integrator="implicitfast",
83+
use_mujoco_contacts=False,
84+
),
85+
collision_cfg=NewtonCollisionPipelineCfg(max_triangle_pairs=2_500_000),
86+
default_shape_cfg=NewtonShapeCfg(margin=0.01),
87+
)
88+
physx = default
89+
90+
91+
_DEPTH_CAMERA_CFG = MultiMeshRayCasterCameraCfg(
92+
prim_path="{ENV_REGEX_NS}/Robot/base_link",
93+
mesh_prim_paths=[
94+
MultiMeshRayCasterCameraCfg.RaycastTargetCfg(
95+
prim_expr=f"{{ENV_REGEX_NS}}/Obstacles/obstacle_{wall_name}", is_shared=False, track_mesh_transforms=True
96+
)
97+
for wall_name, _ in OBSTACLE_SCENE_CFG.wall_cfgs.items()
98+
]
99+
+ [
100+
MultiMeshRayCasterCameraCfg.RaycastTargetCfg(
101+
prim_expr=f"{{ENV_REGEX_NS}}/Obstacles/obstacle_{i}", is_shared=False, track_mesh_transforms=True
102+
)
103+
for i in range(OBSTACLE_SCENE_CFG.max_num_obstacles)
104+
],
105+
offset=MultiMeshRayCasterCameraCfg.OffsetCfg(
106+
pos=(0.15, 0.0, 0.04), rot=(1.0, 0.0, 0.0, 0.0), convention="world"
107+
),
108+
update_period=0.1,
109+
pattern_cfg=PinholeCameraPatternCfg(
110+
width=480, height=270, focal_length=0.193, horizontal_aperture=0.36, vertical_aperture=0.21
111+
),
112+
data_types=["distance_to_image_plane"],
113+
max_distance=10.0,
114+
depth_clipping_behavior="max",
115+
)
116+
117+
118+
@configclass
119+
class NavigationDepthCameraCfg(PresetCfg):
120+
"""Depth-camera implementations supported by the ARL navigation task."""
121+
122+
default = _DEPTH_CAMERA_CFG
123+
newton_mjwarp = _DEPTH_CAMERA_CFG.replace(
124+
class_type=(
125+
"isaaclab_tasks.manager_based.drone_arl.navigation.config.arl_robot_1.newton_camera:"
126+
"ArlNewtonMultiMeshRayCasterCamera"
127+
)
128+
)
129+
physx = default
130+
131+
132+
@configclass
133+
class NavigationObstacleCollectionCfg(PresetCfg):
134+
"""Backend representations of the shared navigation obstacle scene."""
135+
136+
default = generate_obstacle_collection(OBSTACLE_SCENE_CFG)
137+
newton_mjwarp = generate_obstacle_collection(OBSTACLE_SCENE_CFG, kinematic=True)
138+
physx = default
139+
140+
59141
##
60142
# Scene definition
61143
##
@@ -64,37 +146,13 @@ class ArlNavigationSceneCfg(InteractiveSceneCfg):
64146
"""Scene configuration for drone navigation with obstacles."""
65147

66148
# obstacles
67-
object_collection = generate_obstacle_collection(OBSTACLE_SCENE_CFG)
149+
object_collection = NavigationObstacleCollectionCfg()
68150

69151
# robots
70152
robot: MultirotorCfg = MISSING
71153

72154
# sensors
73-
depth_camera = MultiMeshRayCasterCameraCfg(
74-
prim_path="{ENV_REGEX_NS}/Robot/base_link",
75-
mesh_prim_paths=[
76-
MultiMeshRayCasterCameraCfg.RaycastTargetCfg(
77-
prim_expr=f"{{ENV_REGEX_NS}}/obstacle_{wall_name}", is_shared=False, track_mesh_transforms=True
78-
)
79-
for wall_name, _ in OBSTACLE_SCENE_CFG.wall_cfgs.items()
80-
]
81-
+ [
82-
MultiMeshRayCasterCameraCfg.RaycastTargetCfg(
83-
prim_expr=f"{{ENV_REGEX_NS}}/obstacle_{i}", is_shared=False, track_mesh_transforms=True
84-
)
85-
for i in range(OBSTACLE_SCENE_CFG.max_num_obstacles)
86-
],
87-
offset=MultiMeshRayCasterCameraCfg.OffsetCfg(
88-
pos=(0.15, 0.0, 0.04), rot=(1.0, 0.0, 0.0, 0.0), convention="world"
89-
),
90-
update_period=0.1,
91-
pattern_cfg=PinholeCameraPatternCfg(
92-
width=480, height=270, focal_length=0.193, horizontal_aperture=0.36, vertical_aperture=0.21
93-
),
94-
data_types=["distance_to_image_plane"],
95-
max_distance=10.0,
96-
depth_clipping_behavior="max",
97-
)
155+
depth_camera = NavigationDepthCameraCfg()
98156

99157
contact_forces = ContactSensorCfg(
100158
prim_path="{ENV_REGEX_NS}/Robot/.*",
@@ -328,6 +386,7 @@ def __post_init__(self):
328386
# general settings
329387
self.decimation = 10
330388
self.episode_length_s = 10.0
389+
331390
# simulation settings
332391
self.sim.dt = 0.01
333392
self.sim.render_interval = self.decimation
@@ -337,7 +396,8 @@ def __post_init__(self):
337396
static_friction=1.0,
338397
dynamic_friction=1.0,
339398
)
340-
self.sim.physics = PhysxCfg(gpu_max_rigid_patch_count=2**21)
399+
self.sim.physics = NavigationPhysicsCfg()
400+
341401
# update sensor update periods
342402
# we tick all the sensors based on the smallest update period (physics update period)
343403
if self.scene.contact_forces is not None:

0 commit comments

Comments
 (0)