1use crate::{Clip, Component, Entity, Geometry, JointId, JsonError, RigInstance, Skeleton, World};
26use serde::{Deserialize, Serialize};
27use serde_json::Value;
28use std::collections::BTreeSet;
29
30#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
31pub enum ControllerParameterKind {
32 Float,
33 Int,
34 Bool,
35 Trigger,
36}
37
38#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
39#[serde(deny_unknown_fields)]
40pub struct ControllerParameter {
41 pub name: String,
42 pub kind: ControllerParameterKind,
43 pub default: f64,
45}
46
47#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
49pub enum LayerBlending {
50 Override,
51 Additive,
52 Other(i64),
53}
54
55#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
58#[serde(deny_unknown_fields)]
59pub enum Motion {
60 Clip(Entity),
61 Pending(Entity),
67 Blend1D {
68 parameter: usize,
69 children: Vec<(f32, Motion)>,
70 },
71 Unsupported(String),
72}
73
74#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
75#[serde(deny_unknown_fields)]
76pub struct ControllerState {
77 pub name: String,
78 pub motion: Motion,
79 pub speed: f64,
80 pub speed_parameter: Option<usize>,
82 pub cycle_offset: f32,
83 pub cycle_offset_parameter: Option<usize>,
85}
86
87#[derive(Clone, Copy, Debug, PartialEq, Serialize, Deserialize)]
91#[serde(deny_unknown_fields)]
92pub struct MaskedJoint {
93 pub skeleton: Entity,
94 #[serde(deserialize_with = "joint_id")]
95 pub joint: JointId,
96 pub weight: f32,
98}
99
100fn joint_id<'de, D: serde::Deserializer<'de>>(d: D) -> Result<JointId, D::Error> {
101 u32::deserialize(d).map(JointId)
102}
103
104fn stated<'de, D: serde::Deserializer<'de>, T: Deserialize<'de>>(d: D) -> Result<Option<T>, D::Error> {
107 Option::<T>::deserialize(d)
108}
109
110#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
112#[serde(deny_unknown_fields)]
113pub enum LayerMask {
114 Joints(Vec<MaskedJoint>),
116 Unsupported(String),
119}
120
121#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
122#[serde(deny_unknown_fields)]
123pub struct ControllerLayer {
124 pub name: String,
125 pub states: Vec<ControllerState>,
126 pub default_state: Option<usize>,
129 pub default_weight: f32,
132 #[serde(deserialize_with = "stated")]
135 pub weight_parameter: Option<usize>,
136 pub blending: LayerBlending,
137 #[serde(deserialize_with = "stated")]
140 pub mask: Option<LayerMask>,
141}
142
143#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
147#[serde(deny_unknown_fields)]
148pub struct Controller {
149 pub name: String,
150 pub parameters: Vec<ControllerParameter>,
151 pub layers: Vec<ControllerLayer>,
152}
153
154#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
156#[serde(deny_unknown_fields)]
157pub struct ControllerBinding {
158 pub controller: Entity,
159 pub rig: Entity,
160}
161
162impl Controller {
163 fn numeric_parameter(&self, index: usize) -> Result<(), String> {
164 let parameter = self
165 .parameters
166 .get(index)
167 .ok_or_else(|| format!("parameter index {index} is out of range"))?;
168 if !matches!(
169 parameter.kind,
170 ControllerParameterKind::Float | ControllerParameterKind::Int
171 ) {
172 return Err(format!(
173 "parameter {} is {:?}, not numeric",
174 parameter.name, parameter.kind
175 ));
176 }
177 Ok(())
178 }
179}
180
181impl Component for Controller {
182 const NAME: &'static str = "core.controller";
183 const GEOMETRY: Geometry = Geometry::None;
184 fn to_json(&self) -> Result<Option<Value>, JsonError> {
185 crate::json::to_value(self).map(Some)
186 }
187 fn apply_linear(&self, _l: &crate::Linear) -> crate::world::Linearity<Self> {
188 crate::world::Linearity::Invariant
189 }
190 fn references(&self, out: &mut Vec<Entity>) {
191 let mut pending: Vec<_> = self
192 .layers
193 .iter()
194 .flat_map(|layer| layer.states.iter().map(|state| &state.motion))
195 .collect();
196 while let Some(motion) = pending.pop() {
197 match motion {
198 Motion::Clip(clip) | Motion::Pending(clip) => out.push(*clip),
199 Motion::Blend1D { children, .. } => pending.extend(children.iter().map(|(_, motion)| motion)),
200 Motion::Unsupported(_) => {}
201 }
202 }
203 for layer in &self.layers {
204 if let Some(LayerMask::Joints(joints)) = &layer.mask {
205 out.extend(joints.iter().map(|j| j.skeleton));
206 }
207 }
208 }
209 fn verify(&self, _e: Entity, w: &World) -> Result<(), String> {
210 if self.name.trim().is_empty() {
211 return Err("empty controller name".into());
212 }
213 let mut names = BTreeSet::new();
214 for parameter in &self.parameters {
215 if parameter.name.trim().is_empty() || !names.insert(¶meter.name) {
216 return Err(format!(
217 "empty or duplicate controller parameter {:?}",
218 parameter.name
219 ));
220 }
221 if !parameter.default.is_finite() {
222 return Err(format!("{}: non-finite default", parameter.name));
223 }
224 match parameter.kind {
225 ControllerParameterKind::Int if parameter.default.fract() != 0. => {
226 return Err(format!("{}: fractional Int default", parameter.name));
227 }
228 ControllerParameterKind::Bool | ControllerParameterKind::Trigger
229 if parameter.default != 0. && parameter.default != 1. =>
230 {
231 return Err(format!("{}: Bool/Trigger default is not 0 or 1", parameter.name));
232 }
233 _ => {}
234 }
235 }
236 for (layer_index, layer) in self.layers.iter().enumerate() {
237 if layer.name.trim().is_empty() {
238 return Err(format!("layer {layer_index}: empty name"));
239 }
240 if !layer.default_weight.is_finite() {
241 return Err(format!("layer {layer_index}: non-finite weight"));
242 }
243 if let Some(parameter) = layer.weight_parameter {
244 self.numeric_parameter(parameter)
245 .map_err(|why| format!("layer {layer_index}: weight {why}"))?;
246 }
247 match &layer.mask {
248 None => {}
249 Some(LayerMask::Unsupported(why)) if why.trim().is_empty() => {
250 return Err(format!("layer {layer_index}: unsupported mask has no reason"));
251 }
252 Some(LayerMask::Unsupported(_)) => {}
253 Some(LayerMask::Joints(joints)) => {
254 let mut seen = BTreeSet::new();
255 for masked in joints {
256 if !masked.weight.is_finite() || !(0.0..=1.0).contains(&masked.weight) {
257 return Err(format!(
258 "layer {layer_index}: mask weight {} is not in 0 to 1",
259 masked.weight
260 ));
261 }
262 if !seen.insert((masked.skeleton, masked.joint)) {
263 return Err(format!(
264 "layer {layer_index}: joint {:?} is masked twice",
265 masked.joint
266 ));
267 }
268 let skeleton = w.get::<Skeleton>(masked.skeleton).ok_or_else(|| {
269 format!(
270 "layer {layer_index}: mask skeleton {:?} holds no core.skeleton",
271 masked.skeleton
272 )
273 })?;
274 if skeleton.index_of(masked.joint).is_none() {
275 return Err(format!(
276 "layer {layer_index}: masked joint {:?} is not in skeleton {:?}",
277 masked.joint, masked.skeleton
278 ));
279 }
280 }
281 }
282 }
283 match layer.default_state {
284 Some(default) if default >= layer.states.len() => {
285 return Err(format!("layer {layer_index}: default state is out of range"));
286 }
287 None if !layer.states.is_empty() => {
288 return Err(format!("layer {layer_index}: has states but no default state"));
289 }
290 _ => {}
291 }
292 for (state_index, state) in layer.states.iter().enumerate() {
293 if state.name.trim().is_empty() {
294 return Err(format!("layer {layer_index} state {state_index}: empty name"));
295 }
296 if !state.speed.is_finite() || !state.cycle_offset.is_finite() {
297 return Err(format!(
298 "layer {layer_index} state {state_index}: non-finite speed/offset"
299 ));
300 }
301 for parameter in [state.speed_parameter, state.cycle_offset_parameter]
302 .into_iter()
303 .flatten()
304 {
305 self.numeric_parameter(parameter)?;
306 }
307 let mut pending = vec![&state.motion];
308 while let Some(motion) = pending.pop() {
309 match motion {
310 Motion::Clip(clip) => {
311 if !w.has::<Clip>(*clip) {
312 return Err(format!("motion {clip:?} holds no core.clip"));
313 }
314 }
315 Motion::Blend1D { parameter, children } => {
316 self.numeric_parameter(*parameter)?;
317 if children.is_empty() {
318 return Err("1D blend has no children".into());
319 }
320 if !children.iter().all(|(threshold, _)| threshold.is_finite())
321 || !children.windows(2).all(|pair| pair[0].0 < pair[1].0)
322 {
323 return Err(
324 "1D blend thresholds must be finite and strictly increasing".into()
325 );
326 }
327 pending.extend(children.iter().map(|(_, motion)| motion));
328 }
329 Motion::Pending(_) => {}
330 Motion::Unsupported(why) => {
331 if why.trim().is_empty() {
332 return Err("unsupported motion has no reason".into());
333 }
334 }
335 }
336 }
337 }
338 }
339 Ok(())
340 }
341}
342
343impl Component for ControllerBinding {
344 const NAME: &'static str = "core.controller_binding";
345 const GEOMETRY: Geometry = Geometry::None;
346 fn to_json(&self) -> Result<Option<Value>, JsonError> {
347 crate::json::to_value(self).map(Some)
348 }
349 fn apply_linear(&self, _l: &crate::Linear) -> crate::world::Linearity<Self> {
350 crate::world::Linearity::Invariant
351 }
352 fn references(&self, out: &mut Vec<Entity>) {
353 out.extend([self.controller, self.rig]);
354 }
355 fn verify(&self, _e: Entity, w: &World) -> Result<(), String> {
356 if !w.has::<Controller>(self.controller) {
357 return Err(format!("{:?} holds no core.controller", self.controller));
358 }
359 if !w.has::<RigInstance>(self.rig) {
360 return Err(format!("{:?} holds no core.rig_instance", self.rig));
361 }
362 Ok(())
363 }
364}
365
366#[cfg(test)]
367mod tests {
368 use super::*;
369 use crate::{Extrapolation, Transform};
370 fn fixture() -> (World, Entity, Controller) {
371 let mut w = World::new();
372 let clip = w.spawn();
373 w.insert(
374 clip,
375 Clip {
376 name: "motion".into(),
377 duration: 1.,
378 extrapolation: Extrapolation::Hold,
379 channels: vec![],
380 root_motion: None,
381 additive: None,
382 },
383 );
384 let controller = w.spawn();
385 let value = Controller {
386 name: "character".into(),
387 parameters: vec![
388 ControllerParameter {
389 name: "speed".into(),
390 kind: ControllerParameterKind::Float,
391 default: 0.,
392 },
393 ControllerParameter {
394 name: "index".into(),
395 kind: ControllerParameterKind::Int,
396 default: 1.,
397 },
398 ControllerParameter {
399 name: "flag".into(),
400 kind: ControllerParameterKind::Bool,
401 default: 0.,
402 },
403 ControllerParameter {
404 name: "fire".into(),
405 kind: ControllerParameterKind::Trigger,
406 default: 1.,
407 },
408 ],
409 layers: vec![ControllerLayer {
410 name: "base".into(),
411 default_state: Some(0),
412 default_weight: 0.75,
413 weight_parameter: None,
414 blending: LayerBlending::Override,
415 mask: None,
416 states: vec![ControllerState {
417 name: "idle".into(),
418 speed: -1.,
419 speed_parameter: Some(0),
420 cycle_offset: 1.5,
421 cycle_offset_parameter: Some(1),
422 motion: Motion::Blend1D {
423 parameter: 0,
424 children: vec![
425 (-1., Motion::Clip(clip)),
426 (
427 1.,
428 Motion::Unsupported("2D tree remains in source dialect".into()),
429 ),
430 ],
431 },
432 }],
433 }],
434 };
435 (w, controller, value)
436 }
437 #[test]
438 fn validates_parameter_defaults_without_rounding_or_clamping() {
439 let (w, e, c) = fixture();
440 assert!(c.verify(e, &w).is_ok());
441 for (index, value) in [(0, f64::NAN), (0, f64::INFINITY), (1, 1.5), (2, 2.), (3, -1.)] {
442 let mut bad = c.clone();
443 bad.parameters[index].default = value;
444 assert!(bad.verify(e, &w).is_err());
445 }
446 let mut bad = c.clone();
447 bad.parameters[1].name = "speed".into();
448 assert!(bad.verify(e, &w).is_err());
449 let mut bad = c.clone();
450 bad.parameters[1].name.clear();
451 assert!(bad.verify(e, &w).is_err());
452 }
453 #[test]
454 fn numeric_refs_blends_and_default_states_are_checked() {
455 let (w, e, c) = fixture();
456 for index in [2, 3, 4] {
457 let mut bad = c.clone();
458 bad.layers[0].states[0].speed_parameter = Some(index);
459 assert!(bad.verify(e, &w).is_err());
460 let mut bad = c.clone();
461 bad.layers[0].states[0].cycle_offset_parameter = Some(index);
462 assert!(bad.verify(e, &w).is_err());
463 let mut bad = c.clone();
464 bad.layers[0].states[0].motion = Motion::Blend1D {
465 parameter: index,
466 children: vec![(0., Motion::Unsupported("not lowered".into()))],
467 };
468 assert!(bad.verify(e, &w).is_err());
469 }
470 for thresholds in [vec![], vec![1., 1.], vec![1., 0.], vec![f32::NAN]] {
471 let mut bad = c.clone();
472 bad.layers[0].states[0].motion = Motion::Blend1D {
473 parameter: 0,
474 children: thresholds
475 .into_iter()
476 .map(|t| (t, Motion::Unsupported("not lowered".into())))
477 .collect(),
478 };
479 assert!(bad.verify(e, &w).is_err());
480 }
481 let mut bad = c.clone();
482 bad.layers[0].default_state = Some(1);
483 assert!(bad.verify(e, &w).is_err());
484 let mut bad = c.clone();
486 bad.layers[0].default_state = None;
487 assert!(
488 bad.verify(e, &w)
489 .unwrap_err()
490 .contains("has states but no default state")
491 );
492 let mut bad = c.clone();
493 bad.layers[0].default_weight = f32::NAN;
494 assert!(bad.verify(e, &w).is_err());
495 let mut bad = c.clone();
496 bad.layers[0].states[0].speed = f64::INFINITY;
497 assert!(bad.verify(e, &w).is_err());
498 let mut bad = c;
499 bad.layers[0].states[0].cycle_offset = f32::INFINITY;
500 assert!(bad.verify(e, &w).is_err());
501 }
502 #[test]
503 fn recursive_motion_references_require_clips_and_keep_named_refusals() {
504 let (mut w, e, mut c) = fixture();
505 let absent = w.spawn();
506 c.layers[0].states[0].motion = Motion::Blend1D {
507 parameter: 0,
508 children: vec![(
509 0.,
510 Motion::Blend1D {
511 parameter: 1,
512 children: vec![(0., Motion::Clip(absent))],
513 },
514 )],
515 };
516 let mut refs = Vec::new();
517 c.references(&mut refs);
518 assert_eq!(refs, vec![absent]);
519 assert!(c.verify(e, &w).is_err());
520 c.layers[0].states[0].motion = Motion::Unsupported(String::new());
521 assert!(c.verify(e, &w).is_err());
522 c.layers[0].states[0].motion = Motion::Unsupported("Direct blend is not lowered".into());
523 assert!(c.verify(e, &w).is_ok());
524 }
525 #[test]
528 fn an_empty_layer_has_no_default_state() {
529 let (w, e, mut c) = fixture();
530 let mut empty = c.layers[0].clone();
531 empty.name = "empty".into();
532 empty.states.clear();
533 empty.default_state = None;
534 c.layers.push(empty);
535 assert!(c.verify(e, &w).is_ok());
536 let json = c.to_json().unwrap().unwrap();
537 assert_eq!(json["layers"][0]["default_state"], 0);
538 assert!(json["layers"][1]["default_state"].is_null());
539 assert_eq!(serde_json::from_value::<Controller>(json).unwrap(), c);
540 c.layers[1].default_state = Some(0);
542 assert!(
543 c.verify(e, &w)
544 .unwrap_err()
545 .contains("default state is out of range")
546 );
547 }
548 fn skeleton_of(w: &mut World, joints: u32) -> Entity {
549 let e = w.spawn();
550 w.insert(
551 e,
552 Skeleton {
553 joints: (0..joints)
554 .map(|i| crate::Joint {
555 id: JointId(i),
556 name: format!("j{i}"),
557 parent: i.checked_sub(1).map(JointId),
558 rest: crate::Rest::Trs {
559 translation: [0.; 3],
560 rotation: [0., 0., 0., 1.],
561 scale: [1.; 3],
562 },
563 })
564 .collect(),
565 },
566 );
567 e
568 }
569 fn masked(skeleton: Entity, joint: u32, weight: f32) -> MaskedJoint {
570 MaskedJoint {
571 skeleton,
572 joint: JointId(joint),
573 weight,
574 }
575 }
576 #[test]
580 fn a_layer_mask_is_checked_against_the_skeletons_it_names() {
581 let (mut w, e, mut c) = fixture();
582 let a = skeleton_of(&mut w, 3);
583 let b = skeleton_of(&mut w, 2);
584 c.layers[0].mask = Some(LayerMask::Joints(vec![
585 masked(a, 0, 1.),
586 masked(a, 2, 0.25),
587 masked(b, 0, 0.),
588 ]));
589 c.layers[0].weight_parameter = Some(0);
590 assert!(c.verify(e, &w).is_ok());
591 let mut refs = Vec::new();
592 c.references(&mut refs);
593 assert!(refs.contains(&a) && refs.contains(&b));
594 for (mask, why) in [
595 (vec![masked(a, 0, 1.), masked(a, 0, 0.5)], "masked twice"),
596 (vec![masked(a, 0, 1.0001)], "not in 0 to 1"),
597 (vec![masked(a, 0, -0.5)], "not in 0 to 1"),
598 (vec![masked(a, 0, f32::NAN)], "not in 0 to 1"),
599 (vec![masked(a, 3, 1.)], "is not in skeleton"),
600 (vec![masked(b, 2, 1.)], "is not in skeleton"),
601 (vec![masked(e, 0, 1.)], "holds no core.skeleton"),
602 ] {
603 let mut bad = c.clone();
604 bad.layers[0].mask = Some(LayerMask::Joints(mask));
605 let err = bad.verify(e, &w).unwrap_err();
606 assert!(err.contains(why), "{err}");
607 }
608 let mut bad = c.clone();
609 bad.layers[0].mask = Some(LayerMask::Unsupported(" ".into()));
610 assert!(bad.verify(e, &w).unwrap_err().contains("no reason"));
611 bad.layers[0].mask = Some(LayerMask::Unsupported("a humanoid body mask".into()));
612 assert!(bad.verify(e, &w).is_ok());
613 for index in [2, 3, 4] {
614 let mut bad = c.clone();
615 bad.layers[0].weight_parameter = Some(index);
616 assert!(bad.verify(e, &w).unwrap_err().contains("weight"));
617 }
618 }
619 #[test]
622 fn a_layer_mask_round_trips_and_its_absence_is_not_unmasked() {
623 let (mut w, _, mut c) = fixture();
624 let a = skeleton_of(&mut w, 2);
625 c.layers[0].weight_parameter = Some(0);
626 c.layers[0].mask = Some(LayerMask::Joints(vec![masked(a, 1, 0.5)]));
627 let json = c.to_json().unwrap().unwrap();
628 assert_eq!(serde_json::from_value::<Controller>(json.clone()).unwrap(), c);
629 c.layers[0].mask = Some(LayerMask::Unsupported("why".into()));
630 let json = c.to_json().unwrap().unwrap();
631 assert_eq!(
632 json["layers"][0]["mask"],
633 serde_json::json!({"Unsupported": "why"})
634 );
635 assert_eq!(serde_json::from_value::<Controller>(json).unwrap(), c);
636 c.layers[0].mask = None;
637 c.layers[0].weight_parameter = None;
638 let json = c.to_json().unwrap().unwrap();
639 assert!(json["layers"][0]["mask"].is_null() && json["layers"][0]["weight_parameter"].is_null());
640 assert_eq!(serde_json::from_value::<Controller>(json.clone()).unwrap(), c);
641 for field in ["mask", "weight_parameter"] {
642 let mut stripped = json.clone();
643 stripped["layers"][0].as_object_mut().unwrap().remove(field);
644 assert!(serde_json::from_value::<Controller>(stripped).is_err(), "{field}");
645 }
646 }
647 #[test]
648 fn controller_and_binding_json_round_trip_keep_entity_ids_and_layer_modes() {
649 let (mut w, e, mut c) = fixture();
650 for blending in [
651 LayerBlending::Override,
652 LayerBlending::Additive,
653 LayerBlending::Other(17),
654 ] {
655 c.layers[0].blending = blending;
656 assert_eq!(
657 serde_json::from_value::<Controller>(c.to_json().unwrap().unwrap()).unwrap(),
658 c
659 );
660 }
661 w.insert(e, c.clone());
662 let rig = w.spawn();
663 let binding = ControllerBinding { controller: e, rig };
664 assert!(binding.verify(e, &w).is_err());
665 let skeleton = w.spawn();
666 w.insert(rig, RigInstance { skeleton });
667 w.insert(rig, Transform::IDENTITY);
668 assert!(binding.verify(e, &w).is_ok());
669 let mut refs = Vec::new();
670 binding.references(&mut refs);
671 assert_eq!(refs, vec![e, rig]);
672 assert_eq!(
673 serde_json::from_value::<ControllerBinding>(binding.to_json().unwrap().unwrap()).unwrap(),
674 binding
675 );
676 assert!(c.remap_primitives(&[0]).is_none());
677 assert!(binding.remap_primitives(&[0]).is_none());
678 let mut json = c.to_json().unwrap().unwrap();
679 json["forgotten_source_field"] = serde_json::json!(true);
680 assert!(serde_json::from_value::<Controller>(json).is_err());
681 let mut invalid = binding;
682 invalid.controller = skeleton;
683 assert!(invalid.verify(e, &w).is_err());
684 }
685}