Lidar Feature
Overview
The Lidar feature provides 360-degree raycast-based distance sensing for the agent, similar to real-world LiDAR sensors used in robotics and autonomous vehicles. This is useful for:
- Navigation and obstacle avoidance
- Environment mapping
- Distance-based decision making
- Detecting entities and blocks around the agent
Configuration
LidarConfig Parameters
from craftground import LidarConfig
lidar_config = LidarConfig(
horizontal_rays=32, # Number of rays in horizontal plane (360° / horizontal_rays = angle between rays)
max_distance=100.0, # Maximum raycast distance in blocks
vertical_angle=0.0, # Vertical angle offset in degrees (0.0 = horizontal)
vertical_rays=1, # Number of vertical layers (1 = single horizontal plane)
vertical_fov=30.0 # Vertical field of view in degrees (used when vertical_rays > 1)
)
Parameters Explained:
- horizontal_rays: Number of rays distributed evenly around 360 degrees
- Example: 32 rays = 11.25° between each ray
- Higher values = better resolution but slower performance
- Typical values: 16-64 for single layer, 32-128 for multi-layer
- max_distance: Maximum distance to raycast in blocks
- Rays that don’t hit anything return this distance
- Typical values: 50.0-200.0 blocks
- Lower values = better performance
- vertical_angle: Vertical angle offset from horizontal plane
- 0.0 = horizontal plane at eye level
- Positive = upward tilt
- Negative = downward tilt
- Only used when vertical_rays = 1
- vertical_rays: Number of vertical layers
- 1 = single horizontal plane (fastest)
-
1 = multiple layers creating a 3D scan
- Example: 8 layers with 30° FOV = 8 layers from -15° to +15°
- vertical_fov: Vertical field of view when using multiple layers
- Only used when vertical_rays > 1
- Distributes vertical rays across this FOV range
- Example: 30.0 = rays from -15° to +15° from center
Usage
Basic Usage (Single Horizontal Layer)
import craftground
from craftground import LidarConfig
# Simple horizontal Lidar
lidar_config = LidarConfig(
horizontal_rays=32,
max_distance=100.0
)
initial_env = craftground.InitialEnvironmentConfig(
lidar_config=lidar_config
)
env = craftground.make(initial_env_config=initial_env)
obs, info = env.reset()
# Access Lidar data
lidar_result = obs["full"].lidar_result
for ray in lidar_result.rays:
print(f"Angle: {ray.angle_horizontal:.1f}°, Distance: {ray.distance:.2f}, Hit: {ray.hit_type}")
Multi-Layer 3D Scanning
# 3D Lidar configuration (like Velodyne)
lidar_config = LidarConfig(
horizontal_rays=64, # 64 rays per layer
max_distance=100.0,
vertical_rays=16, # 16 vertical layers
vertical_fov=30.0 # -15° to +15° vertical coverage
)
Downward-Looking Lidar
# Look down for ground distance measurement
lidar_config = LidarConfig(
horizontal_rays=16,
max_distance=50.0,
vertical_angle=-45.0, # Look 45° downward
vertical_rays=1
)
Observation Structure
The Lidar result is available in obs["full"].lidar_result:
lidar_result = obs["full"].lidar_result
# Metadata
lidar_result.horizontal_rays # int: Number of horizontal rays
lidar_result.vertical_rays # int: Number of vertical layers
lidar_result.max_distance # float: Maximum distance
# Ray data (list of rays)
for ray in lidar_result.rays:
ray.distance # float: Distance to hit (or max_distance if miss)
ray.hit_type # int: 0=MISS, 1=BLOCK, 2=ENTITY
ray.block_name # str: Block translation key (if hit_type==1)
ray.entity_name # str: Entity translation key (if hit_type==2)
ray.angle_horizontal # float: Horizontal angle in degrees (0-360)
ray.angle_vertical # float: Vertical angle in degrees
Processing Lidar Data
Convert to NumPy Arrays
import numpy as np
lidar_result = obs["full"].lidar_result
# Extract distances
distances = np.array([ray.distance for ray in lidar_result.rays])
# Extract hit types
hit_types = np.array([ray.hit_type for ray in lidar_result.rays])
# Extract angles
angles_h = np.array([ray.angle_horizontal for ray in lidar_result.rays])
angles_v = np.array([ray.angle_vertical for ray in lidar_result.rays])
# Find closest obstacle
min_distance = np.min(distances)
min_angle = angles_h[np.argmin(distances)]
print(f"Closest obstacle at {min_distance:.2f} blocks, angle {min_angle:.1f}°")
Reshape for Multi-Layer Lidar
# For multi-layer Lidar, reshape into 2D array
h_rays = lidar_result.horizontal_rays
v_rays = lidar_result.vertical_rays
distances_2d = distances.reshape(v_rays, h_rays)
# Now distances_2d[v][h] gives distance at vertical layer v, horizontal angle h
Convert to Point Cloud
import numpy as np
def lidar_to_pointcloud(lidar_result, player_yaw, player_pitch):
"""Convert Lidar rays to 3D point cloud"""
points = []
for ray in lidar_result.rays:
if ray.hit_type == 0: # Skip misses
continue
# Calculate 3D position
angle_h_rad = np.radians(player_yaw + ray.angle_horizontal)
angle_v_rad = np.radians(player_pitch + ray.angle_vertical)
x = ray.distance * np.cos(angle_v_rad) * np.sin(angle_h_rad)
y = ray.distance * np.sin(angle_v_rad)
z = ray.distance * np.cos(angle_v_rad) * np.cos(angle_h_rad)
points.append([x, y, z])
return np.array(points)
# Usage
obs, info = env.reset()
player_yaw = obs["full"].yaw
player_pitch = obs["full"].pitch
pointcloud = lidar_to_pointcloud(obs["full"].lidar_result, player_yaw, player_pitch)
Performance Considerations
Lidar raycast can be computationally expensive:
- Total rays =
horizontal_rays × vertical_rays - Each ray performs a raycast through the world
Performance Tips:
- Start with fewer rays: Use 16-32 horizontal rays for testing
- Reduce max_distance: Shorter raycasts are faster
- Use single layer first: vertical_rays=1 is much faster than multi-layer
- Profile your application: Measure actual performance impact
Recommended Configurations:
Fast (Real-time RL training):
LidarConfig(horizontal_rays=16, max_distance=50.0, vertical_rays=1)
Balanced:
LidarConfig(horizontal_rays=32, max_distance=100.0, vertical_rays=1)
High Resolution:
LidarConfig(horizontal_rays=64, max_distance=100.0, vertical_rays=1)
3D Scanning:
LidarConfig(horizontal_rays=64, max_distance=100.0, vertical_rays=16, vertical_fov=30.0)
Example Use Cases
1. Obstacle Avoidance
def get_safe_direction(lidar_result):
"""Find the direction with the most open space"""
distances = np.array([ray.distance for ray in lidar_result.rays])
angles = np.array([ray.angle_horizontal for ray in lidar_result.rays])
# Find direction with maximum distance
best_idx = np.argmax(distances)
return angles[best_idx]
2. Wall Following
def detect_walls(lidar_result, threshold=5.0):
"""Detect nearby walls"""
walls = []
for ray in lidar_result.rays:
if ray.hit_type == 1 and ray.distance < threshold:
walls.append((ray.angle_horizontal, ray.distance))
return walls
3. Entity Detection
def find_nearest_entity(lidar_result):
"""Find nearest entity"""
entities = [(ray.distance, ray.angle_horizontal, ray.entity_name)
for ray in lidar_result.rays if ray.hit_type == 2]
if entities:
return min(entities, key=lambda x: x[0])
return None
Technical Details
- Raycasts are performed from the player’s eye position
- Horizontal angles are relative to player’s yaw (0° = forward)
- Vertical angles are relative to player’s pitch (0° = level)
- Block and entity hits are both detected
- Closest hit (block or entity) is returned for each ray
- Ray order: Iterate through vertical layers, then horizontal angles within each layer
See Also
- Observation Space Documentation
- Initial Environment Configuration
- Example:
examples/lidar_example.py