From 48bbcaed1ec4ea9276163713f47298cfa0904dc5 Mon Sep 17 00:00:00 2001 From: Karim Date: Thu, 2 Jul 2026 00:29:02 +0200 Subject: [PATCH] Nativer 3D-wgpu-Renderer (render3d, M0+M1): Wand-Extrusion, Kamera, Licht Eigenstaendige Crate wie render2d (render/window-Stufung, serde-only Mesh- schicht headless testbar). Wand-Extrusion (Band via Links-Normale, Quader mit nach aussen zeigenden Normalen), handgerechnete Mat4 (perspektiv+ortho, wgpu- Clip-Z [0,1], 5 Kamera-Presets), Directional-Light + Tiefenpuffer + Backface- Culling. Orbit-Spike (cargo run --features window --bin spike3d). Plus Port- Briefing mit M2..M9-Milestones (three.js-Viewport-Bestandsaufnahme). --- docs/design/wgpu-3d-renderer-briefing.md | 265 +++ src-tauri/render3d/.gitignore | 1 + src-tauri/render3d/Cargo.lock | 2605 ++++++++++++++++++++++ src-tauri/render3d/Cargo.toml | 54 + src-tauri/render3d/src/bin/spike3d.rs | 264 +++ src-tauri/render3d/src/gpu.rs | 301 +++ src-tauri/render3d/src/lib.rs | 275 +++ src-tauri/render3d/src/math.rs | 239 ++ src-tauri/render3d/src/mesh.rs | 136 ++ src-tauri/render3d/src/shaders.rs | 57 + src-tauri/render3d/src/types.rs | 182 ++ 11 files changed, 4379 insertions(+) create mode 100644 docs/design/wgpu-3d-renderer-briefing.md create mode 100644 src-tauri/render3d/.gitignore create mode 100644 src-tauri/render3d/Cargo.lock create mode 100644 src-tauri/render3d/Cargo.toml create mode 100644 src-tauri/render3d/src/bin/spike3d.rs create mode 100644 src-tauri/render3d/src/gpu.rs create mode 100644 src-tauri/render3d/src/lib.rs create mode 100644 src-tauri/render3d/src/math.rs create mode 100644 src-tauri/render3d/src/mesh.rs create mode 100644 src-tauri/render3d/src/shaders.rs create mode 100644 src-tauri/render3d/src/types.rs diff --git a/docs/design/wgpu-3d-renderer-briefing.md b/docs/design/wgpu-3d-renderer-briefing.md new file mode 100644 index 0000000..d8f5e14 --- /dev/null +++ b/docs/design/wgpu-3d-renderer-briefing.md @@ -0,0 +1,265 @@ +# 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 Hinweise auf KI-Werkzeuge — nicht im Code, in Kommentaren +oder in der Historie. Kommentare deutsch, Identifier englisch. Kein Commit ohne +Ansage der Hauptinstanz. + +## 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. diff --git a/src-tauri/render3d/.gitignore b/src-tauri/render3d/.gitignore new file mode 100644 index 0000000..ea8c4bf --- /dev/null +++ b/src-tauri/render3d/.gitignore @@ -0,0 +1 @@ +/target diff --git a/src-tauri/render3d/Cargo.lock b/src-tauri/render3d/Cargo.lock new file mode 100644 index 0000000..736f41d --- /dev/null +++ b/src-tauri/render3d/Cargo.lock @@ -0,0 +1,2605 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 4 + +[[package]] +name = "ab_glyph" +version = "0.2.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "01c0457472c38ea5bd1c3b5ada5e368271cb550be7a4ca4a0b4634e9913f6cc2" +dependencies = [ + "ab_glyph_rasterizer", + "owned_ttf_parser", +] + +[[package]] +name = "ab_glyph_rasterizer" +version = "0.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "366ffbaa4442f4684d91e2cd7c5ea7c4ed8add41959a31447066e279e432b618" + +[[package]] +name = "ahash" +version = "0.8.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a15f179cd60c4584b8a8c596927aadc462e27f2ca70c04e0071964a73ba7a75" +dependencies = [ + "cfg-if", + "getrandom", + "once_cell", + "version_check", + "zerocopy", +] + +[[package]] +name = "aho-corasick" +version = "1.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ddd31a130427c27518df266943a5308ed92d4b226cc639f5a8f1002816174301" +dependencies = [ + "memchr", +] + +[[package]] +name = "android-activity" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0f2a1bb052857d5dd49572219344a7332b31b76405648eabac5bc68978251bcd" +dependencies = [ + "android-properties", + "bitflags 2.13.0", + "cc", + "jni", + "libc", + "log", + "ndk", + "ndk-context", + "ndk-sys 0.6.0+11769913", + "num_enum", + "thiserror 2.0.18", +] + +[[package]] +name = "android-properties" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc7eb209b1518d6bb87b283c20095f5228ecda460da70b44f0802523dea6da04" + +[[package]] +name = "android_system_properties" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311" +dependencies = [ + "libc", +] + +[[package]] +name = "anstream" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "824a212faf96e9acacdbd09febd34438f8f711fb84e09a8916013cd7815ca28d" +dependencies = [ + "anstyle", + "anstyle-parse", + "anstyle-query", + "anstyle-wincon", + "colorchoice", + "is_terminal_polyfill", + "utf8parse", +] + +[[package]] +name = "anstyle" +version = "1.0.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "940b3a0ca603d1eade50a4846a2afffd5ef57a9feac2c0e2ec2e14f9ead76000" + +[[package]] +name = "anstyle-parse" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "52ce7f38b242319f7cabaa6813055467063ecdc9d355bbb4ce0c68908cd8130e" +dependencies = [ + "utf8parse", +] + +[[package]] +name = "anstyle-query" +version = "1.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "40c48f72fd53cd289104fc64099abca73db4166ad86ea0b4341abe65af83dadc" +dependencies = [ + "windows-sys 0.61.2", +] + +[[package]] +name = "anstyle-wincon" +version = "3.0.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "291e6a250ff86cd4a820112fb8898808a366d8f9f58ce16d1f538353ad55747d" +dependencies = [ + "anstyle", + "once_cell_polyfill", + "windows-sys 0.61.2", +] + +[[package]] +name = "arrayref" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "76a2e8124351fda1ef8aaaa3bbd7ebbcb486bbcd4225aca0aa0d84bb2db8fecb" + +[[package]] +name = "arrayvec" +version = "0.7.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f02882884d3e1bc524fb12c79f107f6ad0e1cfd498c536ffb494301740995dfe" + +[[package]] +name = "as-raw-xcb-connection" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "175571dd1d178ced59193a6fc02dde1b972eb0bc56c892cde9beeceac5bf0f6b" + +[[package]] +name = "ash" +version = "0.38.0+1.3.281" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0bb44936d800fea8f016d7f2311c6a4f97aebd5dc86f09906139ec848cf3a46f" +dependencies = [ + "libloading", +] + +[[package]] +name = "atomic-waker" +version = "1.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0" + +[[package]] +name = "bit-set" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0481a0e032742109b1133a095184ee93d88f3dc9e0d28a5d033dc77a073f44f" +dependencies = [ + "bit-vec", +] + +[[package]] +name = "bit-vec" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d2c54ff287cfc0a34f38a6b832ea1bd8e448a330b3e40a50859e6488bee07f22" + +[[package]] +name = "bitflags" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" + +[[package]] +name = "bitflags" +version = "2.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b4388bee8683e3d04af747c73422af53102d2bd24d9eadb6cbc100baef4b43f8" + +[[package]] +name = "block" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0d8c1fef690941d3e7788d328517591fecc684c084084702d6ff1641e993699a" + +[[package]] +name = "block2" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2c132eebf10f5cad5289222520a4a058514204aed6d791f1cf4fe8088b82d15f" +dependencies = [ + "objc2", +] + +[[package]] +name = "bumpalo" +version = "3.20.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72f5acc6cb2ba439de613abc23857ec3d78374d8ed5ac84e9d11336e87da8649" + +[[package]] +name = "bytemuck" +version = "1.25.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c8efb64bd706a16a1bdde310ae86b351e4d21550d98d056f22f8a7f7a2183fec" +dependencies = [ + "bytemuck_derive", +] + +[[package]] +name = "bytemuck_derive" +version = "1.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f9abbd1bc6865053c427f7198e6af43bfdedc55ab791faed4fbd361d789575ff" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.118", +] + +[[package]] +name = "bytes" +version = "1.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ae3f5d315924270530207e2a68396c3cc547f6dca3fbdca317cfb1a51edb593" + +[[package]] +name = "calloop" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b99da2f8558ca23c71f4fd15dc57c906239752dd27ff3c00a1d56b685b7cbfec" +dependencies = [ + "bitflags 2.13.0", + "log", + "polling", + "rustix 0.38.44", + "slab", + "thiserror 1.0.69", +] + +[[package]] +name = "calloop-wayland-source" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "95a66a987056935f7efce4ab5668920b5d0dac4a7c99991a67395f13702ddd20" +dependencies = [ + "calloop", + "rustix 0.38.44", + "wayland-backend", + "wayland-client", +] + +[[package]] +name = "cc" +version = "1.2.65" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e228eec9be7c17ccb640b59b36a5cd805ea2a564a4c5e162c2f659fea30d3b96" +dependencies = [ + "find-msvc-tools", + "jobserver", + "libc", + "shlex", +] + +[[package]] +name = "cfg-if" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" + +[[package]] +name = "cfg_aliases" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fd16c4719339c4530435d38e511904438d07cce7950afa3718a84ac36c10e89e" + +[[package]] +name = "cfg_aliases" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724" + +[[package]] +name = "codespan-reporting" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3538270d33cc669650c4b093848450d380def10c331d38c768e34cac80576e6e" +dependencies = [ + "termcolor", + "unicode-width", +] + +[[package]] +name = "colorchoice" +version = "1.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d07550c9036bf2ae0c684c4297d503f838287c83c53686d05370d0e139ae570" + +[[package]] +name = "com" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7e17887fd17353b65b1b2ef1c526c83e26cd72e74f598a8dc1bee13a48f3d9f6" +dependencies = [ + "com_macros", +] + +[[package]] +name = "com_macros" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d375883580a668c7481ea6631fc1a8863e33cc335bf56bfad8d7e6d4b04b13a5" +dependencies = [ + "com_macros_support", + "proc-macro2", + "syn 1.0.109", +] + +[[package]] +name = "com_macros_support" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ad899a1087a9296d5644792d7cb72b8e34c1bec8e7d4fbc002230169a6e8710c" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "combine" +version = "4.6.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba5a308b75df32fe02788e748662718f03fde005016435c444eea572398219fd" +dependencies = [ + "bytes", + "memchr", +] + +[[package]] +name = "concurrent-queue" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ca0197aee26d1ae37445ee532fefce43251d24cc7c166799f4d46817f1d3973" +dependencies = [ + "crossbeam-utils", +] + +[[package]] +name = "core-foundation" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91e195e091a93c46f7102ec7818a2aa394e1e1771c3ab4825963fa03e45afb8f" +dependencies = [ + "core-foundation-sys", + "libc", +] + +[[package]] +name = "core-foundation-sys" +version = "0.8.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" + +[[package]] +name = "core-graphics" +version = "0.23.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c07782be35f9e1140080c6b96f0d44b739e2278479f64e02fdab4e32dfd8b081" +dependencies = [ + "bitflags 1.3.2", + "core-foundation", + "core-graphics-types", + "foreign-types", + "libc", +] + +[[package]] +name = "core-graphics-types" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "45390e6114f68f718cc7a830514a96f903cccd70d02a8f6d9f643ac4ba45afaf" +dependencies = [ + "bitflags 1.3.2", + "core-foundation", + "libc", +] + +[[package]] +name = "crossbeam-utils" +version = "0.8.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28" + +[[package]] +name = "cursor-icon" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f27ae1dd37df86211c42e150270f82743308803d90a6f6e6651cd730d5e1732f" + +[[package]] +name = "d3d12" +version = "22.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bdbd1f579714e3c809ebd822c81ef148b1ceaeb3d535352afc73fd0c4c6a0017" +dependencies = [ + "bitflags 2.13.0", + "libloading", + "winapi", +] + +[[package]] +name = "defmt" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a6e524506490a1953d237cb87b1cfc1e46f88c18f10a22dfe0f507dc6bfc7f7f" +dependencies = [ + "bitflags 1.3.2", + "defmt-macros", +] + +[[package]] +name = "defmt-macros" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0a27770e9c8f719a79d8b638281f4d828f77d8fd61e0bd94451b9b85e576a0b" +dependencies = [ + "defmt-parser", + "proc-macro-error2", + "proc-macro2", + "quote", + "syn 2.0.118", +] + +[[package]] +name = "defmt-parser" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "10d60334b3b2e7c9d91ef8150abfb6fa4c1c39ebbcf4a81c2e346aad939fee3e" +dependencies = [ + "thiserror 2.0.18", +] + +[[package]] +name = "dispatch" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bd0c93bb4b0c6d9b77f4435b0ae98c24d17f1c45b2ff844c6151a07256ca923b" + +[[package]] +name = "dlib" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ab8ecd87370524b461f8557c119c405552c396ed91fc0a8eec68679eab26f94a" +dependencies = [ + "libloading", +] + +[[package]] +name = "document-features" +version = "0.2.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d4b8a88685455ed29a21542a33abd9cb6510b6b129abadabdcef0f4c55bc8f61" +dependencies = [ + "litrs", +] + +[[package]] +name = "downcast-rs" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75b325c5dbd37f80359721ad39aca5a29fb04c89279657cffdda8736d0c0b9d2" + +[[package]] +name = "dpi" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d8b14ccef22fc6f5a8f4d7d768562a182c04ce9a3b3157b91390b52ddfdf1a76" + +[[package]] +name = "env_filter" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "900d271a03799a1ee8d1ca9b19893b48ca674a9284fefcfb85f05e74ed314217" +dependencies = [ + "log", + "regex", +] + +[[package]] +name = "env_logger" +version = "0.11.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "de671bd27a75a797dc9ae289ba1e77276e75e2026408aab65185384e2d5cd3f6" +dependencies = [ + "anstream", + "anstyle", + "env_filter", + "jiff", + "log", +] + +[[package]] +name = "equivalent" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" + +[[package]] +name = "errno" +version = "0.3.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb" +dependencies = [ + "libc", + "windows-sys 0.61.2", +] + +[[package]] +name = "find-msvc-tools" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5baebc0774151f905a1a2cc41989300b1e6fbb29aff0ceffa1064fdd3088d582" + +[[package]] +name = "foldhash" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2" + +[[package]] +name = "foreign-types" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d737d9aa519fb7b749cbc3b962edcf310a8dd1f4b67c91c4f83975dbdd17d965" +dependencies = [ + "foreign-types-macros", + "foreign-types-shared", +] + +[[package]] +name = "foreign-types-macros" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a5c6c585bc94aaf2c7b51dd4c2ba22680844aba4c687be581871a6f518c5742" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.118", +] + +[[package]] +name = "foreign-types-shared" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aa9a19cbb55df58761df49b23516a86d432839add4af60fc256da840f66ed35b" + +[[package]] +name = "futures-core" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7e3450815272ef58cec6d564423f6e755e25379b217b0bc688e295ba24df6b1d" + +[[package]] +name = "futures-task" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "037711b3d59c33004d3856fbdc83b99d4ff37a24768fa1be9ce3538a1cde4393" + +[[package]] +name = "futures-util" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "389ca41296e6190b48053de0321d02a77f32f8a5d2461dd38762c0593805c6d6" +dependencies = [ + "futures-core", + "futures-task", + "pin-project-lite", + "slab", +] + +[[package]] +name = "gethostname" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1bd49230192a3797a9a4d6abe9b3eed6f7fa4c8a8a4947977c6f80025f92cbd8" +dependencies = [ + "rustix 1.1.4", + "windows-link", +] + +[[package]] +name = "getrandom" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "899def5c37c4fd7b2664648c28120ecec138e4d395b459e5ca34f9cce2dd77fd" +dependencies = [ + "cfg-if", + "libc", + "r-efi", + "wasip2", +] + +[[package]] +name = "gl_generator" +version = "0.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a95dfc23a2b4a9a2f5ab41d194f8bfda3cabec42af4e39f08c339eb2a0c124d" +dependencies = [ + "khronos_api", + "log", + "xml-rs", +] + +[[package]] +name = "glow" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bd348e04c43b32574f2de31c8bb397d96c9fcfa1371bd4ca6d8bdc464ab121b1" +dependencies = [ + "js-sys", + "slotmap", + "wasm-bindgen", + "web-sys", +] + +[[package]] +name = "glutin_wgl_sys" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2c4ee00b289aba7a9e5306d57c2d05499b2e5dc427f84ac708bd2c090212cf3e" +dependencies = [ + "gl_generator", +] + +[[package]] +name = "gpu-alloc" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "45cf04b2726f02df5508c6de726acdc90cdf97ac771a9a0ffd8ba10a6e696bf9" +dependencies = [ + "bitflags 2.13.0", + "gpu-alloc-types", +] + +[[package]] +name = "gpu-alloc-types" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b2bbed164dd10ed526c2e4fe3e721ca4a71c61730e5aafac6844b417b3227058" +dependencies = [ + "bitflags 2.13.0", +] + +[[package]] +name = "gpu-allocator" +version = "0.26.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fdd4240fc91d3433d5e5b0fc5b67672d771850dc19bbee03c1381e19322803d7" +dependencies = [ + "log", + "presser", + "thiserror 1.0.69", + "winapi", + "windows", +] + +[[package]] +name = "gpu-descriptor" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b89c83349105e3732062a895becfc71a8f921bb71ecbbdd8ff99263e3b53a0ca" +dependencies = [ + "bitflags 2.13.0", + "gpu-descriptor-types", + "hashbrown 0.15.5", +] + +[[package]] +name = "gpu-descriptor-types" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fdf242682df893b86f33a73828fb09ca4b2d3bb6cc95249707fc684d27484b91" +dependencies = [ + "bitflags 2.13.0", +] + +[[package]] +name = "hashbrown" +version = "0.15.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9229cfe53dfd69f0609a49f65461bd93001ea1ef889cd5529dd176593f5338a1" +dependencies = [ + "foldhash", +] + +[[package]] +name = "hashbrown" +version = "0.17.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed5909b6e89a2db4456e54cd5f673791d7eca6732202bbf2a9cc504fe2f9b84a" + +[[package]] +name = "hassle-rs" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "af2a7e73e1f34c48da31fb668a907f250794837e08faa144fd24f0b8b741e890" +dependencies = [ + "bitflags 2.13.0", + "com", + "libc", + "libloading", + "thiserror 1.0.69", + "widestring", + "winapi", +] + +[[package]] +name = "hermit-abi" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc0fef456e4baa96da950455cd02c081ca953b141298e41db3fc7e36b1da849c" + +[[package]] +name = "hexf-parse" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dfa686283ad6dd069f105e5ab091b04c62850d3e4cf5d67debad1933f55023df" + +[[package]] +name = "indexmap" +version = "2.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d466e9454f08e4a911e14806c24e16fba1b4c121d1ea474396f396069cf949d9" +dependencies = [ + "equivalent", + "hashbrown 0.17.1", +] + +[[package]] +name = "is_terminal_polyfill" +version = "1.70.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a6cb138bb79a146c1bd460005623e142ef0181e3d0219cb493e02f7d08a35695" + +[[package]] +name = "itoa" +version = "1.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f42a60cbdf9a97f5d2305f08a87dc4e09308d1276d28c869c684d7777685682" + +[[package]] +name = "jiff" +version = "0.2.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ccfe6121cbe750cf81efa362d85c0bde7ea298ec43092d3a193baca59cdbd634" +dependencies = [ + "defmt", + "jiff-static", + "log", + "portable-atomic", + "portable-atomic-util", + "serde_core", +] + +[[package]] +name = "jiff-static" +version = "0.2.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e165e897f662d428f3cd3828a919dbe067c2d42bb1031eede74ef9d27ecdedd2" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.118", +] + +[[package]] +name = "jni" +version = "0.22.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5efd9a482cf3a427f00d6b35f14332adc7902ce91efb778580e180ff90fa3498" +dependencies = [ + "cfg-if", + "combine", + "jni-macros", + "jni-sys 0.4.1", + "log", + "simd_cesu8", + "thiserror 2.0.18", + "walkdir", + "windows-link", +] + +[[package]] +name = "jni-macros" +version = "0.22.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a00109accc170f0bdb141fed3e393c565b6f5e072365c3bd58f5b062591560a3" +dependencies = [ + "proc-macro2", + "quote", + "rustc_version", + "simd_cesu8", + "syn 2.0.118", +] + +[[package]] +name = "jni-sys" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41a652e1f9b6e0275df1f15b32661cf0d4b78d4d87ddec5e0c3c20f097433258" +dependencies = [ + "jni-sys 0.4.1", +] + +[[package]] +name = "jni-sys" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c6377a88cb3910bee9b0fa88d4f42e1d2da8e79915598f65fb0c7ee14c878af2" +dependencies = [ + "jni-sys-macros", +] + +[[package]] +name = "jni-sys-macros" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "38c0b942f458fe50cdac086d2f946512305e5631e720728f2a61aabcd47a6264" +dependencies = [ + "quote", + "syn 2.0.118", +] + +[[package]] +name = "jobserver" +version = "0.1.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9afb3de4395d6b3e67a780b6de64b51c978ecf11cb9a462c66be7d4ca9039d33" +dependencies = [ + "getrandom", + "libc", +] + +[[package]] +name = "js-sys" +version = "0.3.103" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "53b44bfcdb3f8d5837a46dae1ca9660a837176eee74a28b229bc626816589102" +dependencies = [ + "cfg-if", + "futures-util", + "wasm-bindgen", +] + +[[package]] +name = "khronos-egl" +version = "6.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6aae1df220ece3c0ada96b8153459b67eebe9ae9212258bb0134ae60416fdf76" +dependencies = [ + "libc", + "libloading", + "pkg-config", +] + +[[package]] +name = "khronos_api" +version = "3.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2db585e1d738fc771bf08a151420d3ed193d9d895a36df7f6f8a9456b911ddc" + +[[package]] +name = "libc" +version = "0.2.186" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "68ab91017fe16c622486840e4c83c9a37afeff978bd239b5293d61ece587de66" + +[[package]] +name = "libloading" +version = "0.8.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d7c4b02199fee7c5d21a5ae7d8cfa79a6ef5bb2fc834d6e9058e89c825efdc55" +dependencies = [ + "cfg-if", + "windows-link", +] + +[[package]] +name = "libredox" +version = "0.1.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c943259e342f1e06ff2da7a83eabdfe7f92ce10262688dbf1895ff0b3e6e4652" +dependencies = [ + "bitflags 2.13.0", + "libc", + "plain", + "redox_syscall 0.9.0", +] + +[[package]] +name = "linux-raw-sys" +version = "0.4.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d26c52dbd32dccf2d10cac7725f8eae5296885fb5703b261f7d0a0739ec807ab" + +[[package]] +name = "linux-raw-sys" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32a66949e030da00e8c7d4434b251670a91556f4144941d37452769c25d58a53" + +[[package]] +name = "litrs" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "11d3d7f243d5c5a8b9bb5d6dd2b1602c0cb0b9db1621bafc7ed66e35ff9fe092" + +[[package]] +name = "lock_api" +version = "0.4.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "224399e74b87b5f3557511d98dff8b14089b3dadafcab6bb93eab67d3aace965" +dependencies = [ + "scopeguard", +] + +[[package]] +name = "log" +version = "0.4.33" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ceec5bc11778974d1bcb055b18002eba7f4b3518b6a0081b3af5f21666da9ad" + +[[package]] +name = "malloc_buf" +version = "0.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "62bb907fe88d54d8d9ce32a3cceab4218ed2f6b7d35617cafe9adf84e43919cb" +dependencies = [ + "libc", +] + +[[package]] +name = "memchr" +version = "2.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "88904434abc2901f197fe8cc55f0445e7ded921dba5911dad2e2b39b48e663c4" + +[[package]] +name = "memmap2" +version = "0.9.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d1219ed1b7f229ee7104d281dd01d6802fe28bb6e95d292942c4daacdeb798c0" +dependencies = [ + "libc", +] + +[[package]] +name = "metal" +version = "0.29.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7ecfd3296f8c56b7c1f6fbac3c71cefa9d78ce009850c45000015f206dc7fa21" +dependencies = [ + "bitflags 2.13.0", + "block", + "core-graphics-types", + "foreign-types", + "log", + "objc", + "paste", +] + +[[package]] +name = "naga" +version = "22.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8bd5a652b6faf21496f2cfd88fc49989c8db0825d1f6746b1a71a6ede24a63ad" +dependencies = [ + "arrayvec", + "bit-set", + "bitflags 2.13.0", + "cfg_aliases 0.1.1", + "codespan-reporting", + "hexf-parse", + "indexmap", + "log", + "rustc-hash", + "spirv", + "termcolor", + "thiserror 1.0.69", + "unicode-xid", +] + +[[package]] +name = "ndk" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3f42e7bbe13d351b6bead8286a43aac9534b82bd3cc43e47037f012ebfd62d4" +dependencies = [ + "bitflags 2.13.0", + "jni-sys 0.3.1", + "log", + "ndk-sys 0.6.0+11769913", + "num_enum", + "raw-window-handle", + "thiserror 1.0.69", +] + +[[package]] +name = "ndk-context" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "27b02d87554356db9e9a873add8782d4ea6e3e58ea071a9adb9a2e8ddb884a8b" + +[[package]] +name = "ndk-sys" +version = "0.5.0+25.2.9519653" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8c196769dd60fd4f363e11d948139556a344e79d451aeb2fa2fd040738ef7691" +dependencies = [ + "jni-sys 0.3.1", +] + +[[package]] +name = "ndk-sys" +version = "0.6.0+11769913" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee6cda3051665f1fb8d9e08fc35c96d5a244fb1be711a03b71118828afc9a873" +dependencies = [ + "jni-sys 0.3.1", +] + +[[package]] +name = "num_enum" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5d0bca838442ec211fa11de3a8b0e0e8f3a4522575b5c4c06ed722e005036f26" +dependencies = [ + "num_enum_derive", + "rustversion", +] + +[[package]] +name = "num_enum_derive" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "680998035259dcfcafe653688bf2aa6d3e2dc05e98be6ab46afb089dc84f1df8" +dependencies = [ + "proc-macro-crate", + "proc-macro2", + "quote", + "syn 2.0.118", +] + +[[package]] +name = "objc" +version = "0.2.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "915b1b472bc21c53464d6c8461c9d3af805ba1ef837e1cac254428f4a77177b1" +dependencies = [ + "malloc_buf", +] + +[[package]] +name = "objc-sys" +version = "0.3.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cdb91bdd390c7ce1a8607f35f3ca7151b65afc0ff5ff3b34fa350f7d7c7e4310" + +[[package]] +name = "objc2" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "46a785d4eeff09c14c487497c162e92766fbb3e4059a71840cecc03d9a50b804" +dependencies = [ + "objc-sys", + "objc2-encode", +] + +[[package]] +name = "objc2-app-kit" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e4e89ad9e3d7d297152b17d39ed92cd50ca8063a89a9fa569046d41568891eff" +dependencies = [ + "bitflags 2.13.0", + "block2", + "libc", + "objc2", + "objc2-core-data", + "objc2-core-image", + "objc2-foundation", + "objc2-quartz-core", +] + +[[package]] +name = "objc2-cloud-kit" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "74dd3b56391c7a0596a295029734d3c1c5e7e510a4cb30245f8221ccea96b009" +dependencies = [ + "bitflags 2.13.0", + "block2", + "objc2", + "objc2-core-location", + "objc2-foundation", +] + +[[package]] +name = "objc2-contacts" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a5ff520e9c33812fd374d8deecef01d4a840e7b41862d849513de77e44aa4889" +dependencies = [ + "block2", + "objc2", + "objc2-foundation", +] + +[[package]] +name = "objc2-core-data" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "617fbf49e071c178c0b24c080767db52958f716d9eabdf0890523aeae54773ef" +dependencies = [ + "bitflags 2.13.0", + "block2", + "objc2", + "objc2-foundation", +] + +[[package]] +name = "objc2-core-image" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "55260963a527c99f1819c4f8e3b47fe04f9650694ef348ffd2227e8196d34c80" +dependencies = [ + "block2", + "objc2", + "objc2-foundation", + "objc2-metal", +] + +[[package]] +name = "objc2-core-location" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "000cfee34e683244f284252ee206a27953279d370e309649dc3ee317b37e5781" +dependencies = [ + "block2", + "objc2", + "objc2-contacts", + "objc2-foundation", +] + +[[package]] +name = "objc2-encode" +version = "4.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ef25abbcd74fb2609453eb695bd2f860d389e457f67dc17cafc8b8cbc89d0c33" + +[[package]] +name = "objc2-foundation" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ee638a5da3799329310ad4cfa62fbf045d5f56e3ef5ba4149e7452dcf89d5a8" +dependencies = [ + "bitflags 2.13.0", + "block2", + "dispatch", + "libc", + "objc2", +] + +[[package]] +name = "objc2-link-presentation" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1a1ae721c5e35be65f01a03b6d2ac13a54cb4fa70d8a5da293d7b0020261398" +dependencies = [ + "block2", + "objc2", + "objc2-app-kit", + "objc2-foundation", +] + +[[package]] +name = "objc2-metal" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dd0cba1276f6023976a406a14ffa85e1fdd19df6b0f737b063b95f6c8c7aadd6" +dependencies = [ + "bitflags 2.13.0", + "block2", + "objc2", + "objc2-foundation", +] + +[[package]] +name = "objc2-quartz-core" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e42bee7bff906b14b167da2bac5efe6b6a07e6f7c0a21a7308d40c960242dc7a" +dependencies = [ + "bitflags 2.13.0", + "block2", + "objc2", + "objc2-foundation", + "objc2-metal", +] + +[[package]] +name = "objc2-symbols" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0a684efe3dec1b305badae1a28f6555f6ddd3bb2c2267896782858d5a78404dc" +dependencies = [ + "objc2", + "objc2-foundation", +] + +[[package]] +name = "objc2-ui-kit" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8bb46798b20cd6b91cbd113524c490f1686f4c4e8f49502431415f3512e2b6f" +dependencies = [ + "bitflags 2.13.0", + "block2", + "objc2", + "objc2-cloud-kit", + "objc2-core-data", + "objc2-core-image", + "objc2-core-location", + "objc2-foundation", + "objc2-link-presentation", + "objc2-quartz-core", + "objc2-symbols", + "objc2-uniform-type-identifiers", + "objc2-user-notifications", +] + +[[package]] +name = "objc2-uniform-type-identifiers" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "44fa5f9748dbfe1ca6c0b79ad20725a11eca7c2218bceb4b005cb1be26273bfe" +dependencies = [ + "block2", + "objc2", + "objc2-foundation", +] + +[[package]] +name = "objc2-user-notifications" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "76cfcbf642358e8689af64cee815d139339f3ed8ad05103ed5eaf73db8d84cb3" +dependencies = [ + "bitflags 2.13.0", + "block2", + "objc2", + "objc2-core-location", + "objc2-foundation", +] + +[[package]] +name = "once_cell" +version = "1.21.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9f7c3e4beb33f85d45ae3e3a1792185706c8e16d043238c593331cc7cd313b50" + +[[package]] +name = "once_cell_polyfill" +version = "1.70.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "384b8ab6d37215f3c5301a95a4accb5d64aa607f1fcb26a11b5303878451b4fe" + +[[package]] +name = "orbclient" +version = "0.3.55" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5df339f526ea9a60e371768d50efc2f2508c7203290731565d1f7a6f71d21747" +dependencies = [ + "libc", + "libredox", +] + +[[package]] +name = "owned_ttf_parser" +version = "0.25.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "36820e9051aca1014ddc75770aab4d68bc1e9e632f0f5627c4086bc216fb583b" +dependencies = [ + "ttf-parser", +] + +[[package]] +name = "parking_lot" +version = "0.12.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93857453250e3077bd71ff98b6a65ea6621a19bb0f559a85248955ac12c45a1a" +dependencies = [ + "lock_api", + "parking_lot_core", +] + +[[package]] +name = "parking_lot_core" +version = "0.9.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2621685985a2ebf1c516881c026032ac7deafcda1a2c9b7850dc81e3dfcb64c1" +dependencies = [ + "cfg-if", + "libc", + "redox_syscall 0.5.18", + "smallvec", + "windows-link", +] + +[[package]] +name = "paste" +version = "1.0.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a" + +[[package]] +name = "percent-encoding" +version = "2.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b4f627cb1b25917193a259e49bdad08f671f8d9708acfd5fe0a8c1455d87220" + +[[package]] +name = "pin-project" +version = "1.1.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2466b2336ed02bcdca6b294417127b90ec92038d1d5c4fbeac971a922e0e0924" +dependencies = [ + "pin-project-internal", +] + +[[package]] +name = "pin-project-internal" +version = "1.1.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c96395f0a926bc13b1c17622aaddda1ecb55d49c8f1bf9777e4d877800a43f8b" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.118", +] + +[[package]] +name = "pin-project-lite" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a89322df9ebe1c1578d689c92318e070967d1042b512afbe49518723f4e6d5cd" + +[[package]] +name = "pkg-config" +version = "0.3.33" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "19f132c84eca552bf34cab8ec81f1c1dcc229b811638f9d283dceabe58c5569e" + +[[package]] +name = "plain" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b4596b6d070b27117e987119b4dac604f3c58cfb0b191112e24771b2faeac1a6" + +[[package]] +name = "polling" +version = "3.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5d0e4f59085d47d8241c88ead0f274e8a0cb551f3625263c05eb8dd897c34218" +dependencies = [ + "cfg-if", + "concurrent-queue", + "hermit-abi", + "pin-project-lite", + "rustix 1.1.4", + "windows-sys 0.61.2", +] + +[[package]] +name = "pollster" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "22686f4785f02a4fcc856d3b3bb19bf6c8160d103f7a99cc258bddd0251dc7f2" + +[[package]] +name = "portable-atomic" +version = "1.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c33a9471896f1c69cecef8d20cbe2f7accd12527ce60845ff44c153bb2a21b49" + +[[package]] +name = "portable-atomic-util" +version = "0.2.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c2a106d1259c23fac8e543272398ae0e3c0b8d33c88ed73d0cc71b0f1d902618" +dependencies = [ + "portable-atomic", +] + +[[package]] +name = "presser" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e8cf8e6a8aa66ce33f63993ffc4ea4271eb5b0530a9002db8455ea6050c77bfa" + +[[package]] +name = "proc-macro-crate" +version = "3.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e67ba7e9b2b56446f1d419b1d807906278ffa1a658a8a5d8a39dcb1f5a78614f" +dependencies = [ + "toml_edit", +] + +[[package]] +name = "proc-macro-error-attr2" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96de42df36bb9bba5542fe9f1a054b8cc87e172759a1868aa05c1f3acc89dfc5" +dependencies = [ + "proc-macro2", + "quote", +] + +[[package]] +name = "proc-macro-error2" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "11ec05c52be0a07b08061f7dd003e7d7092e0472bc731b4af7bb1ef876109802" +dependencies = [ + "proc-macro-error-attr2", + "proc-macro2", + "quote", + "syn 2.0.118", +] + +[[package]] +name = "proc-macro2" +version = "1.0.106" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8fd00f0bb2e90d81d1044c2b32617f68fcb9fa3bb7640c23e9c748e53fb30934" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "profiling" +version = "1.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d595e54a326bc53c1c197b32d295e14b169e3cfeaa8dc82b529f947fba6bcf5" + +[[package]] +name = "quick-xml" +version = "0.39.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cdcc8dd4e2f670d309a5f0e83fe36dfdc05af317008fea29144da1a2ac858e5e" +dependencies = [ + "memchr", +] + +[[package]] +name = "quote" +version = "1.0.46" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dfbc457d0c7a0759a614551b11a6409e5951f6c7537be1f1b7682b9ae9230368" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "r-efi" +version = "5.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f" + +[[package]] +name = "range-alloc" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ca45419789ae5a7899559e9512e58ca889e41f04f1f2445e9f4b290ceccd1d08" + +[[package]] +name = "raw-window-handle" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "20675572f6f24e9e76ef639bc5552774ed45f1c30e2951e1e99c59888861c539" + +[[package]] +name = "redox_syscall" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4722d768eff46b75989dd134e5c353f0d6296e5aaa3132e776cbdb56be7731aa" +dependencies = [ + "bitflags 1.3.2", +] + +[[package]] +name = "redox_syscall" +version = "0.5.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed2bf2547551a7053d6fdfafda3f938979645c44812fbfcda098faae3f1a362d" +dependencies = [ + "bitflags 2.13.0", +] + +[[package]] +name = "redox_syscall" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c5102a6aaa05aa011a238e178e6bca86d2cb56fc9f586d37cb80f5bca6e07759" +dependencies = [ + "bitflags 2.13.0", +] + +[[package]] +name = "regex" +version = "1.12.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f1292b7759ae1cb9ec195452d1390a074f0cd8541ab7a5a8c31cd6db45d4a6ba" +dependencies = [ + "aho-corasick", + "memchr", + "regex-automata", + "regex-syntax", +] + +[[package]] +name = "regex-automata" +version = "0.4.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e1dd4122fc1595e8162618945476892eefca7b88c52820e74af6262213cae8f" +dependencies = [ + "aho-corasick", + "memchr", + "regex-syntax", +] + +[[package]] +name = "regex-syntax" +version = "0.8.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d6f6ff9a378485b298a5286656da665ba74413d36db0979633275d2e708145d4" + +[[package]] +name = "render3d" +version = "0.1.0" +dependencies = [ + "bytemuck", + "env_logger", + "naga", + "pollster", + "serde", + "serde_json", + "wgpu", + "winit", +] + +[[package]] +name = "renderdoc-sys" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "19b30a45b0cd0bcca8037f3d0dc3421eaf95327a17cad11964fb8179b4fc4832" + +[[package]] +name = "rustc-hash" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2" + +[[package]] +name = "rustc_version" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cfcb3a22ef46e85b45de6ee7e79d063319ebb6594faafcf1c225ea92ab6e9b92" +dependencies = [ + "semver", +] + +[[package]] +name = "rustix" +version = "0.38.44" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fdb5bc1ae2baa591800df16c9ca78619bf65c0488b41b96ccec5d11220d8c154" +dependencies = [ + "bitflags 2.13.0", + "errno", + "libc", + "linux-raw-sys 0.4.15", + "windows-sys 0.59.0", +] + +[[package]] +name = "rustix" +version = "1.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6fe4565b9518b83ef4f91bb47ce29620ca828bd32cb7e408f0062e9930ba190" +dependencies = [ + "bitflags 2.13.0", + "errno", + "libc", + "linux-raw-sys 0.12.1", + "windows-sys 0.61.2", +] + +[[package]] +name = "rustversion" +version = "1.0.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d" + +[[package]] +name = "same-file" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502" +dependencies = [ + "winapi-util", +] + +[[package]] +name = "scoped-tls" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e1cf6437eb19a8f4a6cc0f7dca544973b0b78843adbfeb3683d1a94a0024a294" + +[[package]] +name = "scopeguard" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" + +[[package]] +name = "sctk-adwaita" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6277f0217056f77f1d8f49f2950ac6c278c0d607c45f5ee99328d792ede24ec" +dependencies = [ + "ab_glyph", + "log", + "memmap2", + "smithay-client-toolkit", + "tiny-skia", +] + +[[package]] +name = "semver" +version = "1.0.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a7852d02fc848982e0c167ef163aaff9cd91dc640ba85e263cb1ce46fae51cd" + +[[package]] +name = "serde" +version = "1.0.228" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a8e94ea7f378bd32cbbd37198a4a91436180c5bb472411e48b5ec2e2124ae9e" +dependencies = [ + "serde_core", + "serde_derive", +] + +[[package]] +name = "serde_core" +version = "1.0.228" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41d385c7d4ca58e59fc732af25c3983b67ac852c1a25000afe1175de458b67ad" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.228" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.118", +] + +[[package]] +name = "serde_json" +version = "1.0.150" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e8014e44b4736ed0538adeecded0fce2a272f22dc9578a7eb6b2d9993c74cfb9" +dependencies = [ + "itoa", + "memchr", + "serde", + "serde_core", + "zmij", +] + +[[package]] +name = "shlex" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8fadd59c855ef2080decdef8ff161eb6661b86933c9d82e5ba29dc602a55aba" + +[[package]] +name = "simd_cesu8" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94f90157bb87cddf702797c5dadfa0be7d266cdf49e22da2fcaa32eff75b2c33" +dependencies = [ + "rustc_version", + "simdutf8", +] + +[[package]] +name = "simdutf8" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3a9fe34e3e7a50316060351f37187a3f546bce95496156754b601a5fa71b76e" + +[[package]] +name = "slab" +version = "0.4.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c790de23124f9ab44544d7ac05d60440adc586479ce501c1d6d7da3cd8c9cf5" + +[[package]] +name = "slotmap" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bdd58c3c93c3d278ca835519292445cb4b0d4dc59ccfdf7ceadaab3f8aeb4038" +dependencies = [ + "version_check", +] + +[[package]] +name = "smallvec" +version = "1.15.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ed6a63f02c8539c91a8685a86f4099661ba3da017932f6ebbea6de3f0fa7c90" + +[[package]] +name = "smithay-client-toolkit" +version = "0.19.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3457dea1f0eb631b4034d61d4d8c32074caa6cd1ab2d59f2327bd8461e2c0016" +dependencies = [ + "bitflags 2.13.0", + "calloop", + "calloop-wayland-source", + "cursor-icon", + "libc", + "log", + "memmap2", + "rustix 0.38.44", + "thiserror 1.0.69", + "wayland-backend", + "wayland-client", + "wayland-csd-frame", + "wayland-cursor", + "wayland-protocols", + "wayland-protocols-wlr", + "wayland-scanner", + "xkeysym", +] + +[[package]] +name = "smol_str" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dd538fb6910ac1099850255cf94a94df6551fbdd602454387d0adb2d1ca6dead" +dependencies = [ + "serde", +] + +[[package]] +name = "spirv" +version = "0.3.0+sdk-1.3.268.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eda41003dc44290527a59b13432d4a0379379fa074b70174882adfbdfd917844" +dependencies = [ + "bitflags 2.13.0", +] + +[[package]] +name = "static_assertions" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" + +[[package]] +name = "strict-num" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6637bab7722d379c8b41ba849228d680cc12d0a45ba1fa2b48f2a30577a06731" + +[[package]] +name = "syn" +version = "1.0.109" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "syn" +version = "2.0.118" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b9ae57f904213ebb649ce6895b8a66c66f0203b9319718f69a5612a065b1422" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "termcolor" +version = "1.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "06794f8f6c5c898b3275aebefa6b8a1cb24cd2c6c79397ab15774837a0bc5755" +dependencies = [ + "winapi-util", +] + +[[package]] +name = "thiserror" +version = "1.0.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52" +dependencies = [ + "thiserror-impl 1.0.69", +] + +[[package]] +name = "thiserror" +version = "2.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4288b5bcbc7920c07a1149a35cf9590a2aa808e0bc1eafaade0b80947865fbc4" +dependencies = [ + "thiserror-impl 2.0.18", +] + +[[package]] +name = "thiserror-impl" +version = "1.0.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.118", +] + +[[package]] +name = "thiserror-impl" +version = "2.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ebc4ee7f67670e9b64d05fa4253e753e016c6c95ff35b89b7941d6b856dec1d5" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.118", +] + +[[package]] +name = "tiny-skia" +version = "0.11.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "83d13394d44dae3207b52a326c0c85a8bf87f1541f23b0d143811088497b09ab" +dependencies = [ + "arrayref", + "arrayvec", + "bytemuck", + "cfg-if", + "log", + "tiny-skia-path", +] + +[[package]] +name = "tiny-skia-path" +version = "0.11.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c9e7fc0c2e86a30b117d0462aa261b72b7a99b7ebd7deb3a14ceda95c5bdc93" +dependencies = [ + "arrayref", + "bytemuck", + "strict-num", +] + +[[package]] +name = "toml_datetime" +version = "1.1.1+spec-1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3165f65f62e28e0115a00b2ebdd37eb6f3b641855f9d636d3cd4103767159ad7" +dependencies = [ + "serde_core", +] + +[[package]] +name = "toml_edit" +version = "0.25.12+spec-1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d2153edc6955a6c354fad8f5efd38b6a8769bdccf9fe50f8e1329f81b0baa5d7" +dependencies = [ + "indexmap", + "toml_datetime", + "toml_parser", + "winnow", +] + +[[package]] +name = "toml_parser" +version = "1.1.2+spec-1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2abe9b86193656635d2411dc43050282ca48aa31c2451210f4202550afb7526" +dependencies = [ + "winnow", +] + +[[package]] +name = "tracing" +version = "0.1.44" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "63e71662fa4b2a2c3a26f570f037eb95bb1f85397f3cd8076caed2f026a6d100" +dependencies = [ + "pin-project-lite", + "tracing-core", +] + +[[package]] +name = "tracing-core" +version = "0.1.36" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "db97caf9d906fbde555dd62fa95ddba9eecfd14cb388e4f491a66d74cd5fb79a" + +[[package]] +name = "ttf-parser" +version = "0.25.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d2df906b07856748fa3f6e0ad0cbaa047052d4a7dd609e231c4f72cee8c36f31" + +[[package]] +name = "unicode-ident" +version = "1.0.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75" + +[[package]] +name = "unicode-segmentation" +version = "1.13.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c6f5d3c3b1bf09027a88a6bc961fc00497d651009560b5463668dc81b0fa87a8" + +[[package]] +name = "unicode-width" +version = "0.1.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7dd6e30e90baa6f72411720665d41d89b9a3d039dc45b8faea1ddd07f617f6af" + +[[package]] +name = "unicode-xid" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853" + +[[package]] +name = "utf8parse" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" + +[[package]] +name = "version_check" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" + +[[package]] +name = "walkdir" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "29790946404f91d9c5d06f9874efddea1dc06c5efe94541a7d6863108e3a5e4b" +dependencies = [ + "same-file", + "winapi-util", +] + +[[package]] +name = "wasip2" +version = "1.0.4+wasi-0.2.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b67efb37e106e55ce722a510d6b5f9c17f083e5fc79afc2badeb12cc313d9487" +dependencies = [ + "wit-bindgen", +] + +[[package]] +name = "wasm-bindgen" +version = "0.2.126" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4b067c0c11094aef6b7a801c1e34a26affafdf3d051dba08456b868789aaf9a4" +dependencies = [ + "cfg-if", + "once_cell", + "rustversion", + "wasm-bindgen-macro", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-futures" +version = "0.4.76" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c62df1340f32221cb9c54d6a27b030e3dba64361d4a95bed55f9aacb44da291d" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + +[[package]] +name = "wasm-bindgen-macro" +version = "0.2.126" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "167ce5e579f6bcf889c4f7175a8a5a585de84e8ff93976ce393efa5f2837aab1" +dependencies = [ + "quote", + "wasm-bindgen-macro-support", +] + +[[package]] +name = "wasm-bindgen-macro-support" +version = "0.2.126" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f3997c7839262f4ef12cf90b818d6340c18e80f263f1a94bf157d0ec4420380e" +dependencies = [ + "bumpalo", + "proc-macro2", + "quote", + "syn 2.0.118", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-shared" +version = "0.2.126" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc1b4cb0cc549fcf58d7dfc081778139b3d283a081644e833e84682ad71cea24" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "wayland-backend" +version = "0.3.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2857dd20b54e916ec7253b3d6b4d5c4d7d4ca2c33c2e11c6c76a99bd8744755d" +dependencies = [ + "cc", + "downcast-rs", + "rustix 1.1.4", + "scoped-tls", + "smallvec", + "wayland-sys", +] + +[[package]] +name = "wayland-client" +version = "0.31.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "645c7c96bb74690c3189b5c9cb4ca1627062bb23693a4fad9d8c3de958260144" +dependencies = [ + "bitflags 2.13.0", + "rustix 1.1.4", + "wayland-backend", + "wayland-scanner", +] + +[[package]] +name = "wayland-csd-frame" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "625c5029dbd43d25e6aa9615e88b829a5cad13b2819c4ae129fdbb7c31ab4c7e" +dependencies = [ + "bitflags 2.13.0", + "cursor-icon", + "wayland-backend", +] + +[[package]] +name = "wayland-cursor" +version = "0.31.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4a52d18780be9b1314328a3de5f930b73d2200112e3849ca6cb11822793fb34d" +dependencies = [ + "rustix 1.1.4", + "wayland-client", + "xcursor", +] + +[[package]] +name = "wayland-protocols" +version = "0.32.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "23d0c813de3daa2ed6520af85a3bd49b0e722a3078506899aa9686fea58dc4b6" +dependencies = [ + "bitflags 2.13.0", + "wayland-backend", + "wayland-client", + "wayland-scanner", +] + +[[package]] +name = "wayland-protocols-plasma" +version = "0.3.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b6d8cf1eb2c1c31ed1f5643c88a6e53538129d4af80030c8cabd1f9fa884d91" +dependencies = [ + "bitflags 2.13.0", + "wayland-backend", + "wayland-client", + "wayland-protocols", + "wayland-scanner", +] + +[[package]] +name = "wayland-protocols-wlr" +version = "0.3.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eb04e52f7836d7c7976c78ca0250d61e33873c34156a2a1fc9474828ec268234" +dependencies = [ + "bitflags 2.13.0", + "wayland-backend", + "wayland-client", + "wayland-protocols", + "wayland-scanner", +] + +[[package]] +name = "wayland-scanner" +version = "0.31.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c324a910fd86ebdc364a3e61ec1f11737d3b1d6c273c0239ee8ff4bc0d24b4a" +dependencies = [ + "proc-macro2", + "quick-xml", + "quote", +] + +[[package]] +name = "wayland-sys" +version = "0.31.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d8eab23fefc9e41f8e841df4a9c707e8a8c4ed26e944ef69297184de2785e3be" +dependencies = [ + "dlib", + "log", + "once_cell", + "pkg-config", +] + +[[package]] +name = "web-sys" +version = "0.3.103" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8622dcb61c0bcc9fffa6938bed81210af2da9a7e4a1a834b2e37a59b6dfb6141" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + +[[package]] +name = "web-time" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a6580f308b1fad9207618087a65c04e7a10bc77e02c8e84e9b00dd4b12fa0bb" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + +[[package]] +name = "wgpu" +version = "22.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e1d1c4ba43f80542cf63a0a6ed3134629ae73e8ab51e4b765a67f3aa062eb433" +dependencies = [ + "arrayvec", + "cfg_aliases 0.1.1", + "document-features", + "js-sys", + "log", + "naga", + "parking_lot", + "profiling", + "raw-window-handle", + "smallvec", + "static_assertions", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", + "wgpu-core", + "wgpu-hal", + "wgpu-types", +] + +[[package]] +name = "wgpu-core" +version = "22.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0348c840d1051b8e86c3bcd31206080c5e71e5933dabd79be1ce732b0b2f089a" +dependencies = [ + "arrayvec", + "bit-vec", + "bitflags 2.13.0", + "cfg_aliases 0.1.1", + "document-features", + "indexmap", + "log", + "naga", + "once_cell", + "parking_lot", + "profiling", + "raw-window-handle", + "rustc-hash", + "smallvec", + "thiserror 1.0.69", + "wgpu-hal", + "wgpu-types", +] + +[[package]] +name = "wgpu-hal" +version = "22.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f6bbf4b4de8b2a83c0401d9e5ae0080a2792055f25859a02bf9be97952bbed4f" +dependencies = [ + "android_system_properties", + "arrayvec", + "ash", + "bit-set", + "bitflags 2.13.0", + "block", + "cfg_aliases 0.1.1", + "core-graphics-types", + "d3d12", + "glow", + "glutin_wgl_sys", + "gpu-alloc", + "gpu-allocator", + "gpu-descriptor", + "hassle-rs", + "js-sys", + "khronos-egl", + "libc", + "libloading", + "log", + "metal", + "naga", + "ndk-sys 0.5.0+25.2.9519653", + "objc", + "once_cell", + "parking_lot", + "profiling", + "range-alloc", + "raw-window-handle", + "renderdoc-sys", + "rustc-hash", + "smallvec", + "thiserror 1.0.69", + "wasm-bindgen", + "web-sys", + "wgpu-types", + "winapi", +] + +[[package]] +name = "wgpu-types" +version = "22.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bc9d91f0e2c4b51434dfa6db77846f2793149d8e73f800fa2e41f52b8eac3c5d" +dependencies = [ + "bitflags 2.13.0", + "js-sys", + "web-sys", +] + +[[package]] +name = "widestring" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72069c3113ab32ab29e5584db3c6ec55d416895e60715417b5b883a357c3e471" + +[[package]] +name = "winapi" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" +dependencies = [ + "winapi-i686-pc-windows-gnu", + "winapi-x86_64-pc-windows-gnu", +] + +[[package]] +name = "winapi-i686-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" + +[[package]] +name = "winapi-util" +version = "0.1.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c2a7b1c03c876122aa43f3020e6c3c3ee5c05081c9a00739faf7503aeba10d22" +dependencies = [ + "windows-sys 0.61.2", +] + +[[package]] +name = "winapi-x86_64-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" + +[[package]] +name = "windows" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e48a53791691ab099e5e2ad123536d0fff50652600abaf43bbf952894110d0be" +dependencies = [ + "windows-core", + "windows-targets", +] + +[[package]] +name = "windows-core" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "33ab640c8d7e35bf8ba19b884ba838ceb4fba93a4e8c65a9059d08afcfc683d9" +dependencies = [ + "windows-targets", +] + +[[package]] +name = "windows-link" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5" + +[[package]] +name = "windows-sys" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" +dependencies = [ + "windows-targets", +] + +[[package]] +name = "windows-sys" +version = "0.59.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" +dependencies = [ + "windows-targets", +] + +[[package]] +name = "windows-sys" +version = "0.61.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ae137229bcbd6cdf0f7b80a31df61766145077ddf49416a728b02cb3921ff3fc" +dependencies = [ + "windows-link", +] + +[[package]] +name = "windows-targets" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" +dependencies = [ + "windows_aarch64_gnullvm", + "windows_aarch64_msvc", + "windows_i686_gnu", + "windows_i686_gnullvm", + "windows_i686_msvc", + "windows_x86_64_gnu", + "windows_x86_64_gnullvm", + "windows_x86_64_msvc", +] + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" + +[[package]] +name = "windows_i686_gnu" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" + +[[package]] +name = "windows_i686_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" + +[[package]] +name = "windows_i686_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" + +[[package]] +name = "winit" +version = "0.30.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a6755fa58a9f8350bd1e472d4c3fcc25f824ec358933bba33306d0b63df5978d" +dependencies = [ + "ahash", + "android-activity", + "atomic-waker", + "bitflags 2.13.0", + "block2", + "bytemuck", + "calloop", + "cfg_aliases 0.2.1", + "concurrent-queue", + "core-foundation", + "core-graphics", + "cursor-icon", + "dpi", + "js-sys", + "libc", + "memmap2", + "ndk", + "objc2", + "objc2-app-kit", + "objc2-foundation", + "objc2-ui-kit", + "orbclient", + "percent-encoding", + "pin-project", + "raw-window-handle", + "redox_syscall 0.4.1", + "rustix 0.38.44", + "sctk-adwaita", + "smithay-client-toolkit", + "smol_str", + "tracing", + "unicode-segmentation", + "wasm-bindgen", + "wasm-bindgen-futures", + "wayland-backend", + "wayland-client", + "wayland-protocols", + "wayland-protocols-plasma", + "web-sys", + "web-time", + "windows-sys 0.52.0", + "x11-dl", + "x11rb", + "xkbcommon-dl", +] + +[[package]] +name = "winnow" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0592e1c9d151f854e6fd382574c3a0855250e1d9b2f99d9281c6e6391af352f1" +dependencies = [ + "memchr", +] + +[[package]] +name = "wit-bindgen" +version = "0.57.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ebf944e87a7c253233ad6766e082e3cd714b5d03812acc24c318f549614536e" + +[[package]] +name = "x11-dl" +version = "2.21.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "38735924fedd5314a6e548792904ed8c6de6636285cb9fec04d5b1db85c1516f" +dependencies = [ + "libc", + "once_cell", + "pkg-config", +] + +[[package]] +name = "x11rb" +version = "0.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9993aa5be5a26815fe2c3eacfc1fde061fc1a1f094bf1ad2a18bf9c495dd7414" +dependencies = [ + "as-raw-xcb-connection", + "gethostname", + "libc", + "libloading", + "once_cell", + "rustix 1.1.4", + "x11rb-protocol", +] + +[[package]] +name = "x11rb-protocol" +version = "0.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ea6fc2961e4ef194dcbfe56bb845534d0dc8098940c7e5c012a258bfec6701bd" + +[[package]] +name = "xcursor" +version = "0.3.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bec9e4a500ca8864c5b47b8b482a73d62e4237670e5b5f1d6b9e3cae50f28f2b" + +[[package]] +name = "xkbcommon-dl" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d039de8032a9a8856a6be89cea3e5d12fdd82306ab7c94d74e6deab2460651c5" +dependencies = [ + "bitflags 2.13.0", + "dlib", + "log", + "once_cell", + "xkeysym", +] + +[[package]] +name = "xkeysym" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9cc00251562a284751c9973bace760d86c0276c471b4be569fe6b068ee97a56" + +[[package]] +name = "xml-rs" +version = "0.8.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3ae8337f8a065cfc972643663ea4279e04e7256de865aa66fe25cec5fb912d3f" + +[[package]] +name = "zerocopy" +version = "0.8.52" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ce1022995ff5ff5d841ad7d994facc23098cd40152f2c1d11cd607c6f530653f" +dependencies = [ + "zerocopy-derive", +] + +[[package]] +name = "zerocopy-derive" +version = "0.8.52" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ae7f38b72ec2a254e2b87ef277cf2cd4fb97cbebf944faa6f33354da0867930" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.118", +] + +[[package]] +name = "zmij" +version = "1.0.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8848ee67ecc8aedbaf3e4122217aff892639231befc6a1b58d29fff4c2cabaa" diff --git a/src-tauri/render3d/Cargo.toml b/src-tauri/render3d/Cargo.toml new file mode 100644 index 0000000..b4fad04 --- /dev/null +++ b/src-tauri/render3d/Cargo.toml @@ -0,0 +1,54 @@ +[package] +name = "render3d" +version = "0.1.0" +edition = "2021" +description = "Nativer wgpu-3D-Renderer fuer die CAD-Modellsicht (Wand-Extrusion + Kamera + Pipeline)" + +# Bewusst NICHT Teil des src-tauri-Workspaces: die Mesh-Bibliothek soll unabhaengig +# von Tauri baubar/testbar bleiben (headless, ohne Webview-Toolchain). Der leere +# [workspace]-Block schliesst die Crate aus einem uebergeordneten Workspace aus, +# sodass `cargo test`/`cargo build` hier eigenstaendig laufen (Muster: render2d). +[workspace] + +[features] +# Standard: nur Mesh-Erzeugung (Wand-Extrusion) + Kamera/Matrizen + WGSL-Quellen. +# Diese Schicht ist serde-only, hat keine GPU-Abhaengigkeit und ist headless +# vollstaendig per `cargo test` pruefbar. +default = [] + +# GPU-Pipeline (wgpu). Baut auch headless (wgpu laedt Vulkan/GL erst zur Laufzeit +# dynamisch), rendert aber nur mit einer aktiven Grafik-Session. Enthaelt Tiefen- +# puffer, View-Projektions-Uniform und ein Directional-Light im Fragment-Shader. +render = ["dep:wgpu", "dep:bytemuck", "dep:pollster"] + +# Fenster-Spike (winit) inkl. lauffaehigem Binary mit Orbit-Kamera. Braucht +# Wayland/X11 + eine Display-Session zur visuellen Verifikation. +window = ["render", "dep:winit", "dep:env_logger"] + +[dependencies] +serde = { version = "1", features = ["derive"] } + +# --- GPU (nur mit Feature "render") ------------------------------------------ +# Standard-Features von wgpu (wgsl, webgpu, dx12, metal). Die Linux-Backends +# Vulkan/GLES aktiviert wgpu selbst ueber seine target-spezifischen Defaults — +# es gibt in wgpu 22 keine expliziten `vulkan`/`gles`-Feature-Namen. +wgpu = { version = "22", optional = true } +bytemuck = { version = "1", optional = true, features = ["derive"] } +pollster = { version = "0.3", optional = true } + +# --- Fenster (nur mit Feature "window") -------------------------------------- +winit = { version = "0.30", optional = true } +env_logger = { version = "0.11", optional = true } + +[dev-dependencies] +serde_json = "1" +# naga validiert die WGSL-Quellen headless (Parser + Validator) im Test, ohne +# GPU/Display. Version an wgpu 22 gekoppelt (transitiv ohnehin vorhanden). +naga = { version = "22", features = ["wgsl-in"] } + +# Der Fenster-Spike ist nur mit Feature "window" verfuegbar; ohne das Feature +# wird das Binary aus dem Build ausgeschlossen (required-features). +[[bin]] +name = "spike3d" +path = "src/bin/spike3d.rs" +required-features = ["window"] diff --git a/src-tauri/render3d/src/bin/spike3d.rs b/src-tauri/render3d/src/bin/spike3d.rs new file mode 100644 index 0000000..14cd3c0 --- /dev/null +++ b/src-tauri/render3d/src/bin/spike3d.rs @@ -0,0 +1,264 @@ +// Standalone-Fenster-Spike (Feature "window"): oeffnet ein winit-Fenster mit +// eigener wgpu-Surface und zeichnet eine Demo-Szene aus extrudierten Waenden (ein +// geschlossener Raum). Eine ORBIT-KAMERA laesst sich mit der Maus drehen (linke +// Taste ziehen = Yaw/Pitch) und mit dem Rad zoomen (Abstand). Nur die +// View-Projektions-Matrix aendert sich — kein Re-Meshing. +// +// Das ist bewusst der entkoppelte Rendering-Spike (M1): Rendering getrennt von der +// Tauri/Webview-Integration verifizieren. Die Anbindung unter die Webview +// (raw-window-handle) folgt in einem spaeteren Milestone. +// +// Start: cargo run --features window --bin spike3d +// (braucht eine aktive Wayland-/X11-Session; headless nicht sichtbar verifizierbar). + +use std::sync::Arc; + +use render3d::gpu::Renderer; +use render3d::math::orbit_eye; +use render3d::types::{Camera, Projection, WallInput}; + +use winit::application::ApplicationHandler; +use winit::event::{ElementState, MouseButton, MouseScrollDelta, WindowEvent}; +use winit::event_loop::{ActiveEventLoop, EventLoop}; +use winit::window::{Window, WindowId}; + +/// Demo-Szene: ein rechteckiger Raum (4 Aussenwaende) plus eine Innenwand. Achsen +/// in Meter; Dicke 0.2 m, Hoehe 2.6 m. Genug, um Extrusion, Tiefenpuffer und +/// Beleuchtung im Orbit zu beurteilen. +fn demo_walls() -> Vec { + let t = 0.2; + let ht = 2.6; + let grey = [0.82, 0.80, 0.76]; + let mk = |a: [f32; 2], b: [f32; 2]| WallInput { + start: a, + end: b, + thickness: t, + height: ht, + base_elevation: 0.0, + color: grey, + }; + // Raum 6 x 4 m. + vec![ + mk([0.0, 0.0], [6.0, 0.0]), // Sued + mk([6.0, 0.0], [6.0, 4.0]), // Ost + mk([6.0, 4.0], [0.0, 4.0]), // Nord + mk([0.0, 4.0], [0.0, 0.0]), // West + mk([3.0, 0.0], [3.0, 2.5]), // Innenwand (Teilung) + ] +} + +/// Zielpunkt (Raum-Mitte in world) und Start-Abstand fuer die Orbit-Kamera. +fn scene_target() -> [f32; 3] { + // Raum 6x4 in der XZ-Ebene, Wandmitte-Hoehe ~1.3. + [3.0, 1.3, 2.0] +} + +struct GpuState { + surface: wgpu::Surface<'static>, + device: wgpu::Device, + queue: wgpu::Queue, + config: wgpu::SurfaceConfiguration, + renderer: Renderer, + window: Arc, +} + +impl GpuState { + fn new(window: Arc) -> Self { + let size = window.inner_size(); + let instance = wgpu::Instance::default(); + let surface = instance + .create_surface(window.clone()) + .expect("Surface erstellen"); + let adapter = pollster::block_on(instance.request_adapter(&wgpu::RequestAdapterOptions { + power_preference: wgpu::PowerPreference::HighPerformance, + force_fallback_adapter: false, + compatible_surface: Some(&surface), + })) + .expect("kein passender GPU-Adapter"); + let (device, queue) = pollster::block_on(adapter.request_device( + &wgpu::DeviceDescriptor { + label: Some("3d.device"), + required_features: wgpu::Features::empty(), + required_limits: wgpu::Limits::default(), + memory_hints: wgpu::MemoryHints::Performance, + }, + None, + )) + .expect("Device anfordern"); + + let caps = surface.get_capabilities(&adapter); + let format = caps + .formats + .iter() + .copied() + .find(|f| f.is_srgb()) + .unwrap_or(caps.formats[0]); + let config = wgpu::SurfaceConfiguration { + usage: wgpu::TextureUsages::RENDER_ATTACHMENT, + format, + width: size.width.max(1), + height: size.height.max(1), + present_mode: caps.present_modes[0], + alpha_mode: caps.alpha_modes[0], + view_formats: vec![], + desired_maximum_frame_latency: 2, + }; + surface.configure(&device, &config); + + let mut renderer = Renderer::new(&device, format); + renderer.upload_walls(&device, &demo_walls()); + + Self { + surface, + device, + queue, + config, + renderer, + window, + } + } + + fn resize(&mut self, w: u32, h: u32) { + if w == 0 || h == 0 { + return; + } + self.config.width = w; + self.config.height = h; + self.surface.configure(&self.device, &self.config); + } + + fn render(&mut self, camera: &Camera) { + let frame = match self.surface.get_current_texture() { + Ok(f) => f, + Err(wgpu::SurfaceError::Lost | wgpu::SurfaceError::Outdated) => { + self.surface.configure(&self.device, &self.config); + return; + } + Err(e) => { + eprintln!("Surface-Fehler: {e:?}"); + return; + } + }; + let view = frame + .texture + .create_view(&wgpu::TextureViewDescriptor::default()); + self.renderer.render( + &self.device, + &self.queue, + &view, + camera, + (self.config.width, self.config.height), + ); + frame.present(); + } +} + +/// Orbit-Zustand: Yaw/Pitch (Radiant) + Abstand. Steuert die Kamera-Position um +/// das feste Ziel (Raum-Mitte). +struct Orbit { + yaw: f32, + pitch: f32, + dist: f32, +} + +impl Default for Orbit { + fn default() -> Self { + // Schraeg von vorn-oben-rechts, ~10 m Abstand. + Self { + yaw: std::f32::consts::FRAC_PI_4, + pitch: 0.5, + dist: 11.0, + } + } +} + +impl Orbit { + fn camera(&self) -> Camera { + let target = scene_target(); + Camera { + eye: orbit_eye(target, self.yaw, self.pitch, self.dist), + target, + up: [0.0, 1.0, 0.0], + projection: Projection::Perspective, + ..Camera::default() + } + } +} + +#[derive(Default)] +struct App { + state: Option, + orbit: Orbit, + dragging: bool, + last_cursor: (f64, f64), +} + +impl ApplicationHandler for App { + fn resumed(&mut self, event_loop: &ActiveEventLoop) { + if self.state.is_some() { + return; + } + let attrs = Window::default_attributes().with_title("render3d — Spike (Orbit)"); + let window = Arc::new(event_loop.create_window(attrs).expect("Fenster erstellen")); + self.state = Some(GpuState::new(window)); + } + + fn window_event( + &mut self, + event_loop: &ActiveEventLoop, + _id: WindowId, + event: WindowEvent, + ) { + let Some(state) = self.state.as_mut() else { + return; + }; + match event { + WindowEvent::CloseRequested => event_loop.exit(), + WindowEvent::Resized(size) => { + state.resize(size.width, size.height); + state.window.request_redraw(); + } + WindowEvent::MouseInput { state: s, button, .. } => { + if button == MouseButton::Left { + self.dragging = s == ElementState::Pressed; + } + } + WindowEvent::CursorMoved { position, .. } => { + if self.dragging { + // Maus-Delta -> Yaw/Pitch (Radiant je px). Pitch klemmt orbit_eye. + let dx = (position.x - self.last_cursor.0) as f32; + let dy = (position.y - self.last_cursor.1) as f32; + self.orbit.yaw -= dx * 0.01; + self.orbit.pitch += dy * 0.01; + let limit = std::f32::consts::FRAC_PI_2 - 0.01; + self.orbit.pitch = self.orbit.pitch.clamp(-limit, limit); + state.window.request_redraw(); + } + self.last_cursor = (position.x, position.y); + } + WindowEvent::MouseWheel { delta, .. } => { + let step = match delta { + MouseScrollDelta::LineDelta(_, y) => y, + MouseScrollDelta::PixelDelta(p) => (p.y as f32) / 40.0, + }; + // Rad -> Abstand (multiplikativ), geklemmt auf sinnvollen Bereich. + let factor = if step > 0.0 { 0.9 } else { 1.0 / 0.9 }; + self.orbit.dist = (self.orbit.dist * factor).clamp(1.5, 200.0); + state.window.request_redraw(); + } + WindowEvent::RedrawRequested => { + let cam = self.orbit.camera(); + state.render(&cam); + } + _ => {} + } + } +} + +fn main() { + env_logger::init(); + let event_loop = EventLoop::new().expect("Event-Loop erstellen"); + event_loop.set_control_flow(winit::event_loop::ControlFlow::Wait); + let mut app = App::default(); + event_loop.run_app(&mut app).expect("App laufen lassen"); +} diff --git a/src-tauri/render3d/src/gpu.rs b/src-tauri/render3d/src/gpu.rs new file mode 100644 index 0000000..a8323de --- /dev/null +++ b/src-tauri/render3d/src/gpu.rs @@ -0,0 +1,301 @@ +// wgpu-Pipeline des nativen 3D-Renderers (nur mit Feature "render"). +// +// Eine Pipeline: extrudierte Wand-Meshes mit Tiefenpuffer, View-Projektions- +// Matrix-Uniform und einem Directional-Light im Fragment-Shader. Backface-Culling +// ist aktiv (die Extrusion liefert konsistent nach aussen zeigende Flaechen, siehe +// mesh.rs) — das haelt das Innere der Waende korrekt verdeckt. +// +// Der Renderer ist fenster-/surface-agnostisch: er bekommt `Device`, `Queue` und +// ein `TextureView` (Surface- oder Offscreen-Textur) plus die aktuelle Kamera und +// zeichnet dort hinein. Die Fenster-Anbindung (winit) liegt separat im Spike-Bin. + +use bytemuck::{Pod, Zeroable}; +use wgpu::util::DeviceExt; + +use crate::math::{view_projection, Mat4}; +use crate::mesh::build_walls_mesh; +use crate::shaders::MESH_WGSL; +use crate::types::{Camera, WallInput, FLOATS_PER_VERTEX}; + +/// Tiefenformat des Z-Puffers (32 Bit Float, ueberall verfuegbar). +pub const DEPTH_FORMAT: wgpu::TextureFormat = wgpu::TextureFormat::Depth32Float; + +/// Uniform-Block, 1:1 zum WGSL-`Globals`-Struct. std140-kompatibel (mat4/vec4 auf +/// 16 Byte ausgerichtet). +#[repr(C)] +#[derive(Clone, Copy, Pod, Zeroable)] +struct Globals { + view_proj: [f32; 16], + light_dir: [f32; 4], + ambient: [f32; 4], +} + +impl Default for Globals { + fn default() -> Self { + Self { + view_proj: crate::math::identity(), + // Richtung ZUM Licht (world), normiert. Entspricht der three.js-Sonne + // bei (6,12,4): das Licht kommt aus dieser Richtung. + light_dir: normalize4([6.0, 12.0, 4.0]), + // Ambienter Sockel 0.6 (wie three.js-AmbientLight(0.6)). + ambient: [0.6, 0.6, 0.6, 1.0], + } + } +} + +fn normalize4(v: [f32; 3]) -> [f32; 4] { + let l = (v[0] * v[0] + v[1] * v[1] + v[2] * v[2]).sqrt().max(1e-9); + [v[0] / l, v[1] / l, v[2] / l, 0.0] +} + +/// GPU-seitige Puffer eines hochgeladenen Meshes. +struct MeshBuffers { + vbo: wgpu::Buffer, + ibo: wgpu::Buffer, + index_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, + bind_group: wgpu::BindGroup, + uniform: wgpu::Buffer, + mesh: Option, + depth: Option, + globals: Globals, + /// Loeschfarbe (Hintergrund). Default heller Grauton wie die three.js-Sicht. + pub clear_color: wgpu::Color, +} + +/// Tiefen-Textur samt View, an eine bestimmte Groesse gebunden. +struct DepthTarget { + view: wgpu::TextureView, + width: u32, + height: u32, +} + +impl Renderer { + /// Erzeugt Pipeline + Layout fuer ein gegebenes Farbformat (Surface). + pub fn new(device: &wgpu::Device, color_format: wgpu::TextureFormat) -> Self { + let module = device.create_shader_module(wgpu::ShaderModuleDescriptor { + label: Some("mesh.wgsl"), + source: wgpu::ShaderSource::Wgsl(MESH_WGSL.into()), + }); + + let bind_group_layout = + device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor { + label: Some("globals.layout"), + entries: &[wgpu::BindGroupLayoutEntry { + binding: 0, + visibility: wgpu::ShaderStages::VERTEX_FRAGMENT, + ty: wgpu::BindingType::Buffer { + ty: wgpu::BufferBindingType::Uniform, + has_dynamic_offset: false, + min_binding_size: wgpu::BufferSize::new( + std::mem::size_of::() as u64 + ), + }, + count: None, + }], + }); + + let pipeline_layout = device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor { + label: Some("3d.layout"), + bind_group_layouts: &[&bind_group_layout], + push_constant_ranges: &[], + }); + + // Vertex-Layout: [pos vec3, normal vec3, color vec3], stride 9*4. + let vertex_layout = wgpu::VertexBufferLayout { + array_stride: (FLOATS_PER_VERTEX * 4) as u64, + step_mode: wgpu::VertexStepMode::Vertex, + attributes: &wgpu::vertex_attr_array![0 => Float32x3, 1 => Float32x3, 2 => Float32x3], + }; + + let pipeline = device.create_render_pipeline(&wgpu::RenderPipelineDescriptor { + label: Some("mesh.pipeline"), + layout: Some(&pipeline_layout), + vertex: wgpu::VertexState { + module: &module, + entry_point: "vs_main", + buffers: &[vertex_layout], + compilation_options: Default::default(), + }, + fragment: Some(wgpu::FragmentState { + module: &module, + entry_point: "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: true, + depth_compare: wgpu::CompareFunction::Less, + stencil: wgpu::StencilState::default(), + bias: wgpu::DepthBiasState::default(), + }), + multisample: wgpu::MultisampleState::default(), + multiview: None, + cache: None, + }); + + let uniform = device.create_buffer(&wgpu::BufferDescriptor { + label: Some("globals.buffer"), + size: std::mem::size_of::() as u64, + usage: wgpu::BufferUsages::UNIFORM | wgpu::BufferUsages::COPY_DST, + mapped_at_creation: false, + }); + let bind_group = device.create_bind_group(&wgpu::BindGroupDescriptor { + label: Some("globals.bind"), + layout: &bind_group_layout, + entries: &[wgpu::BindGroupEntry { + binding: 0, + resource: uniform.as_entire_binding(), + }], + }); + + Self { + pipeline, + bind_group, + uniform, + mesh: None, + depth: None, + globals: Globals::default(), + // #e9e9e9 heller Hintergrund (wie die three.js-Modellsicht). + clear_color: wgpu::Color { + r: 0.914, + g: 0.914, + b: 0.914, + a: 1.0, + }, + } + } + + /// Erzeugt das Mesh aus geflachten Waenden und laedt die Puffer hoch. + pub fn upload_walls(&mut self, device: &wgpu::Device, walls: &[WallInput]) { + let mesh = build_walls_mesh(walls); + if mesh.indices.is_empty() { + self.mesh = None; + return; + } + let vbo = device.create_buffer_init(&wgpu::util::BufferInitDescriptor { + label: Some("mesh.vbo"), + contents: bytemuck::cast_slice(&mesh.verts), + usage: wgpu::BufferUsages::VERTEX, + }); + let ibo = device.create_buffer_init(&wgpu::util::BufferInitDescriptor { + label: Some("mesh.ibo"), + contents: bytemuck::cast_slice(&mesh.indices), + usage: wgpu::BufferUsages::INDEX, + }); + self.mesh = Some(MeshBuffers { + vbo, + ibo, + index_count: mesh.indices.len() as u32, + }); + } + + /// Setzt die Lichtrichtung (Richtung ZUM Licht, world) und den ambienten Sockel. + pub fn set_light(&mut self, dir_to_light: [f32; 3], ambient: f32) { + self.globals.light_dir = normalize4(dir_to_light); + self.globals.ambient = [ambient, ambient, ambient, 1.0]; + } + + /// 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); + if ok { + return; + } + let texture = device.create_texture(&wgpu::TextureDescriptor { + label: Some("depth"), + size: wgpu::Extent3d { + width: w.max(1), + height: h.max(1), + depth_or_array_layers: 1, + }, + mip_level_count: 1, + sample_count: 1, + dimension: wgpu::TextureDimension::D2, + format: DEPTH_FORMAT, + usage: wgpu::TextureUsages::RENDER_ATTACHMENT, + view_formats: &[], + }); + let view = texture.create_view(&wgpu::TextureViewDescriptor::default()); + self.depth = Some(DepthTarget { + view, + width: w.max(1), + height: h.max(1), + }); + } + + /// Zeichnet einen Frame in `view` (Surface- oder Offscreen-Textur). Die Kamera + /// liefert die View-Projektions-Matrix; `viewport` bestimmt das Seitenverhaeltnis + /// und die Tiefenpuffer-Groesse. + pub fn render( + &mut self, + device: &wgpu::Device, + queue: &wgpu::Queue, + view: &wgpu::TextureView, + camera: &Camera, + viewport: (u32, u32), + ) { + let (w, h) = viewport; + let aspect = if h > 0 { w as f32 / h as f32 } else { 1.0 }; + let vp: Mat4 = view_projection(camera, aspect); + self.globals.view_proj = vp; + queue.write_buffer(&self.uniform, 0, bytemuck::bytes_of(&self.globals)); + + self.ensure_depth(device, w, h); + let depth_view = &self.depth.as_ref().unwrap().view; + + let mut encoder = device.create_command_encoder(&wgpu::CommandEncoderDescriptor { + label: Some("3d.encoder"), + }); + { + let mut pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor { + label: Some("3d.pass"), + color_attachments: &[Some(wgpu::RenderPassColorAttachment { + view, + resolve_target: None, + ops: wgpu::Operations { + load: wgpu::LoadOp::Clear(self.clear_color), + store: wgpu::StoreOp::Store, + }, + })], + depth_stencil_attachment: Some(wgpu::RenderPassDepthStencilAttachment { + view: depth_view, + depth_ops: Some(wgpu::Operations { + load: wgpu::LoadOp::Clear(1.0), + store: wgpu::StoreOp::Store, + }), + stencil_ops: None, + }), + timestamp_writes: None, + occlusion_query_set: None, + }); + + if let Some(m) = &self.mesh { + pass.set_pipeline(&self.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); + } + } + queue.submit(std::iter::once(encoder.finish())); + } +} diff --git a/src-tauri/render3d/src/lib.rs b/src-tauri/render3d/src/lib.rs new file mode 100644 index 0000000..49b33fd --- /dev/null +++ b/src-tauri/render3d/src/lib.rs @@ -0,0 +1,275 @@ +// Nativer wgpu-3D-Renderer fuer die CAD-Modellsicht. +// +// Aufbau in Schichten (bewusst getrennt, siehe Feature-Flags in Cargo.toml): +// - `types` : serde-only Eingabe (geflachte Waende, Kamera) + Mesh-Ausgabe. +// - `mesh` : Wand-Extrusion (Band aus Achse+Dicke, hochgezogen auf Hoehe). +// GPU-frei, headless per `cargo test` pruefbar. Kern-Port der +// three.js-`ExtrudeGeometry`-Wanderzeugung. +// - `math` : Mat4 + Kamera (View/Projektion, Perspektive + Orthografie, +// die fuenf Presets) + Orbit-Helfer. Handgerechnet, testbar. +// - `shaders` : WGSL-Quelle (View-Projektion + Directional-Light). +// - `gpu` : wgpu-Pipeline mit Tiefenpuffer + Backface-Culling (Feature "render"). +// - `bin/spike3d` : winit-Fenster mit Orbit-Kamera (Feature "window"). +// +// Standard-Build (`cargo test`/`cargo build` ohne Features) enthaelt nur die +// GPU-freien Schichten und ist damit unabhaengig von einer Display-Session. + +pub mod math; +pub mod mesh; +pub mod shaders; +pub mod types; + +#[cfg(feature = "render")] +pub mod gpu; + +pub use math::{ + look_at, orbit_eye, orthographic, perspective, preset_camera, projection_matrix, + view_matrix, view_projection, Mat4, +}; +pub use mesh::{build_walls_mesh, extrude_wall}; +pub use types::{ + Camera, CameraPreset, Mesh, Point2, Projection, Rgb, WallInput, 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::{Mesh, WallInput, FLOATS_PER_VERTEX}; + + /// Bequemer Bau einer achsparallelen Wand entlang +X. + fn wall_x(len: f32, thickness: f32, height: f32) -> WallInput { + WallInput { + start: [0.0, 0.0], + end: [len, 0.0], + thickness, + height, + base_elevation: 0.0, + color: [0.8, 0.8, 0.8], + } + } + + /// Liest Position + Normale eines Vertex (index i) aus dem interleaved Puffer. + fn vert(mesh: &Mesh, i: usize) -> ([f32; 3], [f32; 3]) { + let b = i * FLOATS_PER_VERTEX; + ( + [mesh.verts[b], mesh.verts[b + 1], mesh.verts[b + 2]], + [mesh.verts[b + 3], mesh.verts[b + 4], mesh.verts[b + 5]], + ) + } + + #[test] + fn eine_wand_hat_quader_zaehlung() { + // Eine Wand -> ein Quader: 24 Vertices, 36 Indizes (12 Dreiecke). + let mesh = build_walls_mesh(&[wall_x(3.0, 0.2, 2.5)]); + assert_eq!(mesh.vertex_count(), VERTS_PER_BOX, "24 Vertices je Quader"); + assert_eq!(mesh.indices.len(), INDICES_PER_BOX, "36 Indizes je Quader"); + assert_eq!(mesh.triangle_count(), 12, "12 Dreiecke je Quader"); + // Kein Index zeigt ausserhalb des Puffers. + let max_idx = *mesh.indices.iter().max().unwrap(); + assert!((max_idx as usize) < mesh.vertex_count()); + } + + #[test] + fn mehrere_waende_addieren_sich() { + let mesh = build_walls_mesh(&[ + wall_x(3.0, 0.2, 2.5), + WallInput { + start: [3.0, 0.0], + end: [3.0, 4.0], + thickness: 0.2, + height: 2.5, + base_elevation: 0.0, + color: [0.8, 0.8, 0.8], + }, + ]); + assert_eq!(mesh.vertex_count(), 2 * VERTS_PER_BOX); + assert_eq!(mesh.indices.len(), 2 * INDICES_PER_BOX); + } + + #[test] + fn bounding_box_deckt_dicke_laenge_hoehe_ab() { + // Wand entlang +X, Laenge 3, Dicke 0.2 -> Band in Z von -0.1..0.1; + // Hoehe 2.5 ab Basis 0. world: (x, y=hoehe, z=grundriss.y). + let mesh = build_walls_mesh(&[wall_x(3.0, 0.2, 2.5)]); + let (min, max) = mesh.bounds(); + // X: 0..3 (Achsenlaenge). + assert!((min[0] - 0.0).abs() < 1e-5, "min x == 0"); + assert!((max[0] - 3.0).abs() < 1e-5, "max x == 3"); + // Y: 0..2.5 (Hoehe). + assert!((min[1] - 0.0).abs() < 1e-5, "min y == 0"); + assert!((max[1] - 2.5).abs() < 1e-5, "max y == 2.5"); + // Z: -0.1..0.1 (halbe Dicke je Seite). + assert!((min[2] + 0.1).abs() < 1e-5, "min z == -0.1"); + assert!((max[2] - 0.1).abs() < 1e-5, "max z == 0.1"); + } + + #[test] + fn basis_hoehe_verschiebt_in_y() { + let mut w = wall_x(3.0, 0.2, 2.5); + w.base_elevation = 3.0; // Obergeschoss. + let mesh = build_walls_mesh(&[w]); + let (min, max) = mesh.bounds(); + assert!((min[1] - 3.0).abs() < 1e-5, "Unterkante auf Basis 3.0"); + assert!((max[1] - 5.5).abs() < 1e-5, "Oberkante 3.0 + 2.5"); + } + + #[test] + fn deckel_normale_zeigt_nach_oben() { + // Der Deckel wird als erstes Quad angehaengt (Vertices 0..3): Normale +Y. + let mesh = build_walls_mesh(&[wall_x(3.0, 0.2, 2.5)]); + let (_, n0) = vert(&mesh, 0); + assert!((n0[0]).abs() < 1e-6); + assert!((n0[1] - 1.0).abs() < 1e-6, "Deckel-Normale +Y"); + assert!((n0[2]).abs() < 1e-6); + } + + #[test] + fn mantel_normalen_zeigen_nach_aussen() { + // Wand entlang +X: die +n-Seite liegt bei z=+0.1 (n = leftNormal von +X = + // (0,1) im Grundriss -> world +Z), die -n-Seite bei z=-0.1. Fuer JEDE + // Mantelflaechen-Normale muss gelten: sie zeigt vom Wand-Zentrum weg. + let mesh = build_walls_mesh(&[wall_x(3.0, 0.2, 2.5)]); + // Wand-Zentrum in world. + let center = [1.5f32, 1.25, 0.0]; + // Alle Vertices durchgehen; fuer jede Flaeche muss die Normale eine + // positive Komponente in Richtung (Vertex - Zentrum) haben (zeigt raus). + let n = mesh.vertex_count(); + for i in 0..n { + let (p, nor) = vert(&mesh, i); + let out = [p[0] - center[0], p[1] - center[1], p[2] - center[2]]; + let d = out[0] * nor[0] + out[1] * nor[1] + out[2] * nor[2]; + // >= 0: die Normale weist nie ins Innere (Backface-Culling korrekt). + assert!( + d >= -1e-5, + "Vertex {i}: Normale zeigt nach innen (dot={d})" + ); + } + } + + #[test] + fn diagonale_wand_hat_gleiche_zaehlung() { + // Auch nicht-achsparallele Waende extrudieren korrekt (nur andere Ecken). + let w = WallInput { + start: [0.0, 0.0], + end: [2.0, 2.0], + thickness: 0.3, + height: 3.0, + base_elevation: 0.0, + color: [0.8, 0.8, 0.8], + }; + let mut mesh = Mesh::default(); + extrude_wall(&mut mesh, &w); + assert_eq!(mesh.vertex_count(), VERTS_PER_BOX); + // Hoehe deckt Y 0..3 ab. + let (min, max) = mesh.bounds(); + assert!((min[1]).abs() < 1e-5 && (max[1] - 3.0).abs() < 1e-5); + } + + #[test] + fn degenerierte_wand_erzeugt_nichts() { + // Start == Ende -> keine Richtung -> uebersprungen (kein Absturz). + let w = WallInput { + start: [1.0, 1.0], + end: [1.0, 1.0], + thickness: 0.2, + height: 2.5, + base_elevation: 0.0, + color: [0.8, 0.8, 0.8], + }; + let mesh = build_walls_mesh(&[w]); + assert_eq!(mesh.vertex_count(), 0); + assert!(mesh.indices.is_empty()); + } + + // --- Kamera / Matrizen ---------------------------------------------------- + + #[test] + fn look_at_setzt_kamera_ins_zentrum() { + use super::math::{transform_point, view_matrix}; + use super::types::Camera; + let cam = Camera { + eye: [0.0, 0.0, 5.0], + target: [0.0, 0.0, 0.0], + up: [0.0, 1.0, 0.0], + ..Camera::default() + }; + let v = view_matrix(&cam); + // Das Blickziel (Ursprung) liegt im View-Raum vor der Kamera bei z=-5 + // (Kamera schaut entlang -Z). + let p = transform_point(&v, [0.0, 0.0, 0.0]); + assert!((p[0]).abs() < 1e-5 && (p[1]).abs() < 1e-5); + assert!((p[2] + 5.0).abs() < 1e-5, "Ziel bei view-z = -5"); + } + + #[test] + fn perspektive_klemmt_z_in_null_bis_eins() { + use super::math::{perspective, transform_point}; + let m = perspective(60.0_f32.to_radians(), 1.0, 1.0, 100.0); + // Punkt auf der Nah-Ebene (view-z = -near) -> clip-z/w == 0. + let near = transform_point(&m, [0.0, 0.0, -1.0]); + assert!((near[2] / near[3]).abs() < 1e-4, "Nah-Ebene -> z=0"); + // Punkt auf der Fern-Ebene (view-z = -far) -> clip-z/w == 1. + let far = transform_point(&m, [0.0, 0.0, -100.0]); + assert!(((far[2] / far[3]) - 1.0).abs() < 1e-4, "Fern-Ebene -> z=1"); + } + + #[test] + fn orbit_eye_haelt_abstand_ein() { + use super::math::orbit_eye; + let target = [1.0, 0.0, 2.0]; + let e = orbit_eye(target, 0.7, 0.4, 10.0); + let d = ((e[0] - target[0]).powi(2) + + (e[1] - target[1]).powi(2) + + (e[2] - target[2]).powi(2)) + .sqrt(); + assert!((d - 10.0).abs() < 1e-4, "Abstand == dist"); + } + + #[test] + fn presets_setzen_projektionsart() { + use super::math::preset_camera; + use super::types::{CameraPreset, Projection}; + let t = [0.0, 0.0, 0.0]; + assert_eq!( + preset_camera(CameraPreset::Top, t, 10.0).projection, + Projection::Orthographic + ); + assert_eq!( + preset_camera(CameraPreset::Front, t, 10.0).projection, + Projection::Orthographic + ); + assert_eq!( + preset_camera(CameraPreset::Side, t, 10.0).projection, + Projection::Orthographic + ); + assert_eq!( + preset_camera(CameraPreset::Iso, t, 10.0).projection, + Projection::Perspective + ); + assert_eq!( + preset_camera(CameraPreset::Persp, t, 10.0).projection, + Projection::Perspective + ); + // Top blickt von oben herab: eye.y > target.y. + let top = preset_camera(CameraPreset::Top, t, 10.0); + assert!(top.eye[1] > t[1], "Top-Kamera ueber dem Ziel"); + } + + /// Validiert die WGSL-Quelle headless ueber naga (Parser + Validator) — faengt + /// Syntax-/Typfehler ohne GPU/Display ab. Nur mit Feature "render", weil naga + /// sonst nicht mitgebaut wird (Muster: render2d). + #[cfg(feature = "render")] + #[test] + fn wgsl_quelle_ist_valide() { + use naga::valid::{Capabilities, ValidationFlags, Validator}; + let src = super::shaders::MESH_WGSL; + let module = naga::front::wgsl::parse_str(src) + .unwrap_or_else(|e| panic!("mesh: WGSL-Parse-Fehler: {e:?}")); + let mut validator = Validator::new(ValidationFlags::all(), Capabilities::all()); + validator + .validate(&module) + .unwrap_or_else(|e| panic!("mesh: WGSL-Validierung fehlgeschlagen: {e:?}")); + } +} diff --git a/src-tauri/render3d/src/math.rs b/src-tauri/render3d/src/math.rs new file mode 100644 index 0000000..0451415 --- /dev/null +++ b/src-tauri/render3d/src/math.rs @@ -0,0 +1,239 @@ +// 4x4-Matrix-Mathematik + Kamera (View/Projektion + Orbit). Handgerechnet, +// spalten-major (WebGL/wgpu-Konvention: `m[col*4 + row]`), damit die Matrizen +// ohne Umsortieren direkt als Uniform hochladbar sind. +// +// WARUM handgerechnet statt `glam`: +// - Die serde-only Standard-Schicht (`default`-Feature) soll — wie in render2d — +// ohne zusaetzliche Crates headless test- und baubar bleiben. `glam` waere eine +// weitere Abhaengigkeit in genau der Schicht, die schlank bleiben muss. +// - Der benoetigte Umfang ist klein (perspective/ortho/look_at + Orbit) und exakt +// testbar. Ein spaeterer Wechsel zu `glam` (nur in der GPU-Schicht) bleibt +// moeglich, ohne die Tests der Mesh-/Kamera-Logik anzufassen. +// +// WICHTIG (Clip-Z-Konvention): wgpu/WebGPU erwartet Tiefe in [0, 1] (NICHT [-1, 1] +// wie OpenGL). Die Projektionen hier bilden daher auf den [0, 1]-Bereich ab. + +use crate::types::{Camera, CameraPreset, Projection}; + +/// 4x4-Matrix, spalten-major (`m[col*4 + row]`). +pub type Mat4 = [f32; 16]; + +/// Einheitsmatrix. +pub fn identity() -> Mat4 { + let mut m = [0.0f32; 16]; + m[0] = 1.0; + m[5] = 1.0; + m[10] = 1.0; + m[15] = 1.0; + m +} + +/// Matrix-Produkt `a * b` (spalten-major). Ergebnis wirkt als `(a*b) * v`. +pub fn mul(a: &Mat4, b: &Mat4) -> Mat4 { + let mut m = [0.0f32; 16]; + for col in 0..4 { + for row in 0..4 { + let mut sum = 0.0f32; + for k in 0..4 { + // a[k-te Spalte][row] * b[col-te Spalte][k] + sum += a[k * 4 + row] * b[col * 4 + k]; + } + m[col * 4 + row] = sum; + } + } + m +} + +/// Wendet die Matrix auf einen Punkt (w=1) an und liefert die kartesische xyz +/// (nach Division durch w). Fuer Tests / Punkt-Projektion. +pub fn transform_point(m: &Mat4, p: [f32; 3]) -> [f32; 4] { + [ + m[0] * p[0] + m[4] * p[1] + m[8] * p[2] + m[12], + m[1] * p[0] + m[5] * p[1] + m[9] * p[2] + m[13], + m[2] * p[0] + m[6] * p[1] + m[10] * p[2] + m[14], + m[3] * p[0] + m[7] * p[1] + m[11] * p[2] + m[15], + ] +} + +// --- Vektor-Helfer (3D) ------------------------------------------------------ + +#[inline] +pub fn sub(a: [f32; 3], b: [f32; 3]) -> [f32; 3] { + [a[0] - b[0], a[1] - b[1], a[2] - b[2]] +} + +#[inline] +pub fn dot(a: [f32; 3], b: [f32; 3]) -> f32 { + a[0] * b[0] + a[1] * b[1] + a[2] * b[2] +} + +#[inline] +pub 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], + ] +} + +#[inline] +pub fn length(a: [f32; 3]) -> f32 { + dot(a, a).sqrt() +} + +#[inline] +pub fn normalize(a: [f32; 3]) -> [f32; 3] { + let l = length(a); + if l > 1e-9 { + [a[0] / l, a[1] / l, a[2] / l] + } else { + [0.0, 0.0, 0.0] + } +} + +/// Right-handed `look_at`-View-Matrix (Kamera blickt von `eye` auf `center`, +/// mit `up`). Spalten-major, Konvention wie three.js/gluLookAt: die Kamera +/// schaut entlang -Z im View-Raum. +pub fn look_at(eye: [f32; 3], center: [f32; 3], up: [f32; 3]) -> Mat4 { + // f = Blickrichtung (vorwaerts), s = rechts, u = echtes Up. + let f = normalize(sub(center, eye)); + let s = normalize(cross(f, up)); + let u = cross(s, f); + + let mut m = identity(); + // Rotationsanteil (Zeilen = s, u, -f). + m[0] = s[0]; + m[4] = s[1]; + m[8] = s[2]; + m[1] = u[0]; + m[5] = u[1]; + m[9] = u[2]; + m[2] = -f[0]; + m[6] = -f[1]; + m[10] = -f[2]; + // Translationsanteil. + m[12] = -dot(s, eye); + m[13] = -dot(u, eye); + m[14] = dot(f, eye); + m +} + +/// Perspektivische Projektion (right-handed), Clip-Z in [0, 1] (wgpu-Konvention). +/// `fov_y` in Radiant, `aspect` = Breite/Hoehe. +pub fn perspective(fov_y: f32, aspect: f32, near: f32, far: f32) -> Mat4 { + let f = 1.0 / (fov_y * 0.5).tan(); + let mut m = [0.0f32; 16]; + m[0] = f / aspect; + m[5] = f; + // Tiefen-Abbildung auf [0,1] (zNear -> 0, zFar -> 1). + m[10] = far / (near - far); + m[11] = -1.0; + m[14] = (near * far) / (near - far); + m +} + +/// Orthografische Projektion (right-handed), Clip-Z in [0, 1] (wgpu-Konvention). +/// Symmetrisches Volumen [-h*aspect, h*aspect] x [-h, h], h = `half_height`. +pub fn orthographic(half_height: f32, aspect: f32, near: f32, far: f32) -> Mat4 { + let right = half_height * aspect; + let top = half_height; + let mut m = identity(); + m[0] = 1.0 / right; + m[5] = 1.0 / top; + // z: [near, far] -> [0, 1] (near -> 0). + m[10] = 1.0 / (near - far); + m[14] = near / (near - far); + m +} + +/// Projektionsmatrix aus einer `Camera` + Seitenverhaeltnis (Breite/Hoehe). +pub fn projection_matrix(cam: &Camera, aspect: f32) -> Mat4 { + match cam.projection { + Projection::Perspective => perspective(cam.fov_y, aspect, cam.near, cam.far), + Projection::Orthographic => { + orthographic(cam.ortho_half_height, aspect, cam.near, cam.far) + } + } +} + +/// View-Matrix aus einer `Camera`. +pub fn view_matrix(cam: &Camera) -> Mat4 { + look_at(cam.eye, cam.target, cam.up) +} + +/// Kombinierte View-Projektions-Matrix (`proj * view`), fertig fuer das Uniform. +pub fn view_projection(cam: &Camera, aspect: f32) -> Mat4 { + let v = view_matrix(cam); + let p = projection_matrix(cam, aspect); + mul(&p, &v) +} + +/// Orbit-Kamera: berechnet die Augen-Position aus Ziel + Kugel-Koordinaten. +/// - `yaw` : Drehung um die Y-Achse (Azimut), Radiant. +/// - `pitch` : Erhebung ueber die XZ-Ebene, Radiant (geklemmt, siehe unten). +/// - `dist` : Abstand vom Ziel. +/// Y-up: Hoehe = dist*sin(pitch); horizontaler Radius = dist*cos(pitch). +pub fn orbit_eye(target: [f32; 3], yaw: f32, pitch: f32, dist: f32) -> [f32; 3] { + // Pitch klemmen, damit die Kamera nicht ueber den Pol kippt (Gimbal-Flip). + let limit = std::f32::consts::FRAC_PI_2 - 0.01; + let p = pitch.clamp(-limit, limit); + let r = dist * p.cos(); + [ + target[0] + r * yaw.cos(), + target[1] + dist * p.sin(), + target[2] + r * yaw.sin(), + ] +} + +/// Baut eine `Camera` fuer eines der fuenf Presets der three.js-Sicht. `target` +/// ist das Blickziel (Modell-Mitte), `dist` der Abstand. Front/Top/Side sind +/// orthografisch achsparallel; Iso/Persp sind perspektivisch. +pub fn preset_camera(preset: CameraPreset, target: [f32; 3], dist: f32) -> Camera { + let mut cam = Camera { + target, + ..Camera::default() + }; + match preset { + // Blick entlang -Z (von vorne), orthografisch. + CameraPreset::Front => { + cam.projection = Projection::Orthographic; + cam.eye = [target[0], target[1], target[2] + dist]; + cam.up = [0.0, 1.0, 0.0]; + cam.ortho_half_height = dist * 0.5; + } + // Blick von oben entlang -Y (Grundriss), orthografisch. Up = -Z, damit + // Modell-Y (world +Z) im Bild nach unten zeigt (wie die 2D-Plan-Sicht). + CameraPreset::Top => { + cam.projection = Projection::Orthographic; + cam.eye = [target[0], target[1] + dist, target[2]]; + cam.up = [0.0, 0.0, -1.0]; + cam.ortho_half_height = dist * 0.5; + } + // Blick entlang -X (von der Seite), orthografisch. + CameraPreset::Side => { + cam.projection = Projection::Orthographic; + cam.eye = [target[0] + dist, target[1], target[2]]; + cam.up = [0.0, 1.0, 0.0]; + cam.ortho_half_height = dist * 0.5; + } + // Isometrischer Ueberblick (perspektivisch, Blick von schraeg oben). + CameraPreset::Iso => { + cam.projection = Projection::Perspective; + let d = dist / 3.0_f32.sqrt(); + cam.eye = [target[0] + d, target[1] + d, target[2] + d]; + cam.up = [0.0, 1.0, 0.0]; + } + // Freie perspektivische Standard-Ansicht (leicht von vorn-oben-rechts). + CameraPreset::Persp => { + cam.projection = Projection::Perspective; + cam.eye = orbit_eye( + target, + std::f32::consts::FRAC_PI_4, + 0.5, + dist, + ); + cam.up = [0.0, 1.0, 0.0]; + } + } + cam +} diff --git a/src-tauri/render3d/src/mesh.rs b/src-tauri/render3d/src/mesh.rs new file mode 100644 index 0000000..5cfc93b --- /dev/null +++ b/src-tauri/render3d/src/mesh.rs @@ -0,0 +1,136 @@ +// Mesh-Erzeugung: extrudiert geflachte Waende zu Quader-Meshes. GPU-frei, headless +// per `cargo test` pruefbar (Muster: render2d/tessellate). +// +// Grundlage ist der Wandband-Ansatz aus src/model/geometry.ts (`wallCorners`): +// u = normalize(end - start) Achsenrichtung im Grundriss +// n = leftNormal(u) = (-u.y, u.x) Links-Normale (90 Grad CCW) +// Band-Ecken = start/end +/- n * thickness/2 (CCW-Umlauf) +// Diese 2D-Band-Ecken werden nach world abgebildet (XZ-Ebene) und entlang +Y von +// `base_elevation` bis `base_elevation + height` extrudiert — analog zur +// three.js-`ExtrudeGeometry`, die den 2D-`clippedBand` hochzieht (Viewport3D.tsx +// `addLayerPrism`). M1 nimmt die stumpfe (rechtwinklige) Band-Form; Gehrungen +// (`computeJoins`/`clippedBand`) und Oeffnungen kommen in spaeteren Milestones. +// +// KOORDINATEN: model [x, y] -> world (x, elevation, y). Also Grundriss in der +// XZ-Ebene, Extrusion entlang +Y (Y-up), exakt wie Viewport3D.tsx: +// "Modell (x,y,z) -> Three (x, z, y) (Z = Hoehe nach oben)". + +use crate::types::{Mesh, Point2, Rgb, WallInput, 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, +/// keine geteilten Normalen). Konstanten dokumentieren die Test-Erwartung. +pub const VERTS_PER_BOX: usize = 24; +pub const INDICES_PER_BOX: usize = 36; + +/// 2D-Einheits-Links-Normale zur Richtung (end - start), wie `leftNormal` im Web: +/// n = (-u.y, u.x). Liefert (0,0) bei Nulllaenge. +fn left_normal(start: Point2, end: Point2) -> Point2 { + let dx = end[0] - start[0]; + let dy = end[1] - start[1]; + let len = (dx * dx + dy * dy).sqrt(); + if len < 1e-9 { + return [0.0, 0.0]; + } + [-dy / len, dx / len] +} + +/// Extrudiert EINE Wand zu einem Quader-Mesh und haengt sie an `mesh` an. +/// +/// Aufbau: die vier Grundriss-Ecken des Bandes (CCW) bilden das Deckel-Rechteck. +/// Boden = gleiche XZ auf `y0`, Deckel = auf `y1`. Jede der sechs Seiten bekommt +/// eigene Vertices mit ihrer (nach aussen zeigenden) Flaechen-Normale — daher +/// flaches Shading und korrektes Backface-Culling. +pub fn extrude_wall(mesh: &mut Mesh, wall: &WallInput) { + let n = left_normal(wall.start, wall.end); + // Degenerierte Wand (Start == Ende) ueberspringen. + if n == [0.0, 0.0] { + return; + } + let h = wall.thickness * 0.5; + + // Vier Band-Ecken im Grundriss (CCW, wie `wallCorners(start,end,thickness)`): + // p0 = start + n*h, p1 = end + n*h, p2 = end - n*h, p3 = start - n*h. + let off = |p: Point2, s: f32| -> Point2 { [p[0] + n[0] * s * h, p[1] + n[1] * s * h] }; + let g0 = off(wall.start, 1.0); + let g1 = off(wall.end, 1.0); + let g2 = off(wall.end, -1.0); + let g3 = off(wall.start, -1.0); + + let y0 = wall.base_elevation; + let y1 = wall.base_elevation + wall.height; + let color = wall.color; + + // world-Position aus Grundriss-XZ + Hoehe: (x, y, z=grundriss.y). + let w = |g: Point2, y: f32| -> [f32; 3] { [g[0], y, g[1]] }; + + // Acht Quader-Ecken (b* = Boden auf y0, t* = Deckel auf y1). + let b0 = w(g0, y0); + let b1 = w(g1, y0); + let b2 = w(g2, y0); + let b3 = w(g3, y0); + let t0 = w(g0, y1); + let t1 = w(g1, y1); + let t2 = w(g2, y1); + let t3 = w(g3, y1); + + // Sechs Seiten als Quads (Ecken CCW von aussen gesehen -> Normale zeigt raus). + // Deckel oben (+Y): t0,t1,t2,t3 von oben CCW. + push_quad(mesh, t0, t1, t2, t3, [0.0, 1.0, 0.0], color); + // Boden unten (-Y): b0,b3,b2,b1 (von unten gesehen CCW). + push_quad(mesh, b0, b3, b2, b1, [0.0, -1.0, 0.0], color); + + // Vier Mantelflaechen. Aussen-Normalen aus dem Grundriss: + // Kante g0->g1 liegt auf der +n-Seite -> Normale +n. + // Kante g2->g3 liegt auf der -n-Seite -> Normale -n. + // Kappen an Start (g3->g0) und Ende (g1->g2) liegen an den Achsen-Enden. + let np = [n[0], 0.0, n[1]]; // +n-Seite (world) + let nn = [-n[0], 0.0, -n[1]]; // -n-Seite + // Achsenrichtung (fuer die End-Kappen-Normalen). + let ux = wall.end[0] - wall.start[0]; + let uz = wall.end[1] - wall.start[1]; + let ul = (ux * ux + uz * uz).sqrt().max(1e-9); + let u = [ux / ul, 0.0, uz / ul]; + let neg_u = [-u[0], 0.0, -u[2]]; + + // +n-Wand: aussen betrachtet CCW = b0, b1, t1, t0. + push_quad(mesh, b0, b1, t1, t0, np, color); + // -n-Wand: aussen betrachtet CCW = b2, b3, t3, t2. + push_quad(mesh, b2, b3, t3, t2, nn, color); + // End-Kappe (Achsen-Ende, Normale +u): b1, b2, t2, t1. + push_quad(mesh, b1, b2, t2, t1, u, color); + // Start-Kappe (Achsen-Anfang, Normale -u): b3, b0, t0, t3. + push_quad(mesh, b3, b0, t0, t3, neg_u, color); +} + +/// Haengt ein Quad (vier Ecken in CCW von aussen) als zwei Dreiecke an, mit fester +/// Flaechen-Normale und Farbe je Vertex. Reihenfolge (0,1,2) + (0,2,3). +fn push_quad( + mesh: &mut Mesh, + a: [f32; 3], + b: [f32; 3], + c: [f32; 3], + d: [f32; 3], + normal: [f32; 3], + color: Rgb, +) { + let base = (mesh.verts.len() / FLOATS_PER_VERTEX) as u32; + for p in [a, b, c, d] { + 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, base, base + 2, base + 3]); +} + +/// Baut ein einzelnes Mesh aus mehreren Waenden (alle in EINEN Puffer). Die +/// Reihenfolge der Vertices ist deterministisch (je Wand ein Quader), damit die +/// Tests stabile Zaehlungen erwarten koennen. +pub fn build_walls_mesh(walls: &[WallInput]) -> Mesh { + let mut mesh = Mesh::default(); + for w in walls { + extrude_wall(&mut mesh, w); + } + mesh +} diff --git a/src-tauri/render3d/src/shaders.rs b/src-tauri/render3d/src/shaders.rs new file mode 100644 index 0000000..0367cbf --- /dev/null +++ b/src-tauri/render3d/src/shaders.rs @@ -0,0 +1,57 @@ +// WGSL-Shader des nativen 3D-Renderers. Als Konstante hier, damit die Quelle auch +// ohne aktives GPU-Feature (headless) im Repo pruefbar bleibt (naga-Test, Muster: +// render2d/shaders). +// +// Beleuchtung: EIN Directional-Light (Sonne) + ambienter Grundterm — das Pendant +// zur three.js-Sicht (AmbientLight 0.6 + DirectionalLight 1.1 bei (6,12,4), siehe +// Viewport3D.tsx). PBR (Rauheit/Metallik/Texturen) folgt in spaeteren Milestones. +// +// Uniform-Layout (group(0) binding(0)): +// view_proj : mat4x4 world -> Clip (proj * view) +// light_dir : vec4 Richtung ZUM Licht (world, xyz; w ungenutzt) +// ambient : vec4 ambienter Grundfaktor (rgb; a ungenutzt) +// +// Vertex-Attribute: position (world), normal (world), color (Albedo). + +/// Der einzige Shader (Vertex + Fragment). Diffuse Lambert-Beleuchtung mit einem +/// Directional-Light plus ambientem Sockel — GPU-Aequivalent des three.js-Setups. +pub const MESH_WGSL: &str = r#" +struct Globals { + view_proj : mat4x4, + light_dir : vec4, + ambient : vec4, +}; +@group(0) @binding(0) var globals : Globals; + +struct VsIn { + @location(0) position : vec3, + @location(1) normal : vec3, + @location(2) color : vec3, +}; + +struct VsOut { + @builtin(position) clip_pos : vec4, + @location(0) world_normal : vec3, + @location(1) color : vec3, +}; + +@vertex +fn vs_main(in : VsIn) -> VsOut { + var out : VsOut; + out.clip_pos = globals.view_proj * vec4(in.position, 1.0); + out.world_normal = in.normal; + out.color = in.color; + return out; +} + +@fragment +fn fs_main(in : VsOut) -> @location(0) vec4 { + let n = normalize(in.world_normal); + let l = normalize(globals.light_dir.xyz); + // Lambert-Diffusanteil; Rueckseiten (n.l < 0) tragen nichts bei. + let diffuse = max(dot(n, l), 0.0); + // Ambienter Sockel (Albedo * ambient) + gerichteter Anteil (Albedo * diffuse). + let shaded = in.color * globals.ambient.rgb + in.color * diffuse; + return vec4(shaded, 1.0); +} +"#; diff --git a/src-tauri/render3d/src/types.rs b/src-tauri/render3d/src/types.rs new file mode 100644 index 0000000..cbd682b --- /dev/null +++ b/src-tauri/render3d/src/types.rs @@ -0,0 +1,182 @@ +// Eingabe- und Ausgabe-Typen des nativen 3D-Renderers. Die Eingabe ist eine +// geflachte Beschreibung der Modellsicht (Waende), wie sie aus dem Web-Modell +// (`src/model/wall.ts`) heruebergereicht wird — reduziert auf das, was die +// Extrusion braucht: Achse (Start/Ende), Dicke, Hoehe, Basis-Hoehe. +// +// Die Typen sind serde-only und bewusst frei von GPU-Abhaengigkeiten, damit die +// Mesh-Erzeugung eigenstaendig (headless) getestet werden kann (Muster: render2d). +// +// KOORDINATEN-KONVENTION (identisch zur three.js-Sicht in src/viewport/Viewport3D.tsx): +// Modell ist 2D in Metern (x nach rechts, y nach "oben" im Grundriss). +// In der 3D-Welt gilt Y-up: world.x = model.x, world.z = model.y, world.y = Hoehe. +// D.h. der Grundriss liegt in der XZ-Ebene, die Extrusion laeuft entlang +Y. + +use serde::{Deserialize, Serialize}; + +/// Ein 2D-Punkt in MODELL-Metern (Grundriss). Wird bei der Extrusion nach +/// world (XZ-Ebene) abgebildet: `[x, y] -> world (x, elevation, y)`. +pub type Point2 = [f32; 2]; + +/// RGB-Farbe/Albedo, Komponenten in [0,1]. Alpha wird (noch) nicht gebraucht. +pub type Rgb = [f32; 3]; + +/// Eine geflachte Wand: Achse (Mittellinie) Start->Ende im Grundriss, plus Dicke, +/// Hoehe und Basis-Hoehe. Aus diesen Feldern wird ein extrudiertes Quader-Mesh +/// erzeugt (Band aus Achse+Dicke, hochgezogen auf `height` ab `base_elevation`). +/// +/// Vereinfachung des Web-`Wall` (siehe src/model/wall.ts): Ecken-Verschneidung +/// (`computeJoins`/`wallCorners`) und Oeffnungen kommen in spaeteren Milestones; +/// M1 extrudiert jede Wand als eigenstaendigen, an den Enden stumpf abgeschlossenen +/// Quader. +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct WallInput { + /// Achsen-Startpunkt im Grundriss (Modell-Meter). + pub start: Point2, + /// Achsen-Endpunkt im Grundriss (Modell-Meter). + pub end: Point2, + /// Wanddicke in Metern (das Band wird um `thickness/2` je Seite versetzt). + pub thickness: f32, + /// Wandhoehe in Metern (Extrusion entlang +Y). + pub height: f32, + /// Basis-Hoehe (Unterkante) in Metern. Default 0 (Geschossboden). + #[serde(default, rename = "baseElevation")] + pub base_elevation: f32, + /// Albedo-Farbe der Wand (RGB 0..1). Default helles Grau wie die three.js-Sicht. + #[serde(default = "default_wall_color")] + pub color: Rgb, +} + +fn default_wall_color() -> Rgb { + [0.82, 0.80, 0.76] +} + +/// 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)] +pub enum Projection { + Perspective, + Orthographic, +} + +impl Default for Projection { + fn default() -> Self { + Projection::Perspective + } +} + +/// Kamera-Parameter (Blickrichtung + Projektion). `eye`/`target`/`up` in world. +/// Fuer die Orbit-Kamera werden `eye`/`target` aus Orbit-Winkeln+Distanz berechnet +/// (siehe `math::orbit_eye`), koennen hier aber auch direkt gesetzt werden. +#[derive(Debug, Clone, Copy, Serialize, Deserialize)] +pub struct Camera { + /// Kamera-Position (world). + pub eye: [f32; 3], + /// Blickziel (world). + pub target: [f32; 3], + /// Up-Vektor (world). Fuer Y-up: [0,1,0]. + pub up: [f32; 3], + /// Projektionsart. + #[serde(default)] + pub projection: Projection, + /// Vertikaler Oeffnungswinkel in Radiant (nur perspektivisch). + #[serde(default = "default_fov")] + pub fov_y: f32, + /// Halbe Hoehe des Sichtvolumens in world-Metern (nur orthografisch). + #[serde(default = "default_ortho_half_height")] + pub ortho_half_height: f32, + /// Nah-/Fern-Ebene. + #[serde(default = "default_near")] + pub near: f32, + #[serde(default = "default_far")] + pub far: f32, +} + +fn default_fov() -> f32 { + // 50 Grad, wie die three.js-PerspectiveCamera-Voreinstellung. + 50.0_f32.to_radians() +} +fn default_ortho_half_height() -> f32 { + 6.0 +} +fn default_near() -> f32 { + 0.05 +} +fn default_far() -> f32 { + 1000.0 +} + +impl Default for Camera { + fn default() -> Self { + Self { + eye: [8.0, 8.0, 8.0], + target: [0.0, 0.0, 0.0], + up: [0.0, 1.0, 0.0], + projection: Projection::Perspective, + fov_y: default_fov(), + ortho_half_height: default_ortho_half_height(), + near: default_near(), + far: default_far(), + } + } +} + +/// Die fuenf Kamera-Presets der three.js-Sicht. `Iso` und `Persp` sind +/// perspektivisch/frei; `Front`/`Top`/`Side` sind achsparallel (orthografisch). +#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)] +pub enum CameraPreset { + Front, + Top, + Side, + Iso, + Persp, +} + +/// Ausgabe der Mesh-Erzeugung: interleaved [pos.xyz, normal.xyz, color.rgb] je +/// Vertex plus Indexpuffer. Bewusst GPU-frei (nur Vecs); die GPU-Schicht laedt +/// diese direkt in Vertex-/Index-Buffer hoch. +#[derive(Debug, Clone, Default, Serialize, Deserialize)] +pub struct Mesh { + /// Interleaved Vertices: je 9 f32 = [px,py,pz, nx,ny,nz, r,g,b]. + pub verts: Vec, + /// Dreiecks-Indizes (0-basiert auf die Vertices). + pub indices: Vec, +} + +/// Anzahl f32 je Vertex im interleaved Puffer: 3 Position + 3 Normale + 3 Farbe. +pub const FLOATS_PER_VERTEX: usize = 9; + +impl Mesh { + /// Anzahl Vertices im Puffer. + pub fn vertex_count(&self) -> usize { + self.verts.len() / FLOATS_PER_VERTEX + } + + /// Anzahl Dreiecke. + pub fn triangle_count(&self) -> usize { + self.indices.len() / 3 + } + + /// Achsparallele Bounding-Box ueber alle Positionen: (min.xyz, max.xyz). + /// Fuer leere Meshes: beide Ecken im Ursprung. + pub fn bounds(&self) -> ([f32; 3], [f32; 3]) { + if self.verts.is_empty() { + return ([0.0; 3], [0.0; 3]); + } + let mut min = [f32::INFINITY; 3]; + let mut max = [f32::NEG_INFINITY; 3]; + let n = self.vertex_count(); + for i in 0..n { + let base = i * FLOATS_PER_VERTEX; + for k in 0..3 { + let v = self.verts[base + k]; + if v < min[k] { + min[k] = v; + } + if v > max[k] { + max[k] = v; + } + } + } + (min, max) + } +}