fix: shadow caster index, torus winding, per-frame device poll ; close stage 15
Bug fixes found while validating stage 15: - demo: set_shadow_caster(Some(0)) selected the default +Z light (packed index 0 from Lights::new()); the demo's warm directional light is packed at index 1. The shadow camera then looked down -Z, so misaligned objects occluded each other (cone/torus rendered black). Use index 1. - primitives::torus: index winding was flipped ([a,c,b]); the outer surface (outward normals, CCW from outside) was culled and only the dark interior stayed visible. Reversed to [a,b,c]/[b,d,c] so it is CCW from outside. - app: call device.poll() each frame in about_to_wait; without it wgpu async callbacks (on_submitted_work_done, map_async) never fire in the windowed loop. Docs: - conf: clarify the embedded-shader fallback is expected/harmless and that SHADOW_SHADER_PATH is kept for API compatibility only. - README item 15: runtime-verified headless. - DRAFT.md: emptied to a completion summary per convention (full stage-15 plan preserved in git history).
This commit is contained in:
@@ -191,4 +191,4 @@ The architecture docs live in `docs/tech/` and are written in **French**. Each d
|
|||||||
12. ✅ **Shadows — shadow mapping (Étape 14, Phase 4.2, optionnel)** — classic two-pass shadow mapping on a **single** light (directional or spot), selected by `Scene::set_shadow_caster(index)`. A depth-only pass (`shadow_shader.wgsl` + dedicated `shadow_pipeline`) renders the scene into a 1024² `Depth32Float` shadow map (`Renderer`-owned, slope-scaled depth bias); the `standard` fragment re-projects each fragment into light space and applies a **PCF 3×3** comparison-sampler test (bind group **@3**, shared). `FrameUniforms` grew from 704 to 784 bytes (`shadow_light_index`, `light_view_proj`, `shadow_params`). Shadows are **off by default** (`shadow_caster = None`) so `simple`/`cube`/`manual`/`spot_test` are unchanged. The `shadow_test` example casts a soft shadow from a cube onto a ground slab. (Done 2026-09-19.)
|
12. ✅ **Shadows — shadow mapping (Étape 14, Phase 4.2, optionnel)** — classic two-pass shadow mapping on a **single** light (directional or spot), selected by `Scene::set_shadow_caster(index)`. A depth-only pass (`shadow_shader.wgsl` + dedicated `shadow_pipeline`) renders the scene into a 1024² `Depth32Float` shadow map (`Renderer`-owned, slope-scaled depth bias); the `standard` fragment re-projects each fragment into light space and applies a **PCF 3×3** comparison-sampler test (bind group **@3**, shared). `FrameUniforms` grew from 704 to 784 bytes (`shadow_light_index`, `light_view_proj`, `shadow_params`). Shadows are **off by default** (`shadow_caster = None`) so `simple`/`cube`/`manual`/`spot_test` are unchanged. The `shadow_test` example casts a soft shadow from a cube onto a ground slab. (Done 2026-09-19.)
|
||||||
13. ✅ **Procedural primitive meshes (Étape 15.A)** — `math::primitives` provides drop-in `Geometry` generators (`cube`, `plane`, `uv_sphere`, `icosphere`, `cylinder`, `cone`, `torus`) with positions + per-face/smooth normals + UVs + indices. Re-exported at `math::*`. The `cube` and `spot_test` examples now reuse `math::cube(1.0)` (the `cube_geometry` helper was factored away; `shadow_test` keeps its generic `box_geometry`). (Done 2026-09-20; 6 unit tests.)
|
13. ✅ **Procedural primitive meshes (Étape 15.A)** — `math::primitives` provides drop-in `Geometry` generators (`cube`, `plane`, `uv_sphere`, `icosphere`, `cylinder`, `cone`, `torus`) with positions + per-face/smooth normals + UVs + indices. Re-exported at `math::*`. The `cube` and `spot_test` examples now reuse `math::cube(1.0)` (the `cube_geometry` helper was factored away; `shadow_test` keeps its generic `box_geometry`). (Done 2026-09-20; 6 unit tests.)
|
||||||
14. ✅ **Unified input (Étape 15.B)** — `core::input::InputState` gives cross-frame **pressed/held/released** semantics for keyboard (physical `KeyCode`) and mouse (buttons, position, per-frame delta, wheel scroll), rotated by `begin_frame`/`end_frame` around `AppHandler::update`. `App` exposes it as a public `input` field, fed from winit `WindowEvent`s and reset each frame. Gamepad is reserved/deferred (DRAFT D7). (Done 2026-09-20; 5 unit tests; winit event handling is host-driven on the CPU, not WGSL.)
|
14. ✅ **Unified input (Étape 15.B)** — `core::input::InputState` gives cross-frame **pressed/held/released** semantics for keyboard (physical `KeyCode`) and mouse (buttons, position, per-frame delta, wheel scroll), rotated by `begin_frame`/`end_frame` around `AppHandler::update`. `App` exposes it as a public `input` field, fed from winit `WindowEvent`s and reset each frame. Gamepad is reserved/deferred (DRAFT D7). (Done 2026-09-20; 5 unit tests; winit event handling is host-driven on the CPU, not WGSL.)
|
||||||
15. ✅ **Orbital camera + final demo (Étape 15.C)** — `resources::CameraController` (yaw/pitch/distance/target, `apply_to` writes into a `Camera`, drag-orbit + wheel-zoom + clamps) drives the new `demo` example: one of each primitive, procedural textures, standard Phong material, a shadow-casting directional light + point + spot, and live mouse-orbit / wheel-zoom / `R` reset / `1`/`2`/`3` view presets. Run with `cargo run -p wsg-lib --example demo`. (Done 2026-09-20; runtime check pending.)
|
15. ✅ **Orbital camera + final demo (Étape 15.C)** — `resources::CameraController` (yaw/pitch/distance/target, `apply_to` writes into a `Camera`, drag-orbit + wheel-zoom + clamps) drives the new `demo` example: one of each primitive, procedural textures, standard Phong material, a shadow-casting directional light + point + spot, and live mouse-orbit / wheel-zoom / `R` reset / `1`/`2`/`3` view presets. Run with `cargo run -p wsg-lib --example demo`. (Done 2026-09-20; runtime-verified headless.)
|
||||||
|
|||||||
+37
-151
@@ -1,159 +1,45 @@
|
|||||||
# DRAFT — Étape suivante
|
# DRAFT — Étape suivante
|
||||||
|
|
||||||
> 📅 **Plan de l'Étape 15 (2026-09-19).** L'Étape 14 (shadow mapping) est **FAIT & vérifié** (voir
|
> 📅 **Étape 15 — TERMINÉE (2026-09-20).** Document vidé conformément à la convention
|
||||||
> historique git + ROADMAP 4.2). Cette étape est une **« grosse étape »** : elle réunit les acquis en
|
> (« ce document est vidé à la complétion de chaque étape »). Le plan détaillé (objectifs,
|
||||||
> un **exemple final interactif** et pose deux briques réutilisables — une **bibliothèque de meshes
|
> décisions D1–D9, détail d'implémentation, périmètre) est archivé dans l'historique git du
|
||||||
> primitifs** et un **module d'input unifié** (clavier / souris / gamepad). Elle relance aussi deux
|
> présent fichier — `git log -- docs/DRAFT.md`.
|
||||||
> items de la ROADMAP : `2.2` (primitifs) et `2.3` (input), prérequis de l'exemple final (Phase 5).
|
|
||||||
>
|
|
||||||
> Source de vérité = code + README.md. Ce document est vidé à la complétion de chaque étape.
|
|
||||||
|
|
||||||
---
|
## Bilan de l'Étape 15 (archive)
|
||||||
|
|
||||||
## Objectif de l'Étape 15
|
**Objectif atteint** : un démonstrateur `demo` interactif cumulant les étapes 8-14 — un sol texturé
|
||||||
|
damier + 6 primitives (cube, sphère UV, icosphère, cylindre, cône, tore) texturées, éclairées
|
||||||
|
(multi-lumières + spot, Étapes 12-13), projetant des **ombres** (Étape 14), et pilotées par une
|
||||||
|
**caméra orbitale** au clavier/souris (glisser = orbite, molette = zoom, `R` = reset, `1`/`2`/`3` =
|
||||||
|
présettes) via un nouveau **module d'input unifié** — le tout dans le workflow déclaratif
|
||||||
|
`AppBuilder` + `AppHandler`, sans importer wgpu.
|
||||||
|
|
||||||
Produire un **démonstrateur `demo`** interactif qui cumule tout le travail des étapes 8-14 :
|
**Trois volets livrés :**
|
||||||
un sol + plusieurs **primitives** (cube, icosphère, cylindre, cône) **texturées**, **éclairées**
|
- **15.A — `math::primitives`** : générateurs de `Geometry` prêts à l'emploi (`cube`, `plane`,
|
||||||
(multi + spot, Étapes 12-13), projetant des **ombres** (Étape 14), et survolées par une **caméra
|
`uv_sphere`, `icosphere`, `cylinder`, `cone`, `torus`) + 6 tests. Le `cube_geometry` des exemples
|
||||||
orbitale** pilotée au **clavier/souris** via un nouveau module d'input unifié — le tout dans le
|
(`cube.rs`, `spot_test.rs`) a été factorisé vers `math::cube(1.0)`.
|
||||||
workflow déclaratif `AppBuilder` + `AppHandler`, sans importer wgpu.
|
- **15.B — `core::input`** : `InputState` unifié (clavier `KeyCode` / souris position+delta+molette,
|
||||||
|
sémantique pressed/held/released) branché dans `App` (`app.input` public, rotation
|
||||||
|
`begin_frame`/`end_frame` autour de `update`) + 5 tests. Gamepad **reporté** (D7) — champ réservé.
|
||||||
|
- **15.C — `demo` + caméra orbitale** : `resources::CameraController` (yaw/pitch/distance/target,
|
||||||
|
`apply_to`) + exemple `demo` (sol + 6 primitives + lumières + ombres + caméra orbitale).
|
||||||
|
|
||||||
Trois volets, à faire en sous-étapes de façon **incrémentale** (chaque volet compilable/testable seul) :
|
**Livraison** : ROADMAP 2.2 (primitifs) et 2.3 (input) cochées (gamepad `[~]` partiel) ; README items
|
||||||
|
13-15 ajoutés ; `cargo fmt --all -- --check` propre ; build workspace + exemples ; 28 tests unitaires
|
||||||
|
+ doctests au vert ; `demo` vérifié au runtime (headless).
|
||||||
|
|
||||||
- **15.A — `math::primitives`** : générateurs de `Geometry` prêts à l'emploi.
|
**Corrections de bugs découvertes en cours de route :**
|
||||||
- **15.B — `core::input`** : `InputState` unifié (clavier/souris/gamepad) branché dans `App`.
|
- *Ombres du `demo`* : `set_shadow_caster(Some(0))` désignait la lumière directionnelle **+Z par
|
||||||
- **15.C — `demo`** : exemple final = primitives + textures + lumières + ombres + caméra orbitale.
|
défaut** préchargée par `Lights::new()` (index 0 packé) au lieu de la lumière chaude du demo
|
||||||
|
(index 1) → la caméra d'ombre regardait −Z et des objets non-alignés s'occluaient mutuellement
|
||||||
|
(tore/cône noircis). Corrigé en `Some(1)`.
|
||||||
|
- *Tore noir* : le winding du tore (`primitives::torus`) était inversé (`[a, c, b]`), la face externe
|
||||||
|
était cullée et seul l'intérieur (normales vers l'extérieur → N·L ≤ 0) restait visible. Corrigé en
|
||||||
|
`[a, b, c]` + `[b, d, c]` (CCW vu de l'extérieur).
|
||||||
|
- *`App::run`* : ajout d'un `device.poll()` par frame — sans lui, les callbacks asynchrones wgpu
|
||||||
|
(`on_submitted_work_done`, `map_async`) ne firent jamais dans la boucle de production.
|
||||||
|
- *Docs* : `STANDARD_SHADER_PATH` pointait vers un fichier absent (le vrai shader est embarqué via
|
||||||
|
`include_str!`) ; le fallback est attendu et inoffensif — doc corrigée.
|
||||||
|
|
||||||
---
|
**Hors périmètre (reporté)** : `set_active_camera` multi-caméras (2.1), `InputUniforms` WGSL
|
||||||
|
(debug/gizmo), GPU-driven (Phase 3), batching matériau (4.3), LOD, HDR/tone mapping, gamepad complet.
|
||||||
## Décisions (D1…Dn) — à confirmer au fil de l'étape
|
|
||||||
|
|
||||||
- **D1 — Emplacement des primitives : `math::primitives`** (à côté de `math::geometry`). Les
|
|
||||||
primitives **produisent une `Geometry`** (purement CPU, sans GPU) ; les ranger dans `math` préserve
|
|
||||||
la cohérence avec `Geometry`/`GeometryError` (`math/geometry.rs`). Exposées via `math::primitives::*`.
|
|
||||||
*Alternative* : `resources::primitives` (discoverabilité près de `Mesh`) — **rejetée** car on reste
|
|
||||||
en mathématiques pures avant tout upload GPU.
|
|
||||||
- **D2 — API des primitives** : chaque générateur renvoie une **`Geometry` complète** (positions +
|
|
||||||
normales + UVs + indices, pas de couleurs → défaut blanc opaque). Signatures proposées :
|
|
||||||
- `cube(size: f32) -> Geometry` (arête `size`, normales/UVs par face, 24 sommets / 36 indices)
|
|
||||||
- `plane(width: f32, depth: f32, seg_x: u32, seg_z: u32) -> Geometry` (normales +Y, UVs étirés)
|
|
||||||
- `uv_sphere(radius: f32, sectors: u32, stacks: u32) -> Geometry` (normales sphériques + UV lat/long)
|
|
||||||
- `icosphere(radius: f32, subdivisions: u32) -> Geometry` (subdivision d'icosaèdre ; normales = position normalisée)
|
|
||||||
- `cylinder(radius: f32, height: f32, sectors: u32) -> Geometry` (couvercle/haut/bas + flanc)
|
|
||||||
- `cone(radius: f32, height: f32, sectors: u32) -> Geometry` (sommet + base ouverte/fermée)
|
|
||||||
- `torus(major: f32, minor: f32, major_segments: u32, minor_segments: u32) -> Geometry` *(bonus)*
|
|
||||||
Chaque fonction documente les conventions de repère (axe Y vers le haut) et l'orientation des
|
|
||||||
normales (pertinent pour l'éclairage car le culling reste désactivé par défaut).
|
|
||||||
- **D3 — Factorisation du `cube_geometry` des exemples** : remplacer le helper local de `cube.rs`
|
|
||||||
(et le `shadow_test`/`spot_test` s'ils en ont un) par `math::primitives::cube(1.0)`. Pas de
|
|
||||||
changement visuel (mêmes normales/UVs) → non-régression vérifiable.
|
|
||||||
- **D4 — Emplacement de l'input : `core::input`** (à côté de `context.rs`/`frame.rs`/`renderer.rs`).
|
|
||||||
`InputState` est **CPU** (consomme les événements winit) ; il n'est **pas** dans WGSL (langage de
|
|
||||||
shader côté GPU, sans I/O) — D4a. Un éventuel uniform `InputUniforms` consultable en WGSL (ex.
|
|
||||||
debug/gizmo) est **hors périmètre** pour l'instant, gardé en note pour une étape ultérieure.
|
|
||||||
- **D5 — Modèle d'état `InputState`** : sémantique **pressed / held / released** par frame, plus
|
|
||||||
souris (position, delta, boutons, molette) et gamepad (v1 minimale). Rotation par
|
|
||||||
`begin_frame()` (à chaque `about_to_wait`, avant `AppHandler::update`) et `end_frame()` (après
|
|
||||||
render), permettant de dériver `pressed`/`released` depuis les événements et de remettre les deltas
|
|
||||||
à zéro proprement.
|
|
||||||
- **D6 — Exposition dans `App`** : `App` possède un `InputState` ; `app.input()` (lecture) /
|
|
||||||
`app.input_mut()` (écriture) disponibles dans `setup`/`update`/`render`. Le `event_handler`
|
|
||||||
(winit 0.30) **forwarde** les `WindowEvent` concernés (KeyboardInput, MouseInput, CursorMoved,
|
|
||||||
MouseWheel, CursorLeft/Entered) et les `DeviceEvent` (MouseMotion, boutons de gamepad si
|
|
||||||
activés) vers `InputState::handle(...)`. `KeyboardInput` demande `listen_device_events` si on veut
|
|
||||||
le delta souris hors capture — à vérifier selon la config winit 0.30.
|
|
||||||
- **D7 — Gamepad (v1 minimale / optionnelle)** : winit fournit les `DeviceEvent::GamepadButtonChanged` /
|
|
||||||
`GamepadAxisChanged` (virtuels) ; on les capture **si présent** dans l'API (`Rc`/`Vec` par slot ou
|
|
||||||
index), sans abstraction complète de mapping. Si le support winit 0.30 s'avère trop fragile, on
|
|
||||||
**reporte** le gamepad (item ROADMAP `2.3` reste alors partiellement `[ ]`) et on livre d'abord
|
|
||||||
clavier/souris — l'exemple final n'a besoin **que** du clavier + souris.
|
|
||||||
- **D8 — Caméra orbitale** : **contrôleur** `CameraController { yaw, pitch, distance, target }` qui
|
|
||||||
met à jour la `Camera` active (position recomputée sphériquement autour de `target`, `with_perspective`
|
|
||||||
inchangée) depuis `InputState`. Le **cline multi-caméras (`set_active_camera`, ROADMAP 2.1) n'est PAS
|
|
||||||
requis** pour l'étape : on pilote la caméra active unique. 2.1 reste un item à part plus tard (ou est
|
|
||||||
optionnel ici si le temps le permet).
|
|
||||||
- **D9 — Exemple `demo`** : scène statique de primitives (sol = `plane` texturé damier ; cube,
|
|
||||||
icosphère, cylindre, cône posés dessus, chacun texturé procéduralement) + lumière directionnelle
|
|
||||||
ombre-porteuse (Étape 14) + point + spot (Étapes 12-13). `update` fait lentement tourner la scène
|
|
||||||
(comme `cube`) **et** met à jour la caméra orbitale via l'input (glisser = orbite, molette = zoom,
|
|
||||||
touches = reset / présettes / bascule light). Un petit helper de **texture procédurale**
|
|
||||||
(damier/couleurs) évite tout asset disque (réutilise le pattern `checkerboard_rgba` de `cube.rs`).
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Détail d'implémentation
|
|
||||||
|
|
||||||
### 15.A — `math::primitives` (bibliothèque de meshes prédéfinis)
|
|
||||||
|
|
||||||
Fichier : `lib/src/math/primitives.rs` (+ `pub mod primitives;` dans `math/mod.rs`).
|
|
||||||
|
|
||||||
- Algorithmes procéduraux classiques ; chaque sortie est une `Geometry` validée
|
|
||||||
(`Geometry::validate` ne doit pas renvoyer d'erreur — invariant testé).
|
|
||||||
- Conventions : axe **Y vers le haut**, origine centrée (sauf `plane` éventuellement ancré),
|
|
||||||
normales **orientées vers l'extérieur**, UVs [0,1]² aussi continus que possible.
|
|
||||||
- `cube` : quotient le code de `cube_geometry` de `cube.rs` (mêmes faces/normales/UVs/indices).
|
|
||||||
|
|
||||||
Tests (dans `math/primitives.rs` ou un `#[cfg(test)]`) :
|
|
||||||
- comptes attendus : cube → 24 sommets / 36 indices ; uv_sphere → `(sectors+1)*(stacks+1)` sommets ;
|
|
||||||
- `icosphere` : subdivision 0 → icosaèdre (12 sommets / 20 faces / 60 indices), refinement augmente ;
|
|
||||||
- normales de longueur == positions, normes ≈ 1 (tolérance), indices dans les bornes ;
|
|
||||||
- `plane(width, depth, 1, 1)` → 4 sommets / 6 indices, normales +Y.
|
|
||||||
|
|
||||||
### 15.B — `core::input` (module d'input unifié)
|
|
||||||
|
|
||||||
Fichier : `lib/src/core/input.rs` (+ `pub mod input;` et `pub use input::InputState;` dans
|
|
||||||
`core/mod.rs` si pertinent).
|
|
||||||
|
|
||||||
Structures :
|
|
||||||
- `struct InputState { keys: KeyStates, mouse: MouseState, gamepad: GamepadState }`
|
|
||||||
- `KeyStates` : ensembles `pressed/held/released` sur une clé
|
|
||||||
(`winit::keyboard::Key`/`VirtualKeyCode` selon la version winit 0.30 — à trancher en D-impl) ;
|
|
||||||
- `MouseState` : `position: (f32, f32)`, `delta: (f32, f32)`, `scroll: (f32, f32)`,
|
|
||||||
boutons `MouseButton` avec sémantique pressed/held/released ;
|
|
||||||
- `GamepadState` : (v1 minimale) tableaux boutons/axes par index, optionnels.
|
|
||||||
- `GamepadState` peut être factorable/ignoré si winit ne livre pas proprement (D7).
|
|
||||||
|
|
||||||
Méthodes publiques :
|
|
||||||
- `begin_frame()` / `end_frame()` — rotation des états, remise à zéro des deltas ;
|
|
||||||
- `handle(event: &winit::event::WindowEvent)` / `handle_device(event: &impl)` —
|
|
||||||
dispatch interne (kbd/mouse/scroll/gamepad) ;
|
|
||||||
- requêtes : `key_pressed/held/released(k)`, `mouse_button_*`, `mouse_position()/delta()/scroll()`.
|
|
||||||
|
|
||||||
Branchement (`app.rs`) :
|
|
||||||
- le `AppRunner` (window_event, ~l.293) forwarde les événements concernés vers `app.input_mut()`,
|
|
||||||
et à chaque `about_to_wait` (l.283) appelle `input_mut().begin_frame()` avant
|
|
||||||
`handler.update()`, puis `input_mut().end_frame()` après la frame.
|
|
||||||
- `App` expose `input()` / `input_mut()`.
|
|
||||||
|
|
||||||
### 15.C — Caméra orbitale + exemple final `demo`
|
|
||||||
|
|
||||||
- `COMBO` : ajouter `OrbitalCamera` (dans `resources/camera.rs` ou un controleur proche) —
|
|
||||||
`pub struct CameraController { pub yaw: f32, pub pitch: f32, pub distance: f32, pub target: Vec3 }`
|
|
||||||
+ `fn apply_to(&self, cam: &mut Camera)` (repositionne `cam.position` en sphériques).
|
|
||||||
- Exemple `lib/examples/demo.rs` (workflow déclaratif, comme `cube`/`shadow_test`) :
|
|
||||||
- `setup` : textures procédurales par mesh, `primitives::*`, matériaux `standard`,
|
|
||||||
lumières directionnelle (ombre-porteuse via `set_shadow_caster`) + point + spot, caméra par défaut ;
|
|
||||||
- `update` : lire `app.input()`, mettre à jour `CameraController` (glisser/déplacer la souris =
|
|
||||||
yaw/pitch ; molette = distance ; `R` = reset ; touches `1/2/3` = présettes de vue), appliquer à
|
|
||||||
la caméra active, rotation lente des primitives ;
|
|
||||||
- doc du fichier en anglais (convention README/exemples), commentaires internes au besoin.
|
|
||||||
|
|
||||||
### Livrables & checklist finale
|
|
||||||
|
|
||||||
- [x] 15.A `math::primitives` + tests (build + `cargo test`)
|
|
||||||
- [x] 15.B `core::input` + boucle `App` + tests légers
|
|
||||||
- [x] 15.C caméra orbitale + exemple `demo` vérifié au runtime
|
|
||||||
- [x] Factorisations exemples (primitives::cube) sans régression visuelle
|
|
||||||
- [x] `cargo fmt --all -- --check` propre, build workspace + exemples + tests
|
|
||||||
- [x] ROADMAP : cocher `2.2` et `2.3` (ou marquer le gamepad `[~]` partiel si reporté) ;
|
|
||||||
README : ajouter les items roadmap N°13-15 (primitives, input, démo)
|
|
||||||
- [ ] Fin d'étape : vider ce `DRAFT.md` (bandeau bilan archivé)
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Périmètre hors-étape (reporté / refusé)
|
|
||||||
|
|
||||||
- `set_active_camera` multi-caméras (ROADMAP 2.1) — sauf si trivial, sinon item séparé.
|
|
||||||
- Uniform `InputUniforms` consultable en WGSL (debug/gizmo shader) — note pour plus tard.
|
|
||||||
- GPU-driven (Phase 3), batching matériau (4.3), LOD, HDR/tone mapping — étapes ultérieures.
|
|
||||||
- Gamepad complet (mapping abstrait) — v1 minimale seulement (D7).
|
|
||||||
|
|||||||
@@ -161,8 +161,10 @@ impl AppHandler for Demo {
|
|||||||
)
|
)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
// The first directional light (packed index 0) casts shadows.
|
// The warm directional light above casts shadows. It is packed at index 1: index 0 is
|
||||||
app.scene.set_shadow_caster(Some(0));
|
// the default +Z directional light pre-loaded by `Lights::new()` (kept here for the
|
||||||
|
// base lighting), so the demo's own light is the SECOND one in the packed array.
|
||||||
|
app.scene.set_shadow_caster(Some(1));
|
||||||
app.scene.set_ambient([0.14, 0.14, 0.16]);
|
app.scene.set_ambient([0.14, 0.14, 0.16]);
|
||||||
|
|
||||||
// 6. Active camera, driven by the orbital controller (position, distance, preset target).
|
// 6. Active camera, driven by the orbital controller (position, distance, preset target).
|
||||||
|
|||||||
@@ -290,6 +290,18 @@ impl<H: AppHandler> ApplicationHandler for AppRunner<H> {
|
|||||||
let Some(app) = self.app.as_mut() else {
|
let Some(app) = self.app.as_mut() else {
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
|
// Poll the device each frame: wgpu only fires async callbacks (queue.on_submitted_work_done,
|
||||||
|
// buffer/texture map_async) when the device is polled, and the event loop never does it on
|
||||||
|
// our behalf. `Wait` with no timeout = block until the most recent submission completes
|
||||||
|
// (i.e. once per frame on a live GPU, which is what we want for the windowed loop).
|
||||||
|
// A failed poll (e.g. a device-lost error) is logged, not fatal: the next frame's poll
|
||||||
|
// will retry, and wgpu surfaces the loss through the device's error handler anyway.
|
||||||
|
if let Err(e) = app.context().device.poll(wgpu::PollType::Wait {
|
||||||
|
submission_index: None,
|
||||||
|
timeout: None,
|
||||||
|
}) {
|
||||||
|
eprintln!("WSG : device.poll() a échoué ({e:?})");
|
||||||
|
}
|
||||||
// Étape 15 (input) : débute la frame d'input (rotation pressed/released + reset deltas),
|
// Étape 15 (input) : débute la frame d'input (rotation pressed/released + reset deltas),
|
||||||
// exécute la logique utilisateur, puis clôt (nettoie les états transitoires).
|
// exécute la logique utilisateur, puis clôt (nettoie les états transitoires).
|
||||||
app.input.begin_frame();
|
app.input.begin_frame();
|
||||||
|
|||||||
@@ -422,8 +422,15 @@ pub fn torus(major: f32, minor: f32, major_segments: u32, minor_segments: u32) -
|
|||||||
let b = a + 1;
|
let b = a + 1;
|
||||||
let c = a + mn + 1;
|
let c = a + mn + 1;
|
||||||
let d = c + 1;
|
let d = c + 1;
|
||||||
|
// Triangles [a, b, c] / [b, d, c] : en face, l'angle u (majeur) croît avec +u et
|
||||||
|
// l'angle v (mineur) croît avec +v ; cross(tang_u, tang_v) pointe vers l'EXTÉRIEUR
|
||||||
|
// du tube (= la normale stockée), donc le winding est CCW vu de l'extérieur —
|
||||||
|
// cohérent avec `front_face: Face::Ccw` (culling des faces arrière).
|
||||||
|
// L'ordre [a, c, b] d'origine était inversé : la face externe (CCW vu de l'extérieur,
|
||||||
|
// normale extérieure) était Cullée et seul l'intérieur du tube, dont les normales
|
||||||
|
// pointent vers l'extérieur, restait visible — le tore apparaissait noir (N·L ≤ 0).
|
||||||
indices
|
indices
|
||||||
.extend_from_slice(&[a as u16, c as u16, b as u16, b as u16, c as u16, d as u16]);
|
.extend_from_slice(&[a as u16, b as u16, c as u16, b as u16, d as u16, c as u16]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Geometry::new(positions)
|
Geometry::new(positions)
|
||||||
|
|||||||
@@ -14,6 +14,11 @@
|
|||||||
/// for file-based loading. This is the unified pipeline shader (Étape 3 : un seul layout pour tous) :
|
/// for file-based loading. This is the unified pipeline shader (Étape 3 : un seul layout pour tous) :
|
||||||
/// it carries the full uniform contract (frame + object bind groups) and supports an unlit mode so flat
|
/// it carries the full uniform contract (frame + object bind groups) and supports an unlit mode so flat
|
||||||
/// 2D rendering is a special case of the 3D lit path. The `basic` family was removed (Étape 5).
|
/// 2D rendering is a special case of the 3D lit path. The `basic` family was removed (Étape 5).
|
||||||
|
///
|
||||||
|
/// NOTE: the shipped `assets/shaders/*.wgsl` files are OPTIONAL — when they are absent (library consumed
|
||||||
|
/// from a checkout without the assets directory, or from a published crate), `PipelineCache::load_shader`
|
||||||
|
/// falls back to the embedded `STANDARD_SHADER` source, which is byte-identical. The fallback is therefore
|
||||||
|
/// expected and harmless, not an error condition.
|
||||||
pub const STANDARD_SHADER_PATH: &str = "assets/shaders/standard_shader.wgsl";
|
pub const STANDARD_SHADER_PATH: &str = "assets/shaders/standard_shader.wgsl";
|
||||||
|
|
||||||
/// The standard (Phong) WGSL shader source code, embedded at compile time via `include_str!`.
|
/// The standard (Phong) WGSL shader source code, embedded at compile time via `include_str!`.
|
||||||
@@ -21,8 +26,9 @@ pub const STANDARD_SHADER_PATH: &str = "assets/shaders/standard_shader.wgsl";
|
|||||||
/// pipeline uses the unified layout (frame @0 + object @1), this is the only shader the library ships.
|
/// pipeline uses the unified layout (frame @0 + object @1), this is the only shader the library ships.
|
||||||
pub const STANDARD_SHADER: &str = include_str!("../shaders/standard_shader.wgsl");
|
pub const STANDARD_SHADER: &str = include_str!("../shaders/standard_shader.wgsl");
|
||||||
|
|
||||||
/// Path to the depth-only **shadow** WGSL shader on disk (Étape 14, D4). Used by the Renderer's
|
/// Path to the depth-only **shadow** WGSL shader on disk (Étape 14, D4). Kept only for API
|
||||||
/// shadow-map pass: a minimal vertex shader that transforms vertices into light-clip space.
|
/// compatibility — the shadow pass always compiles the embedded `SHADOW_SHADER` directly
|
||||||
|
/// (it is internal to the library, no external file is ever read).
|
||||||
pub const SHADOW_SHADER_PATH: &str = "assets/shaders/shadow_shader.wgsl";
|
pub const SHADOW_SHADER_PATH: &str = "assets/shaders/shadow_shader.wgsl";
|
||||||
|
|
||||||
/// The depth-only shadow WGSL shader source, embedded at compile time via `include_str!`
|
/// The depth-only shadow WGSL shader source, embedded at compile time via `include_str!`
|
||||||
|
|||||||
Reference in New Issue
Block a user