fix(site-map): Map 3 Progress tab Show in 3D flew to a blank spot

The section's height was passed as 0, so the camera target sat far below the map. Use the design surface height, and make the camera fly-to fall back to the surface height when given a wrong one.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
DEls 2026-10-21 05:43:57 +02:00
parent 36da7f9f20
commit 2933251b2e
2 changed files with 5 additions and 2 deletions

View file

@ -4,6 +4,7 @@ import * as React from "react";
import type { SiteModelData } from "@/lib/site-map-v2/model-types";
import type { ProgressState } from "@/lib/site-map-v2/progress";
import { layerCatalog, layerTotals, sectionSummary } from "@/lib/site-map-v3/layer-progress";
import { decodeGrid, sampleGrid } from "@/lib/site-map-v3/grid";
import type { Selection } from "../scene/types";
const css = (n: number) => `#${n.toString(16).padStart(6, "0")}`;
@ -13,6 +14,7 @@ export function BuildProgressPanel({ model, state, selected, setSelected, select
select(sel: Selection): void; focus(e: number, n: number, z: number, dist: number): void; onOpen(sectionKey: string): void; canEdit: boolean;
}) {
const catalog = React.useMemo(() => layerCatalog(model), [model]);
const design = React.useMemo(() => decodeGrid(model.surface), [model.surface]);
const rows = model.road.sections.map(s => ({ s, sum: sectionSummary(model, state, s.key, selected) })).filter(r => r.sum.layersTotal > 0);
const full = rows.filter(r => r.sum.layersDone === r.sum.layersTotal).length, none = rows.filter(r => r.sum.fraction === 0).length;
const toggle = (id: string) => { const n = new Set(selected); if (n.has(id)) n.delete(id); else n.add(id); setSelected(n); };
@ -41,7 +43,7 @@ export function BuildProgressPanel({ model, state, selected, setSelected, select
<button type="button" className="min-w-0 flex-1 text-left" onClick={() => select({ type: "section", section: s.key })}>
<b className="block truncate">{s.name} {s.from}–{Math.round(s.to)} m</b>
<span className="mt-1 flex items-center gap-1" aria-label={`${sum.layersDone} of ${sum.layersTotal} layers done`}>{sum.layers.map(l => <i key={l.id} title={l.name} className="h-3 w-3 rounded-sm border" style={{ background: l.done >= 0.999 ? "#22c55e" : l.done > 0 ? "#f59e0b" : "transparent" }} />)}<span className="ml-1 text-xs tabular-nums text-muted-foreground">{sum.layersDone}/{sum.layersTotal}</span></span></button>
{c && <button type="button" className="min-h-9 rounded-md border px-2 text-xs hover:bg-muted" onClick={() => { select({ type: "section", section: s.key }); focus(c[0], c[1], 0, 60); }}>Show in 3D</button>}
{c && <button type="button" className="min-h-9 rounded-md border px-2 text-xs hover:bg-muted" onClick={() => { select({ type: "section", section: s.key }); focus(c[0], c[1], sampleGrid(design, c[0], c[1]) ?? model.surface.zmin, 60); }}>Show in 3D</button>}
<button type="button" disabled={!canEdit} className="min-h-9 rounded-md border px-2 text-xs hover:bg-muted disabled:opacity-50" onClick={() => { select({ type: "section", section: s.key }); onOpen(s.key); }}>Tick layers</button>
</li>; })}</ul></section>}
{!rows.length && <p className="text-muted-foreground">Tick at least one layer above to see the sections.</p>}

View file

@ -142,7 +142,8 @@ export function createSiteMap(container: HTMLElement, model: SiteModelData, stat
}
/** Fly to a spot, tilted so it reads in 3D, and mark it with a beacon for a few seconds. */
function focus(e: number, n: number, z: number, dist: number) {
const t = ctx.W(e, n, z), off = camera.position.clone().sub(controls.target); off.y = 0;
const zz = Number.isFinite(z) && Math.abs(z - zRef) < 200 ? z : ctx.designZ(e, n); // a wrong height would fly the camera off the map
const t = ctx.W(e, n, zz), off = camera.position.clone().sub(controls.target); off.y = 0;
if (off.lengthSq() < 1) off.set(-0.3, 0, 0.8);
off.normalize().multiplyScalar(dist * 0.62); off.y = dist * 0.78;
const p0 = camera.position.clone(), t0 = controls.target.clone(), p1 = t.clone().add(off);