Nordstern-3D: View-Styles wireframe + hidden-line + schönere Schattierung
- Neue set_render_style(style)-Methode: shaded/white/wireframe/hidden (textured fällt vorerst auf shaded zurück, bis die Textur-Pipeline steht). Voll durchverdrahtet vom TopBar-Darstellungs-Dropdown zum Nordstern-Viewport (setRenderStyle statt nur setWhiteMode). - Feature-Edge-Extraktion (edges.rs): Kanten aus dem Mesh, dedupliziert + Crease-Erkennung (nur Silhouette/Knickkanten, keine Triangulierungs- Diagonalen) → sauberes Architektur-Drahtgitter. Reuse der Grid- LineList-Pipeline. - wireframe = nur Kanten; hidden = flach-weisse Flächen (Depth-Bias) + sichtbare Kanten obenauf (klassischer Hidden-Line-Look). - Grundschattierung: dezentes Gegenlicht (Fill-Light), damit abgewandte Flächen nicht mehr in Schwarz absaufen.
This commit is contained in:
@@ -0,0 +1,250 @@
|
|||||||
|
// Modell-Kanten (Feature-Edges) fuer die Draufsicht-Stile "wireframe" und
|
||||||
|
// "hidden" des 3D-Renderers.
|
||||||
|
//
|
||||||
|
// Leitet aus einem fertigen Dreiecks-`Mesh` (types::Mesh, interleaved
|
||||||
|
// [pos, normal, color]) eine schlanke Kantenliste ab und liefert sie im
|
||||||
|
// GLEICHEN Vertex-Format wie das Bodengitter (grid.rs): interleaved
|
||||||
|
// [px,py,pz, r,g,b], je 2 Vertices = 1 Liniensegment (LineList). Dadurch teilt
|
||||||
|
// sich die Kanten-Zeichnung Shader (GRID_WGSL) und Vertex-Layout mit dem Grid;
|
||||||
|
// die GPU-Schicht (gpu.rs) laedt die Vertices einmalig bei `set_model` in einen
|
||||||
|
// Linien-Vertex-Buffer und cached sie (nicht je Frame).
|
||||||
|
//
|
||||||
|
// FEATURE-EDGE-STATT-ALLE-DREIECKSKANTEN: Wuerde man jede Dreieckskante zeichnen,
|
||||||
|
// erschienen auch die Triangulierungs-Diagonalen jeder rechteckigen Flaeche
|
||||||
|
// (jede Wand-/Deckenseite ist aus 2 Dreiecken) — ein unruhiges Gitter. Statt
|
||||||
|
// dessen dedupliziert diese Schicht geteilte Kanten UND unterdrueckt Kanten
|
||||||
|
// zwischen zwei koplanaren Dreiecken (Flaechen-Innendiagonalen): gezeichnet
|
||||||
|
// werden nur Silhouetten-/Randkanten (Kante gehoert nur einem Dreieck) und
|
||||||
|
// Knickkanten ("Crease": die beiden angrenzenden Dreiecke stehen deutlich
|
||||||
|
// zueinander gewinkelt). Ergebnis ist ein sauberer Architektur-Drahtgitter-/
|
||||||
|
// Hidden-Line-Look statt einer Dreiecks-Suppe.
|
||||||
|
//
|
||||||
|
// GPU-frei (nur `Vec<f32>`), damit headless per `cargo test` pruefbar (Muster:
|
||||||
|
// grid.rs / mesh.rs / section.rs).
|
||||||
|
|
||||||
|
use std::collections::HashMap;
|
||||||
|
|
||||||
|
use crate::types::{Mesh, FLOATS_PER_VERTEX};
|
||||||
|
|
||||||
|
/// f32 je Kanten-Vertex im interleaved Puffer: 3 Position (world) + 3 Farbe.
|
||||||
|
/// Bewusst identisch zu `grid::GRID_FLOATS_PER_VERTEX`, damit dieselbe
|
||||||
|
/// LineList-Pipeline/derselbe Shader (GRID_WGSL) beide zeichnen kann.
|
||||||
|
pub const EDGE_FLOATS_PER_VERTEX: usize = 6;
|
||||||
|
|
||||||
|
/// Farbe der Modell-Kanten (RGB 0..1). Dezentes, dunkles Anthrazit — deutlich
|
||||||
|
/// genug fuer den Hidden-Line-Look, ohne hart schwarz zu wirken.
|
||||||
|
pub const EDGE_COLOR: [f32; 3] = [0.16, 0.16, 0.18];
|
||||||
|
|
||||||
|
/// Quantisierungsfaktor fuer den Kanten-Schluessel: Positionen werden auf 0.1 mm
|
||||||
|
/// (1e4 pro Meter) gerundet, damit geometrisch zusammenfallende Endpunkte aus
|
||||||
|
/// verschiedenen (pro Flaeche duplizierten) Vertices denselben Schluessel teilen.
|
||||||
|
const QUANT: f32 = 1.0e4;
|
||||||
|
|
||||||
|
/// Kosinus des Crease-Winkels: liegen die Normalen zweier angrenzender Dreiecke
|
||||||
|
/// enger beieinander (dot > COS), gelten sie als koplanar → die geteilte Kante
|
||||||
|
/// wird NICHT gezeichnet (Flaechen-Innendiagonale). Darunter = Knickkante →
|
||||||
|
/// zeichnen. cos(15°) ≈ 0.966: Ebenen-Diagonalen (dot≈1) fallen weg, echte
|
||||||
|
/// Bauteil-Kanten (≥ ~90°) bleiben.
|
||||||
|
const CREASE_COS: f32 = 0.966;
|
||||||
|
|
||||||
|
/// Ganzzahliger Positions-Schluessel (quantisiert) fuer die Kanten-Deduplizierung.
|
||||||
|
type Key = [i64; 3];
|
||||||
|
|
||||||
|
fn key_of(p: [f32; 3]) -> Key {
|
||||||
|
[
|
||||||
|
(p[0] * QUANT).round() as i64,
|
||||||
|
(p[1] * QUANT).round() as i64,
|
||||||
|
(p[2] * QUANT).round() as i64,
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Sammlung je Kante: die (erste gesehene) Position beider Endpunkte plus die
|
||||||
|
/// Normalen aller angrenzenden Dreiecke — daraus entscheidet sich Rand/Knick.
|
||||||
|
struct EdgeRec {
|
||||||
|
p0: [f32; 3],
|
||||||
|
p1: [f32; 3],
|
||||||
|
normals: Vec<[f32; 3]>,
|
||||||
|
}
|
||||||
|
|
||||||
|
fn sub(a: [f32; 3], b: [f32; 3]) -> [f32; 3] {
|
||||||
|
[a[0] - b[0], a[1] - b[1], a[2] - b[2]]
|
||||||
|
}
|
||||||
|
|
||||||
|
fn cross(a: [f32; 3], b: [f32; 3]) -> [f32; 3] {
|
||||||
|
[
|
||||||
|
a[1] * b[2] - a[2] * b[1],
|
||||||
|
a[2] * b[0] - a[0] * b[2],
|
||||||
|
a[0] * b[1] - a[1] * b[0],
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
fn normalize(v: [f32; 3]) -> [f32; 3] {
|
||||||
|
let l = (v[0] * v[0] + v[1] * v[1] + v[2] * v[2]).sqrt();
|
||||||
|
if l <= 1e-12 {
|
||||||
|
[0.0, 0.0, 0.0]
|
||||||
|
} else {
|
||||||
|
[v[0] / l, v[1] / l, v[2] / l]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn dot(a: [f32; 3], b: [f32; 3]) -> f32 {
|
||||||
|
a[0] * b[0] + a[1] * b[1] + a[2] * b[2]
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Baut die Feature-Edge-LineList aus dem Mesh (siehe Moduldoc).
|
||||||
|
///
|
||||||
|
/// Ausgabe: interleaved `[px,py,pz, r,g,b, ...]`, je 2 Vertices = 1 Segment.
|
||||||
|
/// Leerer Vektor bei leerem/degeneriertem Mesh.
|
||||||
|
pub fn build_mesh_edges(mesh: &Mesh) -> Vec<f32> {
|
||||||
|
if mesh.indices.len() < 3 {
|
||||||
|
return Vec::new();
|
||||||
|
}
|
||||||
|
let pos = |i: u32| -> [f32; 3] {
|
||||||
|
let b = i as usize * FLOATS_PER_VERTEX;
|
||||||
|
[mesh.verts[b], mesh.verts[b + 1], mesh.verts[b + 2]]
|
||||||
|
};
|
||||||
|
|
||||||
|
// Kanten-Schluessel (kanonisch sortiertes Endpunkt-Paar) -> Datensatz.
|
||||||
|
let mut edges: HashMap<(Key, Key), EdgeRec> = HashMap::new();
|
||||||
|
|
||||||
|
for tri in mesh.indices.chunks_exact(3) {
|
||||||
|
let (a, b, c) = (pos(tri[0]), pos(tri[1]), pos(tri[2]));
|
||||||
|
// Geometrische Flaechennormale (robuster als die pro-Vertex-Normalen).
|
||||||
|
let n = normalize(cross(sub(b, a), sub(c, a)));
|
||||||
|
if n == [0.0, 0.0, 0.0] {
|
||||||
|
continue; // entartetes Dreieck ueberspringen
|
||||||
|
}
|
||||||
|
for (u, v) in [(a, b), (b, c), (c, a)] {
|
||||||
|
let (ku, kv) = (key_of(u), key_of(v));
|
||||||
|
if ku == kv {
|
||||||
|
continue; // Null-Laengen-Kante
|
||||||
|
}
|
||||||
|
// Kanonische Reihenfolge, damit u->v und v->u denselben Schluessel haben.
|
||||||
|
let (key, p0, p1) = if ku <= kv {
|
||||||
|
((ku, kv), u, v)
|
||||||
|
} else {
|
||||||
|
((kv, ku), v, u)
|
||||||
|
};
|
||||||
|
edges
|
||||||
|
.entry(key)
|
||||||
|
.or_insert_with(|| EdgeRec {
|
||||||
|
p0,
|
||||||
|
p1,
|
||||||
|
normals: Vec::new(),
|
||||||
|
})
|
||||||
|
.normals
|
||||||
|
.push(n);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut verts: Vec<f32> = Vec::new();
|
||||||
|
let mut push = |p: [f32; 3]| {
|
||||||
|
verts.push(p[0]);
|
||||||
|
verts.push(p[1]);
|
||||||
|
verts.push(p[2]);
|
||||||
|
verts.push(EDGE_COLOR[0]);
|
||||||
|
verts.push(EDGE_COLOR[1]);
|
||||||
|
verts.push(EDGE_COLOR[2]);
|
||||||
|
};
|
||||||
|
|
||||||
|
for rec in edges.values() {
|
||||||
|
if should_draw(&rec.normals) {
|
||||||
|
push(rec.p0);
|
||||||
|
push(rec.p1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
verts
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Entscheidet, ob eine Kante gezeichnet wird: Randkante (nur ein Dreieck) oder
|
||||||
|
/// Knickkante (zwei angrenzende Dreiecke stehen ueber dem Crease-Winkel zuein-
|
||||||
|
/// ander). Koplanare geteilte Kanten (Flaechendiagonalen) werden unterdrueckt.
|
||||||
|
fn should_draw(normals: &[[f32; 3]]) -> bool {
|
||||||
|
match normals.len() {
|
||||||
|
0 => false,
|
||||||
|
1 => true, // Silhouette/Rand: gehoert nur einem Dreieck
|
||||||
|
_ => {
|
||||||
|
// Zeichnen, sobald irgendein Normalen-Paar deutlich abknickt.
|
||||||
|
for i in 0..normals.len() {
|
||||||
|
for j in (i + 1)..normals.len() {
|
||||||
|
if dot(normals[i], normals[j]) < CREASE_COS {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
/// Hilfsbau: Mesh aus (pos, dummy-normal, dummy-color) + Indizes.
|
||||||
|
fn mesh(positions: &[[f32; 3]], indices: &[u32]) -> Mesh {
|
||||||
|
let mut verts = Vec::new();
|
||||||
|
for p in positions {
|
||||||
|
verts.extend_from_slice(&[p[0], p[1], p[2], 0.0, 1.0, 0.0, 0.8, 0.8, 0.8]);
|
||||||
|
}
|
||||||
|
Mesh {
|
||||||
|
verts,
|
||||||
|
indices: indices.to_vec(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn segment_count(v: &[f32]) -> usize {
|
||||||
|
v.len() / EDGE_FLOATS_PER_VERTEX / 2
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn single_triangle_has_three_boundary_edges() {
|
||||||
|
let m = mesh(
|
||||||
|
&[[0.0, 0.0, 0.0], [1.0, 0.0, 0.0], [0.0, 0.0, 1.0]],
|
||||||
|
&[0, 1, 2],
|
||||||
|
);
|
||||||
|
let e = build_mesh_edges(&m);
|
||||||
|
assert_eq!(segment_count(&e), 3);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn coplanar_quad_drops_shared_diagonal() {
|
||||||
|
// Ebenes Quad aus 2 Dreiecken in der XZ-Ebene: geteilte Diagonale ist
|
||||||
|
// koplanar -> weg; es bleiben die 4 Umriss-Kanten.
|
||||||
|
let m = mesh(
|
||||||
|
&[
|
||||||
|
[0.0, 0.0, 0.0],
|
||||||
|
[1.0, 0.0, 0.0],
|
||||||
|
[1.0, 0.0, 1.0],
|
||||||
|
[0.0, 0.0, 1.0],
|
||||||
|
],
|
||||||
|
&[0, 1, 2, 0, 2, 3],
|
||||||
|
);
|
||||||
|
let e = build_mesh_edges(&m);
|
||||||
|
assert_eq!(segment_count(&e), 4);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn folded_quad_keeps_crease() {
|
||||||
|
// Zwei Dreiecke im 90°-Knick teilen die Kante (0,0,0)-(1,0,0):
|
||||||
|
// Normalen stehen senkrecht -> Knickkante bleibt erhalten.
|
||||||
|
let m = mesh(
|
||||||
|
&[
|
||||||
|
[0.0, 0.0, 0.0],
|
||||||
|
[1.0, 0.0, 0.0],
|
||||||
|
[1.0, 0.0, 1.0], // erstes Dreieck in XZ (Normale +Y)
|
||||||
|
[1.0, 1.0, 0.0], // zweites Dreieck kippt nach oben (Normale +Z)
|
||||||
|
],
|
||||||
|
&[0, 1, 2, 0, 3, 1],
|
||||||
|
);
|
||||||
|
let e = build_mesh_edges(&m);
|
||||||
|
// geteilte Knickkante + je 2 Randkanten pro Dreieck = 5 Segmente.
|
||||||
|
assert_eq!(segment_count(&e), 5);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn empty_mesh_yields_no_edges() {
|
||||||
|
let m = Mesh::default();
|
||||||
|
assert!(build_mesh_edges(&m).is_empty());
|
||||||
|
}
|
||||||
|
}
|
||||||
+204
-57
@@ -12,12 +12,66 @@
|
|||||||
use bytemuck::{Pod, Zeroable};
|
use bytemuck::{Pod, Zeroable};
|
||||||
use wgpu::util::DeviceExt;
|
use wgpu::util::DeviceExt;
|
||||||
|
|
||||||
|
use crate::edges::{build_mesh_edges, EDGE_FLOATS_PER_VERTEX};
|
||||||
use crate::grid::{build_ground_grid, GRID_FLOATS_PER_VERTEX};
|
use crate::grid::{build_ground_grid, GRID_FLOATS_PER_VERTEX};
|
||||||
use crate::math::{view_projection, Mat4};
|
use crate::math::{view_projection, Mat4};
|
||||||
use crate::mesh::{build_scene_mesh, build_walls_mesh};
|
use crate::mesh::{build_scene_mesh, build_walls_mesh};
|
||||||
use crate::shaders::{GRID_WGSL, MESH_WGSL};
|
use crate::shaders::{GRID_WGSL, MESH_WGSL};
|
||||||
use crate::types::{Camera, Mesh, MeshInput, SlabInput, WallInput, FLOATS_PER_VERTEX};
|
use crate::types::{Camera, Mesh, MeshInput, SlabInput, WallInput, FLOATS_PER_VERTEX};
|
||||||
|
|
||||||
|
/// Darstellungsart des 3D-Renderers (Oberleiste: shaded/white/textured/
|
||||||
|
/// wireframe/hidden). Bestimmt, welche Pipelines je Frame gezeichnet werden und
|
||||||
|
/// welchen `mode.x`-Wert der Mesh-Shader bekommt.
|
||||||
|
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
||||||
|
pub enum RenderStyle {
|
||||||
|
/// Beleuchtete Bauteilfarben (Default).
|
||||||
|
Shaded,
|
||||||
|
/// Clay-Look: einheitlich helles Material, beleuchtet.
|
||||||
|
White,
|
||||||
|
/// Mangels Textur-Pipeline VORERST identisch zu `Shaded` behandelt (kein Fake).
|
||||||
|
Textured,
|
||||||
|
/// Nur Modell-Kanten (Feature-Edges), keine gefuellten Flaechen.
|
||||||
|
Wireframe,
|
||||||
|
/// Hidden-Line: flach-weisse Flaechen (fuellen den Depth-Buffer) + dunkle
|
||||||
|
/// Kanten obenauf, tiefengetestet -> nur sichtbare Kanten erscheinen.
|
||||||
|
Hidden,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl RenderStyle {
|
||||||
|
/// Parst den API-String; unbekannte Werte fallen auf `Shaded` zurueck.
|
||||||
|
pub fn from_str_or_shaded(s: &str) -> Self {
|
||||||
|
match s {
|
||||||
|
"white" => RenderStyle::White,
|
||||||
|
"textured" => RenderStyle::Textured,
|
||||||
|
"wireframe" => RenderStyle::Wireframe,
|
||||||
|
"hidden" => RenderStyle::Hidden,
|
||||||
|
_ => RenderStyle::Shaded,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// `mode.x`-Wert fuer den Mesh-Shader (siehe shaders.rs): 0 = Material/shaded
|
||||||
|
/// (auch textured), 1 = weiss/Clay, 2 = flach-weisse Hidden-Line-Flaechen.
|
||||||
|
fn mesh_mode(self) -> f32 {
|
||||||
|
match self {
|
||||||
|
RenderStyle::White => 1.0,
|
||||||
|
RenderStyle::Hidden => 2.0,
|
||||||
|
// Shaded, Textured, Wireframe (Flaechen werden bei Wireframe eh nicht
|
||||||
|
// gezeichnet) -> Material.
|
||||||
|
_ => 0.0,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Werden in diesem Stil gefuellte Flaechen gezeichnet?
|
||||||
|
fn draws_faces(self) -> bool {
|
||||||
|
!matches!(self, RenderStyle::Wireframe)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Werden in diesem Stil Modell-Kanten gezeichnet?
|
||||||
|
fn draws_edges(self) -> bool {
|
||||||
|
matches!(self, RenderStyle::Wireframe | RenderStyle::Hidden)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Tiefenformat des Z-Puffers (32 Bit Float, ueberall verfuegbar).
|
/// Tiefenformat des Z-Puffers (32 Bit Float, ueberall verfuegbar).
|
||||||
pub const DEPTH_FORMAT: wgpu::TextureFormat = wgpu::TextureFormat::Depth32Float;
|
pub const DEPTH_FORMAT: wgpu::TextureFormat = wgpu::TextureFormat::Depth32Float;
|
||||||
|
|
||||||
@@ -69,8 +123,10 @@ struct MeshBuffers {
|
|||||||
index_count: u32,
|
index_count: u32,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// GPU-seitiger Vertex-Buffer des Bodengitters (LineList, kein Indexpuffer).
|
/// GPU-seitiger Vertex-Buffer einer LineList (kein Indexpuffer). Format
|
||||||
struct GridBuffers {
|
/// [pos vec3, color vec3] — genutzt fuer das Bodengitter UND die Modell-Kanten
|
||||||
|
/// (identisches Layout/Shader, siehe grid.rs / edges.rs).
|
||||||
|
struct LineBuffers {
|
||||||
vbo: wgpu::Buffer,
|
vbo: wgpu::Buffer,
|
||||||
vertex_count: u32,
|
vertex_count: u32,
|
||||||
}
|
}
|
||||||
@@ -80,19 +136,32 @@ struct GridBuffers {
|
|||||||
/// Ziel-Groesse (neu) angelegt.
|
/// Ziel-Groesse (neu) angelegt.
|
||||||
pub struct Renderer {
|
pub struct Renderer {
|
||||||
pipeline: wgpu::RenderPipeline,
|
pipeline: wgpu::RenderPipeline,
|
||||||
/// Eigene, schlanke Linien-Pipeline (LineList, unlit) fuer das Bodengitter.
|
/// Wie `pipeline`, aber mit positivem Depth-Bias (Flaechen leicht nach hinten
|
||||||
|
/// geschoben). Nur fuer den Hidden-Line-Stil: so gewinnen die danach an ihrer
|
||||||
|
/// wahren Tiefe gezeichneten Kanten den Less-Test und liegen sauber obenauf,
|
||||||
|
/// waehrend verdeckte Kanten weiterhin weggeschnitten werden (kein Z-Fighting).
|
||||||
|
hidden_face_pipeline: wgpu::RenderPipeline,
|
||||||
|
/// Eigene, schlanke Linien-Pipeline (LineList, unlit) fuer das Bodengitter
|
||||||
|
/// UND die Modell-Kanten (wireframe/hidden — identisches Layout/Shader).
|
||||||
/// Teilt sich das `Globals`-Uniform/Bind-Group (nur View-Projektion) mit der
|
/// Teilt sich das `Globals`-Uniform/Bind-Group (nur View-Projektion) mit der
|
||||||
/// Mesh-Pipeline und rendert in denselben Pass/Tiefenpuffer.
|
/// Mesh-Pipeline und rendert in denselben Pass/Tiefenpuffer.
|
||||||
grid_pipeline: wgpu::RenderPipeline,
|
grid_pipeline: wgpu::RenderPipeline,
|
||||||
bind_group: wgpu::BindGroup,
|
bind_group: wgpu::BindGroup,
|
||||||
uniform: wgpu::Buffer,
|
uniform: wgpu::Buffer,
|
||||||
mesh: Option<MeshBuffers>,
|
mesh: Option<MeshBuffers>,
|
||||||
|
/// Modell-Kanten (Feature-Edges, edges.rs) als LineList. Einmalig bei
|
||||||
|
/// `set_model`/`upload_*` erzeugt und gecacht (nicht je Frame). Gezeichnet in
|
||||||
|
/// den Stilen wireframe/hidden.
|
||||||
|
edges: Option<LineBuffers>,
|
||||||
/// Bodengitter-Vertices (None = nicht erzeugt). Wird nur gezeichnet, wenn
|
/// Bodengitter-Vertices (None = nicht erzeugt). Wird nur gezeichnet, wenn
|
||||||
/// zusaetzlich `grid_visible` gesetzt ist.
|
/// zusaetzlich `grid_visible` gesetzt ist.
|
||||||
grid: Option<GridBuffers>,
|
grid: Option<LineBuffers>,
|
||||||
/// Ob das Bodengitter im naechsten `render` gezeichnet wird. Default false —
|
/// Ob das Bodengitter im naechsten `render` gezeichnet wird. Default false —
|
||||||
/// bestehende Aufrufer ohne `set_ground_grid` bekommen KEIN Gitter.
|
/// bestehende Aufrufer ohne `set_ground_grid` bekommen KEIN Gitter.
|
||||||
grid_visible: bool,
|
grid_visible: bool,
|
||||||
|
/// Aktuelle Darstellungsart (Default `Shaded`). Einzige Quelle der Wahrheit;
|
||||||
|
/// `set_white_mode` bildet auf `Shaded`/`White` ab.
|
||||||
|
style: RenderStyle,
|
||||||
depth: Option<DepthTarget>,
|
depth: Option<DepthTarget>,
|
||||||
msaa: Option<MsaaTarget>,
|
msaa: Option<MsaaTarget>,
|
||||||
/// Farbformat des Render-Ziels (Surface), fuer die MSAA-Zwischentextur.
|
/// Farbformat des Render-Ziels (Surface), fuer die MSAA-Zwischentextur.
|
||||||
@@ -155,47 +224,64 @@ impl Renderer {
|
|||||||
attributes: &wgpu::vertex_attr_array![0 => Float32x3, 1 => Float32x3, 2 => Float32x3],
|
attributes: &wgpu::vertex_attr_array![0 => Float32x3, 1 => Float32x3, 2 => Float32x3],
|
||||||
};
|
};
|
||||||
|
|
||||||
let pipeline = device.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
|
// Mesh-Pipeline-Fabrik: identische Beschreibung fuer die Standard-Flaechen
|
||||||
label: Some("mesh.pipeline"),
|
// (kein Bias) und die Hidden-Line-Flaechen (positiver Depth-Bias, damit die
|
||||||
layout: Some(&pipeline_layout),
|
// spaeter gezeichneten Kanten obenauf liegen). Nur `bias` unterscheidet sich.
|
||||||
vertex: wgpu::VertexState {
|
let make_mesh_pipeline = |label: &str, bias: wgpu::DepthBiasState| {
|
||||||
module: &module,
|
device.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
|
||||||
entry_point: Some("vs_main"),
|
label: Some(label),
|
||||||
buffers: &[vertex_layout],
|
layout: Some(&pipeline_layout),
|
||||||
compilation_options: Default::default(),
|
vertex: wgpu::VertexState {
|
||||||
|
module: &module,
|
||||||
|
entry_point: Some("vs_main"),
|
||||||
|
buffers: &[vertex_layout.clone()],
|
||||||
|
compilation_options: Default::default(),
|
||||||
|
},
|
||||||
|
fragment: Some(wgpu::FragmentState {
|
||||||
|
module: &module,
|
||||||
|
entry_point: Some("fs_main"),
|
||||||
|
targets: &[Some(wgpu::ColorTargetState {
|
||||||
|
format: color_format,
|
||||||
|
blend: Some(wgpu::BlendState::REPLACE),
|
||||||
|
write_mask: wgpu::ColorWrites::ALL,
|
||||||
|
})],
|
||||||
|
compilation_options: Default::default(),
|
||||||
|
}),
|
||||||
|
primitive: wgpu::PrimitiveState {
|
||||||
|
topology: wgpu::PrimitiveTopology::TriangleList,
|
||||||
|
// Aussen zeigende Flaechen sind CCW (mesh.rs) -> Rueckseiten cullen.
|
||||||
|
front_face: wgpu::FrontFace::Ccw,
|
||||||
|
cull_mode: Some(wgpu::Face::Back),
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
|
depth_stencil: Some(wgpu::DepthStencilState {
|
||||||
|
format: DEPTH_FORMAT,
|
||||||
|
depth_write_enabled: Some(true),
|
||||||
|
depth_compare: Some(wgpu::CompareFunction::Less),
|
||||||
|
stencil: wgpu::StencilState::default(),
|
||||||
|
bias,
|
||||||
|
}),
|
||||||
|
multisample: wgpu::MultisampleState {
|
||||||
|
count: SAMPLE_COUNT,
|
||||||
|
mask: !0,
|
||||||
|
alpha_to_coverage_enabled: false,
|
||||||
|
},
|
||||||
|
multiview_mask: None,
|
||||||
|
cache: None,
|
||||||
|
})
|
||||||
|
};
|
||||||
|
let pipeline = make_mesh_pipeline("mesh.pipeline", wgpu::DepthBiasState::default());
|
||||||
|
// Hidden-Line-Flaechen leicht nach hinten schieben (Depth-Bias auf
|
||||||
|
// Dreiecke wirkt zuverlaessig — anders als bei Linien), damit die Kanten
|
||||||
|
// an ihrer wahren Tiefe den Less-Test gewinnen.
|
||||||
|
let hidden_face_pipeline = make_mesh_pipeline(
|
||||||
|
"mesh.hidden.pipeline",
|
||||||
|
wgpu::DepthBiasState {
|
||||||
|
constant: 4,
|
||||||
|
slope_scale: 1.0,
|
||||||
|
clamp: 0.0,
|
||||||
},
|
},
|
||||||
fragment: Some(wgpu::FragmentState {
|
);
|
||||||
module: &module,
|
|
||||||
entry_point: Some("fs_main"),
|
|
||||||
targets: &[Some(wgpu::ColorTargetState {
|
|
||||||
format: color_format,
|
|
||||||
blend: Some(wgpu::BlendState::REPLACE),
|
|
||||||
write_mask: wgpu::ColorWrites::ALL,
|
|
||||||
})],
|
|
||||||
compilation_options: Default::default(),
|
|
||||||
}),
|
|
||||||
primitive: wgpu::PrimitiveState {
|
|
||||||
topology: wgpu::PrimitiveTopology::TriangleList,
|
|
||||||
// Aussen zeigende Flaechen sind CCW (mesh.rs) -> Rueckseiten cullen.
|
|
||||||
front_face: wgpu::FrontFace::Ccw,
|
|
||||||
cull_mode: Some(wgpu::Face::Back),
|
|
||||||
..Default::default()
|
|
||||||
},
|
|
||||||
depth_stencil: Some(wgpu::DepthStencilState {
|
|
||||||
format: DEPTH_FORMAT,
|
|
||||||
depth_write_enabled: Some(true),
|
|
||||||
depth_compare: Some(wgpu::CompareFunction::Less),
|
|
||||||
stencil: wgpu::StencilState::default(),
|
|
||||||
bias: wgpu::DepthBiasState::default(),
|
|
||||||
}),
|
|
||||||
multisample: wgpu::MultisampleState {
|
|
||||||
count: SAMPLE_COUNT,
|
|
||||||
mask: !0,
|
|
||||||
alpha_to_coverage_enabled: false,
|
|
||||||
},
|
|
||||||
multiview_mask: None,
|
|
||||||
cache: None,
|
|
||||||
});
|
|
||||||
|
|
||||||
// Bodengitter-Pipeline: dieselbe Bind-Group (Globals/View-Projektion) und
|
// Bodengitter-Pipeline: dieselbe Bind-Group (Globals/View-Projektion) und
|
||||||
// dasselbe Depth-/MSAA-/Farbziel wie die Mesh-Pipeline, aber LineList-
|
// dasselbe Depth-/MSAA-/Farbziel wie die Mesh-Pipeline, aber LineList-
|
||||||
@@ -268,12 +354,15 @@ impl Renderer {
|
|||||||
|
|
||||||
Self {
|
Self {
|
||||||
pipeline,
|
pipeline,
|
||||||
|
hidden_face_pipeline,
|
||||||
grid_pipeline,
|
grid_pipeline,
|
||||||
bind_group,
|
bind_group,
|
||||||
uniform,
|
uniform,
|
||||||
mesh: None,
|
mesh: None,
|
||||||
|
edges: None,
|
||||||
grid: None,
|
grid: None,
|
||||||
grid_visible: false,
|
grid_visible: false,
|
||||||
|
style: RenderStyle::Shaded,
|
||||||
depth: None,
|
depth: None,
|
||||||
msaa: None,
|
msaa: None,
|
||||||
color_format,
|
color_format,
|
||||||
@@ -305,12 +394,32 @@ impl Renderer {
|
|||||||
self.upload_mesh(device, build_scene_mesh(walls, slabs, meshes));
|
self.upload_mesh(device, build_scene_mesh(walls, slabs, meshes));
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Laedt ein fertiges Mesh in die GPU-Puffer (oder loescht es bei leer).
|
/// Laedt ein fertiges Mesh in die GPU-Puffer (oder loescht es bei leer) und
|
||||||
|
/// erzeugt ZUGLEICH die gecachten Modell-Kanten (Feature-Edges, edges.rs) fuer
|
||||||
|
/// die Stile wireframe/hidden — einmalig hier, nicht je Frame.
|
||||||
fn upload_mesh(&mut self, device: &wgpu::Device, mesh: Mesh) {
|
fn upload_mesh(&mut self, device: &wgpu::Device, mesh: Mesh) {
|
||||||
if mesh.indices.is_empty() {
|
if mesh.indices.is_empty() {
|
||||||
self.mesh = None;
|
self.mesh = None;
|
||||||
|
self.edges = None;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Kanten aus der Mesh-Geometrie ableiten und (falls vorhanden) hochladen.
|
||||||
|
let edge_verts = build_mesh_edges(&mesh);
|
||||||
|
self.edges = if edge_verts.is_empty() {
|
||||||
|
None
|
||||||
|
} else {
|
||||||
|
let vbo = device.create_buffer_init(&wgpu::util::BufferInitDescriptor {
|
||||||
|
label: Some("edges.vbo"),
|
||||||
|
contents: bytemuck::cast_slice(&edge_verts),
|
||||||
|
usage: wgpu::BufferUsages::VERTEX,
|
||||||
|
});
|
||||||
|
Some(LineBuffers {
|
||||||
|
vbo,
|
||||||
|
vertex_count: (edge_verts.len() / EDGE_FLOATS_PER_VERTEX) as u32,
|
||||||
|
})
|
||||||
|
};
|
||||||
|
|
||||||
let vbo = device.create_buffer_init(&wgpu::util::BufferInitDescriptor {
|
let vbo = device.create_buffer_init(&wgpu::util::BufferInitDescriptor {
|
||||||
label: Some("mesh.vbo"),
|
label: Some("mesh.vbo"),
|
||||||
contents: bytemuck::cast_slice(&mesh.verts),
|
contents: bytemuck::cast_slice(&mesh.verts),
|
||||||
@@ -342,10 +451,21 @@ impl Renderer {
|
|||||||
self.globals.sun_color = [sun[0], sun[1], sun[2], 1.0];
|
self.globals.sun_color = [sun[0], sun[1], sun[2], 1.0];
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Darstellungsart umschalten: `true` = weisses Clay-Material (Beleuchtung
|
/// Darstellungsart setzen (shaded/white/textured/wireframe/hidden). Einzige
|
||||||
/// bleibt aktiv, siehe shaders.rs), `false` = normale Bauteil-/Vertexfarbe.
|
/// Quelle der Wahrheit; `render` leitet daraus Pipeline-Wahl und `mode.x` ab.
|
||||||
|
pub fn set_render_style(&mut self, style: RenderStyle) {
|
||||||
|
self.style = style;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Rueckwaerts-kompatibler Schalter: `true` bildet auf `White`, `false` auf
|
||||||
|
/// `Shaded` ab (kein separater Zustand mehr — vermeidet Widerspruch zum
|
||||||
|
/// `style`-Feld). Neue Aufrufer nutzen `set_render_style`.
|
||||||
pub fn set_white_mode(&mut self, white: bool) {
|
pub fn set_white_mode(&mut self, white: bool) {
|
||||||
self.globals.mode[0] = if white { 1.0 } else { 0.0 };
|
self.style = if white {
|
||||||
|
RenderStyle::White
|
||||||
|
} else {
|
||||||
|
RenderStyle::Shaded
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Setzt/aktualisiert das Referenz-Bodengitter (grid.rs). Erzeugt den Linien-
|
/// Setzt/aktualisiert das Referenz-Bodengitter (grid.rs). Erzeugt den Linien-
|
||||||
@@ -372,7 +492,7 @@ impl Renderer {
|
|||||||
contents: bytemuck::cast_slice(&verts),
|
contents: bytemuck::cast_slice(&verts),
|
||||||
usage: wgpu::BufferUsages::VERTEX,
|
usage: wgpu::BufferUsages::VERTEX,
|
||||||
});
|
});
|
||||||
self.grid = Some(GridBuffers {
|
self.grid = Some(LineBuffers {
|
||||||
vbo,
|
vbo,
|
||||||
vertex_count: (verts.len() / GRID_FLOATS_PER_VERTEX) as u32,
|
vertex_count: (verts.len() / GRID_FLOATS_PER_VERTEX) as u32,
|
||||||
});
|
});
|
||||||
@@ -453,6 +573,9 @@ impl Renderer {
|
|||||||
let aspect = if h > 0 { w as f32 / h as f32 } else { 1.0 };
|
let aspect = if h > 0 { w as f32 / h as f32 } else { 1.0 };
|
||||||
let vp: Mat4 = view_projection(camera, aspect);
|
let vp: Mat4 = view_projection(camera, aspect);
|
||||||
self.globals.view_proj = vp;
|
self.globals.view_proj = vp;
|
||||||
|
// `mode.x` fuer den Mesh-Shader aus dem aktuellen Stil ableiten (Material/
|
||||||
|
// Clay/Hidden-Flaechen). Wireframe zeichnet keine Flaechen -> Wert egal.
|
||||||
|
self.globals.mode[0] = self.style.mesh_mode();
|
||||||
queue.write_buffer(&self.uniform, 0, bytemuck::bytes_of(&self.globals));
|
queue.write_buffer(&self.uniform, 0, bytemuck::bytes_of(&self.globals));
|
||||||
|
|
||||||
self.ensure_depth(device, w, h);
|
self.ensure_depth(device, w, h);
|
||||||
@@ -493,16 +616,40 @@ impl Renderer {
|
|||||||
multiview_mask: None,
|
multiview_mask: None,
|
||||||
});
|
});
|
||||||
|
|
||||||
if let Some(m) = &self.mesh {
|
// 1) Gefuellte Flaechen — in allen Stilen ausser "wireframe". Im
|
||||||
pass.set_pipeline(&self.pipeline);
|
// Hidden-Line-Stil die leicht nach hinten gebiaste Pipeline, damit
|
||||||
pass.set_bind_group(0, &self.bind_group, &[]);
|
// die folgenden Kanten sauber obenauf liegen.
|
||||||
pass.set_vertex_buffer(0, m.vbo.slice(..));
|
if self.style.draws_faces() {
|
||||||
pass.set_index_buffer(m.ibo.slice(..), wgpu::IndexFormat::Uint32);
|
if let Some(m) = &self.mesh {
|
||||||
pass.draw_indexed(0..m.index_count, 0, 0..1);
|
let face_pipeline = if self.style == RenderStyle::Hidden {
|
||||||
|
&self.hidden_face_pipeline
|
||||||
|
} else {
|
||||||
|
&self.pipeline
|
||||||
|
};
|
||||||
|
pass.set_pipeline(face_pipeline);
|
||||||
|
pass.set_bind_group(0, &self.bind_group, &[]);
|
||||||
|
pass.set_vertex_buffer(0, m.vbo.slice(..));
|
||||||
|
pass.set_index_buffer(m.ibo.slice(..), wgpu::IndexFormat::Uint32);
|
||||||
|
pass.draw_indexed(0..m.index_count, 0, 0..1);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Bodengitter NACH dem Modell zeichnen (gemeinsamer Tiefenpuffer ->
|
// 2) Modell-Kanten (wireframe/hidden) — LineList-Pipeline (wie Grid).
|
||||||
// das Modell verdeckt das Gitter korrekt). Nur wenn sichtbar + vorhanden.
|
// Im Hidden-Stil obenauf, tiefengetestet gegen die (gebiasten)
|
||||||
|
// Flaechen -> verdeckte Kanten fallen weg. Im Wireframe ohne
|
||||||
|
// Flaechen -> alle Kanten sichtbar.
|
||||||
|
if self.style.draws_edges() {
|
||||||
|
if let Some(e) = &self.edges {
|
||||||
|
pass.set_pipeline(&self.grid_pipeline);
|
||||||
|
pass.set_bind_group(0, &self.bind_group, &[]);
|
||||||
|
pass.set_vertex_buffer(0, e.vbo.slice(..));
|
||||||
|
pass.draw(0..e.vertex_count, 0..1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 3) Bodengitter NACH dem Modell zeichnen (gemeinsamer Tiefenpuffer ->
|
||||||
|
// das Modell verdeckt das Gitter korrekt). Unabhaengig vom Stil, nur
|
||||||
|
// wenn sichtbar + vorhanden.
|
||||||
if self.grid_visible {
|
if self.grid_visible {
|
||||||
if let Some(g) = &self.grid {
|
if let Some(g) = &self.grid {
|
||||||
pass.set_pipeline(&self.grid_pipeline);
|
pass.set_pipeline(&self.grid_pipeline);
|
||||||
|
|||||||
@@ -14,6 +14,7 @@
|
|||||||
// Standard-Build (`cargo test`/`cargo build` ohne Features) enthaelt nur die
|
// Standard-Build (`cargo test`/`cargo build` ohne Features) enthaelt nur die
|
||||||
// GPU-freien Schichten und ist damit unabhaengig von einer Display-Session.
|
// GPU-freien Schichten und ist damit unabhaengig von einer Display-Session.
|
||||||
|
|
||||||
|
pub mod edges;
|
||||||
pub mod grid;
|
pub mod grid;
|
||||||
pub mod math;
|
pub mod math;
|
||||||
pub mod mesh;
|
pub mod mesh;
|
||||||
|
|||||||
@@ -16,9 +16,16 @@
|
|||||||
// sky_color : vec4<f32> Himmels-Ambient (rgb; von oben)
|
// sky_color : vec4<f32> Himmels-Ambient (rgb; von oben)
|
||||||
// ground_color : vec4<f32> Boden-Ambient (rgb; von unten)
|
// ground_color : vec4<f32> Boden-Ambient (rgb; von unten)
|
||||||
// sun_color : vec4<f32> Farbe/Staerke des Directional-Lights (rgb)
|
// sun_color : vec4<f32> Farbe/Staerke des Directional-Lights (rgb)
|
||||||
// mode : vec4<f32> x = Darstellungsart (0 = shaded/Material, 1 = weiss/Clay)
|
// mode : vec4<f32> x = Darstellungsart (0 = shaded/Material,
|
||||||
|
// 1 = weiss/Clay, 2 = hidden-Line-Flaechen/flach-weiss)
|
||||||
//
|
//
|
||||||
// Vertex-Attribute: position (world), normal (world), color (Albedo).
|
// Vertex-Attribute: position (world), normal (world), color (Albedo).
|
||||||
|
//
|
||||||
|
// FILL-LIGHT: Zusaetzlich zum Haupt-Directional (Sonne) wirkt ein schwaches
|
||||||
|
// GEGEN-Fuelllicht aus der entgegengesetzten Azimut-Richtung (leicht von oben),
|
||||||
|
// abgeleitet aus der Sonnenrichtung — es hebt die vom Hauptlicht abgewandten
|
||||||
|
// Flaechen an, damit sie nicht in reinem Schwarz absaufen. Dezent (≈ 0.28 der
|
||||||
|
// Sonne, leicht kuehl), physikalisch als Himmels-/Bounce-Fuellung plausibel.
|
||||||
|
|
||||||
/// Der einzige Shader (Vertex + Fragment). Hemisphaerisches Ambient (Himmel/Boden)
|
/// Der einzige Shader (Vertex + Fragment). Hemisphaerisches Ambient (Himmel/Boden)
|
||||||
/// + Lambert-Directional — GPU-Aequivalent eines Architektur-Massenmodell-Lichts.
|
/// + Lambert-Directional — GPU-Aequivalent eines Architektur-Massenmodell-Lichts.
|
||||||
@@ -58,6 +65,7 @@ fn vs_main(in : VsIn) -> VsOut {
|
|||||||
fn fs_main(in : VsOut) -> @location(0) vec4<f32> {
|
fn fs_main(in : VsOut) -> @location(0) vec4<f32> {
|
||||||
let n = normalize(in.world_normal);
|
let n = normalize(in.world_normal);
|
||||||
let l = normalize(globals.light_dir.xyz);
|
let l = normalize(globals.light_dir.xyz);
|
||||||
|
let style = globals.mode.x;
|
||||||
|
|
||||||
// Hemisphaerisches Ambient: Mischfaktor aus der Vertikal-Komponente der
|
// Hemisphaerisches Ambient: Mischfaktor aus der Vertikal-Komponente der
|
||||||
// Normalen (n.y = +1 -> voll Himmel, n.y = -1 -> voll Boden).
|
// Normalen (n.y = +1 -> voll Himmel, n.y = -1 -> voll Boden).
|
||||||
@@ -67,18 +75,40 @@ fn fs_main(in : VsOut) -> @location(0) vec4<f32> {
|
|||||||
// Gerichteter Lambert-Anteil (Sonne); Rueckseiten (n.l < 0) tragen nichts bei.
|
// Gerichteter Lambert-Anteil (Sonne); Rueckseiten (n.l < 0) tragen nichts bei.
|
||||||
let diffuse = max(dot(n, l), 0.0) * globals.sun_color.rgb;
|
let diffuse = max(dot(n, l), 0.0) * globals.sun_color.rgb;
|
||||||
|
|
||||||
// Weiss-Modus (Clay-Look): Material-/Vertexfarbe durch ein einheitliches
|
// Schwaches Gegen-Fuelllicht: aus entgegengesetztem Azimut, leicht von oben,
|
||||||
// Hellgrau ersetzen, Beleuchtung (Ambient + Directional) bleibt aktiv — so
|
// aus der Sonnenrichtung abgeleitet — hebt abgewandte Flaechen an (kein
|
||||||
// liest sich die Volumetrie ohne Bauteil-Farben ab (Vectorworks „white").
|
// reines Schwarz). Dezent (0.28) und leicht kuehl getoent.
|
||||||
let albedo = mix(in.color, vec3<f32>(0.82, 0.82, 0.80), globals.mode.x);
|
let fill_l = normalize(vec3<f32>(-l.x, abs(l.y) * 0.35 + 0.15, -l.z));
|
||||||
|
let fill = max(dot(n, fill_l), 0.0) * globals.sun_color.rgb
|
||||||
|
* vec3<f32>(0.24, 0.27, 0.30);
|
||||||
|
|
||||||
var shaded = albedo * (ambient + diffuse);
|
// Weiss-Modus (Clay, style==1) bzw. Hidden-Flaechen (style==2): Material-/
|
||||||
|
// Vertexfarbe durch ein einheitliches Hell ersetzen. Clay = warmes Hellgrau,
|
||||||
|
// Hidden = nahezu Weiss (damit die dunklen Kanten obenauf klar lesbar sind).
|
||||||
|
let clay = vec3<f32>(0.82, 0.82, 0.80);
|
||||||
|
let paper = vec3<f32>(0.97, 0.97, 0.97);
|
||||||
|
var albedo = in.color;
|
||||||
|
if (style > 1.5) {
|
||||||
|
albedo = paper;
|
||||||
|
} else if (style > 0.5) {
|
||||||
|
albedo = clay;
|
||||||
|
}
|
||||||
|
|
||||||
|
var shaded = albedo * (ambient + diffuse + fill);
|
||||||
|
|
||||||
// Sanfte Kantenbetonung: senkrechte Flaechen (n.y nahe 0) minimal abdunkeln,
|
// Sanfte Kantenbetonung: senkrechte Flaechen (n.y nahe 0) minimal abdunkeln,
|
||||||
// damit sich Waende vom hellen Deckel/Boden abheben (ohne Postprocessing).
|
// damit sich Waende vom hellen Deckel/Boden abheben (ohne Postprocessing).
|
||||||
let edge = 0.90 + 0.10 * abs(n.y);
|
let edge = 0.90 + 0.10 * abs(n.y);
|
||||||
shaded = shaded * edge;
|
shaded = shaded * edge;
|
||||||
|
|
||||||
|
// Hidden-Line-Flaechen: sehr flach halten (fast unlit, nahezu weiss), damit
|
||||||
|
// die dunklen Modell-Kanten die Form tragen und die Flaechen nur den Depth-
|
||||||
|
// Buffer fuellen (verdeckte Kanten wegschneiden). Leichte Hemi-Toenung bleibt,
|
||||||
|
// damit die Silhouette auch ohne Kante nicht voellig verschwindet.
|
||||||
|
if (style > 1.5) {
|
||||||
|
shaded = albedo * (0.88 + 0.12 * hemi_t);
|
||||||
|
}
|
||||||
|
|
||||||
return vec4<f32>(shaded, 1.0);
|
return vec4<f32>(shaded, 1.0);
|
||||||
}
|
}
|
||||||
"#;
|
"#;
|
||||||
|
|||||||
@@ -320,9 +320,29 @@ impl WebModelRenderer {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Darstellungsart der Oberleiste setzen. Gueltige Werte:
|
||||||
|
/// - `"shaded"` — beleuchtete Bauteilfarben (Default).
|
||||||
|
/// - `"white"` — Clay-Look: einheitlich helles Material, beleuchtet
|
||||||
|
/// (identisch zum bisherigen `set_render_mode_white(true)`).
|
||||||
|
/// - `"textured"` — mangels Textur-Pipeline VORERST identisch zu `"shaded"`
|
||||||
|
/// behandelt (KEIN Fake-Stub); Platzhalter fuer eine spaetere Textur-Schicht.
|
||||||
|
/// - `"wireframe"`— nur Modell-Kanten (Feature-Edges), keine Flaechen.
|
||||||
|
/// - `"hidden"` — Hidden-Line: flach-weisse Flaechen fuellen den Depth-
|
||||||
|
/// Buffer, dunkle Kanten liegen tiefengetestet obenauf (nur sichtbare).
|
||||||
|
/// Unbekannte Werte fallen auf `"shaded"` zurueck. Wirkt erst beim naechsten
|
||||||
|
/// `render`. Loest `set_render_mode_white` ab (das bleibt kompatibel).
|
||||||
|
pub fn set_render_style(&mut self, style: &str) -> Result<(), JsValue> {
|
||||||
|
self.renderer
|
||||||
|
.set_render_style(crate::gpu::RenderStyle::from_str_or_shaded(style));
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
/// Darstellungsart umschalten: `true` = weisses Clay-Material (beleuchtet,
|
/// Darstellungsart umschalten: `true` = weisses Clay-Material (beleuchtet,
|
||||||
/// ohne Bauteil-Farben — Vectorworks „white"), `false` = normale Material-
|
/// ohne Bauteil-Farben — Vectorworks „white"), `false` = normale Material-
|
||||||
/// farbe (shaded, Default). Wirkt erst beim naechsten `render`.
|
/// farbe (shaded, Default). Wirkt erst beim naechsten `render`. RUECKWAERTS-
|
||||||
|
/// KOMPATIBEL — bildet intern auf denselben `render_style`-Zustand ab wie
|
||||||
|
/// `set_render_style` (true→„white", false→„shaded"), sodass es keine zwei
|
||||||
|
/// widersprechenden Zustaende gibt.
|
||||||
pub fn set_render_mode_white(&mut self, white: bool) {
|
pub fn set_render_mode_white(&mut self, white: bool) {
|
||||||
self.renderer.set_white_mode(white);
|
self.renderer.set_white_mode(white);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,12 +6,13 @@
|
|||||||
// INTEGRATIONS-SPIKE (Umfang bewusst schmal): Massenmodell (Wände mit Öffnungs-
|
// INTEGRATIONS-SPIKE (Umfang bewusst schmal): Massenmodell (Wände mit Öffnungs-
|
||||||
// Teilquadern + Deckenplatten, alle Geschosse gestapelt) + Orbit-Navigation +
|
// Teilquadern + Deckenplatten, alle Geschosse gestapelt) + Orbit-Navigation +
|
||||||
// Kamera-Presets (`view3d`, von der Oberleiste getrieben — s. u.) + Darstellungs-
|
// Kamera-Presets (`view3d`, von der Oberleiste getrieben — s. u.) + Darstellungs-
|
||||||
// art (shaded/white, `renderMode`, ebenfalls Oberleiste) + ein-/ausblendbares
|
// art (volle `renderMode`-Union der Oberleiste — shaded/white/textured/
|
||||||
// Boden-Referenzraster (`gridVisible`/`groundElevation`, Overlay-Umschalter im
|
// wireframe/hidden — via `set_render_style` in Rust; "textured" faellt dort
|
||||||
// Viewport selbst). Auswahl/Raycast, Griffe/Zeichnen sowie die übrigen
|
// vorerst auf "shaded" zurueck) + ein-/ausblendbares Boden-Referenzraster
|
||||||
// Darstellungsarten der three.js-Sicht (texturiert/Drahtgitter/Kanten, HLR)
|
// (`gridVisible`/`groundElevation`, Overlay-Umschalter im Viewport selbst).
|
||||||
// folgen später — bis dahin gilt: RECHTS unterdrückt weiterhin nur das
|
// Auswahl/Raycast sowie Griffe/Zeichnen der three.js-Sicht folgen später —
|
||||||
// Browser-Menü (kein Kontextmenü im Engine-Pfad).
|
// bis dahin gilt: RECHTS unterdrückt weiterhin nur das Browser-Menü (kein
|
||||||
|
// Kontextmenü im Engine-Pfad).
|
||||||
//
|
//
|
||||||
// KAMERA-PRESETS: `view3d`/`renderMode` kommen als Props von der Oberleiste
|
// KAMERA-PRESETS: `view3d`/`renderMode` kommen als Props von der Oberleiste
|
||||||
// (TopBar → App.tsx → Viewport3D-Umschalter, dieselbe Quelle wie die three.js-
|
// (TopBar → App.tsx → Viewport3D-Umschalter, dieselbe Quelle wie die three.js-
|
||||||
@@ -170,9 +171,8 @@ export function Wasm3DViewport({
|
|||||||
project: Project;
|
project: Project;
|
||||||
/** Kanonischer Blickwinkel (Oberleiste) — s. Datei-Kommentar. */
|
/** Kanonischer Blickwinkel (Oberleiste) — s. Datei-Kommentar. */
|
||||||
view3d?: View3d;
|
view3d?: View3d;
|
||||||
/** Darstellungsart (Oberleiste): nur `shaded`/`white` wirken hier (die
|
/** Darstellungsart (Oberleiste) — alle fünf Werte wirken hier, s.
|
||||||
* übrigen Modi der three.js-Sicht sind im Engine-Pfad noch nicht gebaut,
|
* Datei-Kommentar. */
|
||||||
* s. Datei-Kommentar) — alles ausser `white` fällt auf shaded zurück. */
|
|
||||||
renderMode?: RenderMode;
|
renderMode?: RenderMode;
|
||||||
/** Boden-Referenzraster sichtbar? Umgeschaltet über den Overlay-Button unten
|
/** Boden-Referenzraster sichtbar? Umgeschaltet über den Overlay-Button unten
|
||||||
* rechts im Viewport (`onToggleGrid`) — Single Source of Truth ist der
|
* rechts im Viewport (`onToggleGrid`) — Single Source of Truth ist der
|
||||||
@@ -188,7 +188,7 @@ export function Wasm3DViewport({
|
|||||||
const {
|
const {
|
||||||
render,
|
render,
|
||||||
updateModel,
|
updateModel,
|
||||||
setWhiteMode,
|
setRenderStyle,
|
||||||
applyViewPreset,
|
applyViewPreset,
|
||||||
setGroundGrid,
|
setGroundGrid,
|
||||||
ready,
|
ready,
|
||||||
@@ -229,14 +229,14 @@ export function Wasm3DViewport({
|
|||||||
redrawRef.current();
|
redrawRef.current();
|
||||||
}, [ready, view3d, applyViewPreset, render]);
|
}, [ready, view3d, applyViewPreset, render]);
|
||||||
|
|
||||||
// Darstellungsart (shaded/weiss) an den Renderer weiterreichen, sobald sie
|
// Darstellungsart (volle RenderMode-Union) an den Renderer weiterreichen,
|
||||||
// wechselt oder der Renderer bereit wird — dann mit der aktuell aktiven
|
// sobald sie wechselt oder der Renderer bereit wird — dann mit der aktuell
|
||||||
// Kamera (Praeset ODER Orbit) neu zeichnen.
|
// aktiven Kamera (Praeset ODER Orbit) neu zeichnen.
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!ready) return;
|
if (!ready) return;
|
||||||
setWhiteMode(renderMode === "white");
|
setRenderStyle(renderMode);
|
||||||
redrawRef.current();
|
redrawRef.current();
|
||||||
}, [ready, renderMode, setWhiteMode]);
|
}, [ready, renderMode, setRenderStyle]);
|
||||||
|
|
||||||
// Boden-Referenzraster: bei Bereitschaft oder Wechsel von Sichtbarkeit/
|
// Boden-Referenzraster: bei Bereitschaft oder Wechsel von Sichtbarkeit/
|
||||||
// Geschoss-Hoehe neu setzen + zeichnen. Fixe Ausdehnung/Abstand (40 m /
|
// Geschoss-Hoehe neu setzen + zeichnen. Fixe Ausdehnung/Abstand (40 m /
|
||||||
|
|||||||
@@ -37,7 +37,10 @@ export interface Camera3d {
|
|||||||
/** Minimal-Typ des WASM-Exports (das echte .d.ts entsteht erst mit `build:engine3d`). */
|
/** Minimal-Typ des WASM-Exports (das echte .d.ts entsteht erst mit `build:engine3d`). */
|
||||||
interface WebModelRendererLike {
|
interface WebModelRendererLike {
|
||||||
set_model(json: string): void;
|
set_model(json: string): void;
|
||||||
set_render_mode_white(white: boolean): void;
|
/** Darstellungsart setzen — `style` ∈ "shaded"|"white"|"wireframe"|"hidden"|
|
||||||
|
* "textured" (deckt die volle `RenderMode`-Union der Oberleiste ab;
|
||||||
|
* "textured" faellt engine-seitig vorerst auf "shaded" zurueck). */
|
||||||
|
set_render_style(style: string): void;
|
||||||
set_view_preset(preset: string): void;
|
set_view_preset(preset: string): void;
|
||||||
/** Boden-Referenzraster in der Welt-XZ-Ebene ein-/ausblenden (Rust-seitig,
|
/** Boden-Referenzraster in der Welt-XZ-Ebene ein-/ausblenden (Rust-seitig,
|
||||||
* `web::set_ground_grid`). `visible=false` zeichnet kein Raster; sonst ein
|
* `web::set_ground_grid`). `visible=false` zeichnet kein Raster; sonst ein
|
||||||
@@ -142,17 +145,19 @@ export function useWasm3dRenderer(
|
|||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Darstellungsart umschalten: `true` = weisses Clay-Material (beleuchtet,
|
* Darstellungsart umschalten — reicht die volle `RenderMode`-Union der
|
||||||
* ohne Bauteil-Farben), `false` = normale Material-/Vertexfarbe (shaded,
|
* Oberleiste ("shaded"/"white"/"textured"/"wireframe"/"hidden") an Rust
|
||||||
* Default). Wirkt erst beim nächsten `render`.
|
* durch (`web::set_render_style`); die Engine entscheidet je Stil, wie
|
||||||
|
* gezeichnet wird ("textured" faellt dort vorerst auf "shaded" zurueck).
|
||||||
|
* Wirkt erst beim nächsten `render`.
|
||||||
*/
|
*/
|
||||||
const setWhiteMode = useCallback((white: boolean) => {
|
const setRenderStyle = useCallback((style: string) => {
|
||||||
const r = rendererRef.current;
|
const r = rendererRef.current;
|
||||||
if (!r) return;
|
if (!r) return;
|
||||||
try {
|
try {
|
||||||
r.set_render_mode_white(white);
|
r.set_render_style(style);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.warn("render3d set_render_mode_white fehlgeschlagen:", e);
|
console.warn("render3d set_render_style fehlgeschlagen:", e);
|
||||||
}
|
}
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
@@ -242,7 +247,7 @@ export function useWasm3dRenderer(
|
|||||||
return {
|
return {
|
||||||
render,
|
render,
|
||||||
updateModel,
|
updateModel,
|
||||||
setWhiteMode,
|
setRenderStyle,
|
||||||
applyViewPreset,
|
applyViewPreset,
|
||||||
setGroundGrid,
|
setGroundGrid,
|
||||||
ready,
|
ready,
|
||||||
|
|||||||
Reference in New Issue
Block a user