1use std::collections::BTreeMap;
10
11use crate::math::Vec3;
12use crate::model::Quad;
13use crate::wire::{Anchor, Mesh, Mode, Ops};
14
15#[derive(Clone, Copy)]
17struct Face<'a> {
18 texture: &'a str,
19 quad: Quad,
20}
21
22fn faces(meshes: &[Mesh]) -> Vec<Face<'_>> {
23 meshes.iter().flat_map(|m| m.quads.iter().map(move |q| Face { texture: &m.texture, quad: *q })).collect()
24}
25
26fn middle(faces: &[Face<'_>]) -> Vec3 {
27 if faces.is_empty() {
28 return Vec3::ZERO;
29 }
30 faces.iter().map(|f| f.quad.center()).fold(Vec3::ZERO, |a, b| a + b) * (1.0 / faces.len() as f32)
31}
32
33fn point(at: Vec3, like: &Quad) -> Quad {
35 Quad { pos: [at; 4], uv: like.uv, normal: like.normal }
36}
37
38fn lerp(a: &Quad, b: &Quad, t: f32) -> [Vec3; 4] {
39 [0, 1, 2, 3].map(|i| a.pos[i].lerp(b.pos[i], t))
40}
41
42pub fn blend(ops: &mut Ops, from: &[Mesh], to: &[Mesh], t: f32) {
46 let t = t.clamp(0.0, 1.0);
47 let (a, b) = (faces(from), faces(to));
48 let (ma, mb) = (middle(&a), middle(&b));
49 let mut old: BTreeMap<&str, Vec<Quad>> = BTreeMap::new();
51 let mut new: BTreeMap<&str, Vec<Quad>> = BTreeMap::new();
52 for i in 0..a.len().max(b.len()) {
53 let fa = a.get(i).map(|f| (f.texture, f.quad));
54 let fb = b.get(i).map(|f| (f.texture, f.quad));
55 let (qa, qb) = match (fa, fb) {
56 (Some((_, qa)), Some((_, qb))) => (qa, qb),
57 (Some((_, qa)), None) => (qa, point(mb, &qa)),
58 (None, Some((_, qb))) => (point(ma, &qb), qb),
59 (None, None) => continue,
60 };
61 let pos = lerp(&qa, &qb, t);
62 let normal = qa.normal.lerp(qb.normal, t).normalize();
63 if let Some((tex, q)) = fa {
64 old.entry(tex).or_default().push(Quad { pos, uv: q.uv, normal });
65 }
66 if let Some((tex, q)) = fb {
67 new.entry(tex).or_default().push(Quad { pos, uv: q.uv, normal });
68 }
69 }
70 let alpha = |k: f32| ((k.clamp(0.0, 1.0) * 255.0).round() as u32) << 24 | 0x00FF_FFFF;
71 for (tex, quads) in &old {
72 if t < 1.0 {
73 ops.mesh(&Anchor::Entity, tex, Mode::Translucent, false, alpha(1.0 - t), quads);
74 }
75 }
76 for (tex, quads) in &new {
77 if t > 0.0 {
78 ops.mesh(&Anchor::Entity, tex, Mode::Translucent, false, alpha(t), quads);
79 }
80 }
81}
82
83pub fn ease(t: f32) -> f32 {
85 let t = t.clamp(0.0, 1.0);
86 t * t * (3.0 - 2.0 * t)
87}
88
89#[cfg(test)]
90mod tests {
91 use super::*;
92
93 fn mesh(tex: &str, at: f32, n: usize) -> Mesh {
94 let q = Quad { pos: [Vec3::new(at, 0.0, 0.0); 4], uv: [[0.0; 2]; 4], normal: Vec3::new(0.0, 1.0, 0.0) };
95 Mesh { texture: tex.into(), quads: vec![q; n] }
96 }
97
98 #[test]
99 fn half_way_draws_both_skins() {
100 let mut ops = Ops::default();
101 blend(&mut ops, &[mesh("a", 0.0, 2)], &[mesh("b", 2.0, 3)], 0.5);
102 assert!(ops.0.iter().filter(|b| **b == 3).count() >= 2);
103 let mut none = Ops::default();
104 blend(&mut none, &[mesh("a", 0.0, 2)], &[mesh("b", 2.0, 3)], 0.0);
105 assert!(!none.is_empty());
106 }
107}