1use std::collections::BTreeSet;
16
17use serde::Serialize;
18use serde_json::Value;
19
20use crate::{Component, Entity, JsonError, Mesh, World};
21
22#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize)]
24#[serde(transparent)]
25pub struct MorphId(pub u32);
26
27#[derive(Clone, Debug, Default, PartialEq, Serialize)]
30pub struct Deltas {
31 pub indices: Option<Vec<u32>>,
32 pub positions: Option<Vec<[f32; 3]>>,
33 pub normals: Option<Vec<[f32; 3]>>,
34 pub tangents: Option<Vec<[f32; 3]>>,
35}
36
37#[derive(Clone, Debug, PartialEq, Serialize)]
39pub struct Frame {
40 pub weight: f32,
41 pub primitives: Vec<Option<Deltas>>,
42}
43
44#[derive(Clone, Debug, PartialEq, Serialize)]
46pub struct Target {
47 pub id: MorphId,
48 pub name: String,
50 pub default_weight: f32,
52 pub frames: Vec<Frame>,
54}
55
56#[derive(Clone, Debug, Default, PartialEq, Serialize)]
58pub struct Morphs {
59 pub targets: Vec<Target>,
60}
61
62impl Deltas {
63 fn entries(&self) -> Option<usize> {
64 [&self.positions, &self.normals, &self.tangents]
65 .into_iter()
66 .flatten()
67 .map(Vec::len)
68 .next()
69 }
70
71 fn attributes_mut(&mut self) -> impl Iterator<Item = &mut Vec<[f32; 3]>> {
72 [&mut self.positions, &mut self.normals, &mut self.tangents]
73 .into_iter()
74 .flatten()
75 }
76
77 fn remapped(&self, from: &[u32]) -> Result<Deltas, String> {
80 let mut out = self.clone();
81 match &self.indices {
82 None => {
83 for (dst, src) in out.attributes_mut().zip(
84 [&self.positions, &self.normals, &self.tangents]
85 .into_iter()
86 .flatten(),
87 ) {
88 *dst = from
89 .iter()
90 .map(|&i| {
91 src.get(i as usize)
92 .copied()
93 .ok_or_else(|| format!("vertex {i} of {}", src.len()))
94 })
95 .collect::<Result<_, _>>()?;
96 }
97 }
98 Some(indices) => {
99 let picked: Vec<(u32, usize)> = (0u32..)
101 .zip(from)
102 .filter_map(|(new, old)| indices.binary_search(old).ok().map(|k| (new, k)))
103 .collect();
104 out.indices = Some(picked.iter().map(|&(new, _)| new).collect());
105 for (dst, src) in out.attributes_mut().zip(
106 [&self.positions, &self.normals, &self.tangents]
107 .into_iter()
108 .flatten(),
109 ) {
110 *dst = picked.iter().map(|&(_, k)| src[k]).collect();
111 }
112 }
113 }
114 Ok(out)
115 }
116}
117
118impl Deltas {
119 fn blended(&self, from: &[crate::VertexBlend]) -> Result<Deltas, String> {
123 let mut out = self.clone();
124 type Picked = (u32, Vec<(Option<usize>, f32)>);
126 let mut picked: Vec<Picked> = Vec::new();
127 for (new, b) in (0u32..).zip(from) {
128 let terms: Vec<(Option<usize>, f32)> = b
129 .terms()
130 .map(|(old, w)| match &self.indices {
131 None => (Some(old as usize), w),
132 Some(indices) => (indices.binary_search(&old).ok(), w),
133 })
134 .collect();
135 if self.indices.is_none() || terms.iter().any(|(k, _)| k.is_some()) {
136 picked.push((new, terms));
137 }
138 }
139 out.indices = self
140 .indices
141 .as_ref()
142 .map(|_| picked.iter().map(|&(new, _)| new).collect());
143 for (dst, src) in out.attributes_mut().zip(
144 [&self.positions, &self.normals, &self.tangents]
145 .into_iter()
146 .flatten(),
147 ) {
148 *dst = picked
149 .iter()
150 .map(|(_, terms)| {
151 let mut sum = [0.0f64; 3];
152 for &(k, w) in terms {
153 let Some(k) = k else { continue };
154 let d = src.get(k).ok_or_else(|| format!("vertex {k} of {}", src.len()))?;
155 for (s, x) in sum.iter_mut().zip(d) {
156 *s += f64::from(w) * f64::from(*x);
157 }
158 }
159 Ok(sum.map(|x| x as f32))
160 })
161 .collect::<Result<_, String>>()?;
162 }
163 Ok(out)
164 }
165}
166
167impl Morphs {
168 pub fn target(&self, id: MorphId) -> Option<&Target> {
170 self.targets.iter().find(|t| t.id == id)
171 }
172}
173
174impl Component for Morphs {
175 const NAME: &'static str = "core.morphs";
176 const GEOMETRY: crate::Geometry = crate::Geometry::PerVertex;
177 fn to_json(&self) -> Result<Option<Value>, JsonError> {
179 let mut targets = Vec::new();
180 for t in &self.targets {
181 let frames: Vec<f32> = t.frames.iter().map(|f| f.weight).collect();
182 targets.push(serde_json::json!({
183 "id": t.id,
184 "name": t.name,
185 "default_weight": crate::json::to_value(&t.default_weight)?,
186 "frames": crate::json::to_value(&frames)?,
187 }));
188 }
189 Ok(Some(serde_json::json!({ "targets": targets })))
190 }
191 fn payload_bytes(&self) -> u64 {
192 let deltas = self
193 .targets
194 .iter()
195 .flat_map(|t| &t.frames)
196 .flat_map(|f| f.primitives.iter().flatten());
197 deltas
198 .map(|d| {
199 let values: usize = [&d.positions, &d.normals, &d.tangents]
200 .into_iter()
201 .flatten()
202 .map(Vec::len)
203 .sum();
204 values as u64 * 12 + d.indices.as_ref().map_or(0, |i| i.len() as u64 * 4)
205 })
206 .sum()
207 }
208 fn remap_primitives(&self, from: &[usize]) -> Option<Result<Self, String>> {
209 let remapped = (|| {
210 let mut out = self.clone();
211 for f in out.targets.iter_mut().flat_map(|t| &mut t.frames) {
212 f.primitives = crate::remap_list(&f.primitives, from)?;
213 }
214 Ok(out)
215 })();
216 Some(remapped)
217 }
218 fn remap_vertices(&self, primitive: usize, from: &[u32]) -> Option<Result<Self, String>> {
219 let remapped = (|| {
220 let mut out = self.clone();
221 for f in out.targets.iter_mut().flat_map(|t| &mut t.frames) {
222 let n = f.primitives.len();
223 let slot = f
224 .primitives
225 .get_mut(primitive)
226 .ok_or_else(|| format!("primitive {primitive} of {n}"))?;
227 if let Some(d) = slot {
228 *d = d.remapped(from)?;
229 }
230 }
231 Ok(out)
232 })();
233 Some(remapped)
234 }
235 fn blend_vertices(&self, primitive: usize, from: &[crate::VertexBlend]) -> Option<Result<Self, String>> {
236 let blended = (|| {
237 let mut out = self.clone();
238 for f in out.targets.iter_mut().flat_map(|t| &mut t.frames) {
239 let n = f.primitives.len();
240 let slot = f
241 .primitives
242 .get_mut(primitive)
243 .ok_or_else(|| format!("primitive {primitive} of {n}"))?;
244 if let Some(d) = slot {
245 *d = d.blended(from)?;
246 }
247 }
248 Ok(out)
249 })();
250 Some(blended)
251 }
252 fn apply_linear(&self, l: &crate::Linear) -> crate::Linearity<Self> {
256 let directions = self
257 .targets
258 .iter()
259 .flat_map(|t| &t.frames)
260 .flat_map(|f| f.primitives.iter().flatten())
261 .any(|d| d.normals.is_some() || d.tangents.is_some());
262 let scale = l.similarity();
263 if directions && scale.is_none() {
264 return crate::Linearity::Unsupported;
265 }
266 let mut out = self.clone();
267 for d in out
268 .targets
269 .iter_mut()
270 .flat_map(|t| &mut t.frames)
271 .flat_map(|f| f.primitives.iter_mut().flatten())
272 {
273 for p in d.positions.iter_mut().flatten() {
274 *p = l.point(*p);
275 }
276 if let Some(s) = scale {
277 let turn = |v: [f32; 3]| l.point(v).map(|x| (f64::from(x) / s) as f32);
279 for v in d.normals.iter_mut().chain(d.tangents.iter_mut()).flatten() {
280 *v = turn(*v);
281 }
282 }
283 }
284 crate::Linearity::Applied(out)
285 }
286 fn verify(&self, e: Entity, w: &World) -> Result<(), String> {
290 let mut ids = BTreeSet::new();
291 let mesh = w.get::<Mesh>(e);
292 for t in &self.targets {
293 let at = |m: String| format!("target {:?}: {m}", t.id);
294 if !ids.insert(t.id) {
295 return Err(at("listed twice".into()));
296 }
297 if !t.default_weight.is_finite() || t.frames.is_empty() {
298 return Err(at("no frames, or a default weight not finite".into()));
299 }
300 if !t.frames.iter().all(|f| f.weight.is_finite())
301 || !t.frames.windows(2).all(|p| p[0].weight < p[1].weight)
302 {
303 return Err(at("frame weights not finite and increasing".into()));
304 }
305 if t.frames.iter().any(|f| f.weight == 0.0) {
307 return Err(at("a frame at weight 0, where the delta is zero".into()));
308 }
309 for (k, f) in t.frames.iter().enumerate() {
310 if let Some(m) = mesh
311 && m.primitives().len() != f.primitives.len()
312 {
313 return Err(at(format!(
314 "frame {k}: {} entries for {} primitives",
315 f.primitives.len(),
316 m.primitives().len()
317 )));
318 }
319 for (i, d) in f.primitives.iter().enumerate() {
320 let Some(d) = d else { continue };
321 let n = mesh.and_then(|m| m.primitives().get(i)).map(|p| p.vertex_count());
322 let entries = match &d.indices {
323 None => n.unwrap_or_else(|| d.entries().unwrap_or(0)),
324 Some(idx) => {
325 if !idx.windows(2).all(|p| p[0] < p[1]) {
326 return Err(at(format!(
327 "frame {k} primitive {i}: sparse indices not ascending and unique"
328 )));
329 }
330 if let (Some(n), Some(&last)) = (n, idx.last())
331 && last as usize >= n
332 {
333 return Err(at(format!("frame {k} primitive {i}: vertex {last} of {n}")));
334 }
335 idx.len()
336 }
337 };
338 for a in [&d.positions, &d.normals, &d.tangents].into_iter().flatten() {
339 if a.len() != entries {
340 return Err(at(format!(
341 "frame {k} primitive {i}: {} deltas for {entries} entries",
342 a.len()
343 )));
344 }
345 if !a.iter().flatten().all(|x| x.is_finite()) {
346 return Err(at(format!("frame {k} primitive {i}: a delta not finite")));
347 }
348 }
349 }
350 }
351 }
352 Ok(())
353 }
354}
355
356#[cfg(test)]
357mod tests {
358 use super::*;
359 use crate::{Linear, Primitive, Semantic, Stream, Topology};
360
361 fn points(n: usize) -> Primitive {
362 Primitive {
363 topology: Topology::Points,
364 streams: [(Semantic::Position, Stream::F32x3(vec![[0.0; 3]; n]))].into(),
365 indices: None,
366 material: None,
367 }
368 }
369
370 fn morphs() -> Morphs {
373 Morphs {
374 targets: vec![
375 Target {
376 id: MorphId(1),
377 name: "smile".into(),
378 default_weight: 0.0,
379 frames: vec![Frame {
380 weight: 1.0,
381 primitives: vec![
382 Some(Deltas {
383 positions: Some(vec![[1.0, 0.0, 0.0], [2.0, 0.0, 0.0], [3.0, 0.0, 0.0]]),
384 normals: Some(vec![[0.0, 1.0, 0.0]; 3]),
385 ..Deltas::default()
386 }),
387 None,
388 ],
389 }],
390 },
391 Target {
392 id: MorphId(2),
393 name: "blink".into(),
394 default_weight: 0.25,
395 frames: [0.5, 1.0]
396 .map(|weight| Frame {
397 weight,
398 primitives: vec![
399 None,
400 Some(Deltas {
401 indices: Some(vec![1, 2]),
402 positions: Some(vec![[0.0, weight, 0.0], [0.0, 2.0 * weight, 0.0]]),
403 ..Deltas::default()
404 }),
405 ],
406 })
407 .into(),
408 },
409 ],
410 }
411 }
412
413 fn errors(m: Morphs) -> Vec<String> {
414 let mut w = World::new();
415 let e = w.spawn();
416 w.insert(e, Mesh::new(vec![points(3), points(4)]));
417 w.insert(e, m);
418 w.verify_components().into_iter().map(|(_, _, m)| m).collect()
419 }
420
421 #[test]
422 fn morphs_verify_and_refuse_malformed_targets() {
423 assert_eq!(errors(morphs()), Vec::<String>::new());
424 let fails = |edit: &dyn Fn(&mut Morphs), what: &str| {
425 let mut m = morphs();
426 edit(&mut m);
427 let e = errors(m);
428 assert!(e.iter().any(|x| x.contains(what)), "{what}: {e:?}");
429 };
430 fails(&|m| m.targets[1].id = MorphId(1), "listed twice");
431 fails(&|m| m.targets[1].frames.reverse(), "increasing");
432 fails(&|m| m.targets[0].frames.clear(), "no frames");
433 fails(
434 &|m| {
435 m.targets[0].frames[0].primitives[0]
436 .as_mut()
437 .unwrap()
438 .positions
439 .as_mut()
440 .unwrap()
441 .pop()
442 .pipe(drop)
443 },
444 "2 deltas for 3 entries",
445 );
446 fails(
447 &|m| m.targets[1].frames[0].primitives[1].as_mut().unwrap().indices = Some(vec![2, 1]),
448 "not ascending",
449 );
450 fails(
451 &|m| m.targets[1].frames[0].primitives[1].as_mut().unwrap().indices = Some(vec![1, 4]),
452 "vertex 4 of 4",
453 );
454 fails(
455 &|m| m.targets[0].frames[0].primitives.pop().pipe(drop),
456 "1 entries for 2 primitives",
457 );
458 fails(
459 &|m| {
460 m.targets[0].frames[0].primitives[0]
461 .as_mut()
462 .unwrap()
463 .normals
464 .as_mut()
465 .unwrap()[0][0] = f32::NAN
466 },
467 "not finite",
468 );
469 }
470
471 #[test]
475 fn deltas_follow_compacted_vertices_dense_and_sparse() {
476 let m = morphs();
477 let dense = m.remap_vertices(0, &[2, 0]).unwrap().unwrap();
478 let d = dense.targets[0].frames[0].primitives[0].as_ref().unwrap();
479 assert_eq!(d.positions, Some(vec![[3.0, 0.0, 0.0], [1.0, 0.0, 0.0]]));
480 assert_eq!(d.normals.as_ref().map(Vec::len), Some(2));
481 let sparse = m.remap_vertices(1, &[2, 0, 2]).unwrap().unwrap();
482 let s = sparse.targets[1].frames[1].primitives[1].as_ref().unwrap();
483 assert_eq!(s.indices, Some(vec![0, 2]));
484 assert_eq!(s.positions, Some(vec![[0.0, 2.0, 0.0], [0.0, 2.0, 0.0]]));
485 assert!(m.remap_vertices(0, &[5]).unwrap().is_err());
487 let rebuilt = m.remap_primitives(&[1]).unwrap().unwrap();
489 assert!(rebuilt.targets[0].frames[0].primitives == [None]);
490 assert!(rebuilt.targets[1].frames[0].primitives[0].is_some());
491 }
492
493 #[test]
496 fn deltas_follow_blended_vertices_dense_and_sparse() {
497 use crate::VertexBlend;
498 let half = |a, b| VertexBlend {
499 sources: [a, b, b],
500 weights: [0.5, 0.5, 0.0],
501 };
502 let m = morphs();
503 let from = [half(0, 2), VertexBlend::copy(1)];
504 let dense = m.blend_vertices(0, &from).unwrap().unwrap();
505 let d = dense.targets[0].frames[0].primitives[0].as_ref().unwrap();
506 let old = m.targets[0].frames[0].primitives[0].as_ref().unwrap();
507 let (p0, p1, p2) = (
508 old.positions.as_ref().unwrap()[0],
509 old.positions.as_ref().unwrap()[1],
510 old.positions.as_ref().unwrap()[2],
511 );
512 assert_eq!(
513 d.positions,
514 Some(vec![[0, 1, 2].map(|k| (p0[k] + p2[k]) / 2.0), p1])
515 );
516 let from = [half(0, 2), VertexBlend::copy(1), VertexBlend::copy(0)];
519 let sparse = m.blend_vertices(1, &from).unwrap().unwrap();
520 let s = sparse.targets[1].frames[1].primitives[1].as_ref().unwrap();
521 let old = m.targets[1].frames[1].primitives[1].as_ref().unwrap();
522 assert_eq!(s.indices, Some(vec![0, 1]));
523 let p = old.positions.as_ref().unwrap();
524 assert_eq!(s.positions, Some(vec![p[1].map(|x| x / 2.0), p[0]]));
525 assert!(m.blend_vertices(0, &[VertexBlend::copy(9)]).unwrap().is_err());
527 }
528
529 #[test]
532 fn deltas_follow_a_linear_map() {
533 let m = morphs();
534 let turn2 = Linear::new([[0.0, 2.0, 0.0], [-2.0, 0.0, 0.0], [0.0, 0.0, 2.0]]);
535 let crate::Linearity::Applied(t) = m.apply_linear(&turn2) else {
536 panic!("a similarity applies")
537 };
538 let d = t.targets[0].frames[0].primitives[0].as_ref().unwrap();
539 assert_eq!(d.positions.as_ref().unwrap()[0], [0.0, 2.0, 0.0]);
540 assert_eq!(d.normals.as_ref().unwrap()[0], [-1.0, 0.0, 0.0]);
541 let stretch = Linear::new([[2.0, 0.0, 0.0], [0.0, 1.0, 0.0], [0.0, 0.0, 1.0]]);
542 assert_eq!(m.apply_linear(&stretch), crate::Linearity::Unsupported);
543 let mut positions_only = morphs();
545 positions_only.targets[0].frames[0].primitives[0]
546 .as_mut()
547 .unwrap()
548 .normals = None;
549 let crate::Linearity::Applied(s) = positions_only.apply_linear(&stretch) else {
550 panic!("positions alone follow any map")
551 };
552 assert_eq!(
553 s.targets[0].frames[0].primitives[0]
554 .as_ref()
555 .unwrap()
556 .positions
557 .as_ref()
558 .unwrap()[2],
559 [6.0, 0.0, 0.0]
560 );
561 }
562
563 trait Pipe: Sized {
564 fn pipe<R>(self, f: impl FnOnce(Self) -> R) -> R {
565 f(self)
566 }
567 }
568 impl<T> Pipe for T {}
569}