Spaces:
Running on Zero
Running on Zero
File size: 9,348 Bytes
1811aab | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 | """The protagonist's out -> back -> out walk.
The walk follows the paved middle of one sidewalk lane: out to a turn point b, back past the start,
and out again to b2 beyond b (so the last leg never stalls at its end). Targets every 2 m along that
line are fed to CARLA's walker AI, which routes round street furniture. Posts, signs and tree trunks
standing on the line are switched off for the clip.
History of the rules (dataset_v1 failures they fix):
* walker AI given only far targets took the grass verge, where lamp posts stand, and walked
through them (~140 windows) -> dense targets on the paving
* CARLA's sidewalk lane often spans the verge (centre line on the grass) and at a block's end
next() runs on across the road -> paved_line()
* Town04 navmesh lies under the terrain (walker rendered underground) -> on_ground(); Town04 is
dropped from the dataset anyway
* the AI's own arrival radius is larger than ours: on "arriving" it wanders to a random place
-> Steerer re-issues targets
"""
from __future__ import annotations
import math
from dataclasses import dataclass, field
import carla
import numpy as np
TARGET_STEP_M = 2.0
SWITCH_RADIUS_M = 4.0
REISSUE_EVERY = 48 # frames
HIDE_RADIUS_M = 0.9
MAX_ATTEMPTS = 300
NAVMESH_FALLBACK_AFTER = 100 # attempts; maps with almost no sidewalk lanes (Town02/04)
@dataclass
class Route:
kind: str # "sidewalk" (paved-line route) or "navmesh" (fallback)
start: carla.Location
b: carla.Location
b2: carla.Location
leg: float
targets: list = field(default_factory=list)
line: list = field(default_factory=list)
hidden_ids: list = field(default_factory=list)
hidden_boxes: list = field(default_factory=list) # box centres of switched-off objects
class RoutePlanner:
def __init__(self, world, rng):
self.world, self.rng, self.map = world, rng, world.get_map()
L = carla.CityObjectLabel
self.L = L
self.see_through = {getattr(L, n) for n in ("Pedestrians", "Rider", "Vegetation", "Poles", "TrafficSigns",
"TrafficLight", "Other") if hasattr(L, n)}
self.all_nav = [p for p in (world.get_random_location_from_navigation() for _ in range(4000))
if p is not None and self.on_ground(p)]
pool = [p for p in self.all_nav if self.on_sidewalk(p)]
# start and turn points must lie on a sidewalk: the navmesh also covers lawns and planters
self.pool = pool if len(pool) >= 50 else self.all_nav
# --- probes
def on_sidewalk(self, p) -> bool:
w = self.map.get_waypoint(p, project_to_road=True, lane_type=carla.LaneType.Sidewalk)
return w is not None and w.transform.location.distance(p) < 1.5
def on_ground(self, p) -> bool:
g = self.world.ground_projection(p + carla.Location(z=3.0), 6.0)
return g is not None and abs(g.location.z - p.z) < 0.3
def ground_label(self, q):
top = q + carla.Location(z=1.5)
hits = [h for h in self.world.cast_ray(top, q + carla.Location(z=-1.5)) if h.label not in self.see_through]
return min(hits, key=lambda h: h.location.distance(top)).label if hits else None
def snap(self, q, tol: float = 1.0):
c = min(self.all_nav, key=lambda p: (p.x - q.x) ** 2 + (p.y - q.y) ** 2)
return c if math.hypot(c.x - q.x, c.y - q.y) < tol else None
def paved_line(self, w, length: float, step: float = TARGET_STEP_M):
"""Points every `step` m along a sidewalk lane, each moved across the lane to the middle of
its paved part. None if a point has no paving under it or the lane jumps (a junction)."""
pts, run_m, prev = [], 0.0, None
while True:
loc, right = w.transform.location, w.transform.get_right_vector()
if prev is not None and loc.distance(prev) > 1.5 * step:
return None
half = 0.5 * w.lane_width
offs = np.arange(-half + 0.2, half - 0.2 + 1e-6, 0.3)
paved = [self.ground_label(loc + right * float(o)) == self.L.Sidewalks for o in offs]
if not any(paved):
return None
best, cur, bs, cs = 0, 0, 0, 0 # longest run of paved samples
for i, p in enumerate(paved):
cur, cs = (cur + 1, cs) if p else (0, i + 1)
if cur > best:
best, bs = cur, cs
pts.append(loc + right * float(np.mean(offs[bs:bs + best])))
prev = loc
if run_m >= length:
return pts
nx = w.next(step)
if not nx:
return None
w, run_m = nx[0], run_m + step
# --- planning
def _spawn(self, bps, start, b):
hb = self.rng.choice(bps)
if hb.has_attribute("is_invincible"):
hb.set_attribute("is_invincible", "true")
yaw = math.degrees(math.atan2(b.y - start.y, b.x - start.x))
return self.world.try_spawn_actor(hb, carla.Transform(start + carla.Location(z=1.0), carla.Rotation(yaw=yaw)))
def plan(self, leg: float, walker_bps):
"""Spawn the protagonist on a valid route. Returns (hero, Route)."""
for attempt in range(MAX_ATTEMPTS):
start = self.rng.choice(self.pool)
w0 = self.map.get_waypoint(start, project_to_road=True, lane_type=carla.LaneType.Sidewalk)
nb = w0.next(leg) if w0 else []
nb2 = w0.next(1.4 * leg) if w0 else []
if (not nb or not nb2) and attempt >= NAVMESH_FALLBACK_AFTER:
far = [q for q in self.all_nav if 0.75 * leg <= math.hypot(q.x - start.x, q.y - start.y) <= leg]
if not far:
continue
b = self.rng.choice(far)
ext = carla.Location(x=start.x + 1.4 * (b.x - start.x), y=start.y + 1.4 * (b.y - start.y), z=b.z)
b2 = min(self.all_nav, key=lambda q: (q.x - ext.x) ** 2 + (q.y - ext.y) ** 2)
hero = self._spawn(walker_bps, start, b)
if hero:
return hero, Route("navmesh", start, b, b2, leg, targets=[b, start, b2])
continue
if not nb or not nb2:
continue
line = self.paved_line(w0, 1.4 * leg) # the whole walk stays on this lane's paving
if line is None:
continue
n_out = max(1, int(leg / TARGET_STEP_M))
start, b, b2 = self.snap(line[0]), self.snap(line[n_out]), self.snap(line[-1])
if start is None or b is None or b2 is None:
continue
if math.hypot(b.x - start.x, b.y - start.y) < 0.6 * leg: # lane bends back on itself
continue
hero = self._spawn(walker_bps, start, b)
if hero:
route = Route("sidewalk", start, b, b2, leg, targets=[b, start, b2], line=line)
out_pts = [p for p in (self.snap(q) for q in line[1:n_out + 1]) if p is not None]
out2_pts = [p for p in (self.snap(q) for q in line[1:]) if p is not None]
if len(out_pts) >= 3 and len(out2_pts) > len(out_pts):
route.targets = out_pts + list(reversed(out_pts[:-1])) + [start] + out2_pts
self.hide_obstacles(route)
return hero, route
raise RuntimeError("could not spawn a protagonist with an out-and-back route")
def hide_obstacles(self, route: Route):
"""Switch off posts, signs, traffic lights and tree trunks standing on the walking line."""
xy = np.array([[p.x, p.y] for p in route.line])
for lab in ("Poles", "TrafficSigns", "TrafficLight", "Vegetation"):
if not hasattr(self.L, lab):
continue
for ob in self.world.get_environment_objects(getattr(self.L, lab)):
bb = ob.bounding_box
if lab == "Vegetation" and bb.extent.z < 1.2:
continue # hedges / bushes: not on the paving
if float(np.min(np.hypot(xy[:, 0] - bb.location.x, xy[:, 1] - bb.location.y))) < HIDE_RADIUS_M:
route.hidden_ids.append(ob.id)
route.hidden_boxes.append([bb.location.x, bb.location.y, bb.location.z])
if route.hidden_ids:
self.world.enable_environment_objects(route.hidden_ids, False)
class Steerer:
"""Feeds the route targets to the walker AI: the next target from SWITCH_RADIUS_M out, and the
current one re-issued every REISSUE_EVERY frames."""
def __init__(self, controller, hero, targets):
self.ctrl, self.hero, self.targets, self.i = controller, hero, targets, 0
self.ctrl.go_to_location(targets[0])
def step(self, frame: int):
p = self.hero.get_location()
t = self.targets[self.i]
if self.i < len(self.targets) - 1 and math.hypot(p.x - t.x, p.y - t.y) < SWITCH_RADIUS_M:
self.i += 1
self.ctrl.go_to_location(self.targets[self.i])
elif frame % REISSUE_EVERY == 0:
self.ctrl.go_to_location(t)
|