use std::collections::HashMap;
use std::hash::{BuildHasherDefault, Hasher};
use super::collect::Instance3D;
#[derive(Default)]
pub(crate) struct IdHasher(u64);
impl Hasher for IdHasher {
fn finish(&self) -> u64 {
self.0
}
fn write(&mut self, bytes: &[u8]) {
for &b in bytes {
self.0 = (self.0 ^ u64::from(b)).wrapping_mul(0x100_0000_01b3);
}
}
fn write_u64(&mut self, n: u64) {
self.0 = n.wrapping_mul(0x9E37_79B9_7F4A_7C15);
}
}
type Models = HashMap<u64, [[f32; 4]; 4], BuildHasherDefault<IdHasher>>;
#[repr(C)]
#[derive(Clone, Copy, Debug, Default, bytemuck::Pod, bytemuck::Zeroable)]
pub(crate) struct MovedInstance {
pub instance: Instance3D,
pub previous: [[f32; 4]; 4],
}
#[derive(Default)]
pub(crate) struct Motion {
pub enabled: bool,
pub reset: bool,
last: Models,
now: Models,
moved: Vec<(u32, MovedInstance)>,
pub instances: Vec<MovedInstance>,
pub runs: Vec<(u32, u32, u32)>,
}
pub(crate) const NO_KEY: u64 = u64::MAX;
impl Motion {
pub fn begin(&mut self) {
self.moved.clear();
self.instances.clear();
self.runs.clear();
if !self.enabled {
self.last.clear();
self.now.clear();
return;
}
std::mem::swap(&mut self.last, &mut self.now);
self.now.clear();
}
pub fn note(&mut self, key: u64, mesh: u32, instance: &Instance3D) {
if !self.enabled || key == NO_KEY {
return;
}
self.now.insert(key, instance.model);
if let Some(&before) = self.last.get(&key) {
let mut previous = before;
previous[0][3] = instance.model[0][3];
if previous != instance.model {
self.moved.push((
mesh,
MovedInstance {
instance: *instance,
previous,
},
));
}
}
}
pub fn finish(&mut self) {
self.moved.sort_unstable_by_key(|m| m.0);
for &(mesh, moved) in &self.moved {
match self.runs.last_mut() {
Some(run) if run.0 == mesh => run.2 += 1,
_ => self.runs.push((mesh, self.instances.len() as u32, 1)),
}
self.instances.push(moved);
}
}
}
#[cfg(test)]
mod tests {
use super::*;
fn at(x: f32) -> Instance3D {
let mut model = glam::Mat4::from_translation(glam::Vec3::new(x, 0.0, 0.0)).to_cols_array_2d();
model[0][3] = 3.0; Instance3D { model, color: [1.0; 4] }
}
#[test]
fn only_models_whose_matrix_changed_are_listed() {
let mut motion = Motion {
enabled: true,
..Default::default()
};
for x in [0.0, 1.0] {
motion.begin();
motion.note(1, 5, &at(0.0));
motion.note(2, 5, &at(x));
motion.note(3, 4, &at(x * 2.0));
motion.note(NO_KEY, 4, &at(x * 3.0));
motion.finish();
}
assert_eq!(motion.runs, vec![(4, 0, 1), (5, 1, 1)]);
assert_eq!(motion.instances[1].previous[3][0], 0.0);
assert_eq!(motion.instances[1].instance.model[3][0], 1.0);
assert_eq!(motion.instances[1].previous[0][3], 3.0);
}
#[test]
fn rotation_animation_preserves_the_previous_matrix() {
let mut motion = Motion {
enabled: true,
..Default::default()
};
let first = at(0.0);
let mut rotated = first;
rotated.model = glam::Mat4::from_rotation_y(0.7).to_cols_array_2d();
rotated.model[0][3] = first.model[0][3];
for instance in [&first, &rotated] {
motion.begin();
motion.note(7, 2, instance);
motion.finish();
}
assert_eq!(motion.instances.len(), 1);
assert_eq!(motion.instances[0].previous, first.model);
assert_eq!(motion.instances[0].instance.model, rotated.model);
}
#[test]
fn a_first_sighting_has_no_motion_and_disabled_keeps_nothing() {
let mut motion = Motion::default();
motion.begin();
motion.note(1, 0, &at(0.0));
motion.finish();
assert!(motion.now.is_empty());
motion.enabled = true;
motion.begin();
motion.note(1, 0, &at(2.0));
motion.finish();
assert!(motion.runs.is_empty());
motion.begin();
motion.note(1, 0, &at(3.0));
motion.finish();
assert_eq!(motion.runs, vec![(0, 0, 1)]);
}
}