use crate::gpu_types::InstanceRaw;
use gizmo_math::Vec3;
#[must_use]
pub fn batch_depth(instances: &[InstanceRaw], cam_pos: Vec3) -> f32 {
if instances.is_empty() {
return 0.0;
}
let mut centroid = Vec3::ZERO;
for inst in instances {
centroid += Vec3::new(inst.model[3][0], inst.model[3][1], inst.model[3][2]);
}
centroid /= instances.len() as f32;
cam_pos.distance(centroid)
}
pub fn sort_back_to_front(instances: &mut [InstanceRaw], cam_pos: Vec3) {
instances.sort_by(|a, b| {
let da = cam_pos.distance_squared(Vec3::new(a.model[3][0], a.model[3][1], a.model[3][2]));
let db = cam_pos.distance_squared(Vec3::new(b.model[3][0], b.model[3][1], b.model[3][2]));
db.partial_cmp(&da).unwrap_or(std::cmp::Ordering::Equal)
});
}
#[cfg(test)]
mod tests {
use super::*;
fn at(x: f32, y: f32, z: f32) -> InstanceRaw {
InstanceRaw::new(
[[1.0, 0.0, 0.0, 0.0], [0.0, 1.0, 0.0, 0.0], [0.0, 0.0, 1.0, 0.0], [x, y, z, 1.0]],
[1.0; 4],
0.5,
0.0,
0.0,
0.0,
0.0,
0.0,
[0.0; 3],
[0.0; 3],
)
}
fn zs(instances: &[InstanceRaw]) -> Vec<f32> {
instances.iter().map(|i| i.model[3][2]).collect()
}
#[test]
fn batch_depth_is_the_distance_to_the_centroid() {
let cam = Vec3::ZERO;
assert!((batch_depth(&[at(0.0, 0.0, -10.0)], cam) - 10.0).abs() < 1e-3);
let d = batch_depth(&[at(3.0, 0.0, -4.0), at(-3.0, 0.0, -4.0)], cam);
assert!((d - 4.0).abs() < 1e-3, "got {d}");
assert_eq!(batch_depth(&[], cam), 0.0, "an empty batch sorts at zero");
}
#[test]
fn sorting_is_independent_of_the_order_they_arrived_in() {
let cam = Vec3::new(0.0, 0.0, 10.0);
let mut near_first = vec![at(0.0, 0.0, 5.0), at(0.0, 0.0, -5.0), at(0.0, 0.0, 0.0)];
let mut far_first = vec![at(0.0, 0.0, -5.0), at(0.0, 0.0, 0.0), at(0.0, 0.0, 5.0)];
sort_back_to_front(&mut near_first, cam);
sort_back_to_front(&mut far_first, cam);
assert_eq!(zs(&near_first), vec![-5.0, 0.0, 5.0], "farthest painted first");
assert_eq!(zs(&near_first), zs(&far_first), "the arrival order must not survive");
}
}