1use crate::mat::Mat4;
8use crate::vec::{Point3, Vec3};
9
10pub fn batch_transform_points(mat: &Mat4, points: &[Point3], out: &mut Vec<Point3>) {
14 out.clear();
15 out.reserve(points.len());
16 for p in points {
17 out.push(mat.mul_point(*p));
18 }
19}
20
21#[must_use]
27pub fn batch_dot(a: &[Vec3], b: &[Vec3]) -> Vec<f64> {
28 debug_assert_eq!(a.len(), b.len());
29 a.iter().zip(b.iter()).map(|(ai, bi)| ai.dot(*bi)).collect()
30}
31
32#[must_use]
38pub fn batch_cross(a: &[Vec3], b: &[Vec3]) -> Vec<Vec3> {
39 debug_assert_eq!(a.len(), b.len());
40 a.iter()
41 .zip(b.iter())
42 .map(|(ai, bi)| ai.cross(*bi))
43 .collect()
44}
45
46#[must_use]
50pub fn batch_normalize(vecs: &[Vec3]) -> Vec<Vec3> {
51 vecs.iter().map(|v| v.normalize().unwrap_or(*v)).collect()
52}
53
54#[must_use]
60pub fn batch_distance_sq(a: &[Point3], b: &[Point3]) -> Vec<f64> {
61 debug_assert_eq!(a.len(), b.len());
62 a.iter()
63 .zip(b.iter())
64 .map(|(ai, bi)| {
65 let d = *ai - *bi;
66 d.length_squared()
67 })
68 .collect()
69}
70
71#[cfg(test)]
72#[allow(clippy::expect_used, clippy::unwrap_used)]
73mod tests {
74 use super::*;
75
76 #[test]
77 fn batch_transform_matches_individual() {
78 let mat = Mat4::translation(1.0, 2.0, 3.0) * Mat4::rotation_z(std::f64::consts::FRAC_PI_4);
79 let points = vec![
80 Point3::new(1.0, 0.0, 0.0),
81 Point3::new(0.0, 1.0, 0.0),
82 Point3::new(0.0, 0.0, 1.0),
83 Point3::new(3.0, 4.0, 5.0),
84 ];
85
86 let mut batch_out = Vec::new();
87 batch_transform_points(&mat, &points, &mut batch_out);
88
89 for (i, p) in points.iter().enumerate() {
90 let expected = mat.mul_point(*p);
91 assert!(
92 (batch_out[i].x() - expected.x()).abs() < 1e-14,
93 "x mismatch at {i}"
94 );
95 assert!(
96 (batch_out[i].y() - expected.y()).abs() < 1e-14,
97 "y mismatch at {i}"
98 );
99 assert!(
100 (batch_out[i].z() - expected.z()).abs() < 1e-14,
101 "z mismatch at {i}"
102 );
103 }
104 }
105
106 #[test]
107 fn batch_dot_matches_individual() {
108 let a = vec![
109 Vec3::new(1.0, 2.0, 3.0),
110 Vec3::new(4.0, 5.0, 6.0),
111 Vec3::new(-1.0, 0.0, 1.0),
112 ];
113 let b = vec![
114 Vec3::new(4.0, 5.0, 6.0),
115 Vec3::new(1.0, 2.0, 3.0),
116 Vec3::new(2.0, 3.0, 4.0),
117 ];
118
119 let results = batch_dot(&a, &b);
120
121 for (i, (ai, bi)) in a.iter().zip(b.iter()).enumerate() {
122 let expected = ai.dot(*bi);
123 assert!((results[i] - expected).abs() < 1e-14, "dot mismatch at {i}");
124 }
125 }
126
127 #[test]
128 fn batch_distance_matches_individual() {
129 let a = vec![
130 Point3::new(1.0, 2.0, 3.0),
131 Point3::new(0.0, 0.0, 0.0),
132 Point3::new(-1.0, -2.0, -3.0),
133 ];
134 let b = vec![
135 Point3::new(4.0, 6.0, 3.0),
136 Point3::new(1.0, 1.0, 1.0),
137 Point3::new(1.0, 2.0, 3.0),
138 ];
139
140 let results = batch_distance_sq(&a, &b);
141
142 for (i, (ai, bi)) in a.iter().zip(b.iter()).enumerate() {
143 let d = *ai - *bi;
144 let expected = d.length_squared();
145 assert!(
146 (results[i] - expected).abs() < 1e-14,
147 "distance_sq mismatch at {i}"
148 );
149 }
150 }
151}