Nordstern: Geo-Kontext-Meshes (Terrain/Import) rendern

Bisher zeigte nur three.js importierte Gebaeude/DXF-Meshes und das
Terrain-TIN (Project.context). Neuer MeshInput-Typ + Kontext-Mesh-Pfad
in render3d (Flat-Normalen, doppelseitig gegen unbekanntes Winding),
projectToModel3d speist project.context jetzt in beide Renderer
(nativ + WASM) ein. Nebenbei: fehlendes layers-Feld in demo_walls()
behoben, das den native3d-Feature-Build zuvor schon brach.
This commit is contained in:
2026-07-04 04:38:00 +02:00
parent 19d002d403
commit 9f6d4e8858
7 changed files with 339 additions and 19 deletions
+98 -2
View File
@@ -34,12 +34,16 @@ pub use math::{
look_at, orbit_eye, orthographic, perspective, preset_camera, projection_matrix,
view_matrix, view_projection, Mat4,
};
pub use mesh::{build_model_mesh, build_walls_mesh, extrude_slab, extrude_wall, triangulate};
pub use mesh::{
append_context_mesh, build_model_mesh, build_scene_mesh, build_walls_mesh, extrude_slab,
extrude_wall, triangulate,
};
pub use section::{
cut_section, ComponentKind, ComponentRef, CutPolygon, SectionEdge, SectionOutput, SectionPlane,
};
pub use types::{
Camera, CameraPreset, Mesh, Point2, Projection, Rgb, SlabInput, WallInput, FLOATS_PER_VERTEX,
Camera, CameraPreset, Mesh, MeshInput, MeshKind, Point2, Projection, Rgb, SlabInput, WallInput,
FLOATS_PER_VERTEX,
};
// --- Tests: Mesh-Erzeugung (Muster wie render2d/tessellate) -------------------
@@ -624,6 +628,98 @@ mod tests {
assert_eq!(mesh.vertex_count(), 0);
}
// --- Rohe Kontext-Meshes (Terrain / importierte Volumen) ------------------
use super::mesh::{append_context_mesh, build_scene_mesh};
use super::types::{MeshInput, MeshKind};
/// Ein flaches Dreieck auf Modell-Hoehe `z` (Modell x,y,z=Hoehe).
fn flat_context_tri(z: f32, kind: MeshKind, color: Option<[f32; 3]>) -> MeshInput {
MeshInput {
positions: vec![0.0, 0.0, z, 1.0, 0.0, z, 0.0, 1.0, z],
indices: vec![0, 1, 2],
kind,
color,
}
}
#[test]
fn context_mesh_ist_doppelseitig_und_mappt_hoehe_nach_y() {
let mut mesh = Mesh::default();
append_context_mesh(&mut mesh, &flat_context_tri(5.0, MeshKind::Terrain, None));
// Ein Quell-Dreieck -> zwei (Vorder-/Rueckseite) = 6 Vertices.
assert_eq!(mesh.triangle_count(), 2);
assert_eq!(mesh.vertex_count(), 6);
// model (x,y,z) -> world (x, z, y): die Hoehe z=5 liegt auf world.y=5.
let (min, max) = mesh.bounds();
assert!((min[1] - 5.0).abs() < 1e-5 && (max[1] - 5.0).abs() < 1e-5, "flach auf y=5");
// Doppelseitig: eine Normale +Y, eine Y.
let mut saw_up = false;
let mut saw_down = false;
for i in 0..mesh.vertex_count() {
let ny = mesh.verts[i * FLOATS_PER_VERTEX + 4];
if ny > 0.5 {
saw_up = true;
}
if ny < -0.5 {
saw_down = true;
}
}
assert!(saw_up && saw_down, "Vorder- UND Rueckseite (±Y)");
}
#[test]
fn context_mesh_default_farbe_je_kind() {
// Ohne explizite Farbe greift die kind-Default-Farbe (three.js mats.*).
let mut mesh = Mesh::default();
append_context_mesh(&mut mesh, &flat_context_tri(0.0, MeshKind::Terrain, None));
let c = [mesh.verts[6], mesh.verts[7], mesh.verts[8]];
assert_eq!(c, MeshKind::Terrain.default_color());
// Explizite Farbe schlaegt den Default.
let mut mesh2 = Mesh::default();
append_context_mesh(&mut mesh2, &flat_context_tri(0.0, MeshKind::Imported, Some([0.1, 0.2, 0.3])));
assert_eq!([mesh2.verts[6], mesh2.verts[7], mesh2.verts[8]], [0.1, 0.2, 0.3]);
}
#[test]
fn context_mesh_ueberspringt_kaputte_indizes_und_entartung() {
let mut mesh = Mesh::default();
// Index 9 out-of-range (nur 3 Vertices) -> Dreieck verworfen, kein Panic.
append_context_mesh(
&mut mesh,
&MeshInput {
positions: vec![0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 1.0, 0.0],
indices: vec![0, 1, 9],
kind: MeshKind::Imported,
color: None,
},
);
assert_eq!(mesh.vertex_count(), 0);
// Kollineares (entartetes) Dreieck -> keine Normale, verworfen.
append_context_mesh(
&mut mesh,
&MeshInput {
positions: vec![0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 2.0, 0.0, 0.0],
indices: vec![0, 1, 2],
kind: MeshKind::Imported,
color: None,
},
);
assert_eq!(mesh.vertex_count(), 0);
}
#[test]
fn build_scene_mesh_haengt_kontext_meshes_hinten_an() {
let model = build_model_mesh(&[wall_x(4.0, 0.2, 2.6)], &[square_slab(4.0, 2.6, 2.85)]);
let scene = build_scene_mesh(
&[wall_x(4.0, 0.2, 2.6)],
&[square_slab(4.0, 2.6, 2.85)],
&[flat_context_tri(0.0, MeshKind::Terrain, None)],
);
// Szene = Modell (Wand+Slab) + 6 Kontext-Vertices (doppelseitiges Dreieck).
assert_eq!(scene.vertex_count(), model.vertex_count() + 6);
}
// --- Kamera / Matrizen ----------------------------------------------------
#[test]