1use alloc::format;
13use alloc::string::String;
14use alloc::string::ToString;
15use alloc::vec;
16use alloc::vec::Vec;
17
18#[derive(Copy, Clone, Debug, Default, PartialEq)]
21pub struct MorphDelta {
22 pub position: [f32; 3],
24 pub normal: [f32; 3],
26}
27
28#[derive(Copy, Clone, Debug, Default, PartialEq, bytemuck::NoUninit)]
32#[repr(C)]
33pub struct MorphEntry {
34 pub target: u32,
36 pub position: [f32; 3],
38 pub normal: [f32; 3],
40}
41
42pub const MORPH_DELTA_EPSILON: f32 = 1e-6;
46
47#[derive(Clone, Debug, Default, PartialEq)]
50pub struct PayloadMorphs {
51 pub names: Vec<String>,
53 pub offsets: Vec<u32>,
56 pub entries: Vec<MorphEntry>,
58}
59
60impl PayloadMorphs {
61 pub fn is_empty(&self) -> bool {
63 self.names.is_empty()
64 }
65
66 pub fn target_count(&self) -> usize {
68 self.names.len()
69 }
70
71 pub fn vertex_count(&self) -> usize {
73 self.offsets.len().saturating_sub(1)
74 }
75
76 pub fn from_dense(
80 names: Vec<String>,
81 vertex_count: usize,
82 deltas: &[MorphDelta],
83 ) -> Result<Self, String> {
84 if deltas.len() != names.len() * vertex_count {
85 return Err(format!(
86 "morph_deltas has {} entries; {} target(s) x {} vertices requires {}",
87 deltas.len(),
88 names.len(),
89 vertex_count,
90 names.len() * vertex_count,
91 ));
92 }
93 if names.is_empty() {
94 return Ok(Self::default());
95 }
96 let mut offsets = Vec::with_capacity(vertex_count + 1);
97 let mut entries = Vec::new();
98 offsets.push(0u32);
99 for v in 0..vertex_count {
100 for (t, name_block) in deltas.chunks_exact(vertex_count).enumerate() {
101 let d = name_block[v];
102 if is_significant(&d) {
103 entries.push(MorphEntry {
104 target: t as u32,
105 position: d.position,
106 normal: d.normal,
107 });
108 }
109 }
110 offsets.push(entries.len() as u32);
111 }
112 Ok(Self {
113 names,
114 offsets,
115 entries,
116 })
117 }
118
119 pub fn to_dense(&self) -> Vec<MorphDelta> {
122 let n = self.vertex_count();
123 let mut out = vec![MorphDelta::default(); self.target_count() * n];
124 for (v, e) in self.vertex_entries() {
125 out[e.target as usize * n + v] = MorphDelta {
126 position: e.position,
127 normal: e.normal,
128 };
129 }
130 out
131 }
132
133 pub(crate) fn vertex_entries(&self) -> impl Iterator<Item = (usize, &MorphEntry)> {
135 self.offsets.windows(2).enumerate().flat_map(move |(v, w)| {
136 self.entries[w[0] as usize..w[1] as usize]
137 .iter()
138 .map(move |e| (v, e))
139 })
140 }
141
142 pub fn validate(&self) -> Result<(), String> {
145 if self.is_empty() {
146 if !self.offsets.is_empty() || !self.entries.is_empty() {
147 return Err("morph block has entries but no targets".to_string());
148 }
149 return Ok(());
150 }
151 if self.offsets.first() != Some(&0) {
152 return Err("morph offsets must start at 0".to_string());
153 }
154 if self.offsets.windows(2).any(|w| w[1] < w[0]) {
155 return Err("morph offsets must not decrease".to_string());
156 }
157 if self.offsets.last().copied().unwrap_or(0) as usize != self.entries.len() {
158 return Err(format!(
159 "morph offsets end at {} but there are {} entries",
160 self.offsets.last().copied().unwrap_or(0),
161 self.entries.len()
162 ));
163 }
164 let targets = self.target_count() as u32;
165 if let Some(e) = self.entries.iter().find(|e| e.target >= targets) {
166 return Err(format!(
167 "morph entry names target {} of {targets}",
168 e.target
169 ));
170 }
171 Ok(())
172 }
173
174 pub fn packed_words(&self) -> Vec<u32> {
178 if self.is_empty() {
179 return Vec::new();
180 }
181 let base = entry_word_base(self.vertex_count());
182 let mut words = Vec::with_capacity(base + self.entries.len() * MORPH_ENTRY_WORDS);
183 words.extend_from_slice(&self.offsets);
184 words.resize(base, 0);
185 words.extend_from_slice(bytemuck::cast_slice::<MorphEntry, u32>(&self.entries));
186 words
187 }
188}
189
190pub(crate) const MORPH_ENTRY_WORDS: usize = 7;
192
193pub(crate) fn entry_word_base(vertex_count: usize) -> usize {
197 (vertex_count + 1 + 3) & !3
198}
199
200fn is_significant(d: &MorphDelta) -> bool {
201 d.position
202 .iter()
203 .chain(d.normal.iter())
204 .any(|x| x.abs() > MORPH_DELTA_EPSILON)
205}
206
207#[cfg(test)]
208mod tests {
209 use super::*;
210
211 fn delta(p: f32) -> MorphDelta {
212 MorphDelta {
213 position: [p, 0.0, 0.0],
214 normal: [0.0, 0.0, 0.0],
215 }
216 }
217
218 fn sample() -> PayloadMorphs {
219 let dense = vec![
221 delta(1.0),
222 delta(0.0),
223 delta(2.0),
224 delta(0.0),
225 delta(0.0),
226 MorphDelta {
227 position: [0.0; 3],
228 normal: [0.0, 0.5, 0.0],
229 },
230 ];
231 PayloadMorphs::from_dense(vec!["a".into(), "b".into()], 3, &dense).expect("dense")
232 }
233
234 #[test]
235 fn sparsifies_and_expands_to_the_same_dense_block() {
236 let m = sample();
237 assert_eq!(m.offsets, vec![0, 1, 1, 3]);
238 assert_eq!(m.entries.len(), 3);
239 assert_eq!(m.entries[1].target, 0);
240 assert_eq!(m.entries[2].target, 1);
241 assert_eq!(m.entries[2].normal, [0.0, 0.5, 0.0]);
242 m.validate().expect("valid");
243 let dense = m.to_dense();
244 assert_eq!(dense.len(), 6);
245 assert_eq!(dense[2], delta(2.0));
246 assert_eq!(dense[5].normal, [0.0, 0.5, 0.0]);
247 assert_eq!(dense[1], MorphDelta::default());
248 let again = PayloadMorphs::from_dense(m.names.clone(), 3, &dense).expect("dense");
249 assert_eq!(again, m);
250 }
251
252 #[test]
253 fn deltas_at_the_epsilon_are_dropped_but_any_component_above_it_is_kept() {
254 let dense = vec![
255 MorphDelta {
256 position: [MORPH_DELTA_EPSILON; 3],
257 normal: [0.0; 3],
258 },
259 MorphDelta {
260 position: [0.0; 3],
261 normal: [0.0, 0.0, -MORPH_DELTA_EPSILON * 2.0],
262 },
263 ];
264 let m = PayloadMorphs::from_dense(vec!["t".into()], 2, &dense).expect("dense");
265 assert_eq!(m.entries.len(), 1);
266 assert_eq!(m.offsets, vec![0, 0, 1]);
267 }
268
269 #[test]
270 fn no_targets_is_the_empty_block() {
271 let m = PayloadMorphs::from_dense(Vec::new(), 5, &[]).expect("dense");
272 assert!(m.is_empty());
273 assert_eq!(m, PayloadMorphs::default());
274 assert!(m.to_dense().is_empty());
275 assert!(m.packed_words().is_empty());
276 }
277
278 #[test]
279 fn a_dense_block_of_the_wrong_length_is_refused() {
280 let err = PayloadMorphs::from_dense(vec!["t".into()], 3, &[delta(1.0)]).unwrap_err();
281 assert!(err.contains("1 target(s) x 3 vertices requires 3"), "{err}");
282 }
283
284 #[test]
285 fn validate_catches_every_table_disagreement() {
286 let mut m = sample();
287 m.offsets[0] = 1;
288 assert!(m.validate().unwrap_err().contains("start at 0"));
289 let mut m = sample();
290 m.offsets[2] = 0;
291 assert!(m.validate().unwrap_err().contains("not decrease"));
292 let mut m = sample();
293 m.offsets[3] = 2;
294 assert!(m.validate().unwrap_err().contains("end at 2"));
295 let mut m = sample();
296 m.entries[0].target = 2;
297 assert!(m.validate().unwrap_err().contains("target 2 of 2"));
298 let mut m = sample();
299 m.names.clear();
300 assert!(m.validate().unwrap_err().contains("no targets"));
301 }
302
303 #[test]
304 fn packed_words_place_entries_at_the_aligned_base() {
305 let m = sample();
306 assert_eq!(entry_word_base(3), 4);
308 assert_eq!(entry_word_base(4), 8);
309 assert_eq!(entry_word_base(0), 4);
310 let words = m.packed_words();
311 assert_eq!(words.len(), 4 + 3 * MORPH_ENTRY_WORDS);
312 assert_eq!(&words[..4], &[0, 1, 1, 3]);
313 assert_eq!(words[4], 0, "entry 0 target");
314 assert_eq!(f32::from_bits(words[5]), 1.0, "entry 0 position.x");
315 assert_eq!(words[4 + 2 * MORPH_ENTRY_WORDS], 1, "entry 2 target");
316 assert_eq!(
317 f32::from_bits(words[4 + 2 * MORPH_ENTRY_WORDS + 5]),
318 0.5,
319 "entry 2 normal.y"
320 );
321 }
322
323 #[test]
324 fn morph_entry_layout_matches_shaders() {
325 use core::mem::{offset_of, size_of};
329 assert_eq!(size_of::<MorphEntry>(), MORPH_ENTRY_WORDS * 4);
330 assert_eq!(offset_of!(MorphEntry, target), 0);
331 assert_eq!(offset_of!(MorphEntry, position), 4);
332 assert_eq!(offset_of!(MorphEntry, normal), 16);
333 }
334}