Skip to main content

primase_core/
rig.rs

1//! Rig occurrences: a skeleton placed and posed, and the meshes, weights and objects that follow it. Definitions
2//! (`core.skeleton`, `core.skin`, `core.morphs`, `core.sockets`) are shared; an occurrence holds what is its own, so
3//! two placed characters sharing one mesh, skin and clips never share a pose.
4//!
5//! - A **rig** is an entity with [`RigInstance`] (which skeleton) and `core.transform` (the rig's space in the world,
6//!   flat like any placement); its [`RigPose`], when it has one, is its joints' local transforms (an authored pose or
7//!   one a pass evaluated), in place of the skeleton's rest.
8//! - A **skinned occurrence** is a placement (or an entity drawing its own mesh) with [`SkinInstance`]: the rig it
9//!   follows and the binding of its mesh's skin it is drawn with.
10//! - [`MorphWeights`] are an occurrence's weights of its mesh's morph targets, with or without a rig.
11//! - An [`Attachment`] places an object at a socket of a rig ([`Sockets`], on the skeleton's entity).
12//!
13//! Nothing here evaluates a pose: deforming a mesh by its rig is a pass's work (toolkit math), so a writer that can't
14//! skin is given geometry a pass has posed.
15
16use std::collections::BTreeSet;
17
18use serde::Serialize;
19use serde_json::Value;
20
21use crate::morphs::{MorphId, Morphs};
22use crate::skeleton::{JointId, Rest, Skeleton};
23use crate::skin::{BindingId, Skin};
24use crate::{Component, Entity, Instance, JsonError, Transform, World};
25
26/// `core.rig_instance`: a placed skeleton (see the module documentation).
27#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize)]
28pub struct RigInstance {
29    /// The entity holding the `core.skeleton`.
30    pub skeleton: Entity,
31}
32
33/// `core.rig_pose`: a rig's joints' local transforms, every joint of its skeleton once, each in its parent's space as
34/// the skeleton's rests are.
35///
36/// `root_applied` is the root motion a pass has already moved the rig's `core.transform` by (`placed * root_applied`
37/// is the transform): a pass that applies root motion again takes that one off first, so repeating it, or moving to
38/// another time, leaves the rig where its own placement and the new root motion put it. A rig moved by anything else
39/// stays as it was moved.
40#[derive(Clone, Debug, PartialEq, Serialize)]
41pub struct RigPose {
42    pub joints: Vec<(JointId, Rest)>,
43    #[serde(skip_serializing_if = "Option::is_none")]
44    pub root_applied: Option<Transform>,
45}
46
47/// `core.skin_instance`: the rig a mesh occurrence follows and the binding of its mesh's skin it is drawn with.
48#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize)]
49pub struct SkinInstance {
50    /// The rig entity ([`RigInstance`]).
51    pub rig: Entity,
52    pub binding: BindingId,
53}
54
55/// `core.morph_weights`: an occurrence's weights of its mesh's morph targets, each target at most once; a target not
56/// listed is at its default weight.
57#[derive(Clone, Debug, Default, PartialEq, Serialize)]
58pub struct MorphWeights(pub Vec<(MorphId, f32)>);
59
60/// A socket's identity within its skeleton's sockets.
61#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize)]
62#[serde(transparent)]
63pub struct SocketId(pub u32);
64
65/// A named place on a joint where things attach (a hand's grip, a sheath).
66#[derive(Clone, Debug, PartialEq, Serialize)]
67pub struct Socket {
68    pub id: SocketId,
69    /// The source's name: provenance, not identity.
70    pub name: String,
71    pub joint: JointId,
72    /// The socket's space in its joint's: `joint = M * socket`.
73    pub offset: Transform,
74}
75
76/// `core.sockets`, on the entity holding the `core.skeleton` they belong to.
77#[derive(Clone, Debug, Default, PartialEq, Serialize)]
78pub struct Sockets(pub Vec<Socket>);
79
80/// `core.attachment`: the object this placement draws follows a rig's socket, placed at `offset` in the socket's
81/// space. Its `core.transform` stays a flat placement (where the object is at the rig's pose as read); a pass that
82/// poses the rig moves it.
83#[derive(Clone, Copy, Debug, PartialEq, Serialize)]
84pub struct Attachment {
85    /// The rig entity ([`RigInstance`]).
86    pub rig: Entity,
87    pub socket: SocketId,
88    pub offset: Transform,
89}
90
91fn finite(m: &Transform) -> bool {
92    m.cols.iter().flatten().all(|x| x.is_finite())
93}
94
95/// The skeleton of the rig entity `rig`, or why there is none.
96fn rig_skeleton(w: &World, rig: Entity) -> Result<&Skeleton, String> {
97    let r = w
98        .get::<RigInstance>(rig)
99        .ok_or_else(|| format!("{rig:?} is not a rig (no core.rig_instance)"))?;
100    w.get::<Skeleton>(r.skeleton)
101        .ok_or_else(|| format!("rig {rig:?}'s skeleton {:?} holds no core.skeleton", r.skeleton))
102}
103
104/// The entity whose mesh an occurrence draws: its prototype's, or its own when it draws one.
105fn drawn(w: &World, e: Entity) -> Entity {
106    w.get::<Instance>(e).map_or(e, |i| i.prototype)
107}
108
109impl Component for RigInstance {
110    const NAME: &'static str = "core.rig_instance";
111    const GEOMETRY: crate::Geometry = crate::Geometry::None;
112    fn to_json(&self) -> Result<Option<Value>, JsonError> {
113        crate::json::to_value(self).map(Some)
114    }
115    fn references(&self, out: &mut Vec<Entity>) {
116        out.push(self.skeleton);
117    }
118    /// Placed (`core.transform`), its skeleton a `core.skeleton`.
119    fn verify(&self, e: Entity, w: &World) -> Result<(), String> {
120        if !w.has::<Transform>(e) {
121            return Err("a rig without a core.transform".into());
122        }
123        rig_skeleton(w, e).map(|_| ())
124    }
125}
126
127impl Component for RigPose {
128    const NAME: &'static str = "core.rig_pose";
129    const GEOMETRY: crate::Geometry = crate::Geometry::None;
130    fn to_json(&self) -> Result<Option<Value>, JsonError> {
131        crate::json::to_value(self).map(Some)
132    }
133    /// On a rig; every joint of its skeleton exactly once; every transform finite, the applied root motion too.
134    fn verify(&self, e: Entity, w: &World) -> Result<(), String> {
135        let skeleton = rig_skeleton(w, e)?;
136        if self.root_applied.as_ref().is_some_and(|m| !finite(m)) {
137            return Err("the applied root motion is not finite".into());
138        }
139        let mut seen = BTreeSet::new();
140        for (j, rest) in &self.joints {
141            if skeleton.index_of(*j).is_none() {
142                return Err(format!("joint {j:?} is not a joint of the rig's skeleton"));
143            }
144            if !seen.insert(*j) {
145                return Err(format!("joint {j:?} posed twice"));
146            }
147            if !finite(&rest.matrix()) {
148                return Err(format!("joint {j:?}: a transform not finite"));
149            }
150        }
151        if seen.len() != skeleton.joints.len() {
152            return Err(format!(
153                "{} of the skeleton's {} joints posed",
154                seen.len(),
155                skeleton.joints.len()
156            ));
157        }
158        Ok(())
159    }
160}
161
162impl Component for SkinInstance {
163    const NAME: &'static str = "core.skin_instance";
164    const GEOMETRY: crate::Geometry = crate::Geometry::None;
165    fn to_json(&self) -> Result<Option<Value>, JsonError> {
166        crate::json::to_value(self).map(Some)
167    }
168    fn references(&self, out: &mut Vec<Entity>) {
169        out.push(self.rig);
170    }
171    /// The occurrence draws a skinned mesh (its prototype's `core.skin`, or its own) with this binding, and the binding
172    /// binds to the rig's skeleton.
173    fn verify(&self, e: Entity, w: &World) -> Result<(), String> {
174        let geometry = drawn(w, e);
175        let skin = w
176            .get::<Skin>(geometry)
177            .ok_or_else(|| format!("{geometry:?}, which it draws, has no core.skin"))?;
178        let binding = skin
179            .binding(self.binding)
180            .ok_or_else(|| format!("binding {:?} is not one of {geometry:?}'s skin", self.binding))?;
181        rig_skeleton(w, self.rig)?;
182        let rig_skeleton_entity = w.get::<RigInstance>(self.rig).map(|r| r.skeleton);
183        if rig_skeleton_entity != Some(binding.skeleton) {
184            return Err(format!(
185                "binding {:?} binds to {:?}, the rig poses {rig_skeleton_entity:?}",
186                self.binding, binding.skeleton
187            ));
188        }
189        Ok(())
190    }
191}
192
193impl Component for MorphWeights {
194    const NAME: &'static str = "core.morph_weights";
195    const GEOMETRY: crate::Geometry = crate::Geometry::None;
196    fn to_json(&self) -> Result<Option<Value>, JsonError> {
197        crate::json::to_value(self).map(Some)
198    }
199    /// The occurrence draws a mesh with `core.morphs` holding each target listed, once, at a finite weight.
200    fn verify(&self, e: Entity, w: &World) -> Result<(), String> {
201        let geometry = drawn(w, e);
202        let morphs = w
203            .get::<Morphs>(geometry)
204            .ok_or_else(|| format!("{geometry:?}, which it draws, has no core.morphs"))?;
205        let mut seen = BTreeSet::new();
206        for (id, weight) in &self.0 {
207            if morphs.target(*id).is_none() {
208                return Err(format!("target {id:?} is not one of {geometry:?}'s morphs"));
209            }
210            if !seen.insert(*id) || !weight.is_finite() {
211                return Err(format!("target {id:?} listed twice, or its weight not finite"));
212            }
213        }
214        Ok(())
215    }
216}
217
218impl Component for Sockets {
219    const NAME: &'static str = "core.sockets";
220    const GEOMETRY: crate::Geometry = crate::Geometry::None;
221    fn to_json(&self) -> Result<Option<Value>, JsonError> {
222        crate::json::to_value(self).map(Some)
223    }
224    /// Beside a `core.skeleton` holding each socket's joint; ids unique; offsets finite.
225    fn verify(&self, e: Entity, w: &World) -> Result<(), String> {
226        let skeleton = w
227            .get::<Skeleton>(e)
228            .ok_or("sockets on an entity without a core.skeleton")?;
229        let mut ids = BTreeSet::new();
230        for s in &self.0 {
231            if !ids.insert(s.id) {
232                return Err(format!("socket {:?} listed twice", s.id));
233            }
234            if skeleton.index_of(s.joint).is_none() {
235                return Err(format!(
236                    "socket {:?}'s joint {:?} is not in the skeleton",
237                    s.id, s.joint
238                ));
239            }
240            if !finite(&s.offset) {
241                return Err(format!("socket {:?}: offset not finite", s.id));
242            }
243        }
244        Ok(())
245    }
246}
247
248impl Component for Attachment {
249    const NAME: &'static str = "core.attachment";
250    const GEOMETRY: crate::Geometry = crate::Geometry::None;
251    fn to_json(&self) -> Result<Option<Value>, JsonError> {
252        crate::json::to_value(self).map(Some)
253    }
254    fn references(&self, out: &mut Vec<Entity>) {
255        out.push(self.rig);
256    }
257    /// Placed (`core.transform`); the rig's skeleton has the socket; the offset finite.
258    fn verify(&self, e: Entity, w: &World) -> Result<(), String> {
259        if !w.has::<Transform>(e) {
260            return Err("an attachment without a core.transform".into());
261        }
262        rig_skeleton(w, self.rig)?;
263        let skeleton = w.get::<RigInstance>(self.rig).map(|r| r.skeleton);
264        let has = skeleton
265            .and_then(|s| w.get::<Sockets>(s))
266            .is_some_and(|s| s.0.iter().any(|k| k.id == self.socket));
267        if !has {
268            return Err(format!(
269                "socket {:?} is not one of the rig's skeleton's",
270                self.socket
271            ));
272        }
273        if !finite(&self.offset) {
274            return Err("offset not finite".into());
275        }
276        Ok(())
277    }
278}
279
280/// `core.joint_map`, on an entity of its own: which joint of skeleton `to` each listed joint of skeleton `from` is, as a
281/// source states it (a table, or a correspondence a pass proved). Each joint at most once on each side; joints of either
282/// skeleton not listed are unmapped (a pass using the map states what it does with them). Names are not evidence: a map
283/// a pass builds by name states what else it checked.
284#[derive(Clone, Debug, PartialEq, Serialize)]
285pub struct JointMap {
286    /// The entity holding the source `core.skeleton`.
287    pub from: Entity,
288    /// The entity holding the target `core.skeleton`.
289    pub to: Entity,
290    pub pairs: Vec<(JointId, JointId)>,
291}
292
293impl Component for JointMap {
294    const NAME: &'static str = "core.joint_map";
295    const GEOMETRY: crate::Geometry = crate::Geometry::None;
296    fn to_json(&self) -> Result<Option<Value>, JsonError> {
297        crate::json::to_value(self).map(Some)
298    }
299    fn references(&self, out: &mut Vec<Entity>) {
300        out.push(self.from);
301        out.push(self.to);
302    }
303    /// Both ends skeletons; every listed joint in its skeleton; no joint twice on either side.
304    fn verify(&self, _e: Entity, w: &World) -> Result<(), String> {
305        let from = w
306            .get::<Skeleton>(self.from)
307            .ok_or_else(|| format!("{:?} holds no core.skeleton", self.from))?;
308        let to = w
309            .get::<Skeleton>(self.to)
310            .ok_or_else(|| format!("{:?} holds no core.skeleton", self.to))?;
311        let (mut a, mut b) = (BTreeSet::new(), BTreeSet::new());
312        for &(x, y) in &self.pairs {
313            if from.index_of(x).is_none() || to.index_of(y).is_none() {
314                return Err(format!("pair {x:?} -> {y:?}: a joint not in its skeleton"));
315            }
316            if !a.insert(x) || !b.insert(y) {
317                return Err(format!("pair {x:?} -> {y:?}: a joint mapped twice"));
318            }
319        }
320        Ok(())
321    }
322}
323
324/// Where a target joint stands inside the source mesh, as an offset from where a fit would put it: the target joint's
325/// frame (its rest, carried into the source's model space) is moved by `translation` (metres, along the source's model
326/// axes) and turned by `rotation` (a unit quaternion `[x, y, z, w]`, about the joint's own origin, in the source's
327/// model axes). The identity offset (zero, `[0, 0, 0, 1]`) is the fit's own placement.
328#[derive(Clone, Copy, Debug, PartialEq, Serialize)]
329pub struct JointOffset {
330    pub translation: [f32; 3],
331    pub rotation: [f32; 4],
332}
333
334impl JointOffset {
335    /// The offset that moves nothing.
336    pub const IDENTITY: JointOffset = JointOffset {
337        translation: [0.0; 3],
338        rotation: [0.0, 0.0, 0.0, 1.0],
339    };
340
341    /// How far a rotation's length may be from 1 and still be a unit quaternion.
342    pub const UNIT_TOLERANCE: f32 = 1e-3;
343
344    /// Finite entries and a rotation of unit length (within [`Self::UNIT_TOLERANCE`]).
345    pub fn check(&self) -> Result<(), String> {
346        if !self
347            .translation
348            .iter()
349            .chain(&self.rotation)
350            .all(|x| x.is_finite())
351        {
352            return Err("an entry not finite".into());
353        }
354        let len = self.rotation.iter().map(|x| x * x).sum::<f32>().sqrt();
355        if (len - 1.0).abs() > Self::UNIT_TOLERANCE {
356            return Err(format!("rotation of length {len}, not a unit quaternion"));
357        }
358        Ok(())
359    }
360
361    /// Whether this is the identity offset (the fit's own placement).
362    pub fn is_identity(&self) -> bool {
363        *self == Self::IDENTITY
364    }
365}
366
367/// `core.joint_placement`, on an entity of its own: where a person (or a tool) stands the target joints of a
368/// [`JointMap`] inside the source mesh, as offsets from the fit's own placement. Offsets are keyed by target joint, each
369/// at most once; a target joint not listed has the identity offset. A map has at most one placement. The placement is
370/// not part of the map: a pass that reads the map's pairs (`map-clip`) never reads it, and only the passes that fit a
371/// mesh to the target (`retarget-skin{placement=stated}`) do.
372#[derive(Clone, Debug, PartialEq, Serialize)]
373pub struct JointPlacement {
374    /// The entity holding the `core.joint_map` the offsets belong to.
375    pub map: Entity,
376    pub offsets: Vec<(JointId, JointOffset)>,
377}
378
379impl Component for JointPlacement {
380    const NAME: &'static str = "core.joint_placement";
381    const GEOMETRY: crate::Geometry = crate::Geometry::None;
382    fn to_json(&self) -> Result<Option<Value>, JsonError> {
383        crate::json::to_value(self).map(Some)
384    }
385    fn references(&self, out: &mut Vec<Entity>) {
386        out.push(self.map);
387    }
388    /// The map holds a `core.joint_map`, no other placement names it, and each offset is of a target joint the map
389    /// lists, once, finite and of a unit rotation.
390    fn verify(&self, e: Entity, w: &World) -> Result<(), String> {
391        let map = w
392            .get::<JointMap>(self.map)
393            .ok_or_else(|| format!("{:?} holds no core.joint_map", self.map))?;
394        if let Some((other, _)) = w
395            .iter::<JointPlacement>()
396            .find(|(o, p)| *o != e && p.map == self.map)
397        {
398            return Err(format!("{other:?} also places the joints of map {:?}", self.map));
399        }
400        let mut seen = BTreeSet::new();
401        for (joint, offset) in &self.offsets {
402            if !map.pairs.iter().any(|(_, to)| to == joint) {
403                return Err(format!("joint {joint:?} is not a target joint the map lists"));
404            }
405            if !seen.insert(*joint) {
406                return Err(format!("joint {joint:?} placed twice"));
407            }
408            offset.check().map_err(|why| format!("joint {joint:?}: {why}"))?;
409        }
410        Ok(())
411    }
412}
413
414#[cfg(test)]
415mod tests {
416    use super::*;
417    use crate::morphs::{Deltas, Frame, Target};
418    use crate::skeleton::Joint;
419    use crate::skin::{Binding, Deformation, Influences, Slot, WeightRule};
420    use crate::{Mesh, Primitive, Semantic, Stream, Topology};
421
422    fn joint(id: u32, parent: Option<u32>) -> Joint {
423        Joint {
424            id: JointId(id),
425            name: format!("j{id}"),
426            parent: parent.map(JointId),
427            rest: Rest::Affine(Transform::IDENTITY),
428        }
429    }
430
431    /// A shared definition (skeleton with a socket, a prototype whose one-primitive mesh is bound whole to joint 2 and
432    /// has one morph target) and two rigs of it, each with a skinned placement of the prototype, posed differently:
433    /// (world, prototype, [rig a, rig b], [placement a, placement b], skeleton).
434    fn world() -> (World, Entity, [Entity; 2], [Entity; 2], Entity) {
435        let mut w = World::new();
436        let skeleton = w.spawn();
437        w.insert(
438            skeleton,
439            Skeleton {
440                joints: vec![joint(1, None), joint(2, Some(1))],
441            },
442        );
443        w.insert(
444            skeleton,
445            Sockets(vec![Socket {
446                id: SocketId(5),
447                name: "grip".into(),
448                joint: JointId(2),
449                offset: Transform::IDENTITY,
450            }]),
451        );
452        let proto = w.spawn();
453        w.insert(
454            proto,
455            Mesh::new(vec![Primitive {
456                topology: Topology::Points,
457                streams: [(Semantic::Position, Stream::F32x3(vec![[0.0; 3]]))].into(),
458                indices: None,
459                material: None,
460            }]),
461        );
462        w.insert(
463            proto,
464            Skin {
465                influences: vec![Some(Influences::Whole { slot: 0 })],
466                weights: WeightRule::SumsToOne,
467                bindings: vec![Binding {
468                    id: BindingId(3),
469                    skeleton,
470                    palettes: vec![Some(vec![Slot {
471                        joint: JointId(2),
472                        inverse_bind: Transform::IDENTITY,
473                    }])],
474                    deformation: Deformation::Linear,
475                }],
476            },
477        );
478        w.insert(
479            proto,
480            Morphs {
481                targets: vec![Target {
482                    id: MorphId(9),
483                    name: "puff".into(),
484                    default_weight: 0.0,
485                    frames: vec![Frame {
486                        weight: 1.0,
487                        primitives: vec![Some(Deltas {
488                            positions: Some(vec![[0.0, 0.1, 0.0]]),
489                            ..Deltas::default()
490                        })],
491                    }],
492                }],
493            },
494        );
495        let mut rigs = [Entity::from_index(0); 2];
496        let mut placements = rigs;
497        for (k, lift) in [0.0f32, 1.0].into_iter().enumerate() {
498            let rig = w.spawn();
499            w.insert(rig, RigInstance { skeleton });
500            w.insert(rig, Transform::IDENTITY);
501            let mut up = Transform::IDENTITY;
502            up.cols[3] = [0.0, lift, 0.0];
503            w.insert(
504                rig,
505                RigPose {
506                    joints: vec![
507                        (JointId(2), Rest::Affine(up)),
508                        (JointId(1), Rest::Affine(Transform::IDENTITY)),
509                    ],
510                    root_applied: None,
511                },
512            );
513            let p = w.spawn();
514            w.insert(
515                p,
516                Instance {
517                    prototype: proto,
518                    variant: 0,
519                },
520            );
521            w.insert(p, Transform::IDENTITY);
522            w.insert(
523                p,
524                SkinInstance {
525                    rig,
526                    binding: BindingId(3),
527                },
528            );
529            w.insert(p, MorphWeights(vec![(MorphId(9), lift)]));
530            rigs[k] = rig;
531            placements[k] = p;
532        }
533        (w, proto, rigs, placements, skeleton)
534    }
535
536    fn errors(w: &World) -> Vec<String> {
537        w.verify_components().into_iter().map(|(_, _, m)| m).collect()
538    }
539
540    /// Two occurrences share one mesh, skin and morphs, each with its own pose and weights; a placement reaches its
541    /// rig and through it the skeleton.
542    #[test]
543    fn occurrences_share_definitions_not_poses() {
544        let (w, proto, rigs, placements, skeleton) = world();
545        assert_eq!(errors(&w), Vec::<String>::new());
546        assert_ne!(w.get::<RigPose>(rigs[0]), w.get::<RigPose>(rigs[1]));
547        assert_ne!(
548            w.get::<MorphWeights>(placements[0]),
549            w.get::<MorphWeights>(placements[1])
550        );
551        assert_eq!(
552            w.closure([placements[0]]),
553            [placements[0], proto, rigs[0], skeleton].into()
554        );
555        // an attachment at the socket of rig b
556        let mut w = w;
557        let sword = w.spawn();
558        w.insert(sword, Transform::IDENTITY);
559        w.insert(
560            sword,
561            Attachment {
562                rig: rigs[1],
563                socket: SocketId(5),
564                offset: Transform::IDENTITY,
565            },
566        );
567        assert_eq!(errors(&w), Vec::<String>::new());
568        assert!(w.closure([sword]).contains(&skeleton));
569    }
570
571    #[test]
572    fn malformed_occurrences_fail_verification() {
573        type Edit<'a> = &'a dyn Fn(&mut World, [Entity; 2], [Entity; 2], Entity);
574        let fails = |edit: Edit, what: &str| {
575            let (mut w, _, rigs, placements, skeleton) = world();
576            edit(&mut w, rigs, placements, skeleton);
577            let e = errors(&w);
578            assert!(e.iter().any(|m| m.contains(what)), "{what}: {e:?}");
579        };
580        // a pose missing a joint, posing one twice, or naming one the skeleton lacks
581        fails(
582            &|w, r, _, _| {
583                w.get_mut::<RigPose>(r[0]).unwrap().joints.pop();
584            },
585            "1 of the skeleton's 2",
586        );
587        fails(
588            &|w, r, _, _| {
589                let p = w.get_mut::<RigPose>(r[0]).unwrap();
590                p.joints[1].0 = JointId(2);
591            },
592            "posed twice",
593        );
594        fails(
595            &|w, r, _, _| w.get_mut::<RigPose>(r[0]).unwrap().joints[0].0 = JointId(7),
596            "not a joint",
597        );
598        // an applied root motion that is not finite
599        fails(
600            &|w, r, _, _| {
601                let mut m = Transform::IDENTITY;
602                m.cols[3][0] = f32::NAN;
603                w.get_mut::<RigPose>(r[0]).unwrap().root_applied = Some(m);
604            },
605            "applied root motion is not finite",
606        );
607        // a rig unplaced; a skin instance with a binding the skin lacks, or following a rig of another skeleton
608        fails(
609            &|w, r, _, _| {
610                w.remove::<Transform>(r[0]);
611            },
612            "a rig without a core.transform",
613        );
614        fails(
615            &|w, _, p, _| w.get_mut::<SkinInstance>(p[0]).unwrap().binding = BindingId(4),
616            "not one of",
617        );
618        fails(
619            &|w, r, _, _| {
620                let other = w.spawn();
621                w.insert(
622                    other,
623                    Skeleton {
624                        joints: vec![joint(1, None), joint(2, Some(1))],
625                    },
626                );
627                w.get_mut::<RigInstance>(r[1]).unwrap().skeleton = other;
628                w.get_mut::<RigPose>(r[1]).unwrap();
629            },
630            "the rig poses",
631        );
632        // weights of a target the mesh lacks, or listed twice
633        fails(
634            &|w, _, p, _| w.get_mut::<MorphWeights>(p[0]).unwrap().0[0].0 = MorphId(1),
635            "not one of",
636        );
637        fails(
638            &|w, _, p, _| {
639                let m = w.get_mut::<MorphWeights>(p[0]).unwrap();
640                m.0.push(m.0[0]);
641            },
642            "listed twice",
643        );
644        // a socket on a joint the skeleton lacks; an attachment to a socket the rig's skeleton lacks
645        fails(
646            &|w, _, _, s| w.get_mut::<Sockets>(s).unwrap().0[0].joint = JointId(8),
647            "not in the skeleton",
648        );
649        fails(
650            &|w, r, _, _| {
651                let a = w.spawn();
652                w.insert(a, Transform::IDENTITY);
653                w.insert(
654                    a,
655                    Attachment {
656                        rig: r[0],
657                        socket: SocketId(6),
658                        offset: Transform::IDENTITY,
659                    },
660                );
661            },
662            "socket SocketId(6)",
663        );
664    }
665
666    /// Two skeletons of two joints, a map pairing them, and a placement of the target's joint 20.
667    fn placed() -> (World, Entity, Entity) {
668        let mut w = World::new();
669        let (a, b) = (w.spawn(), w.spawn());
670        for (e, ids) in [(a, [1, 2]), (b, [10, 20])] {
671            w.insert(
672                e,
673                Skeleton {
674                    joints: vec![joint(ids[0], None), joint(ids[1], Some(ids[0]))],
675                },
676            );
677        }
678        let map = w.spawn();
679        w.insert(
680            map,
681            JointMap {
682                from: a,
683                to: b,
684                pairs: vec![(JointId(1), JointId(10)), (JointId(2), JointId(20))],
685            },
686        );
687        let placement = w.spawn();
688        w.insert(
689            placement,
690            JointPlacement {
691                map,
692                offsets: vec![(
693                    JointId(20),
694                    JointOffset {
695                        translation: [0.0, 0.1, 0.0],
696                        ..JointOffset::IDENTITY
697                    },
698                )],
699            },
700        );
701        (w, map, placement)
702    }
703
704    /// A placement is of one map, of target joints the map lists, once each, with finite entries and a unit rotation.
705    #[test]
706    fn malformed_placements_fail_verification() {
707        let (w, ..) = placed();
708        assert_eq!(errors(&w), Vec::<String>::new());
709        let fails = |edit: &dyn Fn(&mut World, Entity, Entity), what: &str| {
710            let (mut w, map, placement) = placed();
711            edit(&mut w, map, placement);
712            let e = errors(&w);
713            assert!(e.iter().any(|m| m.contains(what)), "{what}: {e:?}");
714        };
715        // a source joint of the map is not one of its targets; neither is a joint of no skeleton
716        fails(
717            &|w, _, p| w.get_mut::<JointPlacement>(p).unwrap().offsets[0].0 = JointId(2),
718            "not a target joint the map lists",
719        );
720        fails(
721            &|w, _, p| w.get_mut::<JointPlacement>(p).unwrap().offsets[0].0 = JointId(99),
722            "not a target joint the map lists",
723        );
724        fails(
725            &|w, _, p| {
726                let o = w.get::<JointPlacement>(p).unwrap().offsets[0];
727                w.get_mut::<JointPlacement>(p).unwrap().offsets.push(o);
728            },
729            "placed twice",
730        );
731        fails(
732            &|w, _, p| w.get_mut::<JointPlacement>(p).unwrap().offsets[0].1.translation[1] = f32::NAN,
733            "not finite",
734        );
735        fails(
736            &|w, _, p| w.get_mut::<JointPlacement>(p).unwrap().offsets[0].1.rotation = [0.0, 0.0, 0.0, 2.0],
737            "not a unit quaternion",
738        );
739        // a placement of an entity that is no map, and a second placement of one map
740        fails(
741            &|w, _, p| {
742                let other = w.spawn();
743                w.get_mut::<JointPlacement>(p).unwrap().map = other;
744            },
745            "holds no core.joint_map",
746        );
747        fails(
748            &|w, map, _| {
749                let second = w.spawn();
750                w.insert(
751                    second,
752                    JointPlacement {
753                        map,
754                        offsets: Vec::new(),
755                    },
756                );
757            },
758            "also places the joints of map",
759        );
760    }
761
762    #[test]
763    fn the_identity_offset_is_the_one_that_moves_nothing() {
764        assert!(JointOffset::IDENTITY.is_identity());
765        assert!(JointOffset::IDENTITY.check().is_ok());
766        let moved = JointOffset {
767            rotation: [0.0, 1.0, 0.0, 0.0],
768            ..JointOffset::IDENTITY
769        };
770        assert!(!moved.is_identity() && moved.check().is_ok());
771    }
772}