Your browser sucks, JavaScript disabled! Gunnar's console needs JavaScript and WebAssembly. Git over SSH on port 2222 does not.

nordisk/facett

facett-map3d/.nornir/route-overlay-nav.md

OSM-3D route overlay + turn-by-turn follow

The car-navigator's headline is "your route": a highlighted line over the streets plus a camera that drives it (turn-by-turn). Both are additive over the existing extruded-city + OrbitCamera machinery (Law #1) — no second renderer, no new camera engine, and every existing snapshot stays byte-identical (the whole feature is opt-in; default Map3D carries no route).

The two pieces

Piece What it is Where
Route geometry a pure design-plane polyline + arc-length table route.rs Route
Route ribbon + markers a bright ground ribbon (reuses the road stroked-line builder) + a disc at each waypoint, baked into the basemap mesh extrude.rs route_overlay_into (uses ribbon_rounded)
Minimap route line the route + a "you are driving here" pip drawn into the top-down inset HUD paint_minimap
Turn-by-turn follow the camera target rides the route point at route_progress, bearing swings to the route tangent, tilt held at a chase pitch apply_route_camera

Projection — the route lands ON the streets

extrude::project_route(ways, waypoints) recomputes the identical footprint_bbox over the same ways the mesh was extruded from (exactly like street_label_anchors), then maps each Mercator-unit waypoint through the same to_design used by every building/road — so the route aligns with the extruded geometry, not a parallel frame. Waypoints arrive in Way::points space (what a korp route collector emits).

Follow — the camera is the existing orbit camera

apply_route_camera writes cam.target = route.point_at(t), set_bearing_deg(route. bearing_deg_at(t)), and a chase set_tilt_deg / dolly — then snap_angles_to_goal, so a pinned-clock A/B is deterministic (no damper trail). The bearing convention (atan2(dx, dz), clockwise from +z) matches both OrbitCamera::bearing_deg and the minimap heading, so the camera looks straight along the road. In Frame (clock unpinned) the follow advances route_progress by dt · route_speed, wrapping at the destination — the trip loops forever for a demo.

The seams (FC-2 / state_json)

Proofs

Headless data A/B — tests/route_proof.rs (facett-map3d/route_overlay_and_turn_by_turn_follow): attaching a route adds triangles + a tile; seeking t: 0→1 moves the camera target monotonically from start to destination; the bearing turns across the L-corner; the follow pose is a pure function of t; follow off leaves the camera untouched.

Robot-UI — tests/route_robot.rs (facett-map3d/route_robot_minimap_paints_and_follows): drives the LIVE Facet (CPU painter). RENDER: with the minimap on, the route paints its line + dots into the bottom-right inset (region pixel-diff > 0), confined there. DRIVE: enabling follow + running real frames advances route_progress and moves the reported camera target — the camera chases the route.

Note (honest scope): the 3D ground-plane ribbon shares the roads' fate — the CPU painter back-face-culls ground-plane faces from above, so roads and the route ribbon are data-proven, not pixel-proven, in the headless painter. The minimap HUD (which rasterises reliably) is the route's on-screen render proof this round; a ground-plane-render fix is a separate, broader change (it touches every road + the render goldens) tracked in the OSM-3D gap ledger.