1use crate::skeletal::{Clip, SkinnedModel, Trs};
11use glam::{Mat4, Quat, Vec3};
12
13const L: usize = 0;
14const R: usize = 1;
15
16#[derive(Default, Clone, Debug)]
19pub struct HumanoidBones {
20 pub hips: Option<usize>,
22 pub spine: Option<usize>,
24 pub chest: Option<usize>,
26 pub neck: Option<usize>,
28 pub head: Option<usize>,
30 pub upper_arm: [Option<usize>; 2],
32 pub forearm: [Option<usize>; 2],
34 pub hand: [Option<usize>; 2],
36 pub thigh: [Option<usize>; 2],
38 pub shin: [Option<usize>; 2],
40 pub foot: [Option<usize>; 2],
42 pub up: Vec3,
44 pub right: Vec3,
46 pub forward: Vec3,
48}
49
50type Rig = HumanoidBones;
51
52pub fn find_bones(model: &SkinnedModel) -> Option<HumanoidBones> {
55 let mut rig = Rig::of(model)?;
56 let f = frame(model, &rig);
57 (rig.up, rig.right, rig.forward) = (f.up, f.right, f.forward);
58 Some(rig)
59}
60
61fn norm(s: &str) -> String {
62 let s = s.to_lowercase().replace("mixamorig", "");
63 s.chars().filter(|c| c.is_alphanumeric()).collect()
64}
65
66fn find(names: &[String], bases: &[&str]) -> Option<usize> {
67 bases.iter().find_map(|b| names.iter().position(|n| n == b))
68}
69
70fn find_side(names: &[String], bases: &[&str], side: usize) -> Option<usize> {
71 let (short, long) = if side == L { ("l", "left") } else { ("r", "right") };
72 bases.iter().find_map(|b| {
73 names.iter().position(|n| {
74 *n == format!("{b}{short}") || *n == format!("{b}{long}") || *n == format!("{long}{b}") || *n == format!("{short}{b}") || n.ends_with(&format!("{long}{b}"))
75 })
76 })
77}
78
79impl Rig {
80 fn of(model: &SkinnedModel) -> Option<Rig> {
81 let names: Vec<String> = model.nodes.iter().map(|n| norm(&n.name)).collect();
82 let side = |bases: &[&str]| [find_side(&names, bases, L), find_side(&names, bases, R)];
83 let rig = Rig {
84 up: Vec3::Y,
85 right: Vec3::NEG_X,
86 forward: Vec3::Z,
87 hips: find(&names, &["hips", "pelvis", "hip"]),
88 spine: find(&names, &["spine", "spine01", "spine1"]),
89 chest: find(&names, &["chest", "spine2", "spine02", "upperchest", "spine03"]),
90 neck: find(&names, &["neck", "neck01"]),
91 head: find(&names, &["head"]),
92 upper_arm: side(&["upperarm", "arm"]),
93 forearm: side(&["forearm", "lowerarm"]),
94 hand: side(&["hand"]),
95 thigh: side(&["thigh", "upperleg", "upleg"]),
96 shin: side(&["shin", "calf", "lowerleg", "leg"]),
97 foot: side(&["foot"]),
98 };
99 let legs = rig.thigh.iter().chain(&rig.shin).all(Option::is_some);
100 (rig.hips.is_some() && legs).then_some(rig)
101 }
102}
103
104struct Frame {
106 up: Vec3,
107 right: Vec3,
108 forward: Vec3,
109 arm_down: [Quat; 2],
110}
111
112fn pos(model: &SkinnedModel, i: Option<usize>) -> Option<Vec3> {
113 Some(model.nodes.get(i?)?.rest.w_axis.truncate())
114}
115
116fn frame(model: &SkinnedModel, rig: &Rig) -> Frame {
117 let up = Vec3::Y;
118 let across = match (pos(model, rig.upper_arm[R]).zip(pos(model, rig.upper_arm[L])), pos(model, rig.thigh[R]).zip(pos(model, rig.thigh[L]))) {
119 (Some((r, l)), _) => r - l,
120 (None, Some((r, l))) => r - l,
121 _ => Vec3::NEG_X,
122 };
123 let right = (across - up * across.dot(up)).normalize_or(Vec3::NEG_X);
124 let forward = up.cross(right).normalize_or(Vec3::Z);
125 let mut arm_down = [Quat::IDENTITY; 2];
127 for s in [L, R] {
128 if let (Some(a), Some(b)) = (pos(model, rig.upper_arm[s]), pos(model, rig.forearm[s])) {
129 let dir = (b - a).normalize_or_zero();
130 let out = if s == L { -right } else { right };
131 let target = (-up * 12f32.to_radians().cos() + out * 12f32.to_radians().sin() + forward * 0.05).normalize();
132 if dir != Vec3::ZERO {
133 arm_down[s] = Quat::from_rotation_arc(dir, target);
134 }
135 }
136 }
137 Frame { up, right, forward, arm_down }
138}
139
140#[derive(Default)]
142struct Pose {
143 rot: Vec<(usize, Quat)>,
144 hips_offset: Vec3,
145}
146
147impl Pose {
148 fn set(&mut self, bone: Option<usize>, q: Quat) {
149 if let Some(b) = bone {
150 self.rot.push((b, q));
151 }
152 }
153}
154
155fn deg(axis: Vec3, d: f32) -> Quat {
156 Quat::from_axis_angle(axis, d.to_radians())
157}
158
159const TAU: f32 = std::f32::consts::TAU;
160
161fn pose(name: &str, t: f32, rig: &Rig, f: &Frame) -> Pose {
163 let mut p = Pose::default();
164 let (rt, up, fw) = (f.right, f.up, f.forward);
165 let swing = |d: f32| deg(rt, d);
167 let turn = |d: f32| deg(up, d);
168 let roll = |d: f32| deg(fw, d);
169 let breathe = (TAU * t / 4.0).sin();
170 let arms_idle = |p: &mut Pose, sway: f32| {
171 for s in [L, R] {
172 p.set(rig.upper_arm[s], swing(2.0 * sway) * f.arm_down[s]);
173 p.set(rig.forearm[s], swing(10.0));
174 }
175 };
176 match name {
177 "Idle" => {
178 p.set(rig.chest, swing(-1.5 * breathe));
179 p.set(rig.spine, roll(1.2 * (TAU * t / 8.0).sin()));
180 p.set(rig.head, turn(8.0 * (TAU * t / 8.0).sin()) * swing(-3.0 + 2.0 * (TAU * t / 5.0).sin()));
181 arms_idle(&mut p, breathe);
182 p.hips_offset = up * (-0.004 * breathe);
183 }
184 "Walk" | "Run" => {
185 let run = name == "Run";
186 let (period, k) = if run { (0.7, 1.6) } else { (1.1, 1.0) };
187 let ph = TAU * t / period;
188 let s = ph.sin();
189 let c = ph.cos();
190 p.set(rig.thigh[L], swing(24.0 * k * s));
191 p.set(rig.thigh[R], swing(-24.0 * k * s));
192 p.set(rig.shin[L], swing(-(8.0 + 32.0 * k * c.max(0.0))));
193 p.set(rig.shin[R], swing(-(8.0 + 32.0 * k * (-c).max(0.0))));
194 p.set(rig.foot[L], swing(8.0 * s));
195 p.set(rig.foot[R], swing(-8.0 * s));
196 p.set(rig.upper_arm[L], swing(-18.0 * k * s) * f.arm_down[L]);
197 p.set(rig.upper_arm[R], swing(18.0 * k * s) * f.arm_down[R]);
198 p.set(rig.forearm[L], swing(15.0 * k));
199 p.set(rig.forearm[R], swing(15.0 * k));
200 p.set(rig.hips, turn(5.0 * s));
201 p.set(rig.spine, swing(if run { -8.0 } else { -2.0 }));
202 p.set(rig.chest, turn(-7.0 * s));
203 p.set(rig.head, turn(2.0 * s));
204 p.hips_offset = up * (0.022 * k * (2.0 * ph).cos());
205 }
206 "Talk" => {
207 let g = (TAU * t * 0.9).sin();
208 p.set(rig.chest, swing(-1.5 * breathe));
209 p.set(rig.head, swing(-4.0 * (TAU * t * 1.3).sin()) * turn(6.0 * (TAU * t / 3.0).sin()));
210 p.set(rig.neck, swing(-2.0 * (TAU * t * 1.3).sin()));
211 p.set(rig.upper_arm[L], f.arm_down[L]);
212 p.set(rig.forearm[L], swing(12.0));
213 p.set(rig.upper_arm[R], swing(18.0 + 6.0 * g) * f.arm_down[R]);
214 p.set(rig.forearm[R], swing(40.0 + 18.0 * g) * turn(-10.0 * g));
215 p.set(rig.hand[R], roll(15.0 * g));
216 }
217 "Wave" => {
218 let w = (TAU * t * 1.5).sin();
219 p.set(rig.upper_arm[L], f.arm_down[L]);
220 p.set(rig.forearm[L], swing(10.0));
221 p.set(rig.upper_arm[R], roll(-150.0) * f.arm_down[R]);
223 p.set(rig.forearm[R], roll(25.0 * w));
224 p.set(rig.head, turn(-6.0));
225 p.set(rig.chest, swing(-1.0 * breathe));
226 }
227 "Sit" => {
228 for s in [L, R] {
229 p.set(rig.thigh[s], swing(88.0));
230 p.set(rig.shin[s], swing(-88.0));
231 p.set(rig.upper_arm[s], swing(32.0) * f.arm_down[s]);
232 p.set(rig.forearm[s], swing(48.0));
233 }
234 p.set(rig.spine, swing(-4.0));
235 p.set(rig.chest, swing(-1.5 * breathe));
236 p.set(rig.head, swing(-6.0) * turn(5.0 * (TAU * t / 6.0).sin()));
237 }
238 "Wipe" => {
239 let w = TAU * t / 1.2;
240 p.set(rig.spine, swing(-14.0));
241 p.set(rig.chest, swing(-6.0) * turn(4.0 * w.sin()));
242 p.set(rig.head, swing(-16.0));
243 p.set(rig.upper_arm[R], turn(16.0 * w.sin()) * swing(50.0 + 8.0 * w.cos()) * f.arm_down[R]);
244 p.set(rig.forearm[R], swing(55.0 + 10.0 * w.sin()));
245 p.set(rig.upper_arm[L], swing(35.0) * f.arm_down[L]);
246 p.set(rig.forearm[L], swing(60.0));
247 }
248 "Look" => {
249 let a = (TAU * t / 6.0).sin();
250 p.set(rig.chest, turn(10.0 * a) * swing(-1.5 * breathe));
251 p.set(rig.head, turn(35.0 * a) * swing(-2.0));
252 arms_idle(&mut p, breathe);
253 }
254 _ => {}
255 }
256 p
257}
258
259fn evaluate(model: &SkinnedModel, order: &[usize], rest_local: &[Mat4], rig: &Rig, p: &Pose) -> Vec<Trs> {
261 let n = model.nodes.len();
262 let mut delta = vec![Quat::IDENTITY; n];
263 for (b, q) in &p.rot {
264 delta[*b] = *q * delta[*b];
265 }
266 let mut world = vec![Mat4::IDENTITY; n];
267 for &i in order {
268 let rest_rot = Trs::from_mat(model.nodes[i].rest).r;
269 let local = Mat4::from_quat(rest_rot.inverse() * delta[i] * rest_rot);
270 let parent = model.nodes[i].parent.map_or(Mat4::IDENTITY, |p| world[p]);
271 world[i] = parent * rest_local[i] * local;
272 if Some(i) == rig.hips {
273 world[i] = Mat4::from_translation(p.hips_offset) * world[i];
274 }
275 }
276 world.into_iter().map(Trs::from_mat).collect()
277}
278
279pub fn add_clips(model: &mut SkinnedModel) {
281 let Some(rig) = Rig::of(model) else { return };
282 let f = frame(model, &rig);
283 let depth = |mut i: usize| {
285 let mut d = 0;
286 while let Some(p) = model.nodes[i].parent {
287 i = p;
288 d += 1;
289 if d > 128 {
290 break;
291 }
292 }
293 d
294 };
295 let mut order: Vec<usize> = (0..model.nodes.len()).collect();
296 order.sort_by_key(|&i| depth(i));
297 let rest_local: Vec<Mat4> = model.nodes.iter().map(|n| n.parent.map_or(n.rest, |p| model.nodes[p].rest.inverse() * n.rest)).collect();
298 let fps = 30.0;
299 for (name, duration) in [("Idle", 8.0), ("Walk", 1.1), ("Run", 0.7), ("Talk", 6.0), ("Wave", 2.0), ("Sit", 6.0), ("Wipe", 2.4), ("Look", 6.0)] {
300 if model.clip(name).is_some() {
301 continue;
302 }
303 let count = (duration * fps) as usize + 1;
304 let frames = (0..count).map(|k| evaluate(model, &order, &rest_local, &rig, &pose(name, k as f32 / fps, &rig, &f))).collect();
305 model.clips.push(Clip { name: name.to_string(), duration, fps, frames });
306 }
307}