Files
DOSSIER-STANDALONE/src/geometry/ceiling.ts
T
karim 3d2d4d6321 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).
2026-07-02 00:12:39 +02:00

114 lines
3.6 KiB
TypeScript

// Geometrie-Helfer für Decken (Slabs) — reine Funktionen über `Vec2`, kein
// externer Kernel. Eine Decke ist ein GESCHLOSSENER Umriss (Polygon) plus eine
// Dicke; diese Helfer normalisieren den Umriss, berechnen Fläche/Umriss-Prüfung
// und liefern eine extrude-fertige Form. Baut auf kernel2d/model/geometry auf.
//
// Bezeichner englisch, Kommentare deutsch (CONVENTIONS.md).
import type { Vec2 } from "../model/types";
import { signedArea, isCCW, vecEqual } from "./kernel2d";
/** Kleinste sinnvolle Deckenfläche (m²) — darunter gilt der Umriss als entartet. */
export const MIN_CEILING_AREA = 1e-4;
/**
* Normalisiert einen Deckenumriss:
* • entfernt aufeinanderfolgende (nahezu) gleiche Punkte,
* • entfernt einen schließenden Duplikat-Endpunkt (pts[last] == pts[0]),
* • erzwingt CCW-Wicklung (positive Fläche) — konsistent für Plan-Füllung und
* 3D-Extrusion.
* Liefert `null`, wenn nach der Bereinigung < 3 Punkte übrig bleiben (kein
* gültiges Polygon).
*/
export function normalizeOutline(pts: Vec2[]): Vec2[] | null {
const clean: Vec2[] = [];
for (const p of pts) {
if (clean.length === 0 || !vecEqual(clean[clean.length - 1], p)) clean.push(p);
}
// Schließenden Duplikat-Endpunkt verwerfen.
if (clean.length > 1 && vecEqual(clean[0], clean[clean.length - 1])) clean.pop();
if (clean.length < 3) return null;
// CCW erzwingen.
return isCCW(clean) ? clean : clean.slice().reverse();
}
/** Ob ein Umriss ein gültiges (nicht entartetes) Deckenpolygon bildet. */
export function isValidOutline(pts: Vec2[]): boolean {
const norm = normalizeOutline(pts);
return norm != null && ceilingArea(norm) >= MIN_CEILING_AREA;
}
/** Fläche eines Deckenumrisses in m² (immer positiv). */
export function ceilingArea(pts: Vec2[]): number {
return Math.abs(signedArea(pts));
}
/**
* Achsparallele Bounding-Box eines Umrisses (Modell-Meter). Leerer/entarteter
* Umriss → Null-Box.
*/
export function outlineBBox(pts: Vec2[]): {
minX: number;
minY: number;
maxX: number;
maxY: number;
} {
let minX = Infinity,
minY = Infinity,
maxX = -Infinity,
maxY = -Infinity;
for (const p of pts) {
minX = Math.min(minX, p.x);
minY = Math.min(minY, p.y);
maxX = Math.max(maxX, p.x);
maxY = Math.max(maxY, p.y);
}
if (!isFinite(minX)) return { minX: 0, minY: 0, maxX: 0, maxY: 0 };
return { minX, minY, maxX, maxY };
}
/** Schwerpunkt (Zentroid) des Umriss-Polygons (für HUD/Beschriftung). */
export function outlineCentroid(pts: Vec2[]): Vec2 {
const a = signedArea(pts);
if (Math.abs(a) < 1e-12) {
// Entartet: einfacher Mittelwert der Punkte.
let sx = 0,
sy = 0;
for (const p of pts) {
sx += p.x;
sy += p.y;
}
const n = pts.length || 1;
return { x: sx / n, y: sy / n };
}
let cx = 0,
cy = 0;
for (let i = 0; i < pts.length; i++) {
const p = pts[i];
const q = pts[(i + 1) % pts.length];
const cr = p.x * q.y - q.x * p.y;
cx += (p.x + q.x) * cr;
cy += (p.y + q.y) * cr;
}
const f = 1 / (6 * a);
return { x: cx * f, y: cy * f };
}
/**
* Punkt-in-Polygon-Test (Ray-Casting) für einen geschlossenen Umriss — dient dem
* Body-Select einer Decke im Grundriss/3D. Randpunkte gelten als innen genug.
*/
export function pointInOutline(p: Vec2, outline: Vec2[]): boolean {
let inside = false;
const n = outline.length;
for (let i = 0, j = n - 1; i < n; j = i++) {
const a = outline[i];
const b = outline[j];
const intersect =
a.y > p.y !== b.y > p.y &&
p.x < ((b.x - a.x) * (p.y - a.y)) / (b.y - a.y || 1e-12) + a.x;
if (intersect) inside = !inside;
}
return inside;
}