//! Test dedicated to **spot lights**. //! //! In this example, **only** a spot light is on (the default directional light is //! removed via `clear_lights()`) and the ambient is deliberately **very low**. The cube therefore //! appears nearly black except where the spot's cone reaches it: you clearly see //! //! 1. a **directed beam** (not an omni halo like the point light), //! 2. a **smoothed edge** (penumbra) at the cone's limit, //! 3. the lighting that **follows the cube** as it rotates (the cone is fixed in world space). //! //! Run with: `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; /// Test handler: cube rotating slowly on two axes, lit **only** by a 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(); // Remove the default directional light to isolate the spot. app.scene.clear_lights(); // Near-zero ambient: the cube is black outside the beam, the cone stands out. app.scene.set_ambient([0.03, 0.03, 0.03]); // The spot is above/behind the camera, aimed at the origin (the cube). // World position (0, 2, 3), cone axis toward (0,0,0). let spot_pos = Vec3::new(0.0, 2.0, 3.0); let spot_dir = (Vec3::ZERO - spot_pos).normalize(); // points at the cube app.scene .add_spot_light( spot_pos, spot_dir, [1.0, 0.9, 0.6], // warm tint 2.0, // intensity 10.0, // attenuation radius (wide, the cube is at ~3.6) 0.45, // half-angle (~26°) — wide enough to cover the cube ) .unwrap(); } fn update(&mut self, app: &mut wsg_lib::App) { // Slow rotation on two axes (X and Y): the cone is fixed in world space, // so a fixed region of the cube stays lit while the cube rotates. // The two axes let you see the beam's effect on the 6 faces without // a favored orientation (a Y rotation alone would leave the +Y/-Y faces fixed). 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; // Y * X composition: the X axis rotates in the frame already oriented by Y, // which gives a precession motion (all vertices pass in front of // the cone in turn). 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, }) }