render3d Textur-Spike: RenderStyle::Textured real (Bild-Textur auf Waenden)

- Zweiter Vertex-Pfad [pos,normal,uv] via build_walls_mesh_textured, ABGELEITET
  aus dem fertigen Mesh (Positionen/Normalen/Indizes 1:1) -> Alt-Pfad
  [pos,normal,color] bitgleich; per Regressionstest belegt.
- Prozedurale 256x256-Schachbrett-Textur (kein Asset, kein image-Crate),
  Sampler Linear/Repeat, Textur-Bind-Group group 1 (Globals bleibt group 0).
- MESH_TEXTURED_WGSL: gleiche Beleuchtung wie MESH_WGSL, Albedo aus textureSample.
  Pipeline in Depth/MSAA/Color-Target bitidentisch zur Haupt-Pipeline.
- UV planar in Metern: Mantel u=entlang Achse/v=Hoehe, Deckel u=x/v=z;
  weltraumstabil, keine Verzerrung an Gehrungen. 1 Kachel = 1 m.
- spike3d: Taste T schaltet Shaded <-> Textured zur Laufzeit (kein Re-Meshing).
- Feature-gegatet, Default-Build/-Darstellung unveraendert. cargo test 58 (default)
  / 59 (--features render, inkl. naga-Test MESH_TEXTURED_WGSL) gruen.
This commit is contained in:
2026-07-05 00:30:34 +02:00
parent ecefe61611
commit 0ca3b1dd57
6 changed files with 565 additions and 15 deletions
+80 -6
View File
@@ -38,23 +38,27 @@ pub use math::{
view_matrix, view_projection, Mat4,
};
pub use mesh::{
append_context_mesh, build_model_mesh, build_scene_mesh, build_walls_mesh, extrude_slab,
extrude_wall, triangulate,
append_context_mesh, build_model_mesh, build_scene_mesh, build_walls_mesh,
build_walls_mesh_textured, extrude_slab, extrude_wall, textured_from_mesh, triangulate,
};
pub use section::{
cut_section, ComponentKind, ComponentRef, CutPolygon, SectionEdge, SectionOutput, SectionPlane,
};
pub use types::{
Camera, CameraPreset, Mesh, MeshInput, MeshKind, Point2, Projection, Rgb, SlabInput, WallInput,
FLOATS_PER_VERTEX,
Camera, CameraPreset, Mesh, MeshInput, MeshKind, Point2, Projection, Rgb, SlabInput,
TexturedMesh, WallInput, FLOATS_PER_VERTEX, TEXTURED_FLOATS_PER_VERTEX,
};
// --- Tests: Mesh-Erzeugung (Muster wie render2d/tessellate) -------------------
#[cfg(test)]
mod tests {
use super::mesh::{build_walls_mesh, extrude_wall, INDICES_PER_BOX, VERTS_PER_BOX};
use super::types::{Hole, Mesh, Opening, WallInput, WallLayer, FLOATS_PER_VERTEX};
use super::mesh::{
build_walls_mesh, build_walls_mesh_textured, extrude_wall, INDICES_PER_BOX, VERTS_PER_BOX,
};
use super::types::{
Hole, Mesh, Opening, WallInput, WallLayer, FLOATS_PER_VERTEX, TEXTURED_FLOATS_PER_VERTEX,
};
/// Bequemer Bau einer achsparallelen Wand entlang +X.
fn wall_x(len: f32, thickness: f32, height: f32) -> WallInput {
@@ -80,6 +84,67 @@ mod tests {
)
}
#[test]
fn texturierter_pfad_ist_geometrisch_deckungsgleich_mit_shaded() {
// Der texturierte Wand-Pfad (`build_walls_mesh_textured`, [pos,normal,uv])
// muss GEOMETRISCH bitgleich zum Alt-Pfad (`build_walls_mesh`,
// [pos,normal,color]) sein: gleiche Vertex-/Index-Zahl und -Reihenfolge,
// identische Positionen und Normalen. Nur der letzte Kanal (Farbe -> UV)
// unterscheidet sich. So ist belegt, dass der additive UV-Kanal den
// Alt-Pfad nicht beruehrt.
let walls = [wall_x(3.0, 0.2, 2.5)];
let shaded = build_walls_mesh(&walls);
let tex = build_walls_mesh_textured(&walls);
assert_eq!(tex.indices, shaded.indices, "Index-Puffer identisch");
assert_eq!(
tex.vertex_count(),
shaded.vertex_count(),
"gleiche Vertex-Zahl"
);
for i in 0..shaded.vertex_count() {
let sb = i * FLOATS_PER_VERTEX;
let tb = i * TEXTURED_FLOATS_PER_VERTEX;
// Position (0..3) und Normale (3..6) bitgleich uebernommen.
for k in 0..6 {
assert_eq!(
shaded.verts[sb + k],
tex.verts[tb + k],
"Vertex {i} Kanal {k}: pos/normal muss identisch sein"
);
}
}
}
#[test]
fn texturierte_uv_ist_weltmassstaeblich() {
// UV-Massstab: `u`/`v` in Metern (1 Kachel = 1 m). Fuer eine Wand entlang +X
// liegt die +Z-Mantelflaeche bei Normale (0,0,1); dort ist die Tangente
// t=(-n.z,0,n.x)=(-1,0,0), also u = -x und v = Hoehe. Die Deckflaeche
// (Normale +Y) projiziert auf u=x, v=z. Wir pruefen, dass es UEBERHAUPT
// Vertices mit v == Wandhoehe (2.5) bzw. v == 0 gibt (Hoehe wandert als v
// mit) — belegt den weltmassstaeblichen Hoehen-Kanal.
let tex = build_walls_mesh_textured(&[wall_x(3.0, 0.2, 2.5)]);
let n = tex.vertex_count();
let mut saw_top = false;
let mut saw_bottom = false;
for i in 0..n {
let b = i * TEXTURED_FLOATS_PER_VERTEX;
let ny = tex.verts[b + 4];
let v = tex.verts[b + 7];
// Nur Mantelflaechen (ny ~ 0) tragen die Hoehe als v.
if ny.abs() < 0.5 {
if (v - 2.5).abs() < 1e-5 {
saw_top = true;
}
if v.abs() < 1e-5 {
saw_bottom = true;
}
}
}
assert!(saw_top, "Mantel-Vertex mit v == Wandhoehe (2.5 m) erwartet");
assert!(saw_bottom, "Mantel-Vertex mit v == 0 (Sockel) erwartet");
}
#[test]
fn eine_wand_hat_quader_zaehlung() {
// Eine Wand -> ein Quader: 24 Vertices, 36 Indizes (12 Dreiecke).
@@ -927,5 +992,14 @@ mod tests {
Validator::new(ValidationFlags::all(), Capabilities::all())
.validate(&cap_module)
.unwrap_or_else(|e| panic!("cap: WGSL-Validierung fehlgeschlagen: {e:?}"));
// Texturierter Wand-Shader (RenderStyle::Textured, Bild-Textur in group 1)
// ebenso headless validieren — faengt Textur-/Sampler-Bindungsfehler ab.
let tex_src = super::shaders::MESH_TEXTURED_WGSL;
let tex_module = naga::front::wgsl::parse_str(tex_src)
.unwrap_or_else(|e| panic!("textured: WGSL-Parse-Fehler: {e:?}"));
Validator::new(ValidationFlags::all(), Capabilities::all())
.validate(&tex_module)
.unwrap_or_else(|e| panic!("textured: WGSL-Validierung fehlgeschlagen: {e:?}"));
}
}