Files
wsg/docs/DRAFT.md
T

11 KiB

DRAFT — Étape suivante

📅 Plan de l'Étape 15 (2026-09-19). L'Étape 14 (shadow mapping) est FAIT & vérifié (voir historique git + ROADMAP 4.2). Cette étape est une « grosse étape » : elle réunit les acquis en un exemple final interactif et pose deux briques réutilisables — une bibliothèque de meshes primitifs et un module d'input unifié (clavier / souris / gamepad). Elle relance aussi deux 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.


Objectif de l'Étape 15

Produire un démonstrateur demo interactif qui cumule tout le travail des étapes 8-14 : un sol + plusieurs primitives (cube, icosphère, cylindre, cône) texturées, éclairées (multi + spot, Étapes 12-13), projetant des ombres (Étape 14), et survolées par une caméra orbitale pilotée au clavier/souris via un nouveau module d'input unifié — le tout dans le workflow déclaratif AppBuilder + AppHandler, sans importer wgpu.

Trois volets, à faire en sous-étapes de façon incrémentale (chaque volet compilable/testable seul) :

  • 15.A — math::primitives : générateurs de Geometry prêts à l'emploi.
  • 15.B — core::input : InputState unifié (clavier/souris/gamepad) branché dans App.
  • 15.C — demo : exemple final = primitives + textures + lumières + ombres + caméra orbitale.

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

  • 15.A math::primitives + tests (build + cargo test)
  • 15.B core::input + boucle App + tests légers
  • 15.C caméra orbitale + exemple demo vérifié au runtime
  • Factorisations exemples (primitives::cube) sans régression visuelle
  • cargo fmt --all -- --check propre, build workspace + exemples + tests
  • 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).