diff --git a/lib/examples/spot_test.rs b/lib/examples/spot_test.rs new file mode 100644 index 0000000..ebe4952 --- /dev/null +++ b/lib/examples/spot_test.rs @@ -0,0 +1,125 @@ +//! Test dédié aux **lumières spot** (Étape 13, Phase 4.2). +//! +//! Dans cet exemple, **seule** une lumière spot est allumée (la directionnelle par défaut est +//! retirée via `clear_lights()`) et l'ambiant est volontairement **très bas**. Le cube apparaît +//! donc quasiment noir sauf là où le cône de la spot l'atteint : on voit clairement +//! +//! 1. un **faisceau orienté** (pas un halo omni comme la lumière ponctuelle), +//! 2. un **bord lissé** (pénombre) à la limite du cône, +//! 3. l'éclairage qui **suit le cube** quand il tourne (le cône est fixe dans l'espace monde). +//! +//! Lance avec : `cargo run -p wsg-lib --example spot_test` +use glam::{Quat, Vec3}; +use wsg_lib::AppHandler; +use wsg_lib::app::AppBuilder; +use wsg_lib::resources::Geometry; +use wsg_lib::utils::WsgError; + +/// Handler de test : cube qui tourne lentement, éclairé **uniquement** par une spot. +struct SpotTest { + angle: f32, +} + +/// Cube unitaire centré à l'origine (mêmes 24 sommets / 36 indices que l'exemple `cube`). +fn cube_geometry() -> Geometry { + let s = 0.5; + let faces: [([f32; 3], [[f32; 3]; 4]); 6] = [ + ( + [0.0, 0.0, 1.0], + [[-s, -s, s], [s, -s, s], [s, s, s], [-s, s, s]], + ), // +Z + ( + [0.0, 0.0, -1.0], + [[s, -s, -s], [-s, -s, -s], [-s, s, -s], [s, s, -s]], + ), // -Z + ( + [1.0, 0.0, 0.0], + [[s, -s, -s], [s, s, -s], [s, s, s], [s, -s, s]], + ), // +X + ( + [-1.0, 0.0, 0.0], + [[-s, -s, s], [-s, s, s], [-s, s, -s], [-s, -s, -s]], + ), // -X + ( + [0.0, 1.0, 0.0], + [[-s, s, -s], [s, s, -s], [s, s, s], [-s, s, s]], + ), // +Y + ( + [0.0, -1.0, 0.0], + [[-s, -s, s], [s, -s, s], [s, -s, -s], [-s, -s, -s]], + ), // -Y + ]; + + let mut positions = Vec::with_capacity(24); + let mut normals = Vec::with_capacity(24); + let mut uvs = Vec::with_capacity(24); + let quad_uvs = [[0.0, 0.0], [1.0, 0.0], [1.0, 1.0], [0.0, 1.0]]; + for (normal, corners) in faces { + for (i, corner) in corners.iter().enumerate() { + positions.push(*corner); + normals.push(normal); + uvs.push(quad_uvs[i]); + } + } + let mut indices = Vec::with_capacity(36); + for face in 0..6u16 { + let b = face * 4; + indices.extend_from_slice(&[b, b + 1, b + 2, b, b + 2, b + 3]); + } + Geometry::new(positions) + .with_normals(normals) + .with_uvs(uvs) + .with_indices(indices) +} + +impl AppHandler for SpotTest { + fn setup(&mut self, app: &mut wsg_lib::App) { + app.scene + .register_shader("standard", wsg_lib::utils::STANDARD_SHADER_PATH) + .unwrap(); + app.scene.add_material_shader("mat", "standard").unwrap(); + app.scene + .create_mesh("cube_mesh", cube_geometry(), Some("mat")) + .unwrap(); + app.scene.add_entity("cube", "cube_mesh").unwrap(); + + // On retire la directionnelle par défaut pour isoler la spot. + app.scene.clear_lights(); + // Ambiant quasi nul : le cube est noir hors du faisceau, le cône saute aux yeux. + app.scene.set_ambient([0.03, 0.03, 0.03]); + + // La spot est au-dessus/derrière-caméra, pointée vers l'origine (le cube). + // Position monde (0, 2, 3), axe du cône vers (0,0,0). + let spot_pos = Vec3::new(0.0, 2.0, 3.0); + let spot_dir = (Vec3::ZERO - spot_pos).normalize(); // pointe vers le cube + app.scene + .add_spot_light( + spot_pos, + spot_dir, + [1.0, 0.9, 0.6], // teinte chaude + 2.0, // intensité + 10.0, // rayon d'atténuation (large, le cube est à ~3.6) + 0.45, // demi-angle (~26°) — assez large pour couvrir le cube + ) + .unwrap(); + } + + fn update(&mut self, app: &mut wsg_lib::App) { + // Rotation lente : le cône est fixe dans l'espace monde, on voit donc une région + // fixe du cube rester éclairée pendant que le cube tourne. + self.angle += 0.01; + let base = *app + .scene + .entity_transform("cube") + .expect("cube entity present"); + let mut transform = base; + transform.rotation = Quat::from_rotation_y(self.angle); + app.scene.set_entity_transform("cube", transform); + } +} + +#[pollster::main] +async fn main() -> Result<(), WsgError> { + let app = AppBuilder::new().title("WSG Spot Test").build().await?; + app.run(SpotTest { angle: 0.0 }) +}