Files
wsg/lib/examples/cube.rs
T
Jérôme Bousquié 5ae978da23 doc
2026-09-21 10:07:32 +02:00

112 lines
5.0 KiB
Rust
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
//! Step 5 — MVP 3D: a lit unit cube that rotates; **Step 10** — the cube is **textured**
//! (procedural checkerboard) via the new diffuse path (bind group `@group(2)`).
//!
//! Demonstrates the MVP goal of ROADMAP 1.3 + 1.5: a 3D mesh with Phong lighting on screen.
//! Follows the declarative workflow (like `simple`): `AppBuilder` + automatic scene, **no wgpu import**.
//! Since Step 7 the scene owns its `PipelineCache`: go through `register_shader` +
//! `add_material_shader`/`add_material_texture` + `create_mesh` + `add_entity`. Since Step 8 the mesh
//! is declared from a **`Geometry`** (positions, normals, indices). Since Step 10 (D4) a texture is
//! registered by id (`add_texture`) and a textured material bound to it (`add_material_texture`);
//! the texture is generated *procedurally* (RGBA 8×8 checkerboard) to stay self-contained, no on-disk asset.
//! The default active camera (`Scene::default`, position (0,0,3), fov 45°) frames the cube, and
//! `AppHandler::update` rotates the entity via `set_entity_transform` each frame.
use glam::{Quat, Vec3};
use wsg_lib::AppHandler;
use wsg_lib::app::AppBuilder;
use wsg_lib::math::cube;
use wsg_lib::resources::Texture;
use wsg_lib::utils::WsgError;
/// Demo handler: rotates the textured cube in `update`.
struct Cube {
/// Cumulative rotation angle (radians), incremented each frame.
angle: f32,
}
/// Generates a *procedural* RGBA 8×8 checkerboard (white/brick), no on-disk asset, to texture the
/// cube (Step 10, D3/D4). Returned as a raw RGBA8 `Vec<u8>`, loadable via `Texture::from_rgba8`.
fn checkerboard_rgba() -> Vec<u8> {
const SIZE: u32 = 8;
let mut rgba = Vec::with_capacity((SIZE * SIZE * 4) as usize);
for y in 0..SIZE {
for x in 0..SIZE {
let even = (x + y) % 2 == 0;
let (r, g, b) = if even { (255, 255, 255) } else { (190, 40, 40) };
rgba.extend_from_slice(&[r, g, b, 255]);
}
}
rgba
}
impl AppHandler for Cube {
fn setup(&mut self, app: &mut wsg_lib::App) {
// Phong shader `standard` (carries the frame + object + texture bind groups, Step 10).
app.scene
.register_shader("standard", wsg_lib::utils::STANDARD_SHADER_PATH)
.unwrap();
// Builds the checkerboard texture with the Context's device/queue (via `app.context()`), then
// registers it in the scene by id; a textured material is then bound to that id.
let (device, queue) = {
let ctx = app.context();
(ctx.device.clone(), ctx.queue.clone())
};
let texture =
Texture::from_rgba8(&device, &queue, 8, 8, &checkerboard_rgba(), "checker").unwrap();
app.scene.add_texture("checker_texture", texture).unwrap();
app.scene
.add_material_texture("cube_material", "standard", "checker_texture")
.unwrap();
app.scene
.create_mesh("cube_mesh", cube(1.0), Some("cube_material"))
.unwrap();
app.scene.add_entity("cube", "cube_mesh").unwrap();
// Step 12 (Phase 4.2): in addition to the default directional light (+Z), a warm **point** light
// is added in front of the cube. Its halo (linear attenuation over the
// radius) is visible on the near face of the cube, on top of the directional lighting.
app.scene
.add_point_light(
Vec3::new(1.0, 0.5, 1.5), // world position, in front/right of the cube
[1.0, 0.7, 0.3], // warm tint
1.0, // intensity
3.0, // attenuation radius
)
.unwrap();
// Step 13 (Phase 4.2): a green **spot** light aimed at the cube from the left.
// The cone (half-angle ~20°) projects a directed beam onto the cube's faces, with a
// smoothed penumbra at the edge and linear attenuation over the radius.
app.scene
.add_spot_light(
Vec3::new(-2.0, 1.0, 1.5), // world position, left/above/behind the camera
Vec3::new(2.0, -1.0, -1.5).normalize(), // cone axis, toward the cube (origin)
[0.3, 1.0, 0.4], // green tint
1.2, // intensity
4.0, // attenuation radius
0.35, // half-angle (~20°) in radians
)
.unwrap();
}
fn update(&mut self, app: &mut wsg_lib::App) {
// Cumulative cube rotation (double axis for a more readable motion).
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 })
}