1use super::{
10 SEQUENCE_STEP, SceneControl, SceneDef, SceneOptions, TEST_SIZE, default_options, morph_deltas,
11};
12use unlit_ecs::{Bundle, DynamicBundle, bundle};
13use unlit3d::prelude::*;
14
15const FRAMES: usize = 6;
17
18pub static SCENE: SceneDef = SceneDef {
21 id: "morphed",
22 title: "Morphed cube",
23 description: "a cube blended by two morph targets over six frames",
24 size: TEST_SIZE,
25 baseline: Some(TEST_SIZE),
26 clear: None,
27 frames: FRAMES as u32,
28 step_seconds: Some(SEQUENCE_STEP),
29 samples: 1,
30 depth: true,
31 ui: false,
32 reproducible_ui: false,
33 build,
34};
35
36struct MorphedCube {
42 mesh: GpuMesh,
43 pipeline: GpuRenderPipeline,
44 weights: Entity,
45}
46
47impl Bundle for MorphedCube {
48 fn into_bundle(self) -> DynamicBundle {
49 bundle![
50 Transform {
51 scale: glam::Vec3::splat(0.9),
52 ..Default::default()
53 },
54 InstanceColor::new(glam::Vec4::new(0.55, 0.85, 1.0, 1.0)),
55 self.mesh,
56 self.pipeline,
57 MorphBinding {
58 weights: self.weights
59 },
60 ]
61 }
62}
63
64fn build(
66 world: &mut World,
67 context: RenderContext,
68 pass: Entity,
69 _ui: Option<Entity>,
70 size: (u32, u32),
71 _options: SceneOptions,
72) -> SceneControl {
73 let mut source = MeshSource::new(&world.root_scope(), context);
74 source.register_lit_family(&world.root_scope());
75 let key = LitPipelineKey::new(default_options());
78 let source_entity = world
79 .scope_mut(pass)
80 .expect("the pass is alive")
81 .spawn(bundle![Source::new(source)]);
82
83 let (positions, uvs, colors, indices) = super::cube();
84 let deltas = morph_deltas(&positions);
85 let mesh = super::with_mesh_source(world, source_entity, |source, world| {
86 source.allocate_lit_mesh(
87 world,
88 &key.options,
89 LitMeshDesc {
90 positions: &positions,
91 uvs: Some(&uvs),
92 colors: Some(&colors),
93 indices: Some(&indices),
94 morph_deltas: Some(deltas),
95 ..Default::default()
96 },
97 )
98 });
99
100 let camera_entity = super::spawn_in(world, source_entity, bundle![camera(size)]);
101 let weights_entity = super::spawn_in(
105 world,
106 source_entity,
107 bundle![MorphWeights {
108 weights: weights(0).to_vec()
109 }],
110 );
111 super::spawn_in(
112 world,
113 source_entity,
114 MorphedCube {
115 mesh,
116 pipeline: GpuRenderPipeline::new(key),
117 weights: weights_entity,
118 },
119 );
120
121 SceneControl {
122 advance: Box::new(move |world, frame, _delta, frame_size| {
123 let frame = frame as usize % FRAMES;
124 world
127 .scope(source_entity)
128 .expect("the source is alive")
129 .query_one::<&mut MorphWeights>(weights_entity)
130 .expect("the weight entity carries a `MorphWeights`")
131 .get()
132 .weights = weights(frame).to_vec();
133 super::aim_camera(
136 world,
137 source_entity,
138 camera_entity,
139 CAMERA_EYE,
140 CAMERA_TARGET,
141 frame_size,
142 );
143 }),
144 snapshot: Box::new(|frame| {
145 (frame < FRAMES as u32).then(|| format!("morphed/frame_{frame:02}.webp"))
146 }),
147 }
148}
149
150const CAMERA_EYE: glam::Vec3 = glam::Vec3::new(1.0, 1.0, 2.8);
152const CAMERA_TARGET: glam::Vec3 = glam::Vec3::new(0.0, 0.2, 0.0);
154
155fn camera(size: (u32, u32)) -> Camera {
157 super::camera_looking_at(CAMERA_EYE, CAMERA_TARGET, size)
158}
159
160fn weights(frame: usize) -> [f32; 2] {
165 let phase = frame as f32 * 0.5;
166 [phase.cos().abs(), phase.sin().abs()]
167}