render3d: Öffnungen im 3D-Vollkörper (mesh.rs) — geteilte Zerlegung mit section.rs

- openings.rs (neu): split_range_by_voids() — die bisher nur in section.rs
  liegende Pfeiler/Brüstung/Sturz-Zerlegung, herausgezogen als koordinaten-
  neutraler Helfer. section.rs und mesh.rs teilen jetzt EIN Öffnungsmodell.
- section.rs: wall_cut_rectangles delegiert den Sortier-/Rechteck-Teil an
  openings::split_range_by_voids (plane-spezifisches Clipping bleibt lokal).
- mesh.rs: extrude_wall emittiert nicht mehr einen Vollblock je Wand, sondern
  Teilkörper (Pfeiler in voller Höhe + Brüstung/Sturz je Öffnung) → echtes Loch;
  Tür (sill==0) lässt die Brüstung weg und reicht bis zum Boden. Ohne Öffnung
  exakt EIN Block, byte-identische Geometrie zu vorher (Alt-Tests unverändert grün).

Gates: cargo test 32/32 (29+3 neue), wasm32 --features web check grün, nativ grün.
This commit is contained in:
2026-07-03 18:47:40 +02:00
parent e3f460c9f8
commit d2503c1191
4 changed files with 283 additions and 48 deletions
+10 -27
View File
@@ -107,11 +107,11 @@
//! fuehren, dass eine Wand als "in eine andere eingebettet" verdeckt erscheint
//! (siehe Test `eck_ueberlappung_verdeckt_kante_am_wandknoten` unten) — real,
//! aber ein Artefakt der fehlenden Verschneidungslogik, kein Bug dieses Moduls.
//! - Oeffnungen werden hier (Cut + Projektion) ausgewertet, aber NICHT von der
//! Vollkoerper-Extrusion in `mesh.rs` — die 3D-Solid-Mesh-Erzeugung extrudiert
//! weiterhin die volle Wandflaeche (siehe `mesh.rs`-Moduldoc). Cut-Ansicht und
//! 3D-Solid-Ansicht sind bis zu diesem Nachtrag also bewusst inkonsistent;
//! das Nachziehen von `mesh.rs` ist eine bekannte, separate Luecke.
//! - Oeffnungen werden hier (Cut + Projektion) UND von der Vollkoerper-
//! Extrusion in `mesh.rs` ausgewertet (`mesh::wall_solid_segments`) — beide
//! nutzen dieselbe Bruestungs-/Sturz-Zerlegung (`crate::openings::
//! split_range_by_voids`), damit Cut-Ansicht und 3D-Solid-Ansicht
//! konsistent bleiben.
//! - 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
@@ -508,28 +508,11 @@ fn wall_cut_rectangles(
if voids.is_empty() {
return vec![(u_lo, u_hi, prism.z0, prism.z1)];
}
voids.sort_by(|a, b| a.0.partial_cmp(&b.0).unwrap());
let mut rects = Vec::new();
let mut cursor = u_lo;
for (v_lo, v_hi, op) in &voids {
if *v_lo > cursor + MIN_SPAN {
rects.push((cursor, *v_lo, prism.z0, prism.z1));
}
let sill_abs = prism.z0 + op.sill.max(0.0);
let head_abs = (sill_abs + op.height).min(prism.z1);
if sill_abs - prism.z0 > MIN_SPAN {
rects.push((*v_lo, *v_hi, prism.z0, sill_abs)); // Bruestung
}
if prism.z1 - head_abs > MIN_SPAN {
rects.push((*v_lo, *v_hi, head_abs, prism.z1)); // Sturz
}
cursor = cursor.max(*v_hi);
}
if u_hi > cursor + MIN_SPAN {
rects.push((cursor, u_hi, prism.z0, prism.z1));
}
rects
// Bruestungs-/Sturz-Zerlegung ist ab hier koordinatenraum-neutral (nur noch
// u-Intervall + Hoehe + geclippte Voids) -> gemeinsame Basis mit
// `mesh::wall_solid_segments` (siehe `crate::openings`-Moduldoc), damit
// Cut-Ansicht und 3D-Solid-Mesh garantiert dasselbe Oeffnungsmodell nutzen.
crate::openings::split_range_by_voids(u_lo, u_hi, prism.z0, prism.z1, voids)
}
// ─────────────────────────────────────────────────────────────────────────────