//! A lit unit cube that rotates, **textured** with the `uv_texture.jpg` asset (an 8×8 UV atlas //! visualization — each labelled cell shows exactly where a face's UVs land) 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`); //! here the texture is a **file asset** (`assets/textures/uv_texture.jpg`, loaded via //! `Texture::from_file`) — the path is resolved against `CARGO_MANIFEST_DIR` so the example //! works from any working directory. //! 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::mesh::cube; use wsg_lib::resources::Texture; use wsg_lib::utils::WsgError; /// Texture asset directory, resolved against the crate root so the example works from any CWD. const TEXTURES: &str = concat!(env!("CARGO_MANIFEST_DIR"), "/examples/assets/textures"); /// Demo handler: rotates the textured cube in `update`. struct Cube { /// Cumulative rotation angle (radians), incremented each frame. angle: f32, } 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(); // Loads the `uv_texture.jpg` asset 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_file( &device, &queue, "uv_atlas", &format!("{TEXTURES}/uv_texture.jpg"), ) .unwrap(); app.scene.add_texture("uv_texture", texture).unwrap(); app.scene .add_material_texture("cube_material", "standard", "uv_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 }) }