1use ponko::*;
4
5const JOINTS: usize = 8;
6const STEP: f32 = 0.5;
7struct Sway(f32);
8struct Bend(AnimationClip);
9
10fn chain() -> Skeleton {
12 Skeleton::new(
13 (0..JOINTS)
14 .map(|i| Joint {
15 name: format!("joint_{i}"),
16 parent: i.checked_sub(1),
17 rest: Transform3D::at(Vec3::Y * if i == 0 { 0.0 } else { STEP }),
18 })
19 .collect(),
20 )
21}
22fn tube(bottom: f32, top: f32, radius: f32) -> MeshData {
27 const SLICES: usize = 12;
28 const ROWS: usize = 28;
29 let mut data = MeshData::default();
30 for row in 0..=ROWS {
31 let y = bottom + (top - bottom) * row as f32 / ROWS as f32;
32 let along = (y / STEP).clamp(0.0, (JOINTS - 1) as f32);
33 let lower = (along.floor() as usize).min(JOINTS - 2);
34 let blend = along - lower as f32;
35 for slice in 0..SLICES {
36 let angle = std::f32::consts::TAU * slice as f32 / SLICES as f32;
37 data.positions.push(vec3(radius * angle.cos(), y, radius * angle.sin()));
38 data.joints.push([lower as u16, (lower + 1) as u16, 0, 0]);
39 data.weights.push([1.0 - blend, blend, 0.0, 0.0]);
40 }
41 }
42 for row in 0..ROWS {
43 for slice in 0..SLICES {
44 let a = (row * SLICES + slice) as u32;
45 let b = (row * SLICES + (slice + 1) % SLICES) as u32;
46 let c = a + SLICES as u32;
47 let d = b + SLICES as u32;
48 data.indices.extend([a, c, b, b, c, d]);
49 }
50 }
51 for slice in 1..SLICES - 1 {
52 let last = (ROWS * SLICES) as u32;
53 data.indices.extend([0, slice as u32, (slice + 1) as u32]);
54 data.indices
55 .extend([last, last + (slice + 1) as u32, last + slice as u32]);
56 }
57 data
58}
59fn sway(ctx: &mut Ctx) {
63 let time = ctx.time() as f32;
64 ctx.world.query::<(&mut Skeleton, &Sway)>().for_each(|(s, phase)| {
65 for (i, pose) in s.pose.iter_mut().enumerate() {
66 pose.rotation = Quat::from_rotation_z((time * 1.5 + phase.0 - i as f32 * 0.45).sin() * 0.16);
67 }
68 });
69 ctx.world.query::<(&mut Skeleton, &Bend)>().for_each(|(s, bend)| {
70 bend.0.sample_blended(time.rem_euclid(bend.0.duration()), 0.5, s);
71 });
72 ctx.world.query::<&mut Animator>().for_each(|a| {
73 a.set_bool(0, ((time / 3.0) as u32).is_multiple_of(2));
74 });
75 if ctx.key_pressed(Key::Escape) {
76 ctx.quit();
77 }
78}
79#[allow(dead_code)]
83fn load_character(ctx: &mut Ctx) {
84 let mesh = ctx.load_mesh("models/character.glb");
86 let skeleton = ctx.load_skeleton("models/character.glb");
87 let hand = skeleton.find("hand_r").expect("model needs hand_r");
88 let character = ctx.world.spawn((Transform3D::default(), Model::new(mesh), skeleton));
89 let skeleton = ctx.world.get_mut::<Skeleton>(character).unwrap();
90 skeleton.pose[hand].rotation = Quat::from_rotation_y(0.3);
91 let local_hand = skeleton.joint_matrix(hand);
92 let transform = ctx.world.get::<Transform3D>(character).unwrap();
93 let world_hand = (transform.matrix() * local_hand).transform_point3(Vec3::ZERO);
94 println!("Hand at {world_hand}");
95 }
97
98fn bend_clip(skeleton: &Skeleton) -> AnimationClip {
100 let rotation = AnimationTrack::new(
101 vec![0.0, 1.0, 2.0],
102 [0.0, 0.3, 0.0]
103 .map(|angle| Vec4::from_array(Quat::from_rotation_z(angle).to_array()))
104 .to_vec(),
105 Interpolation::Linear,
106 );
107 AnimationClip::new(
108 "bend",
109 skeleton,
110 vec![JointAnimation {
111 joint: 1,
112 name: "joint_1".into(),
113 translation: None,
114 rotation: Some(rotation),
115 scale: None,
116 }],
117 )
118}
119#[allow(dead_code)]
122fn play_clips(ctx: &mut Ctx) {
123 let mut skeleton = ctx.load_skeleton("models/character.glb");
125 let walk = ctx.load_clip("models/character.glb", "walk");
126 let run = ctx.load_clip("models/character.glb", "run");
127 let time = ctx.time() as f32;
128 let loop_time = |clip: &AnimationClip| {
129 if clip.duration() > 0.0 {
130 time.rem_euclid(clip.duration())
131 } else {
132 0.0
133 }
134 };
135 skeleton.reset_pose();
136 walk.sample(loop_time(&walk), &mut skeleton);
137 run.sample_blended(loop_time(&run), 0.5, &mut skeleton);
138 }
141
142fn bend_animator(skeleton: &Skeleton) -> Animator {
144 let idle = AnimationClip::new("idle", skeleton, vec![]);
145 let bend = bend_clip(skeleton);
146 let states = vec![
147 AnimationState::new(Motion::Clip(idle)),
148 AnimationState::new(Motion::Clip(bend)),
149 ];
150 let transition = |from, to, active| AnimationTransition {
151 from: Some(from),
152 to,
153 conditions: vec![AnimationCondition::Bool(0, active)],
154 exit_time: None,
155 duration: 0.12,
156 offset: 0.0,
157 };
158 let graph = AnimationGraph::new(
159 vec![AnimationParameter::Bool(false)],
160 states,
161 vec![transition(0, 1, true), transition(1, 0, false)],
162 0,
163 );
164 Animator::new(graph)
165}
166#[allow(dead_code)]
169fn animate_now() {
170 let mut skeleton = chain();
172 let mut animator = bend_animator(&skeleton);
173 animator.manual = true;
174 animator.set_bool(0, true);
175 animator.update(0.016, &mut skeleton);
176 animator.crossfade(0, 0.12, 0.0);
178 animator.update(0.016, &mut skeleton);
179 for name in animator.drain_events() {
180 println!("Animation event: {name}");
181 }
182 }
184
185#[allow(dead_code)]
186fn layers_and_root_motion(skeleton: &Skeleton, wave: AnimationClip) -> Animator {
187 let mut animator = bend_animator(skeleton);
189 let waving = AnimationGraph::new(vec![], vec![AnimationState::new(Motion::Clip(wave))], vec![], 0);
190 let mut layer = AnimationLayer::new(waving);
191 layer.mask = JointMask::descendants(skeleton, "joint_3"); layer.weight = 0.8;
193 let wave = animator.add_layer(layer);
194 animator.layer_mut(wave).weight = 0.5; animator.root_joint = 0;
197 animator.apply_root_motion = true;
198 animator
200}
201
202fn main() {
203 Game::new("Crabby: skinned meshes", 1000, 700).system(sway).run(|ctx| {
204 ctx.clear(Color::hex(0xABCBDD));
205 ctx.camera3d.position = vec3(6.0, 4.0, 9.0);
206 ctx.camera3d.look_at(vec3(0.0, 1.7, 0.0));
207 let green = ctx.create_material(MaterialDesc::color(Color::hex(0x398F79)));
208 let gold = ctx.create_material(MaterialDesc::color(Color::hex(0xF2BD50)));
209 let white = ctx.default_material();
210 let ground = ctx.create_mesh(&MeshData::plane(20.0), white);
211 ctx.world.spawn((Transform3D::default(), Model::new(ground)));
212 let skeleton = chain();
214 let mesh = ctx.create_skinned_mesh(&tube(0.0, 3.5, 0.22), green, &skeleton);
215 let band = ctx.create_skinned_mesh(&tube(2.2, 2.5, 0.25), gold, &chain());
217 let ball = ctx.create_mesh(&MeshData::sphere(0.18, 16), gold);
219 let bend = bend_clip(&skeleton);
220 for i in 0..3 {
221 let tentacle = ctx.world.spawn((
222 Transform3D::at(vec3((i as f32 - 1.0) * 2.5, 0.0, 0.0)),
223 Model::new(mesh),
224 skeleton.clone(),
225 Sway(i as f32 * 1.8),
226 Bend(bend.clone()),
227 ));
228 if i == 2 {
229 ctx.world.insert(tentacle, bend_animator(&skeleton));
230 }
231 if i == 0 {
232 ctx.world.spawn((Model::new(band), SkinOf(tentacle)));
234 let tip = skeleton.find("joint_7").unwrap();
235 ctx.world.spawn((
236 Transform3D::default(),
237 Model::new(ball),
238 AttachToJoint {
239 skeleton: tentacle,
240 joint: tip,
241 offset: Transform3D::at(vec3(0.0, 0.12, 0.0)),
242 },
243 ));
244 }
246 }
247 });
248}
249
250#[allow(dead_code)]
253fn procedural_pose(ctx: &mut Ctx) {
254 let target = vec3(0.7, 0.6, 0.2);
255 ctx.world
256 .query::<(&Transform3D, &mut Skeleton)>()
257 .for_each(|(world, skeleton)| {
258 two_bone_ik(skeleton, world, [0, 1, 2], target, Vec3::Z, 1.0);
259 look_at_joint(skeleton, world, 5, Vec3::Y, target, 0.3, 0.2);
261 look_at_joint(skeleton, world, 6, Vec3::Y, target, 0.5, 0.5);
262 look_at_joint(skeleton, world, 7, Vec3::Y, target, 0.8, 1.0);
263 });
264}
265
266#[allow(dead_code)]
267fn debug_bones(ctx: &mut Ctx, character: Entity) {
268 ctx.draw_skeleton(character, Color::RED);
269}
270