1use 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#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize)]
28pub struct RigInstance {
29 pub skeleton: Entity,
31}
32
33#[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#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize)]
49pub struct SkinInstance {
50 pub rig: Entity,
52 pub binding: BindingId,
53}
54
55#[derive(Clone, Debug, Default, PartialEq, Serialize)]
58pub struct MorphWeights(pub Vec<(MorphId, f32)>);
59
60#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize)]
62#[serde(transparent)]
63pub struct SocketId(pub u32);
64
65#[derive(Clone, Debug, PartialEq, Serialize)]
67pub struct Socket {
68 pub id: SocketId,
69 pub name: String,
71 pub joint: JointId,
72 pub offset: Transform,
74}
75
76#[derive(Clone, Debug, Default, PartialEq, Serialize)]
78pub struct Sockets(pub Vec<Socket>);
79
80#[derive(Clone, Copy, Debug, PartialEq, Serialize)]
84pub struct Attachment {
85 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
95fn 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
104fn 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 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 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 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 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 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 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#[derive(Clone, Debug, PartialEq, Serialize)]
285pub struct JointMap {
286 pub from: Entity,
288 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 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#[derive(Clone, Copy, Debug, PartialEq, Serialize)]
329pub struct JointOffset {
330 pub translation: [f32; 3],
331 pub rotation: [f32; 4],
332}
333
334impl JointOffset {
335 pub const IDENTITY: JointOffset = JointOffset {
337 translation: [0.0; 3],
338 rotation: [0.0, 0.0, 0.0, 1.0],
339 };
340
341 pub const UNIT_TOLERANCE: f32 = 1e-3;
343
344 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 pub fn is_identity(&self) -> bool {
363 *self == Self::IDENTITY
364 }
365}
366
367#[derive(Clone, Debug, PartialEq, Serialize)]
373pub struct JointPlacement {
374 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 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 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 #[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 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 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 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 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 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 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 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 #[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 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 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}