1use super::{
14 BendSkin, SEQUENCE_STEP, SceneControl, SceneDef, SceneOptions, TEST_SIZE, default_options,
15 morph_deltas, rest_pose,
16};
17use unlit_ecs::{Bundle, DynamicBundle, bundle};
18use unlit3d::prelude::*;
19
20const FRAMES: usize = 6;
22
23const INSTANCES: usize = 3;
25
26struct Instance {
32 transform: Transform,
33 tint: glam::Vec4,
34 mesh: GpuMesh,
35 pipeline: GpuRenderPipeline,
36 pose: Entity,
37 weights: Entity,
38}
39
40impl Bundle for Instance {
41 fn into_bundle(self) -> DynamicBundle {
42 bundle![
43 self.transform,
44 InstanceColor::new(self.tint),
45 self.mesh,
46 self.pipeline,
47 SkinBinding { pose: self.pose },
48 MorphBinding {
49 weights: self.weights
50 },
51 ]
52 }
53}
54
55pub static SCENE: SceneDef = SceneDef {
58 id: "instanced_skinned_morph",
59 title: "Instanced skin + morph",
60 description: "three cubes sharing one mesh, deformed per instance",
61 size: TEST_SIZE,
62 baseline: Some(TEST_SIZE),
63 clear: None,
64 frames: FRAMES as u32,
65 step_seconds: Some(SEQUENCE_STEP),
66 samples: 1,
67 depth: true,
68 ui: false,
69 reproducible_ui: false,
70 build,
71};
72
73fn build(
75 world: &mut World,
76 context: RenderContext,
77 pass: Entity,
78 _ui: Option<Entity>,
79 size: (u32, u32),
80 _options: SceneOptions,
81) -> SceneControl {
82 let mut source = MeshSource::new(&world.root_scope(), context);
83 source.register_lit_family(&world.root_scope());
84 let key = LitPipelineKey::new(default_options());
86 let source_entity = world
87 .scope_mut(pass)
88 .expect("the pass is alive")
89 .spawn(bundle![Source::new(source)]);
90
91 let (positions, uvs, colors, indices) = super::cube();
92 let skin = BendSkin::new(&positions, 0.0);
93 let deltas = morph_deltas(&positions);
94 let mesh = super::with_mesh_source(world, source_entity, |source, world| {
95 source.allocate_lit_mesh(
96 world,
97 &key.options,
98 LitMeshDesc {
99 positions: &positions,
100 uvs: Some(&uvs),
101 colors: Some(&colors),
102 normals: None,
103 indices: Some(&indices),
104 joints: Some(&skin.joints),
105 weights: Some(&skin.weights),
106 morph_deltas: Some(deltas),
107 },
108 )
109 });
110
111 let camera_entity = super::spawn_in(world, source_entity, bundle![camera(size)]);
112
113 let mut pose_entities = Vec::new();
117 let mut weight_entities = Vec::new();
118 for instance in 0..INSTANCES {
119 let pose_entity = super::spawn_in(
120 world,
121 source_entity,
122 bundle![SkinPose {
123 matrices: rest_pose(2)
124 }],
125 );
126 let weight_entity = super::spawn_in(
127 world,
128 source_entity,
129 bundle![MorphWeights {
130 weights: instance_weights(0, instance).to_vec()
131 }],
132 );
133 let color = match instance {
134 0 => glam::Vec4::new(1.0, 0.75, 0.55, 1.0),
135 1 => glam::Vec4::new(0.55, 0.85, 1.0, 1.0),
136 _ => glam::Vec4::new(0.7, 1.0, 0.6, 1.0),
137 };
138 super::spawn_in(
139 world,
140 source_entity,
141 Instance {
142 transform: Transform {
143 translation: glam::Vec3::new((instance as f32 - 1.0) * 1.3, 0.0, 0.0),
144 scale: glam::Vec3::splat(0.8),
145 ..Default::default()
146 },
147 tint: color,
148 mesh: mesh.clone(),
149 pipeline: GpuRenderPipeline::new(key.clone()),
150 pose: pose_entity,
151 weights: weight_entity,
152 },
153 );
154 pose_entities.push(pose_entity);
155 weight_entities.push(weight_entity);
156 }
157
158 let mut skins: Vec<BendSkin> = (0..INSTANCES)
161 .map(|_| BendSkin::new(&positions, 0.0))
162 .collect();
163
164 SceneControl {
165 advance: Box::new(move |world, frame, _delta, frame_size| {
166 let frame = frame as usize % FRAMES;
167 {
170 let scope = world.scope(source_entity).expect("the source is alive");
171 for instance in 0..INSTANCES {
172 skins[instance].set_angle(instance_angle(frame, instance));
173 *scope
174 .query_one::<&mut SkinPose>(pose_entities[instance])
175 .expect("the pose entity carries a `SkinPose`")
176 .get() = skins[instance].pose();
177 scope
178 .query_one::<&mut MorphWeights>(weight_entities[instance])
179 .expect("the weight entity carries a `MorphWeights`")
180 .get()
181 .weights = instance_weights(frame, instance).to_vec();
182 }
183 }
184 super::aim_camera(
187 world,
188 source_entity,
189 camera_entity,
190 CAMERA_EYE,
191 CAMERA_TARGET,
192 frame_size,
193 );
194 }),
195 snapshot: Box::new(|frame| {
196 (frame < FRAMES as u32)
197 .then(|| format!("instanced_skinned_morph/frame_{frame:02}.webp"))
198 }),
199 }
200}
201
202const CAMERA_EYE: glam::Vec3 = glam::Vec3::new(0.0, 1.3, 3.6);
205const CAMERA_TARGET: glam::Vec3 = glam::Vec3::new(0.0, 0.2, 0.0);
207
208fn camera(size: (u32, u32)) -> Camera {
210 super::camera_looking_at(CAMERA_EYE, CAMERA_TARGET, size)
211}
212
213fn instance_angle(frame: usize, instance: usize) -> f32 {
218 let phase = frame as f32 * 0.9 + instance as f32 * 0.7;
219 phase.sin() * 0.55
220}
221
222fn instance_weights(frame: usize, instance: usize) -> [f32; 2] {
224 let phase = frame as f32 * 0.5 + instance as f32 * 1.1;
225 [phase.cos().abs(), phase.sin().abs()]
226}