Nordstern-3D: Boden-Referenzraster + bessere Kamera-Steuerung
- Boden-Grid-Fläche auf y = baseElevation des aktiven Geschosses, ein-/ausschaltbar (Overlay-Button im Viewport, State in viewSlice grid3dVisible). Eigene LineList-Pipeline in der Engine (grid.rs, GRID_WGSL, geteilte View-Projection/Depth mit der Mesh-Pipeline), neue set_ground_grid(visible, elevation, extent, spacing)-Methode. Minor-Linien dezent, jede 5. als Major betont. - Kamera-Steuerung überarbeitet (vorher nur Mitte=Orbit, Pan auf Shift+Mitte versteckt): Links=Orbit, Mitte/Rechts=Pan (bewegen), Rad=Zoom zum Cursor. Laptop-freundlich, konsistent mit der 2D-Plan-Navigation (dort Mitte=Pan).
This commit is contained in:
@@ -12,9 +12,10 @@
|
||||
use bytemuck::{Pod, Zeroable};
|
||||
use wgpu::util::DeviceExt;
|
||||
|
||||
use crate::grid::{build_ground_grid, GRID_FLOATS_PER_VERTEX};
|
||||
use crate::math::{view_projection, Mat4};
|
||||
use crate::mesh::{build_scene_mesh, build_walls_mesh};
|
||||
use crate::shaders::MESH_WGSL;
|
||||
use crate::shaders::{GRID_WGSL, MESH_WGSL};
|
||||
use crate::types::{Camera, Mesh, MeshInput, SlabInput, WallInput, FLOATS_PER_VERTEX};
|
||||
|
||||
/// Tiefenformat des Z-Puffers (32 Bit Float, ueberall verfuegbar).
|
||||
@@ -68,14 +69,30 @@ struct MeshBuffers {
|
||||
index_count: u32,
|
||||
}
|
||||
|
||||
/// GPU-seitiger Vertex-Buffer des Bodengitters (LineList, kein Indexpuffer).
|
||||
struct GridBuffers {
|
||||
vbo: wgpu::Buffer,
|
||||
vertex_count: u32,
|
||||
}
|
||||
|
||||
/// Der native 3D-Renderer: haelt die Pipeline, das Uniform (View-Projektion +
|
||||
/// Licht) und das aktuell hochgeladene Mesh. Ein Tiefenpuffer wird passend zur
|
||||
/// Ziel-Groesse (neu) angelegt.
|
||||
pub struct Renderer {
|
||||
pipeline: wgpu::RenderPipeline,
|
||||
/// Eigene, schlanke Linien-Pipeline (LineList, unlit) fuer das Bodengitter.
|
||||
/// Teilt sich das `Globals`-Uniform/Bind-Group (nur View-Projektion) mit der
|
||||
/// Mesh-Pipeline und rendert in denselben Pass/Tiefenpuffer.
|
||||
grid_pipeline: wgpu::RenderPipeline,
|
||||
bind_group: wgpu::BindGroup,
|
||||
uniform: wgpu::Buffer,
|
||||
mesh: Option<MeshBuffers>,
|
||||
/// Bodengitter-Vertices (None = nicht erzeugt). Wird nur gezeichnet, wenn
|
||||
/// zusaetzlich `grid_visible` gesetzt ist.
|
||||
grid: Option<GridBuffers>,
|
||||
/// Ob das Bodengitter im naechsten `render` gezeichnet wird. Default false —
|
||||
/// bestehende Aufrufer ohne `set_ground_grid` bekommen KEIN Gitter.
|
||||
grid_visible: bool,
|
||||
depth: Option<DepthTarget>,
|
||||
msaa: Option<MsaaTarget>,
|
||||
/// Farbformat des Render-Ziels (Surface), fuer die MSAA-Zwischentextur.
|
||||
@@ -180,6 +197,60 @@ impl Renderer {
|
||||
cache: None,
|
||||
});
|
||||
|
||||
// Bodengitter-Pipeline: dieselbe Bind-Group (Globals/View-Projektion) und
|
||||
// dasselbe Depth-/MSAA-/Farbziel wie die Mesh-Pipeline, aber LineList-
|
||||
// Topologie, ein eigener unlit-Shader (konstante Vertexfarbe) und ein
|
||||
// schlankeres Vertex-Layout [pos vec3, color vec3].
|
||||
let grid_module = device.create_shader_module(wgpu::ShaderModuleDescriptor {
|
||||
label: Some("grid.wgsl"),
|
||||
source: wgpu::ShaderSource::Wgsl(GRID_WGSL.into()),
|
||||
});
|
||||
let grid_vertex_layout = wgpu::VertexBufferLayout {
|
||||
array_stride: (GRID_FLOATS_PER_VERTEX * 4) as u64,
|
||||
step_mode: wgpu::VertexStepMode::Vertex,
|
||||
attributes: &wgpu::vertex_attr_array![0 => Float32x3, 1 => Float32x3],
|
||||
};
|
||||
let grid_pipeline = device.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
|
||||
label: Some("grid.pipeline"),
|
||||
layout: Some(&pipeline_layout),
|
||||
vertex: wgpu::VertexState {
|
||||
module: &grid_module,
|
||||
entry_point: Some("vs_main"),
|
||||
buffers: &[grid_vertex_layout],
|
||||
compilation_options: Default::default(),
|
||||
},
|
||||
fragment: Some(wgpu::FragmentState {
|
||||
module: &grid_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::LineList,
|
||||
// Linien haben keine Vorder-/Rueckseite -> kein Culling.
|
||||
cull_mode: None,
|
||||
..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,
|
||||
});
|
||||
|
||||
let uniform = device.create_buffer(&wgpu::BufferDescriptor {
|
||||
label: Some("globals.buffer"),
|
||||
size: std::mem::size_of::<Globals>() as u64,
|
||||
@@ -197,9 +268,12 @@ impl Renderer {
|
||||
|
||||
Self {
|
||||
pipeline,
|
||||
grid_pipeline,
|
||||
bind_group,
|
||||
uniform,
|
||||
mesh: None,
|
||||
grid: None,
|
||||
grid_visible: false,
|
||||
depth: None,
|
||||
msaa: None,
|
||||
color_format,
|
||||
@@ -274,6 +348,36 @@ impl Renderer {
|
||||
self.globals.mode[0] = if white { 1.0 } else { 0.0 };
|
||||
}
|
||||
|
||||
/// Setzt/aktualisiert das Referenz-Bodengitter (grid.rs). Erzeugt den Linien-
|
||||
/// Vertex-Buffer aus den Parametern neu und merkt sich die Sichtbarkeit; der
|
||||
/// naechste `render` zeichnet das Gitter (bzw. nichts bei `visible == false`).
|
||||
/// Ohne Aufruf bleibt das Gitter unsichtbar (Default), damit bestehende
|
||||
/// Aufrufer unveraendert bleiben.
|
||||
pub fn set_ground_grid(
|
||||
&mut self,
|
||||
device: &wgpu::Device,
|
||||
visible: bool,
|
||||
elevation: f32,
|
||||
extent: f32,
|
||||
spacing: f32,
|
||||
) {
|
||||
self.grid_visible = visible;
|
||||
let verts = build_ground_grid(elevation, extent, spacing);
|
||||
if verts.is_empty() {
|
||||
self.grid = None;
|
||||
return;
|
||||
}
|
||||
let vbo = device.create_buffer_init(&wgpu::util::BufferInitDescriptor {
|
||||
label: Some("grid.vbo"),
|
||||
contents: bytemuck::cast_slice(&verts),
|
||||
usage: wgpu::BufferUsages::VERTEX,
|
||||
});
|
||||
self.grid = Some(GridBuffers {
|
||||
vbo,
|
||||
vertex_count: (verts.len() / GRID_FLOATS_PER_VERTEX) as u32,
|
||||
});
|
||||
}
|
||||
|
||||
/// Stellt sicher, dass ein Tiefenpuffer passend zur Ziel-Groesse existiert.
|
||||
fn ensure_depth(&mut self, device: &wgpu::Device, w: u32, h: u32) {
|
||||
let ok = matches!(&self.depth, Some(d) if d.width == w && d.height == h);
|
||||
@@ -396,6 +500,17 @@ impl Renderer {
|
||||
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 ->
|
||||
// das Modell verdeckt das Gitter korrekt). Nur wenn sichtbar + vorhanden.
|
||||
if self.grid_visible {
|
||||
if let Some(g) = &self.grid {
|
||||
pass.set_pipeline(&self.grid_pipeline);
|
||||
pass.set_bind_group(0, &self.bind_group, &[]);
|
||||
pass.set_vertex_buffer(0, g.vbo.slice(..));
|
||||
pass.draw(0..g.vertex_count, 0..1);
|
||||
}
|
||||
}
|
||||
}
|
||||
queue.submit(std::iter::once(encoder.finish()));
|
||||
}
|
||||
|
||||
@@ -0,0 +1,176 @@
|
||||
// Referenz-Bodengitter (Ground-Grid) fuer den 3D-Renderer.
|
||||
//
|
||||
// Erzeugt CPU-seitig die Linien-Vertices eines quadratischen Referenzgitters in
|
||||
// der Welt-XZ-Ebene (Grundriss-Ebene), zentriert um den Welt-Ursprung
|
||||
// (x=0, z=0), auf konstanter Hoehe `y = elevation`. Damit hat der 3D-Viewport
|
||||
// einen dezenten Boden + raeumliche Orientierung.
|
||||
//
|
||||
// KOORDINATEN (wie types.rs): Welt-Y ist die Hoehe, XZ die Grundriss-Ebene. Das
|
||||
// Gitter liegt also flach in XZ auf konstantem y = elevation.
|
||||
//
|
||||
// Diese Schicht ist GPU-frei (nur `Vec<f32>`), damit sie headless per `cargo
|
||||
// test` pruefbar bleibt (Muster: mesh.rs/section.rs). Die GPU-Schicht (gpu.rs)
|
||||
// laedt die erzeugten Vertices in einen Linien-Vertex-Buffer und zeichnet sie
|
||||
// ueber eine eigene `LineList`-Pipeline (shaders::GRID_WGSL).
|
||||
|
||||
/// f32 je Gitter-Vertex im interleaved Puffer: 3 Position (world) + 3 Farbe (rgb).
|
||||
pub const GRID_FLOATS_PER_VERTEX: usize = 6;
|
||||
|
||||
/// Farbe einer normalen ("Minor") Gitterlinie (RGB 0..1). Gedecktes Neutralgrau,
|
||||
/// das sich vom hellen Hintergrund (#f5f5f5) und vom Modell (~0.8er Grau) abhebt,
|
||||
/// ohne zu dominieren.
|
||||
pub const MINOR_COLOR: [f32; 3] = [0.72, 0.72, 0.74];
|
||||
|
||||
/// Farbe einer "Major"-Linie (jede 5. Linie, inkl. Mittelachse): etwas kraeftiger
|
||||
/// (dunkler) als Minor, damit die Rasterweite ablesbar bleibt. Auf dem hellen
|
||||
/// Hintergrund liest sich "dunkler = betonter" am besten (CAD-uebliche Optik).
|
||||
pub const MAJOR_COLOR: [f32; 3] = [0.55, 0.55, 0.58];
|
||||
|
||||
/// Jede n-te Linie (vom Zentrum aus gezaehlt) wird als Major-Linie eingefaerbt.
|
||||
const MAJOR_EVERY: i32 = 5;
|
||||
|
||||
/// Anzahl der Gitterlinien PRO Richtung fuer gegebene Ausdehnung/Weite.
|
||||
///
|
||||
/// Linien sitzen bei `i * spacing` fuer `i` in `-steps..=steps`, wobei
|
||||
/// `steps = floor((extent/2) / spacing)`. Ergebnis ist damit immer ungerade
|
||||
/// (die Mittelachse `i = 0` ist stets dabei). 0, falls Parameter unbrauchbar.
|
||||
pub fn line_count(extent: f32, spacing: f32) -> usize {
|
||||
if !(extent > 0.0) || !(spacing > 0.0) {
|
||||
return 0;
|
||||
}
|
||||
let steps = ((extent * 0.5) / spacing).floor() as i32;
|
||||
if steps < 0 {
|
||||
return 0;
|
||||
}
|
||||
(2 * steps + 1) as usize
|
||||
}
|
||||
|
||||
/// Baut die Linien-Vertices (Positions + Farbe, interleaved) fuer das Bodengitter.
|
||||
///
|
||||
/// - Quadratisches Raster in der Welt-XZ-Ebene, zentriert um (0,0), auf `y = elevation`.
|
||||
/// - `extent` = Gesamt-Kantenlaenge (Meter); tatsaechlich genutzte Halb-Ausdehnung
|
||||
/// ist `steps * spacing` (auf ein ganzes Vielfaches der Weite gerundet), damit
|
||||
/// das Gitter ein sauberes Quadrat bildet, dessen Linien sich an den Ecken treffen.
|
||||
/// - `spacing` = Linienabstand (Meter).
|
||||
/// - Jede `MAJOR_EVERY`-te Linie (inkl. Mittelachse) bekommt `MAJOR_COLOR`, sonst `MINOR_COLOR`.
|
||||
///
|
||||
/// Ausgabe: `LineList`-Vertices (je 2 aufeinanderfolgende Vertices = 1 Segment),
|
||||
/// interleaved `[px,py,pz, r,g,b, ...]`. Leerer Vektor bei unbrauchbaren Parametern.
|
||||
pub fn build_ground_grid(elevation: f32, extent: f32, spacing: f32) -> Vec<f32> {
|
||||
let n = line_count(extent, spacing);
|
||||
if n == 0 {
|
||||
return Vec::new();
|
||||
}
|
||||
let steps = (n as i32 - 1) / 2;
|
||||
let half = steps as f32 * spacing;
|
||||
|
||||
// 2 Richtungen * n Linien * 2 Endpunkte * 6 f32.
|
||||
let mut verts: Vec<f32> = Vec::with_capacity(2 * n * 2 * GRID_FLOATS_PER_VERTEX);
|
||||
|
||||
let mut push = |x: f32, z: f32, c: [f32; 3]| {
|
||||
verts.push(x);
|
||||
verts.push(elevation);
|
||||
verts.push(z);
|
||||
verts.push(c[0]);
|
||||
verts.push(c[1]);
|
||||
verts.push(c[2]);
|
||||
};
|
||||
|
||||
for i in -steps..=steps {
|
||||
let c = if i % MAJOR_EVERY == 0 {
|
||||
MAJOR_COLOR
|
||||
} else {
|
||||
MINOR_COLOR
|
||||
};
|
||||
let p = i as f32 * spacing;
|
||||
// Linie parallel zur X-Achse (konstantes z = p): von -half bis +half in x.
|
||||
push(-half, p, c);
|
||||
push(half, p, c);
|
||||
// Linie parallel zur Z-Achse (konstantes x = p): von -half bis +half in z.
|
||||
push(p, -half, c);
|
||||
push(p, half, c);
|
||||
}
|
||||
|
||||
verts
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn line_count_inclusive_and_odd() {
|
||||
// extent 20, spacing 1 -> steps 10 -> 21 Linien je Richtung.
|
||||
assert_eq!(line_count(20.0, 1.0), 21);
|
||||
// extent 10, spacing 2 -> steps 2 (half=5, 5/2=2.5 floor 2) -> 5 Linien.
|
||||
assert_eq!(line_count(10.0, 2.0), 5);
|
||||
// Mittelachse immer dabei -> ungerade.
|
||||
assert_eq!(line_count(3.0, 1.0) % 2, 1);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn degenerate_params_yield_empty() {
|
||||
assert_eq!(line_count(0.0, 1.0), 0);
|
||||
assert_eq!(line_count(20.0, 0.0), 0);
|
||||
assert_eq!(line_count(-5.0, 1.0), 0);
|
||||
assert!(build_ground_grid(0.0, 0.0, 1.0).is_empty());
|
||||
assert!(build_ground_grid(0.0, 20.0, 0.0).is_empty());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn vertex_count_matches_lines() {
|
||||
let extent = 20.0;
|
||||
let spacing = 1.0;
|
||||
let n = line_count(extent, spacing); // 21
|
||||
let verts = build_ground_grid(0.0, extent, spacing);
|
||||
// 2 Richtungen * n Linien * 2 Endpunkte * 6 f32.
|
||||
assert_eq!(verts.len(), 2 * n * 2 * GRID_FLOATS_PER_VERTEX);
|
||||
// = 84 Vertices.
|
||||
assert_eq!(verts.len() / GRID_FLOATS_PER_VERTEX, 4 * n);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn all_vertices_lie_on_elevation_plane_and_within_extent() {
|
||||
let elevation = 2.7;
|
||||
let extent = 20.0;
|
||||
let spacing = 1.0;
|
||||
let half = 10.0; // steps=10 * spacing=1
|
||||
let verts = build_ground_grid(elevation, extent, spacing);
|
||||
for v in verts.chunks(GRID_FLOATS_PER_VERTEX) {
|
||||
let (x, y, z) = (v[0], v[1], v[2]);
|
||||
assert_eq!(y, elevation, "alle Vertices auf y = elevation");
|
||||
assert!(x.abs() <= half + 1e-6);
|
||||
assert!(z.abs() <= half + 1e-6);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn first_line_endpoints_and_center_is_major() {
|
||||
let extent = 20.0;
|
||||
let spacing = 1.0;
|
||||
let half = 10.0;
|
||||
let verts = build_ground_grid(0.0, extent, spacing);
|
||||
// Erste Linie: i = -steps = -10 (kein Vielfaches von 5? -10 % 5 == 0 -> Major).
|
||||
// X-parallele Linie bei z = -10, von x=-half..+half.
|
||||
assert_eq!(&verts[0..3], &[-half, 0.0, -10.0]);
|
||||
assert_eq!(&verts[3..6], &MAJOR_COLOR); // -10 ist Vielfaches von 5 -> Major
|
||||
assert_eq!(&verts[6..9], &[half, 0.0, -10.0]);
|
||||
|
||||
// Mittelachse (i = 0) muss als Major vorkommen: suche Vertex bei z=0 X-Linie.
|
||||
let center_is_major = verts
|
||||
.chunks(GRID_FLOATS_PER_VERTEX)
|
||||
.any(|v| v[2] == 0.0 && v[3..6] == MAJOR_COLOR);
|
||||
assert!(center_is_major);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn extent_snaps_to_spacing_multiple() {
|
||||
// extent 21, spacing 2 -> steps = floor(10.5/2) = 5 -> half = 10 (nicht 10.5).
|
||||
let verts = build_ground_grid(0.0, 21.0, 2.0);
|
||||
let max_abs = verts
|
||||
.chunks(GRID_FLOATS_PER_VERTEX)
|
||||
.flat_map(|v| [v[0].abs(), v[2].abs()])
|
||||
.fold(0.0_f32, f32::max);
|
||||
assert_eq!(max_abs, 10.0);
|
||||
}
|
||||
}
|
||||
@@ -14,6 +14,7 @@
|
||||
// Standard-Build (`cargo test`/`cargo build` ohne Features) enthaelt nur die
|
||||
// GPU-freien Schichten und ist damit unabhaengig von einer Display-Session.
|
||||
|
||||
pub mod grid;
|
||||
pub mod math;
|
||||
pub mod mesh;
|
||||
pub(crate) mod openings;
|
||||
@@ -808,5 +809,13 @@ mod tests {
|
||||
validator
|
||||
.validate(&module)
|
||||
.unwrap_or_else(|e| panic!("mesh: WGSL-Validierung fehlgeschlagen: {e:?}"));
|
||||
|
||||
// Bodengitter-Shader ebenso headless validieren.
|
||||
let grid_src = super::shaders::GRID_WGSL;
|
||||
let grid_module = naga::front::wgsl::parse_str(grid_src)
|
||||
.unwrap_or_else(|e| panic!("grid: WGSL-Parse-Fehler: {e:?}"));
|
||||
Validator::new(ValidationFlags::all(), Capabilities::all())
|
||||
.validate(&grid_module)
|
||||
.unwrap_or_else(|e| panic!("grid: WGSL-Validierung fehlgeschlagen: {e:?}"));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -82,3 +82,43 @@ fn fs_main(in : VsOut) -> @location(0) vec4<f32> {
|
||||
return vec4<f32>(shaded, 1.0);
|
||||
}
|
||||
"#;
|
||||
|
||||
/// WGSL des Referenz-Bodengitters (grid.rs). Eigene, schlanke `LineList`-Pipeline:
|
||||
/// KONSTANTE Farbe (unlit — unabhaengig von Normalen/Licht), nur View-Projektion
|
||||
/// aus demselben `Globals`-Uniform (group(0) binding(0)) wie die Mesh-Pipeline.
|
||||
/// Vertex-Attribute: position (world), color (rgb). Depth-Test laeuft ueber den
|
||||
/// gemeinsamen Tiefenpuffer, sodass das Modell das Gitter korrekt verdeckt.
|
||||
pub const GRID_WGSL: &str = r#"
|
||||
struct Globals {
|
||||
view_proj : mat4x4<f32>,
|
||||
light_dir : vec4<f32>,
|
||||
sky_color : vec4<f32>,
|
||||
ground_color : vec4<f32>,
|
||||
sun_color : vec4<f32>,
|
||||
mode : vec4<f32>,
|
||||
};
|
||||
@group(0) @binding(0) var<uniform> globals : Globals;
|
||||
|
||||
struct VsIn {
|
||||
@location(0) position : vec3<f32>,
|
||||
@location(1) color : vec3<f32>,
|
||||
};
|
||||
|
||||
struct VsOut {
|
||||
@builtin(position) clip_pos : vec4<f32>,
|
||||
@location(0) color : vec3<f32>,
|
||||
};
|
||||
|
||||
@vertex
|
||||
fn vs_main(in : VsIn) -> VsOut {
|
||||
var out : VsOut;
|
||||
out.clip_pos = globals.view_proj * vec4<f32>(in.position, 1.0);
|
||||
out.color = in.color;
|
||||
return out;
|
||||
}
|
||||
|
||||
@fragment
|
||||
fn fs_main(in : VsOut) -> @location(0) vec4<f32> {
|
||||
return vec4<f32>(in.color, 1.0);
|
||||
}
|
||||
"#;
|
||||
|
||||
@@ -327,6 +327,25 @@ impl WebModelRenderer {
|
||||
self.renderer.set_white_mode(white);
|
||||
}
|
||||
|
||||
/// Schaltet das Referenz-Bodengitter (grid.rs) ein/aus und setzt seine
|
||||
/// Parameter. `elevation_m` = Hoehe der XZ-Gitterebene (world-Meter, i. d. R.
|
||||
/// die z=0-Ebene des aktiven Geschosses); `extent_m` = quadratische Kantenlaenge
|
||||
/// (auf ein Vielfaches von `spacing_m` gerundet); `spacing_m` = Linienabstand.
|
||||
/// Das Gitter ist zentriert um den Welt-Ursprung (x=0, z=0). Wirkt erst beim
|
||||
/// naechsten `render`. Default ohne Aufruf: unsichtbar (bestehende Aufrufer
|
||||
/// bleiben unveraendert). Jede 5. Linie (inkl. Mittelachse) ist eine Major-Linie.
|
||||
pub fn set_ground_grid(
|
||||
&mut self,
|
||||
visible: bool,
|
||||
elevation_m: f32,
|
||||
extent_m: f32,
|
||||
spacing_m: f32,
|
||||
) -> Result<(), JsValue> {
|
||||
self.renderer
|
||||
.set_ground_grid(&self.device, visible, elevation_m, extent_m, spacing_m);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Surface an eine neue Pixelgroesse anpassen (DPR beachtet der Aufrufer).
|
||||
pub fn resize(&mut self, width: u32, height: u32) {
|
||||
let (w, h) = (width.max(1), height.max(1));
|
||||
|
||||
Reference in New Issue
Block a user