Skip to main content

unlit3d_examples/scenes/
mesh_and_ui.rs

1//! The mesh-and-UI scene: a cube and a rich egui panel in one pass.
2//!
3//! Ported from the `mesh_and_ui_in_one_frame` snapshot test: the frame where
4//! the mesh source and the UI source have to agree on record order, scissor
5//! state and the frame's load ops. The snapshot `mesh_and_ui.webp` freezes the
6//! second frame.
7
8use super::{SceneControl, SceneDef, SceneOptions, TEST_SIZE, camera_view, cube, default_options};
9use unlit_ecs::{Bundle, DynamicBundle, bundle};
10use unlit3d::prelude::*;
11
12/// The colour the scene clears to.
13const CLEAR_COLOR: wgpu::Color = wgpu::Color {
14    r: 0.02,
15    g: 0.02,
16    b: 0.02,
17    a: 1.0,
18};
19
20/// The scene's one visible object: a cube tilted towards the camera.
21///
22/// The mesh and its pipeline are what a spawn has to name, so they are the
23/// fields; the placement belongs to the scene, so it is the bundle's own.
24struct TiltedCube {
25    mesh: GpuMesh,
26    pipeline: GpuRenderPipeline,
27}
28
29impl Bundle for TiltedCube {
30    fn into_bundle(self) -> DynamicBundle {
31        bundle![
32            Transform {
33                translation: glam::Vec3::new(0.0, 0.2, 0.0),
34                rotation: glam::Quat::from_rotation_y(0.6),
35                scale: glam::Vec3::splat(0.7),
36            },
37            self.mesh,
38            self.pipeline,
39        ]
40    }
41}
42
43/// The scene: a cube with the same rich interface over it.
44pub static SCENE: SceneDef = SceneDef {
45    id: "mesh_and_ui",
46    title: "Mesh and UI",
47    description: "a cube with the rich egui panel over it, one pass",
48    size: TEST_SIZE,
49    baseline: Some(TEST_SIZE),
50    clear: Some(CLEAR_COLOR),
51    frames: 2,
52    step_seconds: None,
53    samples: 1,
54    depth: true,
55    ui: true,
56    reproducible_ui: false,
57    build,
58};
59
60/// Build the mesh-and-UI scene's world content.
61fn build(
62    world: &mut World,
63    context: RenderContext,
64    pass: Entity,
65    ui: Option<Entity>,
66    size: (u32, u32),
67    options: SceneOptions,
68) -> SceneControl {
69    let mut source = MeshSource::new(&world.root_scope(), context);
70    source.register_lit_family(&world.root_scope());
71    let key = LitPipelineKey::new(default_options());
72    let source_entity = world
73        .scope_mut(pass)
74        .expect("the pass is alive")
75        .spawn(bundle![Source::new(source)]);
76
77    let mesh = super::with_mesh_source(world, source_entity, |source, world| {
78        let (positions, uvs, colors, indices) = cube();
79        source.allocate_lit_mesh(
80            world,
81            &key.options,
82            LitMeshDesc {
83                positions: &positions,
84                uvs: Some(&uvs),
85                colors: Some(&colors),
86                indices: Some(&indices),
87                ..Default::default()
88            },
89        )
90    });
91
92    let camera_entity = super::spawn_in(world, source_entity, bundle![camera_view(size)]);
93    super::spawn_in(
94        world,
95        source_entity,
96        TiltedCube {
97            mesh,
98            pipeline: GpuRenderPipeline::new(key),
99        },
100    );
101
102    if options.ui {
103        super::spawn_in(
104            world,
105            ui.expect("the UI source was mounted"),
106            bundle![super::ui_only::rich_panel()],
107        );
108    }
109
110    SceneControl {
111        // Static: egui needs no per-frame advance, and the camera only has to
112        // keep the frame's aspect so a resized window does not stretch it.
113        advance: Box::new(move |world, _frame, _delta, frame_size| {
114            super::aim_camera(
115                world,
116                source_entity,
117                camera_entity,
118                super::DEFAULT_EYE,
119                super::DEFAULT_TARGET,
120                frame_size,
121            );
122        }),
123        snapshot: Box::new(|frame| (frame == 1).then(|| "mesh_and_ui.webp".to_owned())),
124    }
125}
126
127// The panel the mesh shows through is the UI-only scene's own rich interface:
128// the two scenes share one panel, and only the world behind it differs.