Browser-BIM (cad): semantisches Modell, abgeleitete 2D/3D-Sichten, Zeichenwerkzeuge
Standalone-Browser-Port von DOSSIER. Enthaelt das semantische Modell mit Plan-/3D-Ableitung, Zeichen- und Editierwerkzeuge, Rhino-artiges Befehlssystem, dockbares Panel-System, Resource-Manager, DXF/.lin/.pat-Import, i18n (de/en) sowie Projektdokumentation und Probe-Harness.
This commit is contained in:
@@ -0,0 +1,100 @@
|
||||
// Gelände-Generierung (TIN) aus Konturen (Höhenlinien).
|
||||
//
|
||||
// Prinzip: Die Stütz-Vertices der Konturen werden als Punktmenge genommen
|
||||
// (sie liegen direkt auf den Höhenlinien = bevorzugte „Breaklines"). Über deren
|
||||
// (x,y)-Projektion wird eine 2D-Delaunay-Triangulation gelegt; die Z-Höhe je
|
||||
// Vertex stammt aus der jeweiligen Kontur. Daraus entsteht ein TIN (Triangulated
|
||||
// Irregular Network) als rohes positions/indices-Mesh (three-frei).
|
||||
//
|
||||
// Gelände ist NICHT semantisch — es ist Kontext-Geometrie (Project.context).
|
||||
|
||||
import Delaunator from "delaunator";
|
||||
import type { Contour, TerrainMesh } from "./types";
|
||||
|
||||
let terrainSeq = 0;
|
||||
|
||||
/**
|
||||
* Erzeugt ein TIN aus Konturen.
|
||||
*
|
||||
* Algorithmus:
|
||||
* 1. Sampling: jeder Stützpunkt jeder Kontur wird ein 3D-Vertex (x, y, z) mit
|
||||
* z = Kontur-Höhe. Es wird KEIN zusätzliches Resampling betrieben — die
|
||||
* Kontur-Vertices selbst sind die Breaklines.
|
||||
* 2. Delaunay: 2D-Triangulation über die (x,y)-Projektion aller Vertices
|
||||
* (Delaunator). Die Z-Höhe je Vertex bleibt erhalten → echtes Gelände-TIN.
|
||||
* 3. Degenerate-Filter: Dreiecke mit (nahezu) null Fläche in der (x,y)-Ebene
|
||||
* bzw. mit nicht-endlichen Koordinaten werden verworfen.
|
||||
*
|
||||
* Robustheit:
|
||||
* • offene UND geschlossene Konturen werden gleich behandelt (nur die Punkte
|
||||
* zählen; closed/offen beeinflusst die TIN-Fläche nicht).
|
||||
* • leere/zu kleine Eingabe (< 3 Punkte) → leeres Mesh (kein Crash).
|
||||
* • NaN/Infinity-Punkte werden vor der Triangulation aussortiert.
|
||||
*/
|
||||
export function generateTerrainFromContours(contours: Contour[]): TerrainMesh {
|
||||
const id = `terrain-${Date.now()}-${terrainSeq++}`;
|
||||
const name = "Gelände";
|
||||
|
||||
// 1. Sampling: alle Kontur-Stützpunkte als (x,y,z)-Vertices sammeln, ungültige
|
||||
// (nicht-endliche) Punkte überspringen.
|
||||
const xs: number[] = [];
|
||||
const ys: number[] = [];
|
||||
const zs: number[] = [];
|
||||
for (const c of contours) {
|
||||
const z = Number.isFinite(c.z) ? c.z : 0;
|
||||
for (const p of c.pts) {
|
||||
if (!Number.isFinite(p.x) || !Number.isFinite(p.y)) continue;
|
||||
xs.push(p.x);
|
||||
ys.push(p.y);
|
||||
zs.push(z);
|
||||
}
|
||||
}
|
||||
|
||||
// Zu wenige Punkte für ein Dreieck → triviales leeres Mesh.
|
||||
if (xs.length < 3) {
|
||||
return { id, type: "terrainMesh", name, positions: [], indices: [] };
|
||||
}
|
||||
|
||||
// 2. Delaunay über die (x,y)-Projektion. Delaunator erwartet ein flaches
|
||||
// [x0,y0, x1,y1, …]-Koordinatenarray.
|
||||
const coords2d = new Float64Array(xs.length * 2);
|
||||
for (let i = 0; i < xs.length; i++) {
|
||||
coords2d[i * 2] = xs[i];
|
||||
coords2d[i * 2 + 1] = ys[i];
|
||||
}
|
||||
// Bei kollinearen Punkten liefert Delaunator eine leere Triangulation — das ist
|
||||
// korrekt (kein Crash). Trotzdem schützen wir gegen einen möglichen Wurf.
|
||||
let triangles: Uint32Array;
|
||||
try {
|
||||
triangles = new Delaunator(coords2d).triangles;
|
||||
} catch {
|
||||
return { id, type: "terrainMesh", name, positions: [], indices: [] };
|
||||
}
|
||||
|
||||
// 3. positions (x,y,z) aufbauen; Z aus zs je Vertex.
|
||||
const positions: number[] = new Array(xs.length * 3);
|
||||
for (let i = 0; i < xs.length; i++) {
|
||||
positions[i * 3] = xs[i];
|
||||
positions[i * 3 + 1] = ys[i];
|
||||
positions[i * 3 + 2] = zs[i];
|
||||
}
|
||||
|
||||
// Degenerierte Dreiecke (Null-Fläche in (x,y)) herausfiltern, damit das TIN
|
||||
// keine entarteten Faces enthält.
|
||||
const indices: number[] = [];
|
||||
const EPS_AREA = 1e-9;
|
||||
for (let t = 0; t < triangles.length; t += 3) {
|
||||
const a = triangles[t];
|
||||
const b = triangles[t + 1];
|
||||
const c = triangles[t + 2];
|
||||
const ax = xs[a], ay = ys[a];
|
||||
const bx = xs[b], by = ys[b];
|
||||
const cx = xs[c], cy = ys[c];
|
||||
// doppelte Dreiecksfläche (Kreuzprodukt) in der (x,y)-Ebene.
|
||||
const area2 = Math.abs((bx - ax) * (cy - ay) - (cx - ax) * (by - ay));
|
||||
if (area2 <= EPS_AREA) continue;
|
||||
indices.push(a, b, c);
|
||||
}
|
||||
|
||||
return { id, type: "terrainMesh", name, positions, indices };
|
||||
}
|
||||
Reference in New Issue
Block a user