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) et2.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 deGeometryprêts à l'emploi. - 15.B —
core::input:InputStateunifié (clavier/souris/gamepad) branché dansApp. - 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é demath::geometry). Les primitives produisent uneGeometry(purement CPU, sans GPU) ; les ranger dansmathpréserve la cohérence avecGeometry/GeometryError(math/geometry.rs). Exposées viamath::primitives::*. Alternative :resources::primitives(discoverabilité près deMesh) — rejetée car on reste en mathématiques pures avant tout upload GPU. - D2 — API des primitives : chaque générateur renvoie une
Geometrycomplète (positions + normales + UVs + indices, pas de couleurs → défaut blanc opaque). Signatures proposées :cube(size: f32) -> Geometry(arêtesize, 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_geometrydes exemples : remplacer le helper local decube.rs(et leshadow_test/spot_tests'ils en ont un) parmath::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é decontext.rs/frame.rs/renderer.rs).InputStateest CPU (consomme les événements winit) ; il n'est pas dans WGSL (langage de shader côté GPU, sans I/O) — D4a. Un éventuel uniformInputUniformsconsultable 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 parbegin_frame()(à chaqueabout_to_wait, avantAppHandler::update) etend_frame()(après render), permettant de dériverpressed/releaseddepuis les événements et de remettre les deltas à zéro proprement. - D6 — Exposition dans
App:Apppossède unInputState;app.input()(lecture) /app.input_mut()(écriture) disponibles danssetup/update/render. Leevent_handler(winit 0.30) forwarde lesWindowEventconcernés (KeyboardInput, MouseInput, CursorMoved, MouseWheel, CursorLeft/Entered) et lesDeviceEvent(MouseMotion, boutons de gamepad si activés) versInputState::handle(...).KeyboardInputdemandelisten_device_eventssi 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/Vecpar 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 ROADMAP2.3reste 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 laCameraactive (position recomputée sphériquement autour detarget,with_perspectiveinchangée) depuisInputState. 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 =planetexturé 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).updatefait lentement tourner la scène (commecube) 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 patterncheckerboard_rgbadecube.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
Geometryvalidée (Geometry::validatene 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 decube_geometrydecube.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: ensemblespressed/held/releasedsur une clé (winit::keyboard::Key/VirtualKeyCodeselon la version winit 0.30 — à trancher en D-impl) ;MouseState:position: (f32, f32),delta: (f32, f32),scroll: (f32, f32), boutonsMouseButtonavec sémantique pressed/held/released ;GamepadState: (v1 minimale) tableaux boutons/axes par index, optionnels.GamepadStatepeut ê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 versapp.input_mut(), et à chaqueabout_to_wait(l.283) appelleinput_mut().begin_frame()avanthandler.update(), puisinput_mut().end_frame()après la frame. Appexposeinput()/input_mut().
15.C — Caméra orbitale + exemple final demo
COMBO: ajouterOrbitalCamera(dansresources/camera.rsou 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)(repositionnecam.positionen sphériques).
- Exemple
lib/examples/demo.rs(workflow déclaratif, commecube/shadow_test) :setup: textures procédurales par mesh,primitives::*, matériauxstandard, lumières directionnelle (ombre-porteuse viaset_shadow_caster) + point + spot, caméra par défaut ;update: lireapp.input(), mettre à jourCameraController(glisser/déplacer la souris = yaw/pitch ; molette = distance ;R= reset ; touches1/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+ boucleApp+ tests légers - 15.C caméra orbitale + exemple
demovérifié au runtime - Factorisations exemples (primitives::cube) sans régression visuelle
cargo fmt --all -- --checkpropre, build workspace + exemples + tests- ROADMAP : cocher
2.2et2.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_cameramulti-caméras (ROADMAP 2.1) — sauf si trivial, sinon item séparé.- Uniform
InputUniformsconsultable 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).