cc26080ed0
spot_test : le cube tourne désormais sur les axes X et Y (quaternions composés Y*X, vitesses légèrement différentes), pour que tous les sommets passent devant le cône et que l'effet du faisceau soit visible sur les 6 faces. Ajout de lib/examples/README.md : table des exemples avec la commande de lancement de chacun + conventions. À maintenir à jour à chaque nouvel exemple.
137 lines
5.1 KiB
Rust
137 lines
5.1 KiB
Rust
//! 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 sur deux axes, éclairé **uniquement** par une spot.
|
|
struct SpotTest {
|
|
angle_x: f32,
|
|
angle_y: 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 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,
|
|
})
|
|
}
|