render3d: Schnitt-Modul — Cut-Polygone + sichtbare/verdeckte Kanten aus Prismen
Analytische Eigen-Engine-Alternative zum OCCT-HLR-Spike: Schnittebene (Punkt+Normale) gegen extrudierte Fussabdruck-Prismen; Cut-Polygone in (u,v)-Schnittkoordinaten mit Komponenten-Referenz (spaetere Schraffur), Projektion der dahinterliegenden Kanten mit Verdeckungstest, getrennt visible/hidden. SectionOutput serde-serialisierbar (Meter). 26 Tests, Example section_svg schreibt docs/welle-c-hlr-spike/section-engine-proof.svg (L-Wand+Bodenplatte: Poché, durchgezogene sichtbare, gestrichelte verdeckte Kanten). Offene Punkte in docs/design/engine-section-pipeline.md.
This commit is contained in:
@@ -0,0 +1,119 @@
|
||||
# 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.
|
||||
|
||||
## 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).
|
||||
- **Keine Oeffnungs-Aussparungen.** `WallInput`/`SlabInput` kennen (noch) keine
|
||||
Tueren/Fenster; der Schnitt schneidet daher immer die volle Wandflaeche.
|
||||
Sobald Oeffnungen im Modell ankommen, muss `wall_prism`/die Cut-Polygon-
|
||||
Bildung sie als Aussparungen (Lochpolygone bzw. mehrere Teil-Rechtecke pro
|
||||
Hoehenband) beruecksichtigen.
|
||||
- **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.
|
||||
@@ -0,0 +1,26 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="576" height="420" viewBox="0 0 576.0 420.0">
|
||||
<rect x="0" y="0" width="576.0" height="420.0" fill="#ffffff"/>
|
||||
<polygon points="48.00,372.00 528.00,372.00 528.00,348.00 48.00,348.00" fill="#c9d2d6" stroke="#2b2b2b" stroke-width="1.2"/>
|
||||
<line x1="108.00" y1="348.00" x2="108.00" y2="48.00" stroke="#111111" stroke-width="1.6"/>
|
||||
<line x1="468.00" y1="348.00" x2="468.00" y2="48.00" stroke="#111111" stroke-width="1.6"/>
|
||||
<line x1="108.00" y1="348.00" x2="468.00" y2="348.00" stroke="#111111" stroke-width="1.6"/>
|
||||
<line x1="108.00" y1="48.00" x2="468.00" y2="48.00" stroke="#111111" stroke-width="1.6"/>
|
||||
<line x1="480.00" y1="348.00" x2="480.00" y2="48.00" stroke="#111111" stroke-width="1.6"/>
|
||||
<line x1="468.00" y1="348.00" x2="480.00" y2="348.00" stroke="#111111" stroke-width="1.6"/>
|
||||
<line x1="468.00" y1="48.00" x2="480.00" y2="48.00" stroke="#111111" stroke-width="1.6"/>
|
||||
<line x1="468.00" y1="348.00" x2="468.00" y2="48.00" stroke="#888888" stroke-width="1.0" stroke-dasharray="5,4"/>
|
||||
<line x1="108.00" y1="348.00" x2="108.00" y2="48.00" stroke="#888888" stroke-width="1.0" stroke-dasharray="5,4"/>
|
||||
<line x1="468.00" y1="348.00" x2="108.00" y2="348.00" stroke="#888888" stroke-width="1.0" stroke-dasharray="5,4"/>
|
||||
<line x1="468.00" y1="48.00" x2="108.00" y2="48.00" stroke="#888888" stroke-width="1.0" stroke-dasharray="5,4"/>
|
||||
<line x1="480.00" y1="348.00" x2="480.00" y2="48.00" stroke="#888888" stroke-width="1.0" stroke-dasharray="5,4"/>
|
||||
<line x1="456.00" y1="348.00" x2="456.00" y2="48.00" stroke="#888888" stroke-width="1.0" stroke-dasharray="5,4"/>
|
||||
<line x1="456.00" y1="348.00" x2="456.00" y2="48.00" stroke="#888888" stroke-width="1.0" stroke-dasharray="5,4"/>
|
||||
<line x1="480.00" y1="348.00" x2="456.00" y2="348.00" stroke="#888888" stroke-width="1.0" stroke-dasharray="5,4"/>
|
||||
<line x1="480.00" y1="48.00" x2="456.00" y2="48.00" stroke="#888888" stroke-width="1.0" stroke-dasharray="5,4"/>
|
||||
<line x1="456.00" y1="348.00" x2="468.00" y2="348.00" stroke="#888888" stroke-width="1.0" stroke-dasharray="5,4"/>
|
||||
<line x1="456.00" y1="48.00" x2="468.00" y2="48.00" stroke="#888888" stroke-width="1.0" stroke-dasharray="5,4"/>
|
||||
<line x1="48.00" y1="372.00" x2="48.00" y2="348.00" stroke="#888888" stroke-width="1.0" stroke-dasharray="5,4"/>
|
||||
<line x1="528.00" y1="372.00" x2="528.00" y2="348.00" stroke="#888888" stroke-width="1.0" stroke-dasharray="5,4"/>
|
||||
<line x1="48.00" y1="372.00" x2="528.00" y2="372.00" stroke="#888888" stroke-width="1.0" stroke-dasharray="5,4"/>
|
||||
<line x1="48.00" y1="348.00" x2="528.00" y2="348.00" stroke="#888888" stroke-width="1.0" stroke-dasharray="5,4"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 2.6 KiB |
Reference in New Issue
Block a user