1use yog_sync::Arc;
6
7use crate::anim::{BonePose, Clip, Pose};
8use crate::math::{Mat4, Vec3};
9use crate::model::{Model, Quad};
10use crate::wire::{Anchor, BoneOp, Look, Ops};
11
12#[derive(Debug, Clone)]
14pub struct Layer {
15 pub clip: Arc<Clip>,
16 pub start: f32,
18 pub speed: f32,
19 pub weight: f32,
21}
22
23#[derive(Debug, Clone)]
25pub struct Rig {
26 pub model: Arc<Model>,
27 pub anchor: Anchor,
30 pub layers: Vec<Layer>,
31 pub manual: Pose,
33 pub look: Look,
34}
35
36impl Rig {
37 pub fn new(model: Arc<Model>, anchor: Anchor) -> Rig {
38 Rig {
39 model,
40 anchor,
41 layers: Vec::new(),
42 manual: Pose::default(),
43 look: Look::default(),
44 }
45 }
46
47 pub fn play(&mut self, clip: Arc<Clip>, now: f32) {
49 self.layers.retain(|l| l.clip.name != clip.name);
50 self.layers.push(Layer {
51 clip,
52 start: now,
53 speed: 1.0,
54 weight: 1.0,
55 });
56 }
57
58 pub fn stop(&mut self, clip: &str) {
60 self.layers.retain(|l| l.clip.name != clip);
61 }
62
63 pub fn playing(&self, clip: &str) -> bool {
64 self.layers.iter().any(|l| l.clip.name == clip)
65 }
66
67 pub fn pose(&mut self, now: f32) -> Pose {
70 self.layers
71 .retain(|l| !l.clip.finished((now - l.start) * l.speed));
72 let mut pose = Pose::default();
73 for l in &self.layers {
74 pose.add(&l.clip.sample((now - l.start) * l.speed), l.weight);
75 }
76 pose.add(&self.manual, 1.0);
77 pose
78 }
79
80 pub fn matrices(&self, pose: &Pose) -> Vec<Mat4> {
82 let mut out: Vec<Mat4> = Vec::with_capacity(self.model.bones.len());
83 for b in &self.model.bones {
84 let p = pose.get(&b.name);
85 let local = crate::math::part(b.pivot + p.position, b.rotation + p.rotation, p.scale);
86 out.push(match b.parent {
87 Some(i) => out[i] * local,
88 None => local,
89 });
90 }
91 out
92 }
93
94 pub fn quads(&self, pose: &Pose) -> Vec<Quad> {
96 let m = self.matrices(pose);
97 self.model
98 .bones
99 .iter()
100 .zip(&m)
101 .flat_map(|(b, m)| b.quads.iter().map(move |q| q.transformed(m)))
102 .collect()
103 }
104
105 pub fn locator(&self, name: &str, pose: &Pose) -> Option<Vec3> {
107 let m = self.matrices(pose);
108 self.model.bones.iter().zip(&m).find_map(|(b, m)| {
109 b.locators
110 .iter()
111 .find(|(n, _)| n == name)
112 .map(|(_, p)| m.transform_point(*p))
113 })
114 }
115
116 pub fn write(&mut self, ops: &mut Ops, now: f32) {
119 let pose = self.pose(now);
120 for (name, p) in &pose.0 {
121 if self.model.bone(name).is_none() {
122 ops.bone(name, &vanilla_op(p));
123 }
124 }
125 let quads = self.quads(&pose);
126 ops.mesh(&self.anchor, &self.model.texture, self.look, &quads);
127 }
128}
129
130pub fn vanilla_op(p: &BonePose) -> BoneOp {
132 BoneOp {
133 rotation: p.rotation,
134 position: p.position,
135 scale: (p.scale != Vec3::ONE).then_some(p.scale),
136 ..Default::default()
137 }
138}
139
140#[cfg(test)]
141mod tests {
142 use super::*;
143 use crate::format::{BoneDef, CubeDef, ModelFile};
144 use yog_data::Ordered;
145
146 fn model() -> Arc<Model> {
147 let file = ModelFile {
148 texture: "m:x".into(),
149 texture_size: [64, 32],
150 bones: vec![
151 BoneDef {
152 name: "a".into(),
153 parent: None,
154 pivot: [0.0, 0.0, 0.0],
155 rotation: [0.0; 3],
156 cubes: vec![],
157 locators: Ordered::default(),
158 },
159 BoneDef {
160 name: "b".into(),
161 parent: Some("a".into()),
162 pivot: [0.0, 10.0, 0.0],
163 rotation: [0.0; 3],
164 cubes: vec![CubeDef {
165 origin: [0.0; 3],
166 size: [1.0; 3],
167 uv: [0.0; 2],
168 inflate: 0.0,
169 mirror: false,
170 }],
171 locators: Ordered(vec![("tip".into(), [0.0, 5.0, 0.0])]),
172 },
173 ],
174 };
175 Arc::new(Model::new(&file).unwrap())
176 }
177
178 #[test]
179 fn children_follow_parents() {
180 let mut rig = Rig::new(model(), Anchor::Model);
181 rig.manual.bone("a").rotation = Vec3::new(0.0, 0.0, std::f32::consts::FRAC_PI_2);
182 let pose = rig.pose(0.0);
183 let tip = rig.locator("tip", &pose).unwrap();
184 assert!(
185 (tip - Vec3::new(-15.0, 0.0, 0.0)).length() < 1e-4,
186 "{tip:?}"
187 );
188 assert_eq!(rig.quads(&pose).len(), 6);
189 }
190}