62b5cac11b
Étape 7 (DRAFT): the PipelineCache moves into the Scene (SceneGpu holds
device + format + cache, wired via Scene::init_gpu in AppRunner::resumed),
so App no longer owns a cache field. Mesh now holds Option<Arc<Material>>
(with material()/set_material()/with_material()); Entity drops material_id
({mesh_id, transform}); iter_entities yields (label, &Mesh, &Transform);
Renderer::render_scene resolves the material from the mesh or the Scene's
lazy default_material(). New declarative Scene helpers: register_shader,
add_material_shader, create_mesh. cube/simple examples migrated; manual
remains low-level and unchanged.
122 lines
4.5 KiB
Rust
122 lines
4.5 KiB
Rust
//! Étape 5 — MVP 3D : un cube unitaire éclairé qui tourne, rendu automatiquement par la boucle `App`.
|
|
//!
|
|
//! Démonstration de l'objectif MVP du ROADMAP 1.3 + 1.5 : un mesh 3D avec éclairage Phong à l'écran.
|
|
//! On suit le workflow déclaratif (comme `simple`) : `AppBuilder` + scène automatique, **sans importer
|
|
//! wgpu**. Depuis l'Étape 7 la scène possède son `PipelineCache` : on passe par `register_shader` +
|
|
//! `add_material_shader` + `create_mesh` + `add_entity` (le matériau est lié au mesh, plus de material_id).
|
|
//! La caméra active par défaut (`Scene::default`, position (0,0,3), fov 45°) cadre le cube, et
|
|
//! `AppHandler::update` fait tourner l'entité via `set_entity_transform` chaque frame.
|
|
use glam::Quat;
|
|
use wsg_lib::AppHandler;
|
|
use wsg_lib::app::AppBuilder;
|
|
use wsg_lib::resources::Vertex;
|
|
use wsg_lib::utils::WsgError;
|
|
|
|
/// Handler de démonstration : fait tourner le cube dans `update`.
|
|
struct Cube {
|
|
/// Angle de rotation cumulé (radians), incrémenté à chaque frame.
|
|
angle: f32,
|
|
}
|
|
|
|
/// Génère les sommets d'un cube unitaire centré à l'origine (arête de 1), une normale par face.
|
|
/// 24 sommets (4 par face) + 36 indices ; la couleur est blanche, l'UV est laissé à zéro (inutilisé
|
|
/// par `standard` pour un matériau sans texture).
|
|
fn cube_vertices() -> Vec<Vertex> {
|
|
let s = 0.5; // demi-arête
|
|
let color = [1.0, 1.0, 1.0, 1.0];
|
|
// Chaque face : (normale sortante, 4 coins). Le culling est désactivé par défaut (PrimitiveState
|
|
// par défaut), donc l'ordre d'enroulement n'affecte pas la visibilité ; seules les normales comptent
|
|
// pour l'éclairage.
|
|
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 verts = Vec::with_capacity(24);
|
|
for (normal, corners) in faces {
|
|
for corner in corners {
|
|
verts.push(Vertex {
|
|
position: corner,
|
|
normal,
|
|
uv: [0.0, 0.0],
|
|
color,
|
|
});
|
|
}
|
|
}
|
|
verts
|
|
}
|
|
|
|
/// Génère les indices d'un cube à partir de ses 24 sommets (2 triangles par face, 36 indices).
|
|
fn cube_indices() -> Vec<u16> {
|
|
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]);
|
|
}
|
|
indices
|
|
}
|
|
|
|
impl AppHandler for Cube {
|
|
fn setup(&mut self, app: &mut wsg_lib::App) {
|
|
// Shader Phong `standard` (porteur des bind groups frame + object). Depuis l'Étape 7 le
|
|
// PipelineCache vit dans la scène : `register_shader` / `add_material_shader` / `create_mesh`
|
|
// en sont la façade déclarative (le matériau est lié au mesh au moment du create_mesh).
|
|
app.scene
|
|
.register_shader("standard", wsg_lib::utils::STANDARD_SHADER_PATH)
|
|
.unwrap();
|
|
app.scene
|
|
.add_material_shader("cube_material", "standard")
|
|
.unwrap();
|
|
app.scene
|
|
.create_mesh(
|
|
"cube_mesh",
|
|
&cube_vertices(),
|
|
Some(&cube_indices()),
|
|
Some("cube_material"),
|
|
)
|
|
.unwrap();
|
|
app.scene.add_entity("cube", "cube_mesh").unwrap();
|
|
}
|
|
|
|
fn update(&mut self, app: &mut wsg_lib::App) {
|
|
// Rotation cumulée du cube (double axe pour un mouvement plus lisible).
|
|
self.angle += 0.02;
|
|
let base = *app
|
|
.scene
|
|
.entity_transform("cube")
|
|
.expect("cube entity present");
|
|
let mut transform = base;
|
|
transform.rotation =
|
|
Quat::from_rotation_y(self.angle) * Quat::from_rotation_x(self.angle * 0.3);
|
|
app.scene.set_entity_transform("cube", transform);
|
|
}
|
|
}
|
|
|
|
#[pollster::main]
|
|
async fn main() -> Result<(), WsgError> {
|
|
let app = AppBuilder::new().title("WSG Cube").build().await?;
|
|
app.run(Cube { angle: 0.0 })
|
|
}
|