2D-Plan-Renderer auf WebGL2 (GPU) + akkumulierter Funktionsstand

Neuer GPU-Renderer fuer den Grundriss (src/plan/glPlan/): Earcut-Tessellierung
(konkav-faehig), gehrte Linienzuege (Miter), echte Papier-mm-Strichbreiten im
Massstab (repliziert den SVG-printStrokeVb-Pfad), Hybrid mit scharfem SVG-Text-
Overlay. GPU ist der Standardpfad; der SVG-Renderer bleibt automatischer Fallback,
falls WebGL2/Shader nicht verfuegbar sind. Imperativer Pan (rAF + CSS-transform)
fuer fluessige Interaktion ohne React-Re-Render je Frame.

Enthaelt zudem den bisher nicht committeten Arbeitsstand des Browser-BIM
(Oeffnungen, Treppen, Raeume, Decken, DXF-Export, Materialbibliothek, Kontext-
Import, Tauri-Compute-Boundary-PoC).
This commit is contained in:
2026-07-02 00:12:39 +02:00
parent cfe5249440
commit 3d2d4d6321
184 changed files with 29421 additions and 669 deletions
+110
View File
@@ -0,0 +1,110 @@
// Gemeinsame Typen + Helfer für den Standort-Import (swisstopo & OSM).
//
// Beide Quellen liefern am Ende dasselbe: geschlossene/offene Polylinien in
// LOKALEN Modell-Metern (bereits um die Origin verschoben, siehe lv95.ts),
// kategorisiert nach Art (Gebäude/Strasse/Wasser/Grün). Die UI wandelt diese in
// `ContourSet`-Kontext-Objekte pro Kategorie um und legt sie in `project.context`.
//
// Bezeichner englisch, Kommentare deutsch (CONVENTIONS.md).
import type { GeoOrigin } from "./lv95";
import type { ContextObject, Contour } from "../model/types";
/** Fachliche Kategorie einer importierten Kontext-Linie. */
export type GeoCategory = "building" | "road" | "water" | "green";
/** Ein importiertes Polygon/Polylinie in lokalen Modell-Metern. */
export interface GeoFeature {
category: GeoCategory;
/** Stützpunkte in lokalen Metern (x=Ost, y=Nord). */
pts: { x: number; y: number }[];
/** Geschlossener Ring (Gebäude/Wasser/Grün) vs. offene Linie (Strasse). */
closed: boolean;
}
/** Ergebnis eines Import-Laufs: Features + verwendete Origin. */
export interface GeoImportResult {
origin: GeoOrigin;
features: GeoFeature[];
}
/** Menschlicher Layer-/Objektname je Kategorie (deutsch, für die Kontext-Liste). */
const CATEGORY_LABEL: Record<GeoCategory, string> = {
building: "Gebäude",
road: "Strassen",
water: "Gewässer",
green: "Grünflächen",
};
/** Layer-Kennung (englischer Identifier) je Kategorie — für spätere Zuordnung. */
const CATEGORY_LAYER: Record<GeoCategory, string> = {
building: "context-buildings",
road: "context-roads",
water: "context-water",
green: "context-green",
};
/** Erzeugt eine (kollisionsarme) ID für ein neues Kontext-Objekt. */
function contextId(prefix: string): string {
return `${prefix}-${Date.now().toString(36)}-${Math.random()
.toString(36)
.slice(2, 8)}`;
}
/**
* Wandelt importierte Features in `ContextObject`s (je Kategorie EIN
* ContourSet). Die Konturen liegen auf z=0 (Grundriss-Kontext); geschlossene
* Ringe (Gebäude/Wasser/Grün) werden als geschlossene Konturen markiert,
* Strassen als offene Linienzüge. Der Aufrufer legt das Ergebnis über
* `onAddContextObjects` in `project.context` ab.
*/
export function featuresToContextObjects(
features: GeoFeature[],
sourceLabel: string,
): ContextObject[] {
const byCat = new Map<GeoCategory, Contour[]>();
for (const f of features) {
if (f.pts.length < 2) continue;
const list = byCat.get(f.category) ?? [];
list.push({
z: 0,
pts: f.pts.map((p) => ({ x: p.x, y: p.y })),
closed: f.closed,
layer: CATEGORY_LAYER[f.category],
});
byCat.set(f.category, list);
}
const objs: ContextObject[] = [];
for (const [cat, contours] of byCat) {
if (contours.length === 0) continue;
objs.push({
id: contextId(`geo-${cat}`),
type: "contourSet",
name: `${CATEGORY_LABEL[cat]} (${sourceLabel})`,
layer: CATEGORY_LAYER[cat],
contours,
});
}
return objs;
}
/**
* Basis-URL des optionalen Geo-Proxys (openbureau-core `/geoproxy`). Wird für
* Overpass zwingend benutzt (CORS unzuverlässig); für geo.admin nur als
* Fallback. Konfiguration über `VITE_GEO_PROXY` (z. B. "/geoproxy" bei
* gleichem Origin oder eine absolute URL auf den API-Server). Leer = kein Proxy.
*/
export function geoProxyBase(): string {
const env = (import.meta as unknown as { env?: Record<string, string> }).env;
return (env?.VITE_GEO_PROXY ?? "").replace(/\/$/, "");
}
/**
* Verpackt eine absolute Upstream-URL für den Geo-Proxy. Ohne konfigurierten
* Proxy wird die URL unverändert zurückgegeben (Direktabruf).
*/
export function viaProxy(url: string): string {
const base = geoProxyBase();
return base ? `${base}?u=${encodeURIComponent(url)}` : url;
}