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:
@@ -13,9 +13,9 @@ use bytemuck::{Pod, Zeroable};
|
||||
use wgpu::util::DeviceExt;
|
||||
|
||||
use crate::math::{view_projection, Mat4};
|
||||
use crate::mesh::{build_model_mesh, build_walls_mesh};
|
||||
use crate::mesh::{build_scene_mesh, build_walls_mesh};
|
||||
use crate::shaders::MESH_WGSL;
|
||||
use crate::types::{Camera, Mesh, SlabInput, WallInput, FLOATS_PER_VERTEX};
|
||||
use crate::types::{Camera, Mesh, MeshInput, SlabInput, WallInput, FLOATS_PER_VERTEX};
|
||||
|
||||
/// Tiefenformat des Z-Puffers (32 Bit Float, ueberall verfuegbar).
|
||||
pub const DEPTH_FORMAT: wgpu::TextureFormat = wgpu::TextureFormat::Depth32Float;
|
||||
@@ -219,9 +219,16 @@ impl Renderer {
|
||||
self.upload_mesh(device, build_walls_mesh(walls));
|
||||
}
|
||||
|
||||
/// Erzeugt das Mesh aus Waenden UND Deckenplatten und laedt die Puffer hoch.
|
||||
pub fn upload_model(&mut self, device: &wgpu::Device, walls: &[WallInput], slabs: &[SlabInput]) {
|
||||
self.upload_mesh(device, build_model_mesh(walls, slabs));
|
||||
/// Erzeugt das Mesh aus Waenden, Deckenplatten UND rohen Kontext-Meshes
|
||||
/// (Terrain/importierte Volumen, siehe `MeshInput`) und laedt die Puffer hoch.
|
||||
pub fn upload_model(
|
||||
&mut self,
|
||||
device: &wgpu::Device,
|
||||
walls: &[WallInput],
|
||||
slabs: &[SlabInput],
|
||||
meshes: &[MeshInput],
|
||||
) {
|
||||
self.upload_mesh(device, build_scene_mesh(walls, slabs, meshes));
|
||||
}
|
||||
|
||||
/// Laedt ein fertiges Mesh in die GPU-Puffer (oder loescht es bei leer).
|
||||
|
||||
@@ -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]
|
||||
|
||||
@@ -58,7 +58,9 @@
|
||||
use std::collections::HashMap;
|
||||
|
||||
use crate::openings::{split_range_by_voids, MIN_SPAN};
|
||||
use crate::types::{Mesh, Opening, Point2, Rgb, SlabInput, WallInput, WallLayer, FLOATS_PER_VERTEX};
|
||||
use crate::types::{
|
||||
Mesh, MeshInput, Opening, Point2, Rgb, SlabInput, WallInput, WallLayer, FLOATS_PER_VERTEX,
|
||||
};
|
||||
|
||||
/// Ein Quader-Mesh besteht aus 6 Seiten (Boden, Deckel, 4 Waende) zu je 2
|
||||
/// Dreiecken = 12 Dreiecke, mit flachen Normalen also 24 Vertices (je Seite 4,
|
||||
@@ -584,6 +586,94 @@ pub fn build_model_mesh(walls: &[WallInput], slabs: &[SlabInput]) -> Mesh {
|
||||
mesh
|
||||
}
|
||||
|
||||
/// Baut die volle Szene: Waende + Deckenplatten (`build_model_mesh`) und haengt
|
||||
/// danach die rohen Kontext-Meshes (Terrain/importierte Volumen, siehe
|
||||
/// `MeshInput`) an — alles in EINEN Puffer. Die Wand-/Slab-Reihenfolge bleibt
|
||||
/// vorne (deterministische Zaehlung fuer die bestehenden Tests); die Kontext-
|
||||
/// Meshes kommen hinten dran.
|
||||
pub fn build_scene_mesh(walls: &[WallInput], slabs: &[SlabInput], meshes: &[MeshInput]) -> Mesh {
|
||||
let mut mesh = build_model_mesh(walls, slabs);
|
||||
for m in meshes {
|
||||
append_context_mesh(&mut mesh, m);
|
||||
}
|
||||
mesh
|
||||
}
|
||||
|
||||
// ── Rohe Kontext-Meshes (Terrain / importierte Volumen) ──────────────────────
|
||||
|
||||
/// Haengt EIN rohes Dreiecks-Mesh (Terrain-TIN oder importiertes Volumen, siehe
|
||||
/// `MeshInput`) an `mesh` an. `positions` ist ein flaches (x,y,z)-Array in
|
||||
/// MODELL-Metern (z = Hoehe), `indices` sind Dreiecks-Indizes. Jede Dreiecks-
|
||||
/// flaeche bekommt eigene Vertices mit ihrer Flaechen-Normale (Flat Shading, wie
|
||||
/// Wand/Slab) — so ist das Ergebnis unabhaengig von geteilten Vertices im
|
||||
/// Quell-Mesh.
|
||||
///
|
||||
/// Koordinaten: model `(x, y, z)` -> world `(x, z, y)` (Hoehe entlang +Y), exakt
|
||||
/// wie `extrude_layer_segment`/`extrude_slab` und `Viewport3D.tsx`.
|
||||
///
|
||||
/// DOPPELSEITIG: jedes Dreieck wird ZWEIMAL emittiert (Vorderseite `a,b,c` mit
|
||||
/// Normale `+n`, Rueckseite `a,c,b` mit `-n`), weil das Quell-Winding von
|
||||
/// Terrain/Import nicht garantiert ist (three.js rendert sie mit `DoubleSide`).
|
||||
/// Da der Pfad Rueckseiten cullt (`gpu.rs`), traegt stets das zum Betrachter
|
||||
/// zeigende Dreieck die korrekte Normale, das andere wird verworfen — keine
|
||||
/// Z-Fighting (beide liegen deckungsgleich, nur eines ist front-facing).
|
||||
pub fn append_context_mesh(mesh: &mut Mesh, input: &MeshInput) {
|
||||
let pos = &input.positions;
|
||||
let vcount = pos.len() / 3;
|
||||
let color = input.effective_color();
|
||||
// world-Position aus Modell (x, y, z=Hoehe): (x, hoehe, y).
|
||||
let vworld = |i: usize| -> [f32; 3] {
|
||||
let b = i * 3;
|
||||
[pos[b], pos[b + 2], pos[b + 1]]
|
||||
};
|
||||
for t in input.indices.chunks_exact(3) {
|
||||
let (ia, ib, ic) = (t[0] as usize, t[1] as usize, t[2] as usize);
|
||||
// Fehlerhafte Indizes robust ueberspringen (kein Panic bei kaputter Eingabe).
|
||||
if ia >= vcount || ib >= vcount || ic >= vcount {
|
||||
continue;
|
||||
}
|
||||
let a = vworld(ia);
|
||||
let b = vworld(ib);
|
||||
let c = vworld(ic);
|
||||
// Flaechen-Normale (b-a) x (c-a); entartete Dreiecke ueberspringen.
|
||||
let ab = [b[0] - a[0], b[1] - a[1], b[2] - a[2]];
|
||||
let ac = [c[0] - a[0], c[1] - a[1], c[2] - a[2]];
|
||||
let gn = [
|
||||
ab[1] * ac[2] - ab[2] * ac[1],
|
||||
ab[2] * ac[0] - ab[0] * ac[2],
|
||||
ab[0] * ac[1] - ab[1] * ac[0],
|
||||
];
|
||||
let len = (gn[0] * gn[0] + gn[1] * gn[1] + gn[2] * gn[2]).sqrt();
|
||||
if len < 1e-12 {
|
||||
continue;
|
||||
}
|
||||
let n = [gn[0] / len, gn[1] / len, gn[2] / len];
|
||||
// Vorderseite + Rueckseite (siehe Moduldoc: doppelseitig).
|
||||
push_ctx_tri(mesh, a, b, c, n, color);
|
||||
push_ctx_tri(mesh, a, c, b, [-n[0], -n[1], -n[2]], color);
|
||||
}
|
||||
}
|
||||
|
||||
/// Haengt EIN Dreieck (drei world-Ecken) mit gegebener Normale + Farbe an —
|
||||
/// Winding wie uebergeben (im Gegensatz zu `push_tri_oriented`, das umsortiert).
|
||||
/// Wird von `append_context_mesh` fuer beide Seiten genutzt.
|
||||
fn push_ctx_tri(
|
||||
mesh: &mut Mesh,
|
||||
a: [f32; 3],
|
||||
b: [f32; 3],
|
||||
c: [f32; 3],
|
||||
normal: [f32; 3],
|
||||
color: Rgb,
|
||||
) {
|
||||
let base = (mesh.verts.len() / FLOATS_PER_VERTEX) as u32;
|
||||
for p in [a, b, c] {
|
||||
mesh.verts.extend_from_slice(&[
|
||||
p[0], p[1], p[2], normal[0], normal[1], normal[2], color[0], color[1], color[2],
|
||||
]);
|
||||
}
|
||||
mesh.indices.extend_from_slice(&[base, base + 1, base + 2]);
|
||||
}
|
||||
|
||||
// ── Deckenplatten (extrudierte Polygone) ─────────────────────────────────────
|
||||
|
||||
/// Signierte Flaeche eines Grundriss-Polygons (Shoelace) in Modell-Koordinaten.
|
||||
|
||||
@@ -128,6 +128,74 @@ fn default_slab_color() -> Rgb {
|
||||
[0.86, 0.86, 0.88]
|
||||
}
|
||||
|
||||
/// Art eines rohen Kontext-Meshes (aus `Project.context`) — bestimmt die
|
||||
/// Default-Einfaerbung, wenn kein explizites `color` mitkommt. Werte sind
|
||||
/// serde-kompatibel mit der TS-Seite (lowercase).
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, Default)]
|
||||
#[serde(rename_all = "lowercase")]
|
||||
pub enum MeshKind {
|
||||
/// Gelaende-TIN (offene Flaeche) — gedecktes Lehm-/Gelaendegruen-grau.
|
||||
Terrain,
|
||||
/// Importiertes Volumen (Gebaeude/DXF) — neutrales Hellgrau.
|
||||
#[default]
|
||||
Imported,
|
||||
}
|
||||
|
||||
impl MeshKind {
|
||||
/// Default-Albedo (RGB 0..1), wenn `MeshInput::color` fehlt. Werte 1:1 aus
|
||||
/// der three.js-Sicht (`src/viewport/Viewport3D.tsx`, `mats.terrain`/
|
||||
/// `mats.mesh`), damit beide Renderer denselben Kontext-Farbton zeigen.
|
||||
pub fn default_color(self) -> Rgb {
|
||||
match self {
|
||||
// 0xa9b0a2 — gedaempftes Lehm-/Gelaendegruen-grau (Clay-Look).
|
||||
MeshKind::Terrain => [0.663, 0.690, 0.635],
|
||||
// 0x9fa6ae — neutrales Hellgrau fuer importierte Meshes.
|
||||
MeshKind::Imported => [0.624, 0.651, 0.682],
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Ein rohes Dreiecks-Mesh aus dem Geo-Kontext (`Project.context`): entweder ein
|
||||
/// Gelaende-TIN (`TerrainMesh`) oder ein importiertes Volumen (`ImportedMesh`,
|
||||
/// z. B. extrudierte OSM-Gebaeude/DXF). Anders als `WallInput`/`SlabInput` bringt
|
||||
/// es die Geometrie bereits FERTIG TRIANGULIERT mit — `mesh.rs` berechnet daraus
|
||||
/// nur noch die Flaechen-Normalen (Flat Shading, wie Wand/Slab) und speist es in
|
||||
/// denselben GPU-Pfad ein.
|
||||
///
|
||||
/// KOORDINATEN wie WallInput/SlabInput: die TS-Seite (`projectToModel3d`) liefert
|
||||
/// `positions` im MODELL-System als flaches `[x, y, z, ...]`-Array (z = Hoehe, in
|
||||
/// Metern); `mesh.rs` bildet jeden Vertex nach world ab: model `(x, y, z)` ->
|
||||
/// world `(x, z, y)`, d. h. Grundriss in der XZ-Ebene, Hoehe entlang +Y (exakt wie
|
||||
/// `Viewport3D.tsx` die rohen positions konsumiert).
|
||||
///
|
||||
/// DOPPELSEITIG: Terrain-/Import-Meshes tragen KEIN garantiertes Winding (die
|
||||
/// three.js-Sicht rendert sie mit `DoubleSide`, siehe `io/geoContext.ts`). Da der
|
||||
/// Nordstern-Pfad Rueckseiten cullt, emittiert `mesh.rs` jedes Dreieck ZWEIMAL
|
||||
/// (beide Windings, entgegengesetzte Normale) — so bleibt die Flaeche aus jeder
|
||||
/// Blickrichtung sichtbar und korrekt beleuchtet.
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct MeshInput {
|
||||
/// Flaches Positions-Array in MODELL-Metern: `[x0,y0,z0, x1,y1,z1, ...]`
|
||||
/// (z = Hoehe). Wird von `mesh.rs` nach world (x, z, y) abgebildet.
|
||||
pub positions: Vec<f32>,
|
||||
/// Dreiecks-Indizes (0-basiert auf die Positions-Vertices, je 3 ein Dreieck).
|
||||
pub indices: Vec<u32>,
|
||||
/// Art des Meshes (Default-Einfaerbung, siehe `MeshKind`). Default `Imported`.
|
||||
#[serde(default)]
|
||||
pub kind: MeshKind,
|
||||
/// Optionale explizite Albedo-Farbe (RGB 0..1). Fehlt sie, greift die
|
||||
/// Default-Farbe je `kind` (siehe `effective_color`).
|
||||
#[serde(default)]
|
||||
pub color: Option<Rgb>,
|
||||
}
|
||||
|
||||
impl MeshInput {
|
||||
/// Effektive Albedo-Farbe: explizit gesetzt (`color`) oder Default je `kind`.
|
||||
pub fn effective_color(&self) -> Rgb {
|
||||
self.color.unwrap_or_else(|| self.kind.default_color())
|
||||
}
|
||||
}
|
||||
|
||||
/// Projektionsart der Kamera. Die three.js-Sicht schaltet zwischen perspektivisch
|
||||
/// (freies Orbit) und orthografisch (die achsparallelen Presets front/top/side um).
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
|
||||
|
||||
@@ -32,7 +32,7 @@ use web_sys::HtmlCanvasElement;
|
||||
use crate::gpu::Renderer;
|
||||
use crate::math::preset_camera;
|
||||
use crate::section::{cut_section, SectionPlane};
|
||||
use crate::types::{Camera, CameraPreset, Projection, SlabInput, WallInput};
|
||||
use crate::types::{Camera, CameraPreset, MeshInput, Projection, SlabInput, WallInput};
|
||||
|
||||
/// JSON-Eingabe des Modells: Waende (bereits in Teilquader zerlegt, siehe
|
||||
/// `projectToWalls3d`) + Deckenplatten. `slabs` optional (leeres Modell ohne
|
||||
@@ -42,6 +42,10 @@ struct ModelInput {
|
||||
walls: Vec<WallInput>,
|
||||
#[serde(default)]
|
||||
slabs: Vec<SlabInput>,
|
||||
/// Rohe Kontext-Meshes (Terrain/importierte Volumen). `#[serde(default)]`
|
||||
/// haelt aeltere Payloads ohne `meshes` gueltig.
|
||||
#[serde(default)]
|
||||
meshes: Vec<MeshInput>,
|
||||
}
|
||||
|
||||
/// Berechnet einen Schnitt/eine Ansicht (`section::cut_section`) gegen das Modell
|
||||
@@ -232,7 +236,7 @@ impl WebModelRenderer {
|
||||
.map_err(|e| JsValue::from_str(&format!("Modell parsen: {e}")))?;
|
||||
self.bounds = model_bounds(&model.walls, &model.slabs);
|
||||
self.renderer
|
||||
.upload_model(&self.device, &model.walls, &model.slabs);
|
||||
.upload_model(&self.device, &model.walls, &model.slabs, &model.meshes);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
|
||||
+11
-6
@@ -92,7 +92,7 @@ pub fn push_walls(value: serde_json::Value) {
|
||||
fn parse_model3d(value: serde_json::Value) -> Result<Model3d, serde_json::Error> {
|
||||
if value.is_array() {
|
||||
let walls = serde_json::from_value::<Vec<WallInput>>(value)?;
|
||||
Ok(Model3d { walls, slabs: Vec::new() })
|
||||
Ok(Model3d { walls, slabs: Vec::new(), meshes: Vec::new() })
|
||||
} else {
|
||||
serde_json::from_value::<Model3d>(value)
|
||||
}
|
||||
@@ -232,7 +232,7 @@ use render3d::gpu::Renderer as Renderer3d;
|
||||
#[cfg(feature = "native3d")]
|
||||
use render3d::math::orbit_eye;
|
||||
#[cfg(feature = "native3d")]
|
||||
use render3d::types::{Camera, Projection, SlabInput, WallInput};
|
||||
use render3d::types::{Camera, MeshInput, Projection, SlabInput, WallInput};
|
||||
#[cfg(feature = "native3d")]
|
||||
use serde::Deserialize;
|
||||
|
||||
@@ -245,6 +245,10 @@ struct Model3d {
|
||||
walls: Vec<WallInput>,
|
||||
#[serde(default)]
|
||||
slabs: Vec<SlabInput>,
|
||||
/// Rohe Kontext-Meshes (Terrain/importierte Volumen). `#[serde(default)]`
|
||||
/// haelt aeltere Payloads ohne `meshes` gueltig.
|
||||
#[serde(default)]
|
||||
meshes: Vec<MeshInput>,
|
||||
}
|
||||
|
||||
#[cfg(feature = "native3d")]
|
||||
@@ -262,7 +266,7 @@ impl GpuState3d {
|
||||
fn new(window: Arc<Window>, model: &Model3d) -> Self {
|
||||
let (surface, device, queue, config) = configure_surface(&window, "3d.device");
|
||||
let mut renderer = Renderer3d::new(&device, config.format);
|
||||
renderer.upload_model(&device, &model.walls, &model.slabs);
|
||||
renderer.upload_model(&device, &model.walls, &model.slabs, &model.meshes);
|
||||
renderer.set_light([6.0, 12.0, 4.0]);
|
||||
Self { surface, device, queue, config, renderer, window }
|
||||
}
|
||||
@@ -315,12 +319,12 @@ fn load_model() -> Model3d {
|
||||
Ok(model) => model,
|
||||
Err(e) => {
|
||||
eprintln!("native3d: Modell-Parse-Fehler ({WALLS_PATH}): {e} — nutze Demo-Waende");
|
||||
Model3d { walls: demo_walls(), slabs: Vec::new() }
|
||||
Model3d { walls: demo_walls(), slabs: Vec::new(), meshes: Vec::new() }
|
||||
}
|
||||
},
|
||||
Err(e) => {
|
||||
eprintln!("native3d: Modell nicht ladbar ({WALLS_PATH}): {e} — nutze Demo-Waende");
|
||||
Model3d { walls: demo_walls(), slabs: Vec::new() }
|
||||
Model3d { walls: demo_walls(), slabs: Vec::new(), meshes: Vec::new() }
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -339,6 +343,7 @@ fn demo_walls() -> Vec<WallInput> {
|
||||
base_elevation: 0.0,
|
||||
color: grey,
|
||||
openings: vec![],
|
||||
layers: None,
|
||||
};
|
||||
vec![
|
||||
mk([0.0, 0.0], [6.0, 0.0]),
|
||||
@@ -715,7 +720,7 @@ impl ApplicationHandler<UserEvent> for App {
|
||||
self.pending3d = Some(model);
|
||||
return;
|
||||
};
|
||||
state.renderer.upload_model(&state.device, &model.walls, &model.slabs);
|
||||
state.renderer.upload_model(&state.device, &model.walls, &model.slabs, &model.meshes);
|
||||
if !self.nav3d {
|
||||
self.orbit = Some(Orbit::framed(&model));
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user