//! 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::math::cube; use wsg_lib::utils::WsgError; /// Handler de test : cube qui tourne lentement sur deux axes, éclairé **uniquement** par une spot. struct SpotTest { angle_x: f32, angle_y: f32, } 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(1.0), 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 sur deux axes (X et Y) : 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. // Les deux axes permettent de voir l'effet du faisceau sur les 6 faces sans // orientation privilégiée (la rotation Y seule laisserait les faces +Y/-Y fixes). self.angle_x += 0.007; self.angle_y += 0.011; let base = *app .scene .entity_transform("cube") .expect("cube entity present"); let mut transform = base; // Composition Y * X : l'axe X tourne dans le repère déjà orienté en Y, // ce qui donne un mouvement de précession (tous les sommets passent devant // le cône à tour de rôle). transform.rotation = Quat::from_rotation_y(self.angle_y) * Quat::from_rotation_x(self.angle_x); 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_x: 0.0, angle_y: 0.0, }) }