Source code for rtgym.arena.arena_shapes.hairpin
"""Hairpin arena and waypoints adapted from grid_and_place."""
import numpy as np
[docs]
def generate_hairpin_arena(sr, **kwargs):
# Convert alley and turn dimensions to grid cells.
n_alleys = kwargs.get("n_alleys", 10)
n_alleys = int(n_alleys)
alley_width = kwargs.get("alley_width", 15) / sr
alley_width = int(alley_width)
alley_height = kwargs.get("alley_height", 100) / sr
alley_height = int(alley_height)
# Set wall thickness and the gap used for each turn.
wall_thickness = kwargs.get("wall_thickness", 1) / sr
wall_thickness = int(wall_thickness)
default_gap = kwargs.get("alley_width", 15)
turn_gap = kwargs.get("turn_gap", default_gap) / sr
turn_gap = int(turn_gap)
border = 5
assert n_alleys >= 2, f"n_alleys must be >= 2, got {n_alleys}"
assert turn_gap < alley_height, (
f"turn_gap ({turn_gap}) must be smaller than alley_height ({alley_height})"
)
# Allocate the alley interiors before inserting alternating walls.
height = alley_height
width = n_alleys * alley_width + (n_alleys - 1) * wall_thickness
arena_map = np.zeros((height, width), dtype=np.float32)
# Walls between alleys.
for i in range(n_alleys - 1):
col_start = (i + 1) * alley_width + i * wall_thickness
col_end = col_start + wall_thickness
if i % 2 == 0:
arena_map[turn_gap:, col_start:col_end] = 1 # gap at top
else:
arena_map[: height - turn_gap, col_start:col_end] = 1 # gap at bottom
# Border (walls on all four sides)
arena_map = np.pad(arena_map, border, mode="constant", constant_values=1)
if kwargs.get("vertical"):
arena_map = np.rot90(arena_map, 1, (0, 1))
return arena_map
[docs]
def hairpin_waypoints(sr, n_alleys=10, alley_width=15, alley_height=100,
wall_thickness=2, turn_gap=None, border=5, direction='forward'):
"""Return grid-coordinate waypoints for the unrotated hairpin layout."""
alley_width_px = int(alley_width / sr)
alley_height_px = int(alley_height / sr)
wall_thickness_px = int(wall_thickness / sr)
if turn_gap is None:
turn_gap_px = alley_width_px
else:
turn_gap_px = int(turn_gap / sr)
col_centers = [
border + i * alley_width_px + i * wall_thickness_px + alley_width_px // 2
for i in range(n_alleys)
]
# Rows inside the alleys — stay a few pixels inside the turn gap so the
# waypoint is reachable without clipping the wall.
margin = max(2, turn_gap_px // 3)
top_row = border + margin
bottom_row = border + alley_height_px - 1 - margin
waypoints = [(bottom_row, col_centers[0])]
for i in range(n_alleys):
if i % 2 == 0:
# Going up alley i
waypoints.append((top_row, col_centers[i]))
if i + 1 < n_alleys:
waypoints.append((top_row, col_centers[i + 1]))
else:
# Going down alley i
waypoints.append((bottom_row, col_centers[i]))
if i + 1 < n_alleys:
waypoints.append((bottom_row, col_centers[i + 1]))
if direction == "backward":
waypoints = waypoints[::-1]
elif direction != "forward":
raise ValueError(f"direction must be 'forward' or 'backward', got {direction}")
return waypoints