1use std::collections::{BTreeMap, BTreeSet};
10
11use serde::Serialize;
12use serde_json::Value;
13
14use crate::frame::SourceFrame;
15use crate::{Component, Entity, JsonError, Transform, World};
16
17#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize)]
19#[serde(transparent)]
20pub struct JointId(pub u32);
21
22#[derive(Clone, Copy, Debug, PartialEq, Serialize)]
25pub enum Rest {
26 Affine(Transform),
28 Trs {
31 translation: [f32; 3],
32 rotation: [f32; 4],
33 scale: [f32; 3],
34 },
35}
36
37impl Rest {
38 pub fn matrix(&self) -> Transform {
40 match *self {
41 Rest::Affine(m) => m,
42 Rest::Trs {
43 translation,
44 rotation,
45 scale,
46 } => {
47 let [x, y, z, w] = rotation.map(f64::from);
48 let norm = (x * x + y * y + z * z + w * w).sqrt();
53 let [x, y, z, w] = if norm > 0.0 && norm.is_finite() {
54 [x / norm, y / norm, z / norm, w / norm]
55 } else {
56 [x, y, z, w]
57 };
58 let r = [
60 [
61 1.0 - 2.0 * (y * y + z * z),
62 2.0 * (x * y + z * w),
63 2.0 * (x * z - y * w),
64 ],
65 [
66 2.0 * (x * y - z * w),
67 1.0 - 2.0 * (x * x + z * z),
68 2.0 * (y * z + x * w),
69 ],
70 [
71 2.0 * (x * z + y * w),
72 2.0 * (y * z - x * w),
73 1.0 - 2.0 * (x * x + y * y),
74 ],
75 ];
76 let mut cols = [[0.0f32; 3]; 4];
77 for (j, col) in r.iter().enumerate() {
78 cols[j] = col.map(|v| (v * f64::from(scale[j])) as f32);
79 }
80 cols[3] = translation;
81 Transform { cols }
82 }
83 }
84 }
85
86 pub fn converted(&self, frame: &SourceFrame) -> Rest {
92 match *self {
93 Rest::Affine(m) => Rest::Affine(frame.transform(&m)),
94 Rest::Trs {
95 translation,
96 rotation,
97 scale,
98 } => {
99 let v = frame.direction([rotation[0], rotation[1], rotation[2]]);
100 let v = if frame.determinant() < 0 { v.map(|x| -x) } else { v };
101 let signs = frame.direction([1.0; 3]);
103 let d = frame.direction(scale);
104 let s = [d[0] * signs[0], d[1] * signs[1], d[2] * signs[2]];
105 Rest::Trs {
106 translation: frame.point(translation),
107 rotation: [v[0], v[1], v[2], rotation[3]],
108 scale: s,
109 }
110 }
111 }
112 }
113
114 fn values(&self) -> Vec<f32> {
115 match self {
116 Rest::Affine(m) => m.cols.iter().flatten().copied().collect(),
117 Rest::Trs {
118 translation,
119 rotation,
120 scale,
121 } => translation.iter().chain(rotation).chain(scale).copied().collect(),
122 }
123 }
124}
125
126#[derive(Clone, Debug, PartialEq, Serialize)]
128pub struct Joint {
129 pub id: JointId,
130 pub name: String,
132 pub parent: Option<JointId>,
134 pub rest: Rest,
135}
136
137#[derive(Clone, Debug, Default, PartialEq, Serialize)]
139pub struct Skeleton {
140 pub joints: Vec<Joint>,
141}
142
143#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize)]
148pub struct AssemblyReference {
149 pub skeleton: Entity,
150}
151
152impl Component for AssemblyReference {
153 const NAME: &'static str = "core.assembly_reference";
154 const GEOMETRY: crate::Geometry = crate::Geometry::None;
155 fn to_json(&self) -> Result<Option<Value>, JsonError> {
156 crate::json::to_value(self).map(Some)
157 }
158 fn references(&self, out: &mut Vec<Entity>) {
159 out.push(self.skeleton);
160 }
161 fn verify(&self, e: Entity, w: &World) -> Result<(), String> {
162 if !w.has::<Skeleton>(e) || !w.has::<Skeleton>(self.skeleton) {
163 return Err("assembly reference and its part must both hold core.skeleton".into());
164 }
165 if e == self.skeleton {
166 return Err("a part cannot be its own assembly reference".into());
167 }
168 Ok(())
169 }
170}
171
172const UNIT_TOLERANCE: f32 = 1e-3;
175
176impl Skeleton {
177 pub fn index_of(&self, id: JointId) -> Option<usize> {
179 self.joints.iter().position(|j| j.id == id)
180 }
181
182 pub fn converted(&self, frame: &SourceFrame) -> Skeleton {
185 Skeleton {
186 joints: self
187 .joints
188 .iter()
189 .map(|j| Joint {
190 rest: j.rest.converted(frame),
191 ..j.clone()
192 })
193 .collect(),
194 }
195 }
196}
197
198impl Component for Skeleton {
199 const NAME: &'static str = "core.skeleton";
200 const GEOMETRY: crate::Geometry = crate::Geometry::None;
202 fn to_json(&self) -> Result<Option<Value>, JsonError> {
203 crate::json::to_value(self).map(Some)
204 }
205 fn verify(&self, _e: Entity, _w: &World) -> Result<(), String> {
208 let mut parents = BTreeMap::new();
209 for j in &self.joints {
210 if parents.insert(j.id, j.parent).is_some() {
211 return Err(format!("joint {:?} listed twice", j.id));
212 }
213 if let Some(i) = j.rest.values().iter().position(|x| !x.is_finite()) {
214 return Err(format!("joint {:?}: rest value {i} is not finite", j.id));
215 }
216 if let Rest::Trs { rotation, .. } = j.rest {
217 let len = rotation.iter().map(|x| x * x).sum::<f32>().sqrt();
218 if (len - 1.0).abs() > UNIT_TOLERANCE {
219 return Err(format!(
220 "joint {:?}: rotation of length {len}, not a unit quaternion",
221 j.id
222 ));
223 }
224 }
225 }
226 for j in &self.joints {
227 match j.parent {
228 Some(p) if p == j.id => return Err(format!("joint {:?} is its own parent", j.id)),
229 Some(p) if !parents.contains_key(&p) => {
230 return Err(format!(
231 "joint {:?}: parent {p:?} is not a joint of the skeleton",
232 j.id
233 ));
234 }
235 _ => {}
236 }
237 let mut seen = BTreeSet::from([j.id]);
239 let mut at = j.parent;
240 while let Some(p) = at {
241 if !seen.insert(p) {
242 return Err(format!("joint {:?}: its parents form a cycle", j.id));
243 }
244 at = parents.get(&p).copied().flatten();
245 }
246 }
247 Ok(())
248 }
249}
250
251#[cfg(test)]
252mod tests {
253 use super::*;
254 use crate::frame::Axis;
255
256 const S: f32 = std::f32::consts::FRAC_1_SQRT_2;
257
258 fn joint(id: u32, parent: Option<u32>, rest: Rest) -> Joint {
259 Joint {
260 id: JointId(id),
261 name: format!("j{id}"),
262 parent: parent.map(JointId),
263 rest,
264 }
265 }
266
267 fn trs(translation: [f32; 3], rotation: [f32; 4], scale: [f32; 3]) -> Rest {
268 Rest::Trs {
269 translation,
270 rotation,
271 scale,
272 }
273 }
274
275 fn errors(s: Skeleton) -> Vec<String> {
276 let mut w = World::new();
277 let e = w.spawn();
278 w.insert(e, s);
279 w.verify_components().into_iter().map(|(_, _, m)| m).collect()
280 }
281
282 #[test]
285 fn trs_composes_translation_rotation_scale() {
286 let m = trs([1.0, 2.0, 3.0], [0.0, 0.0, S, S], [2.0, 1.0, 1.0]).matrix();
287 let near = |a: [f32; 3], b: [f32; 3]| a.iter().zip(b).all(|(x, y)| (x - y).abs() < 1e-6);
288 assert!(near(m.cols[0], [0.0, 2.0, 0.0]), "{m:?}");
289 assert!(near(m.cols[1], [-1.0, 0.0, 0.0]), "{m:?}");
290 assert!(near(m.cols[2], [0.0, 0.0, 1.0]), "{m:?}");
291 assert_eq!(m.cols[3], [1.0, 2.0, 3.0]);
292 }
293
294 #[test]
298 fn the_matrix_of_a_quaternion_rounded_to_f32_has_columns_of_the_scale_stored() {
299 let m = trs([0.0; 3], [0.0, 0.0, S, S], [1.0, 1.0, 1.0]).matrix();
300 for c in &m.cols[..3] {
301 let len = c.iter().map(|v| f64::from(*v).powi(2)).sum::<f64>().sqrt();
302 assert!((len - 1.0).abs() < 1e-9, "{c:?} has length {len}");
303 }
304 let scaled = trs([0.0; 3], [0.0, 0.0, S, S], [2.0, 1.0, 3.0]).matrix();
305 for (c, scale) in scaled.cols[..3].iter().zip([2.0, 1.0, 3.0]) {
306 let len = c.iter().map(|v| f64::from(*v).powi(2)).sum::<f64>().sqrt();
307 assert!((len - scale).abs() < 1e-9, "{c:?} has length {len}, not {scale}");
308 }
309 }
310
311 #[test]
319 fn frame_conversion_is_exact_for_proper_and_mirroring_frames() {
320 let enu = SourceFrame::ENU;
321 let r = trs([1.0, 2.0, 3.0], [0.0, 0.0, S, S], [1.0, 2.0, 3.0]).converted(&enu);
322 assert_eq!(r, trs([1.0, 3.0, -2.0], [0.0, S, 0.0, S], [1.0, 3.0, 2.0]));
323 let mirror = SourceFrame::new([Axis::PosX, Axis::PosY, Axis::NegZ], 1.0);
324 assert_eq!(
325 trs([0.0; 3], [S, 0.0, 0.0, S], [1.0; 3]).converted(&mirror),
326 trs([0.0; 3], [-S, 0.0, 0.0, S], [1.0; 3])
327 );
328 assert_eq!(
329 trs([0.0; 3], [0.0, 0.0, S, S], [1.0; 3]).converted(&mirror),
330 trs([0.0; 3], [0.0, 0.0, S, S], [1.0; 3])
331 );
332 let mirrored = trs([0.0; 3], [0.0, 0.0, 0.0, 1.0], [-2.0, 3.0, 4.0]);
335 assert_eq!(mirrored.converted(&SourceFrame::WORLD), mirrored);
336 assert_eq!(
337 mirrored.converted(&enu),
338 trs([0.0; 3], [0.0, 0.0, 0.0, 1.0], [-2.0, 4.0, 3.0])
339 );
340 for frame in [SourceFrame::WORLD, enu, mirror] {
341 for rest in [
342 trs([1.0, 2.0, 3.0], [0.0, 0.0, S, S], [1.0, 2.0, 3.0]),
343 trs([0.5, -1.0, 2.0], [S, 0.0, 0.0, S], [2.0, 1.0, 1.0]),
344 trs([0.5, -1.0, 2.0], [S, 0.0, 0.0, S], [-2.0, 3.0, 4.0]),
345 trs([1.0, 2.0, 3.0], [0.0, 0.0, S, S], [2.0, -1.0, -0.5]),
346 ] {
347 let a = rest.converted(&frame).matrix();
348 let b = frame.transform(&rest.matrix());
349 let near = a
350 .cols
351 .iter()
352 .flatten()
353 .zip(b.cols.iter().flatten())
354 .all(|(x, y)| (x - y).abs() < 1e-6);
355 assert!(near, "{frame:?}: {a:?} vs {b:?}");
356 }
357 }
358 let s = Skeleton {
360 joints: vec![
361 joint(7, None, Rest::Affine(Transform::IDENTITY)),
362 joint(3, Some(7), trs([1.0, 2.0, 3.0], [0.0, 0.0, 0.0, 1.0], [1.0; 3])),
363 ],
364 };
365 let c = s.converted(&enu);
366 assert_eq!(c.joints[0].rest, Rest::Affine(Transform::IDENTITY));
367 assert_eq!(
368 (c.joints[1].id, c.joints[1].parent),
369 (JointId(3), Some(JointId(7)))
370 );
371 assert_eq!(c.index_of(JointId(3)), Some(1));
372 }
373
374 #[test]
377 fn malformed_skeletons_fail_verification() {
378 let id = Rest::Affine(Transform::IDENTITY);
379 let unit = [0.0, 0.0, 0.0, 1.0];
380 assert!(
381 errors(Skeleton {
382 joints: vec![joint(2, Some(1), id), joint(1, None, id)],
383 })
384 .is_empty()
385 );
386 let fails = |joints: Vec<Joint>, what: &str| {
387 let e = errors(Skeleton { joints });
388 assert!(e.iter().any(|m| m.contains(what)), "{what}: {e:?}");
389 };
390 fails(vec![joint(1, None, id), joint(1, None, id)], "listed twice");
391 fails(vec![joint(1, Some(9), id)], "is not a joint");
392 fails(vec![joint(1, Some(1), id)], "its own parent");
393 fails(vec![joint(1, Some(2), id), joint(2, Some(1), id)], "cycle");
394 fails(
395 vec![joint(1, None, trs([f32::NAN, 0.0, 0.0], unit, [1.0; 3]))],
396 "not finite",
397 );
398 fails(
399 vec![joint(1, None, trs([0.0; 3], [0.0, 0.0, 0.0, 2.0], [1.0; 3]))],
400 "not a unit quaternion",
401 );
402 }
403}