1use std::sync::Arc;
6
7use crate::anim::{BonePose, Clip, Pose};
8use crate::math::{Mat4, Vec3};
9use crate::model::{Model, Quad};
10use crate::wire::{Anchor, BoneOp, Mode, 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 mode: Mode,
34 pub glint: bool,
35 pub argb: u32,
36}
37
38impl Rig {
39 pub fn new(model: Arc<Model>, anchor: Anchor) -> Rig {
40 Rig { model, anchor, layers: Vec::new(), manual: Pose::default(), mode: Mode::Cutout, glint: false, argb: 0xFFFF_FFFF }
41 }
42
43 pub fn play(&mut self, clip: Arc<Clip>, now: f32) {
45 self.layers.retain(|l| l.clip.name != clip.name);
46 self.layers.push(Layer { clip, start: now, speed: 1.0, weight: 1.0 });
47 }
48
49 pub fn stop(&mut self, clip: &str) {
51 self.layers.retain(|l| l.clip.name != clip);
52 }
53
54 pub fn playing(&self, clip: &str) -> bool {
55 self.layers.iter().any(|l| l.clip.name == clip)
56 }
57
58 pub fn pose(&mut self, now: f32) -> Pose {
61 self.layers.retain(|l| !l.clip.finished((now - l.start) * l.speed));
62 let mut pose = Pose::default();
63 for l in &self.layers {
64 pose.add(&l.clip.sample((now - l.start) * l.speed), l.weight);
65 }
66 pose.add(&self.manual, 1.0);
67 pose
68 }
69
70 pub fn matrices(&self, pose: &Pose) -> Vec<Mat4> {
72 let mut out: Vec<Mat4> = Vec::with_capacity(self.model.bones.len());
73 for b in &self.model.bones {
74 let p = pose.get(&b.name);
75 let local = crate::math::part(b.pivot + p.position, b.rotation + p.rotation, p.scale);
76 out.push(match b.parent {
77 Some(i) => out[i] * local,
78 None => local,
79 });
80 }
81 out
82 }
83
84 pub fn quads(&self, pose: &Pose) -> Vec<Quad> {
86 let m = self.matrices(pose);
87 self.model.bones.iter().zip(&m).flat_map(|(b, m)| b.quads.iter().map(move |q| q.transformed(m))).collect()
88 }
89
90 pub fn locator(&self, name: &str, pose: &Pose) -> Option<Vec3> {
92 let m = self.matrices(pose);
93 self.model.bones.iter().zip(&m).find_map(|(b, m)| {
94 b.locators.iter().find(|(n, _)| n == name).map(|(_, p)| m.transform_point(*p))
95 })
96 }
97
98 pub fn write(&mut self, ops: &mut Ops, now: f32) {
101 let pose = self.pose(now);
102 for (name, p) in &pose.0 {
103 if self.model.bone(name).is_none() {
104 ops.bone(name, &vanilla_op(p));
105 }
106 }
107 let quads = self.quads(&pose);
108 ops.mesh(&self.anchor, &self.model.texture, self.mode, self.glint, self.argb, &quads);
109 }
110}
111
112pub fn vanilla_op(p: &BonePose) -> BoneOp {
114 BoneOp {
115 rotation: p.rotation,
116 position: p.position,
117 scale: (p.scale != Vec3::ONE).then_some(p.scale),
118 ..Default::default()
119 }
120}
121
122#[cfg(test)]
123mod tests {
124 use super::*;
125 use crate::format::{BoneDef, CubeDef, ModelFile};
126 use yog_data::Ordered;
127
128 fn model() -> Arc<Model> {
129 let file = ModelFile {
130 texture: "x.png".into(),
131 texture_size: [64, 32],
132 bones: vec![
133 BoneDef { name: "a".into(), parent: None, pivot: [0.0, 0.0, 0.0], rotation: [0.0; 3], cubes: vec![], locators: Ordered::default() },
134 BoneDef {
135 name: "b".into(),
136 parent: Some("a".into()),
137 pivot: [0.0, 10.0, 0.0],
138 rotation: [0.0; 3],
139 cubes: vec![CubeDef { origin: [0.0; 3], size: [1.0; 3], uv: [0.0; 2], inflate: 0.0, mirror: false }],
140 locators: Ordered(vec![("tip".into(), [0.0, 5.0, 0.0])]),
141 },
142 ],
143 };
144 Arc::new(Model::new(&file, "m").unwrap())
145 }
146
147 #[test]
148 fn children_follow_parents() {
149 let mut rig = Rig::new(model(), Anchor::Model);
150 rig.manual.bone("a").rotation = Vec3::new(0.0, 0.0, std::f32::consts::FRAC_PI_2);
151 let pose = rig.pose(0.0);
152 let tip = rig.locator("tip", &pose).unwrap();
153 assert!((tip - Vec3::new(-15.0, 0.0, 0.0)).length() < 1e-4, "{tip:?}");
154 assert_eq!(rig.quads(&pose).len(), 6);
155 }
156}