# Briefing — Nativer wgpu-3D-Renderer (M0 Design + M1 Spike)

> Schwester-Dokument zu `wgpu-2d-renderer-briefing.md`. Beschreibt den Weg vom
> jetzigen three.js-3D-View (WebGL im WebKitGTK-Webview) zu einer nativen
> wgpu-Engine (Rust). Dieses Dokument ist der ANFANG: M0 (Bestandsaufnahme +
> Port-Plan) und M1 (entkoppelter Standalone-Spike). Noch NICHT die Migration.

## Commit-/Spuren-Regel
Wie im ganzen Repo: keine Fremd-Tool-Hinweise im Code, in Kommentaren oder der Historie. Kommentare deutsch, Identifier englisch. Kein Commit ohne Absprache.

## Warum
Der 3D-View laeuft heute als three.js/WebGL im Tauri-Webview (WebKitGTK). Wie beim
2D-Plan bremst dieser Compositor unter Linux (siehe `wgpu-2d-renderer-briefing.md`
und Memo `webkitgtk-bottleneck`). Die schwere 3D-Grafik soll daher **nativ mit wgpu**
gerendert werden (Rust), die Webview macht nur noch UI-Chrome. Das umgeht den
Webview-Compositor komplett und teilt sich die Toolchain mit dem 2D-Renderer
(`src-tauri/render2d/`, gleiche Feature-Stufung, gleiche Test-Muster).

---

## 1. Bestandsaufnahme — was der three.js-View rendert

Referenz: `src/viewport/Viewport3D.tsx` (analysiert), plus die Geometrie-Grundlage
in `src/model/geometry.ts`, `src/model/wall.ts`, `src/model/joins.ts`,
`src/geometry/opening.ts`, `src/geometry/stair.ts`. Zeilenangaben beziehen sich auf
den Stand der Analyse.

### Koordinaten-Konvention (verbindlich)
Das Modell ist 2D in Metern (`x`, `y`) plus Hoehe `z`. Die 3D-Welt ist **Y-up**:

```
world.x = model.x
world.y = Hoehe (z)
world.z = model.y
```

D.h. **der Grundriss liegt in der XZ-Ebene, die Extrusion laeuft entlang +Y.**
Belegt u.a. in `Viewport3D.tsx`:
- Kommentar (~Z. 473): „Modell (x,y,z) → Three (x, z, y) (Z = Hoehe nach oben)".
- Kontext-Mesh (~Z. 1679–1684): `verts[i]=pos.x; verts[i+1]=pos.z (Hoehe); verts[i+2]=pos.y`.
- Wand-Griffe (~Z. 764): `new THREE.Vector3(wall.start.x, zBottom, wall.start.y)`.
- Workplane-Raycast (~Z. 627): `{ x: hit.x, y: hit.z }` (Three → Modell).

Diese Konvention ist in `render3d` 1:1 uebernommen (`types.rs`, `mesh.rs`).

### Waende (der Kern)
- Funktion `addWallMeshes()` / `addLayerPrism()` (~Z. 1939–2039, 2237–2271).
- **Mesh-Weg:** `THREE.ExtrudeGeometry` (~Z. 2248) ueber die 2D-Bandform, die
  `clippedBand(p1, p2, offA, offB, startCut, endCut)` liefert (~Z. 2237; Funktion in
  `src/model/geometry.ts:87`). Extrudiert wird um `depth = zTop - zBottom`.
- Die Bandform kommt aus Achse + Dicke: `wallBand`/`wallCorners`
  (`geometry.ts:53`/`:70`) versetzen die Achse um `thickness/2` entlang der
  **Links-Normale** `leftNormal(u) = (-u.y, u.x)` (`geometry.ts:17`), CCW-Umlauf.
- `ExtrudeGeometry` liegt in der XY-Ebene und waechst entlang +Z; three.js dreht das
  Prisma daher um +90 Grad um X und setzt es auf `topY` (~Z. 2266–2271). In wgpu
  extrudieren wir direkt in world (XZ-Grundriss, +Y-Hoehe) und sparen die Drehung.
- **Hoehe/Basis:** `wallVerticalExtent(project, wall)` (`wall.ts:56`) liefert
  absolute `zBottom`/`zTop` (aus `wall.bottom`/`wall.top`-Ankern bzw. Geschoss-
  `baseElevation + wall.height`).
- **Mehrschichtig:** je `wt.layers`-Schicht ein eigenes Prisma mit Dicken-Offset
  (~Z. 1991–2015). M1 extrudiert vereinfacht EINE Schicht (Gesamtdicke).
- **Ecken/Gehrung:** `computeJoins()` (`joins.ts:45`) berechnet Schnittlinien
  (`startCut`/`endCut`), die `clippedBand` an L-Ecken auf Gehrung zieht
  (`miterLine`, `joins.ts:101`). M1 laesst das noch weg (stumpfe Enden).

