geop_ops_model/placed/
motion.rs1use geop_core_math::geop_error::{GeopError, GeopResult};
8use geop_core_program::{
9 element::{ElementReference, HasElements, Kind, whole},
10 reader::Reader,
11 target::{Target, TargetReference},
12};
13use serde::{Deserialize, Serialize};
14
15#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
20pub struct MotionStudy {
21 pub drivers: Vec<String>,
24 pub keys: Vec<Vec<f64>>,
27 pub frames: usize,
30}
31
32impl MotionStudy {
33 pub fn validate(&self) -> GeopResult<()> {
37 if self.keys.is_empty() {
38 return Err(GeopError::new("a motion needs a keyframe: add a row"));
39 }
40 for (k, key) in self.keys.iter().enumerate() {
41 if key.len() != self.drivers.len() + 1 {
42 return Err(GeopError::new(format!(
43 "keyframe {} has {} values for {} drivers and its time",
44 k + 1,
45 key.len(),
46 self.drivers.len()
47 )));
48 }
49 if key.iter().any(|v| !v.is_finite()) {
50 return Err(GeopError::new(format!(
51 "keyframe {} holds a value that is no number: {key:?}",
52 k + 1
53 )));
54 }
55 }
56 for (k, pair) in self.keys.windows(2).enumerate() {
57 if pair[1][0] <= pair[0][0] {
58 return Err(GeopError::new(format!(
59 "keyframe {} is at {} s, no later than the one before it at {} s",
60 k + 2,
61 pair[1][0],
62 pair[0][0]
63 )));
64 }
65 }
66 if self.frames < 2 {
67 return Err(GeopError::new(format!(
68 "a motion is played at its start and its end at least: 2 frames, not {}",
69 self.frames
70 )));
71 }
72 Ok(())
73 }
74
75 pub fn span(&self) -> (f64, f64) {
77 let time = |key: Option<&Vec<f64>>| key.and_then(|k| k.first().copied());
78 let start = time(self.keys.first()).unwrap_or(0.0);
79 (start, time(self.keys.last()).unwrap_or(start))
80 }
81
82 pub fn at(&self, t: f64) -> Vec<(String, f64)> {
86 let after = self.keys.iter().position(|key| key[0] > t);
87 let value = |d: usize| match after {
88 Some(0) => self.keys[0][d + 1],
89 None => self.keys.last().map_or(0.0, |key| key[d + 1]),
90 Some(k) => {
91 let (a, b) = (&self.keys[k - 1], &self.keys[k]);
92 let s = (t - a[0]) / (b[0] - a[0]);
93 a[d + 1] + s * (b[d + 1] - a[d + 1])
94 }
95 };
96 (self.drivers.iter().enumerate())
97 .map(|(d, driver)| (driver.clone(), value(d)))
98 .collect()
99 }
100
101 pub fn times(&self) -> Vec<f64> {
103 let (start, end) = self.span();
104 let frames = self.frames.max(2);
105 (0..frames)
106 .map(|k| start + (end - start) * k as f64 / (frames - 1) as f64)
107 .collect()
108 }
109}
110
111impl Target for MotionStudy {}
113
114impl Kind for MotionStudy {
116 const NAME: &'static str = "motion";
117}
118
119const PREFIX: &str = "motion:";
121
122impl HasElements<MotionStudy> for MotionStudy {
123 type Element<'a> = &'a MotionStudy;
124
125 fn element(&self, name: &str) -> GeopResult<&MotionStudy> {
126 whole(self, name)
127 }
128
129 fn names_target(name: &str) -> bool {
130 name.starts_with(PREFIX)
131 }
132}
133
134pub fn motion_study(step: &str) -> TargetReference<MotionStudy> {
136 TargetReference::new(format!("{PREFIX}{step}"))
137}
138
139pub fn motion_studies<'a>(reader: &Reader<'a>) -> Vec<(String, &'a MotionStudy)> {
142 (reader.targets_of::<MotionStudy>().into_iter())
143 .filter_map(|target| {
144 let step = target.name().strip_prefix(PREFIX)?.to_string();
145 let study = reader
146 .retrieve_element(&ElementReference::whole(target))
147 .ok()?;
148 Some((step, study))
149 })
150 .collect()
151}