Ray Caster#
A ray caster reports the first collision along each ray in a chosen pattern. Rays do not bounce or respond to materials and opacity. Quadruped tasks use this sensor to measure terrain height around the robot.
The ray-casting implementation depends on the physics backend. With PhysX and
OV PhysX, configured meshes are loaded into Warp
when the sensor initializes and must remain static. With Newton, the sensor
queries the live scene BVH and therefore sees every collision shape in its
environment, global terrain, and dynamic bodies. Newton ignores
mesh_prim_paths; use
isaaclab_newton.sensors.NewtonRaycastSensorCfg to make that behavior
explicit and to access Newton-specific options.
Choose a ray pattern and a parent frame. The frame and sensor offset set the pose; the pattern sets the ray origins and directions. Built-in patterns include lidar and grids.
@configclass
class RaycasterSensorSceneCfg(InteractiveSceneCfg):
"""Design the scene with sensors on the robot."""
# ground plane
ground = AssetBaseCfg(
prim_path="/World/Ground",
spawn=sim_utils.UsdFileCfg(
usd_path=f"{ISAAC_NUCLEUS_DIR}/Environments/Terrains/rough_plane.usd",
scale=(1, 1, 1),
),
)
# lights
dome_light = AssetBaseCfg(
prim_path="/World/Light", spawn=sim_utils.DomeLightCfg(intensity=3000.0, color=(0.75, 0.75, 0.75))
)
# robot
robot = replace(ANYMAL_C_CFG, prim_path="{ENV_REGEX_NS}/Robot")
ray_caster = RayCasterCfg(
prim_path="{ENV_REGEX_NS}/Robot/base",
update_period=1 / 60,
offset=RayCasterCfg.OffsetCfg(pos=(0, 0, 0.5)),
mesh_prim_paths=["/World/Ground"],
ray_alignment="yaw",
pattern_cfg=patterns.LidarPatternCfg(
channels=100, vertical_fov_range=[-90, 90], horizontal_fov_range=[-90, 90], horizontal_res=1.0
),
debug_vis=bool(args_cli.visualizer),
)
Pattern angles use degrees. Enable debug visualization to inspect the pattern, then disable it when measuring performance.
Read hit positions from data.ray_hits_w during simulation:
def run_simulator(sim: sim_utils.SimulationContext, scene: InteractiveScene):
.
.
.
# Simulate physics
while sim.is_running():
.
.
.
# print information from the sensors
print("-------------------------------")
print(scene["ray_caster"])
print("Ray cast hit results: ", scene["ray_caster"].data.ray_hits_w)
-------------------------------
Ray-caster @ '/World/envs/env_.*/Robot/base/lidar_cage':
view type : <class 'isaacsim.core.experimental.prims.xform_prim.XformPrim'>
update period (s) : 0.016666666666666666
number of meshes : 1
number of sensors : 1
number of rays/sensor: 18000
total number of rays : 18000
Ray cast hit results: tensor([[[-0.3698, 0.0357, 0.0000],
[-0.3698, 0.0357, 0.0000],
[-0.3698, 0.0357, 0.0000],
...,
[ inf, inf, inf],
[ inf, inf, inf],
[ inf, inf, inf]]], device='cuda:0')
-------------------------------
Hit positions have shape (N, B, 3), where N is the number of sensors and
B is the number of rays. Missed rays return inf. The repeated points in
this example come from rays at the bottom pole of the hemispherical lidar
pattern: all 180 horizontal samples meet there.
Try different patterns in the example below to inspect their hit positions.
Code for raycaster_sensor.py
1# Copyright (c) 2022-2026, The Isaac Lab Project Developers (https://github.com/isaac-sim/IsaacLab/blob/main/CONTRIBUTORS.md).
2# All rights reserved.
3#
4# SPDX-License-Identifier: BSD-3-Clause
5
6"""Cast a lidar-style ray pattern against rough terrain."""
7
8import argparse
9from typing import TYPE_CHECKING
10
11import torch
12
13import isaaclab.sim as sim_utils
14from isaaclab.app import add_launcher_args, launch_simulation
15from isaaclab.assets import AssetBaseCfg
16from isaaclab.physics import PhysicsCfg
17from isaaclab.scene import InteractiveSceneCfg
18from isaaclab.sensors.ray_caster import RayCasterCfg, patterns
19from isaaclab.utils import configclass, instantiate, replace
20from isaaclab.utils.assets import ISAAC_NUCLEUS_DIR
21
22from isaaclab_assets.robots.anymal import ANYMAL_C_CFG
23
24if TYPE_CHECKING:
25 from isaaclab.scene import InteractiveScene
26
27parser = argparse.ArgumentParser(description="Example on using the raycaster sensor.")
28parser.add_argument("--num_envs", type=int, default=1, help="Number of environments to spawn.")
29parser.add_argument("--log_interval", type=int, default=100, help="Steps between compact sensor summaries.")
30parser.add_argument("--max_steps", type=int, default=-1, help="Stop after this many steps; negative runs forever.")
31parser.add_argument(
32 "--physics",
33 default="isaacsim_physx",
34 choices=["isaacsim_physx"],
35 help="Physics backend.",
36)
37add_launcher_args(parser)
38parser.set_defaults(visualizer=["kit"])
39args_cli = parser.parse_args()
40if args_cli.log_interval < 1:
41 parser.error("--log_interval must be at least 1.")
42if args_cli.max_steps == 0 or args_cli.max_steps < -1:
43 parser.error("--max_steps must be positive or -1.")
44
45
46@configclass
47class RaycasterSensorSceneCfg(InteractiveSceneCfg):
48 """Design the scene with sensors on the robot."""
49
50 # ground plane
51 ground = AssetBaseCfg(
52 prim_path="/World/Ground",
53 spawn=sim_utils.UsdFileCfg(
54 usd_path=f"{ISAAC_NUCLEUS_DIR}/Environments/Terrains/rough_plane.usd",
55 scale=(1, 1, 1),
56 ),
57 )
58
59 # lights
60 dome_light = AssetBaseCfg(
61 prim_path="/World/Light", spawn=sim_utils.DomeLightCfg(intensity=3000.0, color=(0.75, 0.75, 0.75))
62 )
63
64 # robot
65 robot = replace(ANYMAL_C_CFG, prim_path="{ENV_REGEX_NS}/Robot")
66
67 ray_caster = RayCasterCfg(
68 prim_path="{ENV_REGEX_NS}/Robot/base",
69 update_period=1 / 60,
70 offset=RayCasterCfg.OffsetCfg(pos=(0, 0, 0.5)),
71 mesh_prim_paths=["/World/Ground"],
72 ray_alignment="yaw",
73 pattern_cfg=patterns.LidarPatternCfg(
74 channels=100, vertical_fov_range=[-90, 90], horizontal_fov_range=[-90, 90], horizontal_res=1.0
75 ),
76 debug_vis=bool(args_cli.visualizer),
77 )
78
79
80def run_simulator(sim: sim_utils.SimulationContext, scene: "InteractiveScene") -> None:
81 """Run the simulator."""
82 # Define simulation stepping
83 sim_dt = sim.get_physics_dt()
84 count = 0
85
86 while sim.is_running() and (args_cli.max_steps < 0 or count < args_cli.max_steps):
87 if count % 500 == 0:
88 # reset the scene entities
89 # root state
90 # we offset the root state by the origin since the states are written in simulation world frame
91 # if this is not done, then the robots will be spawned at the (0, 0, 0) of the simulation world
92 root_pose = scene["robot"].data.default_root_pose.torch.clone()
93 root_pose[:, :3] += scene.env_origins
94 scene["robot"].write_root_pose_to_sim_index(root_pose=root_pose)
95 root_vel = scene["robot"].data.default_root_vel.torch.clone()
96 scene["robot"].write_root_velocity_to_sim_index(root_velocity=root_vel)
97 # set joint positions with some noise
98 joint_pos, joint_vel = (
99 scene["robot"].data.default_joint_pos.torch.clone(),
100 scene["robot"].data.default_joint_vel.torch.clone(),
101 )
102 joint_pos += torch.rand_like(joint_pos) * 0.1
103 scene["robot"].write_joint_position_to_sim_index(position=joint_pos)
104 scene["robot"].write_joint_velocity_to_sim_index(velocity=joint_vel)
105 # clear internal buffers
106 scene.reset()
107 print("[INFO]: Resetting robot state...")
108 targets = scene["robot"].data.default_joint_pos.torch
109 scene["robot"].set_joint_position_target_index(target=targets)
110 scene.write_data_to_sim()
111 sim.step()
112 count += 1
113 scene.update(sim_dt)
114
115 if count % args_cli.log_interval == 0:
116 hits = scene["ray_caster"].data.ray_hits_w.torch
117 valid = torch.isfinite(hits).all(dim=-1)
118 print(f"[INFO] step={count} ray hit rate={valid.float().mean().item():.1%}")
119
120
121def main() -> None:
122 """Run the ray-caster example."""
123 with launch_simulation(cfg=PhysicsCfg(), launcher_args=args_cli) as physics_cfg:
124 sim_cfg = sim_utils.SimulationCfg(dt=0.005, device=args_cli.device, physics=physics_cfg)
125 sim = sim_utils.SimulationContext(sim_cfg)
126 sim.set_camera_view(eye=[3.5, 3.5, 3.5], target=[0.0, 0.0, 0.0])
127 scene_cfg = RaycasterSensorSceneCfg(num_envs=args_cli.num_envs, env_spacing=2.0)
128 scene: InteractiveScene = instantiate(scene_cfg)
129 sim.reset()
130 print("[INFO]: Setup complete...")
131 run_simulator(sim, scene)
132
133
134if __name__ == "__main__":
135 main()