### Oeffnungen (Fenster/Tueren)
- `addOpeningMeshes()` (~Z. 2056–2169) + Segmentierung in `addWallMeshes` (~Z.
  1968–2035). **Kein CSG/Boolean:** die Wand wird entlang der Achse in Segmente
  zerlegt (`openingInterval`, `geometry/opening.ts:31`), und je Oeffnung entstehen
  bis zu drei Prismen: Wand DAVOR, **Bruestung** unter dem Fenster (`sillRel`),
  **Sturz** ueber der Oeffnung (`headRel`). Rahmen/Fluegel als `BoxGeometry`;
  Glas semitransparent, Tuerfluegel um `swingAngle` gedreht.

### Treppen
- `addStairMeshes()` (~Z. 2357–2409). `stairGeometry()` (`geometry/stair.ts`)
  liefert Trittflaechen (Footprint + Steig-Hoehe) + optionalen Podest-Umriss; jede
  Stufe als extrudierter Block (`ExtrudeGeometry`), Hoehe = `stairVerticalExtent`.

### Decken/Platten
- `addCeilingMesh()` (~Z. 2290–2347). `ceiling.outline` als `ExtrudeGeometry`, Tiefe
  = Deckenstaerke, waechst nach unten von `zTop` (`ceilingVerticalExtent`, `wall.ts:80`).

### Raeume
- Nicht als eigenstaendige 3D-Koerper gerendert (2D-Grundriss-Repraesentation).

### Kontext/Gelaende
- `buildContext()` (~Z. 1651–1718). Terrain/importierte Meshes als rohe
  `BufferGeometry` (Positions/Indices, Koordinaten-Swap wie oben); Hoehenlinien als
  `LineSegments`. Dazu ein `GridHelper` (~Z. 421) auf OKFF-Hoehe.

### Materialien
- `MeshLambertMaterial` (Waende/Oeffnungen/Treppen, per Komponente eingefaerbt,
  ~Z. 2176–2206), `MeshStandardMaterial` (Weiss-/Textur-Modus + Terrain, PBR:
  `roughness`/`metalness`/`aoMap`, ~Z. 445–491, 2001–2015), `MeshBasicMaterial`
  (Hidden-Line-Flaechen + immer-oben-Marker), `LineBasicMaterial` (Kanten/2D-
  Zeichnungen). Render-Modi: shaded / white / textured / wireframe / hidden-line.

### Beleuchtung
- `AmbientLight(0xffffff, 0.6)` (~Z. 416) + `DirectionalLight(0xffffff, 1.1)` bei
  `(6, 12, 4)` (~Z. 417). **Keine Schatten** konfiguriert. Keine Hemisphere/Point-
  Lights.

### Kamera + Presets
- Zwei Kameras: `PerspectiveCamera(fov, 1, 0.1, 1000)` (~Z. 367) und
  `OrthographicCamera(-1,1,1,-1, 0.1, 5000)` (~Z. 374). `applyView3d()` (~Z.
  1566–1633) setzt fuenf Presets:
  - **front** — Richtung `(0,0,1)`, orthografisch.
  - **side** — Richtung `(1,0,0)`, orthografisch.
  - **top** — Richtung `(0,1,~0)`, orthografisch (Rotation gesperrt).
  - **iso** — Richtung `(1,1,1)` normiert, orthografisch.
  - **perspective** — Richtung `(0.62,0.5,0.7)` normiert, perspektivisch.
- Umschalten perspektiv/ortho ueber `active = perspective ? camera : orthoCamera`
  (~Z. 1602); Ortho-Frustum aus den Modell-Bounds (`updateOrthoFrustum`, ~Z. 1522).
- **OrbitControls** (~Z. 391–413): Mitteltaste orbit, Shift+Mitte pan, Rad zoom
  (linke/rechte Taste fuer Auswahl/Kontextmenue umgewidmet).

### Griffe / Gizmos
- Editier-Griffe (`SphereGeometry`, ~Z. 711–814): Endpunkt (orange), Hoehe (blau),
  Verschieben (gruen); `depthTest:false` (immer sichtbar). Drag ueber Workplane-
  Raycast. Fuer den nativen Renderer spaeter relevant (eigener Overlay-Pass).

### Schnittebene
- **Nicht implementiert:** keine `renderer.clippingPlanes` / `localClippingEnabled`.
  Schnitte laufen aktuell 2D. Fuer wgpu ein eigenständiger spaeterer Milestone
  (Clip-Distances im Shader oder Stencil-Capping).

