//! A lit unit cube that rotates, **textured** with a procedural checkerboard via the diffuse path //! (bind group `@group(2)`). //! //! A 3D mesh with Phong lighting on screen — the library's 3D showcase. //! Follows the declarative workflow (like `simple`): `AppBuilder` + automatic scene, **no wgpu import**. //! The scene owns its `PipelineCache`: go through `register_shader` + //! `add_material_shader`/`add_material_texture` + `create_mesh` + `add_entity`. The mesh //! is declared from a **`Geometry`** (positions, normals, indices). 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. Returned as a raw RGBA8 `Vec`, loadable via `Texture::from_rgba8`. fn checkerboard_rgba() -> Vec { 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). 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(); // 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(); // 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 }) }