76029dbf68
WallInput bekommt openings (from/to/sill/height, serde-default). Der Cut durch eine Öffnung liefert mehrere einfache Rechtecke (Brüstung, Sturz, Leibungen) statt Lochpolygon — kompatibel zu render2d::FillPolygon. Der Verdeckungstest behandelt Öffnungen als Durchblick (u-Zuordnung exakt im Elevationsfall, konservativ undurchsichtig bei kantennaher Ausrichtung); Fensterrahmen-Kanten werden als projizierte Kanten ausgegeben. Tests 26 auf 29, Beweis-SVG mit Fenster + dahinterliegender Wand neu generiert (sichtbares Band exakt im Fensterausschnitt). mesh.rs (3D-Vollkoerper) beruecksichtigt Öffnungen weiterhin nicht — dokumentierte Luecke.
187 lines
11 KiB
Markdown
187 lines
11 KiB
Markdown
# Schnitt/Ansicht-Pipeline: analytische Prismen-Projektion (`render3d::section`)
|
||
|
||
> Gegenstueck zum OCCT-WASM-HLR-Spike (`docs/welle-c-hlr-spike/FEASIBILITY.md`):
|
||
> statt eines generischen CAD-Kernels nutzt dieser Ansatz eine Invariante des
|
||
> Modells, um Schnitt/Ansicht rein analytisch (kein Hidden-Line-Removal-Solver)
|
||
> zu berechnen. Implementiert in `src-tauri/render3d/src/section.rs`.
|
||
|
||
## Ansatz
|
||
|
||
Jedes Bauteil in diesem Modell ist ein **Prisma**: ein 2D-Grundriss-Fussabdruck-
|
||
Polygon (Wand-Band bzw. Decken-Umriss, siehe `mesh.rs`), konstant extrudiert
|
||
ueber ein Hoehenintervall `[z0, z1]`. Diese Einschraenkung — konstanter
|
||
Querschnitt ueber die gesamte Hoehe — macht die Schnittgeometrie trivial im
|
||
Vergleich zu generischem HLR:
|
||
|
||
- **Schnitt einer vertikalen Ebene mit einem Prisma** = 2D-Geraden/Polygon-
|
||
Clipping IM GRUNDRISS (die Schnittebene projiziert im Grundriss auf eine
|
||
Linie) → ein oder mehrere u-Intervalle, in denen die Linie das Fussabdruck-
|
||
Polygon durchquert. Jedes Intervall × `[z0, z1]` ist das Cut-Rechteck. Da die
|
||
Hoehe unabhaengig von der Grundriss-Position ist, ist das Ergebnis **immer
|
||
ein Rechteck**, nie ein Trapez — auch bei diagonalen Wandachsen.
|
||
- **Projektion/Ansicht** (Kanten hinter der Ebene, in Blickrichtung) reduziert
|
||
sich auf ein **2D-Sichtbarkeitsproblem im Grundriss** kombiniert mit einem
|
||
Hoehen-Ueberlappungstest: da alle Seitenflaechen der Prismen vertikal sind,
|
||
genuegt ein Sichtstrahl-Test im Grundriss (verdeckt ein naeheres Prisma-
|
||
Fussabdruckpolygon die Sichtlinie?) plus Ueberlappung der Hoehenintervalle.
|
||
|
||
Das ersetzt einen 66-MB-WASM-CAD-Kernel durch closed-form Arithmetik in reinem
|
||
Rust, ohne zusaetzliches Laufzeitgewicht ueber `render3d` hinaus.
|
||
|
||
## `SectionOutput`-Format
|
||
|
||
Modul: `render3d::section`. Alle Groessen in **Metern**.
|
||
|
||
```rust
|
||
pub struct SectionPlane { pub point: [f32; 3], pub normal: [f32; 3] }
|
||
|
||
pub struct SectionOutput {
|
||
pub cut_polygons: Vec<CutPolygon>, // JSON: "cutPolygons"
|
||
pub visible_edges: Vec<SectionEdge>, // JSON: "visibleEdges"
|
||
pub hidden_edges: Vec<SectionEdge>, // JSON: "hiddenEdges"
|
||
}
|
||
|
||
pub struct CutPolygon { pub component: ComponentRef, pub color: Rgb, pub pts: Vec<[f32; 2]> }
|
||
pub struct SectionEdge { pub component: ComponentRef, pub a: [f32; 2], pub b: [f32; 2] }
|
||
pub struct ComponentRef { pub kind: ComponentKind /* Wall | Slab */, pub index: usize }
|
||
```
|
||
|
||
**Koordinatensystem der Ausgabe (u, v):**
|
||
|
||
- Ursprung: `SectionPlane::point`, projiziert.
|
||
- `u` (horizontal): Strecke entlang der Schnittebene, senkrecht zur
|
||
Blickrichtung, berechnet als `normalize(cross(normal, world_up))` — dieselbe
|
||
rechtshaendige Konvention wie `math::look_at`s `right`-Vektor. Bei den vier
|
||
Standard-Konstruktoren (`looking_plus_x/minus_x/plus_y/minus_y`) ist `u`
|
||
direkt die jeweils andere Grundriss-Achse.
|
||
- `v` (vertikal, "Hoehe"): `v = world.y`, ABSOLUT. Diese Codebasis ist
|
||
durchgaengig Y-up (`types.rs`/`mesh.rs`/`math.rs`: „world.y = Hoehe"); `v`
|
||
folgt bewusst dieser etablierten Konvention statt einer wortwoertlichen
|
||
„world.z"-Lesart, um modulübergreifend konsistent zu bleiben.
|
||
- Nur **vertikale** Schnittebenen (Normale ohne Hoehen-Komponente) sind
|
||
unterstuetzt — Grundriss-/Horizontalschnitte bleiben Sache der bestehenden
|
||
2D-Plan-Pipeline.
|
||
|
||
`cut_polygons` sind bei diesem Modell immer Rechtecke (siehe oben), aber als
|
||
generischer Punktering abgelegt — kompatibel zu `render2d::types::FillPolygon`
|
||
(`pts: Vec<Point>`), dem vorgesehenen Zielformat fuer die spaetere 1:1-
|
||
Uebersetzung in die Plan-/Schnitt-Ansicht.
|
||
|
||
## Oeffnungen (Tueren/Fenster)
|
||
|
||
`WallInput` hat seit diesem Nachtrag ein Feld `openings: Vec<Opening>`
|
||
(`types.rs`), unabhaengig von der reinen Achse/Dicke/Hoehe: je Oeffnung ein
|
||
Intervall entlang der Wandachse (`from`/`to`, Meter ab `start`) plus Bruestungs-
|
||
und Kopfhoehe (`sill`/`height`, relativ zu `base_elevation`). Eine Tuer ist der
|
||
Sonderfall `sill == 0.0`.
|
||
|
||
**Cut-Polygone.** Trifft die Schnittlinie eine Wand exakt im Bereich einer
|
||
Oeffnung, splittet `wall_cut_rectangles` das sonst einzelne Vollrechteck
|
||
`[z0,z1]` in bis zu ZWEI Teil-Rechtecke: Bruestung `[z0, z0+sill]` und Sturz
|
||
`[z0+sill+height, z1]`. Gewaehlt wurde diese Mehrfach-Rechteck-Darstellung
|
||
BEWUSST gegenueber einem Loch-Polygon (Ring mit Aussparung): mehrere einfache,
|
||
konvexe Ringe sind direkt kompatibel zum Zielformat `render2d::types::
|
||
FillPolygon` (nur einfache Ringe, kein Loch-Format), waehrend ein
|
||
Loch-Polygon eine zusaetzliche Datenstruktur (Ring-mit-Loch) noetig gemacht
|
||
haette, die die Zielstruktur (noch) nicht kennt. Ausserhalb einer Oeffnung
|
||
bleibt es beim einzelnen Vollrechteck (Regressionsfall).
|
||
|
||
Die Zuordnung "welche Wandachsen-Position entspricht dieser Schnittposition"
|
||
ist fuer den STANDARDFALL (Schnittebene senkrecht zur Wandachse — der uebliche
|
||
architektonische Wandquerschnitt) EXAKT konstant ueber das Cut-Intervall. Fuer
|
||
schraege Wand/Ebene-Kombinationen wird sie als AFFINE Funktion (`axis_map`)
|
||
exakt mitgefuehrt (keine Naeherung noetig, da die Abbildung linear ist).
|
||
|
||
**Projektion/Ansicht.** Eine Wand mit Oeffnungen bekommt zusaetzlich zur
|
||
Boxen-Drahtsilhouette die vier Rahmenkanten jeder Oeffnung (zwei Leibungen,
|
||
Sturz, Bruestung), berechnet auf der Wandachsen-Mittellinie (dieselbe
|
||
Vereinfachung wie die Bounding-Box-Verdeckung generell — keine eigene
|
||
Dicken-Aufloesung der Leibungsflaeche). Fuer die Verdeckung wird jede
|
||
Wand-Bounding-Box um ihre Oeffnungen als "Loch" reduziert
|
||
(`PrismBounds::opening_voids`): ein anderes (oder dasselbe) Bauteil hinter der
|
||
Wand wird in genau dem (u, Hoehe)-Rechteck der Oeffnung NICHT von dieser Wand
|
||
verdeckt — "Durchblick". Die eigenen Rahmenkanten einer Oeffnung werden dabei
|
||
NICHT gegen das eigene Bauteil auf Verdeckung geprueft (ein Loch kann sich
|
||
nicht selbst verdecken); gegen alle anderen Bauteile gilt die normale
|
||
Verdeckungslogik unveraendert (inkl. der bereits dokumentierten
|
||
Selbstverdeckung der Rueckseite eines ANDEREN Bauteils durch dessen eigene
|
||
Vorderseite).
|
||
|
||
GENAUIGKEIT (bewusste Vereinfachung): die u-Zuordnung fuer den Durchblick ist
|
||
nur DANN aussagekraeftig, wenn die Wandachse hinreichend parallel zur u-Achse
|
||
der Schnittebene steht — das ist GENAU der Fall, in dem man die Wand als
|
||
Elevation/Ansicht von vorne sieht (und ein Fenster ueberhaupt als Durchblick
|
||
sichtbar waere). Steht die Wand naeher an "senkrecht zur u-Achse" (Wand auf
|
||
Kante gesehen bzw. der reine Cut-Fall), wird die Durchblick-Berechnung
|
||
uebersprungen und die Wand bleibt fuer die Verdeckung VOLL UNDURCHSICHTIG
|
||
(konservativ hidden) — Schwelle `AXIS_ALIGN_EPS = 1e-3` in `section.rs`. Ein
|
||
Kante-auf-Kante gesehenes Fenster liefert ohnehin keine sinnvolle
|
||
Durchblick-Flaeche in der Projektion.
|
||
|
||
Getestet in `section::tests` (u. a. `schnitt_durch_fenster_liefert_bruestung_
|
||
und_sturz`, `schnitt_neben_dem_fenster_liefert_volles_rechteck`,
|
||
`ansicht_zeigt_fensterrahmen_sichtbar_und_durchblick_bei_dahinterliegender_
|
||
kante`) und im Beweis-SVG (`examples/section_svg.rs`): Schenkel A der L-Wand
|
||
hat dort ein Fenster, eine kurze zusaetzliche Wand steht dahinter genau im
|
||
Fensterband und erscheint im SVG als durchgezogene (sichtbare) Linie zwischen
|
||
Bruestungs- und Sturzhoehe, gestrichelt (verdeckt) darueber/darunter.
|
||
|
||
## Bekannte Luecken
|
||
|
||
- **Keine Wandknoten-Verschneidung (T-/X-Stoesse).** Wie in `mesh.rs`
|
||
(M1-Stand) werden Waende als eigenstaendige, stumpf abgeschlossene Quader
|
||
behandelt, die sich an Ecken UEBERLAPPEN statt sich zu vereinen (kein
|
||
Miter-Join). Der Schnitt-Extraktor erbt das: an einem L-/T-Knoten kann eine
|
||
Wand als „in die andere eingebettet" verdeckt erscheinen (im Testmodul
|
||
`section::tests` bewusst als reales, erwartetes Verhalten dokumentiert und
|
||
geprueft — kein Bug dieses Moduls, sondern ein Artefakt der fehlenden
|
||
Verschneidungslogik weiter oben in der Pipeline).
|
||
- **Oeffnungen sind NICHT in der Vollkoerper-Extrusion (`mesh.rs`)
|
||
nachgezogen.** `wall_prism`/`wall_cut_rectangles`/die Verdeckung in
|
||
`section.rs` werten `WallInput::openings` vollstaendig aus (siehe Abschnitt
|
||
"Oeffnungen" oben), aber `mesh::extrude_wall` extrudiert weiterhin die volle
|
||
Wandflaeche ohne Aussparung (`mesh.rs`-Moduldoc: „Oeffnungen kommen in
|
||
spaeteren Milestones"). Cut-/Ansichts-Pipeline und 3D-Solid-Mesh sind bis zum
|
||
Nachziehen von `mesh.rs` also bewusst inkonsistent — eine bekannte, separate
|
||
Luecke (nicht Gegenstand dieses Nachtrags).
|
||
- **Oeffnungs-Durchblick nutzt dieselbe achsparallele Bounding-Box-Naeherung**
|
||
wie die allgemeine Verdeckung (kein exaktes Polygon-Clipping der Lochflaeche
|
||
gegen dahinterliegende Kanten) und wird bei Wand-Orientierungen nahe
|
||
"senkrecht zur u-Achse" konservativ auf "kein Durchblick" zurueckgestuft
|
||
(siehe Abschnitt "Oeffnungen" oben, `AXIS_ALIGN_EPS`).
|
||
- **Keine echte Component-/Material-Id.** `WallInput`/`SlabInput` haben aktuell
|
||
keine eigene Id; `ComponentRef` referenziert daher nur `(Art, Index im
|
||
Eingabe-Array)` + die rohe Albedo-Farbe als Material-Platzhalter. Sobald ein
|
||
echtes Ressourcen-/Material-System existiert, sollte `ComponentRef` auf eine
|
||
stabile Id umgestellt werden (Indizes sind nicht stabil ueber Modell-Edits).
|
||
- **Verdeckungstest nutzt Bounding-Boxen, nicht die exakte Fussabdruckform.**
|
||
Fuer Wand-Baender und (i. d. R. konvexe) Deckenumrisse ist das exakt; bei
|
||
stark konkaven oder diagonalen Grundrissen kann es zu Ueberverdeckung
|
||
fuehren (ein Prisma blockiert dann auch Bereiche seiner eigenen „Nischen").
|
||
- **Kein generisches HLR.** Der Ansatz funktioniert NUR, weil alle Bauteile
|
||
Prismen mit konstantem Querschnitt sind. Fuer echte gekrümmte oder nicht-
|
||
prismatische Geometrie (Bogenwaende, Freiformdaecher, …) waere er nicht
|
||
anwendbar — dafuer bliebe der OCCT-Weg (oder eine eigene, generischere
|
||
HLR-Implementierung) die Referenz.
|
||
- **Keine robuste Sonderfallbehandlung fuer Vertices exakt auf der
|
||
Schnittlinie** (Toleranz-basiert, kein Tie-Breaking/Pertubation) — die
|
||
Testszenarien vermeiden diesen Fall bewusst.
|
||
|
||
## Vergleich zum OCCT-WASM-Spike
|
||
|
||
| | OCCT-WASM (`docs/welle-c-hlr-spike`) | Dieser Ansatz (`render3d::section`) |
|
||
|---|---|---|
|
||
| Verfahren | Generisches HLR (`HLRAppli_ReflectLines`) | Analytische Prismen-Projektion |
|
||
| Anwendbarkeit | Beliebige BREP-Geometrie | Nur Prismen (konstanter Querschnitt über Höhe) |
|
||
| Zusaetzliches Gewicht | ~62,8 MB WASM (~19,6 MB gzip), separater Lazy-Chunk | Keins — reines Rust in `render3d`, kein weiteres WASM-Asset |
|
||
| Modul-Init | ~450 ms (Browser) / ~680 ms (Node) | Kein Initialisierungsschritt (kein Fremd-Modul zu laden) |
|
||
| Rechenzeit (L-Wand + Platte) | ~21 ms (reiner HLR-Lauf, gemessen im Browser) | Nicht separat gemessen (kein Millisekunden-Timer im Test); die Operationen sind reine Vektor-/Intervall-Arithmetik über wenige Kanten (O(Anzahl-Prismen × Kanten-pro-Prisma) mit kleinen Konstanten) und liegen der Groessenordnung nach klar unter 1 ms fuer Szenen dieser Groesse — eine belastbare Messung steht noch aus |
|
||
| Sichtbarkeit (verdeckte Kanten) | Exakt (echter HLR-Solver) | Naeherung über Bounding-Boxen im Grundriss + Hoehenintervall-Ueberlappung; exakt fuer achsparallele/konvexe Fussabdruecke |
|
||
| Reifegrad | Isolierter Spike, nicht verdrahtet | Isolierter Spike (dieses Modul), nicht in `render2d`/die Plan-Ansicht verdrahtet |
|
||
|
||
**Fazit:** Fuer den ueberwiegenden Regelfall dieses Projekts (Waende, Decken —
|
||
alles Prismen) ist die analytische Loesung der pragmatischere Weg: kein
|
||
zusaetzliches WASM-Gewicht, keine Fremd-Bibliothek, headless testbar wie der
|
||
Rest von `render3d`. Der OCCT-Weg bleibt die Referenz, falls/sobald echte
|
||
generische Volumenkoerper (Booleans, gekrümmte Flaechen) ins Modell kommen.
|