### Tiefe / Culling
- Tiefentest three.js-Standard aktiv. Backface-Culling per Default (Ausnahme:
  Terrain/Import `DoubleSide`). Diverse Overlays mit `depthTest:false`.

---

## 2. Port-Plan nach wgpu

### Datenfluss
Web-Modell → **geflachte Eingabe** (`WallInput`, spaeter Oeffnungen/Treppen/Decken)
→ `render3d`-Mesh-Erzeugung → GPU-Buffers → Draw. Analog zum 2D-Pfad (`Scene` →
Tessellierung → Buffers). Die Eingabe ist bewusst serde-only und GPU-frei, damit die
Mesh-Logik headless testbar bleibt.

### Mesh-Erzeugung (Waende extrudieren)
- Band aus Achse + Dicke ueber die Links-Normale (`(-u.y, u.x) * thickness/2`,
  CCW), exakt wie `wallCorners`. Extrusion in world: XZ-Grundriss, +Y von
  `base_elevation` bis `+height`.
- Ein Quader = 6 Seiten, je eigene Vertices mit Flaechen-Normale (flaches Shading,
  korrektes Backface-Culling). 24 Vertices / 36 Indizes je Wand.
- Spaeter: mehrschichtige Waende (je Schicht ein Prisma), Gehrung
  (`computeJoins`/`clippedBand`-Port), Oeffnungs-Segmentierung (Bruestung/Sturz).

### Kamera (View/Projektion, Presets)
- `look_at` (right-handed, Kamera blickt entlang -Z im View-Raum), `perspective`
  und `orthographic` — beide auf **Clip-Z in [0,1]** (wgpu-Konvention, NICHT [-1,1]).
- Fuenf Presets (`preset_camera`): front/top/side orthografisch achsparallel,
  iso/persp perspektivisch. `top` mit up=-Z, damit Modell-Y im Bild nach unten
  zeigt (wie die 2D-Sicht).
- Orbit-Kamera aus Yaw/Pitch/Distanz (`orbit_eye`, Pitch geklemmt gegen Pol-Flip).

### Beleuchtung
- Zunaechst EIN Directional-Light (Richtung ZUM Licht) + ambienter Sockel im
  Fragment-Shader (WGSL) — das GPU-Aequivalent zu `AmbientLight(0.6)` +
  `DirectionalLight(1.1)@(6,12,4)`. Diffuses Lambert. PBR (Rauheit/Metallik/
  Texturen/AO) spaeter.

### Tiefenpuffer + Culling
- `Depth32Float`-Attachment, `depth_compare = Less`, `depth_write = true`.
- `front_face = Ccw`, `cull_mode = Back` (die Extrusion liefert konsistent nach
  aussen zeigende CCW-Flaechen).

### Matrix-Mathematik
- Handgerechnet (kein `glam`) in der serde-only Schicht — begruendet in `math.rs`:
  die Standard-Schicht soll wie in render2d ohne Zusatz-Crates headless test-/baubar
  bleiben; der Umfang (perspective/ortho/look_at + Orbit) ist klein und exakt
  testbar. Ein spaeterer Wechsel zu `glam` (nur in der GPU-Schicht) bleibt moeglich,
  ohne die Kamera-Tests anzufassen. Alles spalten-major, direkt als Uniform ladbar.

### Milestones M2..Mn
- **M2 — Mehrschichtige Waende + Gehrung:** Port von `computeJoins`/`miterLine` +
  `clippedBand` → gehrte Bandformen je Schicht; Farben/Materialien je Komponente.
- **M3 — Oeffnungen:** Achsen-Segmentierung (Bruestung/Sturz) + Rahmen/Glas/Fluegel
  als eigene Meshes; Tuerschwenk-Winkel.
- **M4 — Treppen + Decken:** Port von `stairGeometry`/`ceilingVerticalExtent`.
- **M5 — Kontext/Gelaende:** rohe Terrain-/Import-Meshes + Hoehenlinien + Grid.
- **M6 — Materialien (PBR):** `MeshStandardMaterial`-Aequivalent (roughness/
  metalness/albedo/AO-Textur); Render-Modi shaded/white/textured/wireframe/hidden.
- **M7 — Schnittebene:** Clip-Distances im Shader oder Stencil-Capping (Feature, das
  der three.js-View gar nicht hat — echter Mehrwert).
- **M8 — Griffe/Gizmos + Picking:** Overlay-Pass (immer-oben) + GPU-/Ray-Picking.
- **M9 — Tauri-Integration:** Surface unter der Webview (raw-window-handle,
  Z-Order, Input-Routing) — siehe `wgpu-2d-renderer-briefing.md` M2 (gleiches
  Integrations-Problem; einmal loesen, fuer 2D+3D nutzen). Kamera-Presets/Orbit-
  Input aus der Webview an den Renderer.

---

## 3. Was in `src-tauri/render3d/` steht (M1)

Neue, eigenstaendige Crate (eigener leerer `[workspace]`-Block, wie render2d), damit
`cargo test`/`build` unabhaengig vom Tauri-Workspace laufen. Feature-Stufung 1:1 wie
render2d:

- `Cargo.toml` — Features `default` (serde-only) / `render` (wgpu) / `window`
  (winit-Spike). `[[bin]] spike3d` mit `required-features = ["window"]`.
- `src/types.rs` — serde-only Eingabe: `WallInput { start, end, thickness, height,
  base_elevation, color }`, `Camera` (+ `Projection`), `CameraPreset`; Ausgabe
  `Mesh` (interleaved `[pos.xyz, normal.xyz, color.rgb]` + Indizes) mit
  `vertex_count`/`triangle_count`/`bounds`. Koordinaten-Konvention dokumentiert.
- `src/mesh.rs` — Wand-Extrusion: `extrude_wall`/`build_walls_mesh`. Band ueber
  Links-Normale, Quader mit sechs eigenen Seiten, nach aussen zeigende Normalen.
- `src/math.rs` — `Mat4` (spalten-major), `perspective`/`orthographic` (Clip-Z
  [0,1]), `look_at`, `view_projection`, `orbit_eye`, `preset_camera` (fuenf Presets).
- `src/shaders.rs` — WGSL (`MESH_WGSL`): View-Projektion-Uniform + Directional-
  Light + ambienter Sockel im Fragment-Shader.
- `src/gpu.rs` (Feature `render`) — `Renderer`: eine Pipeline mit Tiefenpuffer,
  View-Projektions-Uniform, Backface-Culling. `upload_walls` → GPU-Buffers,
  `render(camera, viewport)`.
- `src/bin/spike3d.rs` (Feature `window`) — winit-Fenster mit Demo-Raum (5
  extrudierte Waende) + **Orbit-Kamera** (linke Maustaste dreht Yaw/Pitch, Rad
  zoomt Abstand). Matrix-getrieben, kein Re-Meshing beim Kamera-Wechsel.
- `src/lib.rs` — Modul-Deklarationen, Re-Exports, Tests.

### Tests (`cargo test`, default-Feature)
Muster wie render2d/`glPlanCompile.test.ts`:
- Quader-Zaehlung (eine Wand → 24 Vertices / 36 Indizes / 12 Dreiecke).
- Mehrere Waende addieren sich.
- Bounding-Box deckt Laenge/Dicke/Hoehe ab; `base_elevation` verschiebt in Y.
- Deckel-Normale = +Y; **alle Mantel-Normalen zeigen nach aussen** (Dot mit
  „Vertex − Zentrum" ≥ 0 → Backface-Culling korrekt).
- Diagonale Wand; degenerierte Wand (Start==Ende) erzeugt nichts (kein Absturz).
- Kamera: `look_at` setzt Ziel auf view-z=-dist; Perspektive klemmt z in [0,1];
  `orbit_eye` haelt den Abstand; Presets setzen die richtige Projektionsart.
- Mit `--features render`: WGSL headless via `naga` (Parser + Validator) validiert.

---

## 4. Build-/Test-Ergebnis

Alle Gates gruen (Toolchain: cargo 1.96, wgpu 22, winit 0.30):
- `cargo test` (default) — **12/12** gruen (Mesh + Kamera).
- `cargo test --features render` — **13/13** gruen (inkl. WGSL-naga-Validierung).
- `cargo build` (default), `--features render`, `--features window` — je gruen,
  **keine Warnungen**.
- Trace-Scan sauber (keine KI-Spuren).
- Web-Gates unberuehrt (nur `src-tauri/` + `docs/` angefasst; `src/` nur gelesen).

**Visuelle Fenster-Verifikation** ist headless NICHT moeglich. Auf einer aktiven
Display-Session pruefbar mit:

```
cargo run --features window --bin spike3d
```

Erwartet: ein Raum aus extrudierten Waenden mit diffuser Beleuchtung; linke
Maustaste dreht die Orbit-Kamera, das Rad zoomt.

---

## 5. Naechste Schritte
1. **M2** starten: `computeJoins`/`clippedBand`-Port für gehrte, mehrschichtige
   Waende (die Bandmath ist im Web bereits verifiziert — gleiche Tests portieren).
2. Oeffnungs-Segmentierung (M3) auf demselben Extrusions-Kern.
3. Die **Tauri-Integration (M9)** gemeinsam mit dem 2D-Renderer loesen (ein Surface-
   Unterbau, ein Input-Routing) — das ist der eigentliche Engpass, nicht das
   Rendering. Erst standalone spiken (dieser Stand), dann unter die Webview.
