1use bincode::{Decode, Encode};
5use core::fmt::Debug;
6use cu29::prelude::*;
7use cu29::units::si::area::square_meter;
8use cu29::units::si::f32::Area as Area32;
9use cu29::units::si::f32::Length as Length32;
10use cu29::units::si::f64::Area as Area64;
11use cu29::units::si::f64::Length as Length64;
12use cu29::units::si::length::meter;
13use cu29_soa_derive::Soa;
14use serde::{Deserialize, Serialize};
15
16#[derive(
21 Default, Debug, Clone, Copy, PartialEq, Encode, Decode, Serialize, Deserialize, Reflect, Soa,
22)]
23pub struct Point2<L: Copy + Debug + 'static> {
24 pub x: L,
25 pub y: L,
26}
27
28#[derive(
33 Default, Debug, Clone, Copy, PartialEq, Encode, Decode, Serialize, Deserialize, Reflect, Soa,
34)]
35pub struct Point3<L: Copy + Debug + 'static> {
36 pub x: L,
37 pub y: L,
38 pub z: L,
39}
40
41pub type Point2f = Point2<Length32>;
42pub type Point2d = Point2<Length64>;
43pub type Point3f = Point3<Length32>;
44pub type Point3d = Point3<Length64>;
45
46pub type Point2u = Point2<u32>;
48pub type Point2i = Point2<i32>;
50
51pub type Point2fSoa<const N: usize> = Point2Soa<Length32, N>;
52pub type Point2dSoa<const N: usize> = Point2Soa<Length64, N>;
53pub type Point3fSoa<const N: usize> = Point3Soa<Length32, N>;
54pub type Point3dSoa<const N: usize> = Point3Soa<Length64, N>;
55pub type Point2uSoa<const N: usize> = Point2Soa<u32, N>;
56pub type Point2iSoa<const N: usize> = Point2Soa<i32, N>;
57
58impl<L: Copy + Debug + 'static> Point2<L> {
59 pub const fn new(x: L, y: L) -> Self {
60 Self { x, y }
61 }
62
63 pub fn with_z(self, z: L) -> Point3<L> {
65 Point3::new(self.x, self.y, z)
66 }
67}
68
69impl<L: Copy + Debug + 'static> Point3<L> {
70 pub const fn new(x: L, y: L, z: L) -> Self {
71 Self { x, y, z }
72 }
73
74 pub fn xy(self) -> Point2<L> {
76 Point2::new(self.x, self.y)
77 }
78}
79
80macro_rules! impl_point_metrics {
81 ($len:ty, $area:ty, $scalar:ty, $sqrt:path) => {
82 impl Point2<$len> {
83 pub fn from_meters(x: $scalar, y: $scalar) -> Self {
84 Self::new(<$len>::new::<meter>(x), <$len>::new::<meter>(y))
85 }
86
87 pub fn distance(self, other: Self) -> $len {
89 let (dx, dy) = ((self.x - other.x).raw(), (self.y - other.y).raw());
90 <$len>::new::<meter>($sqrt(dx * dx + dy * dy))
91 }
92
93 pub fn lerp(self, other: Self, ratio: $scalar) -> Self {
96 Self::new(
97 self.x + (other.x - self.x) * ratio,
98 self.y + (other.y - self.y) * ratio,
99 )
100 }
101 }
102
103 impl Point3<$len> {
104 pub fn from_meters(x: $scalar, y: $scalar, z: $scalar) -> Self {
105 Self::new(
106 <$len>::new::<meter>(x),
107 <$len>::new::<meter>(y),
108 <$len>::new::<meter>(z),
109 )
110 }
111
112 pub fn distance(self, other: Self) -> $len {
114 let (dx, dy, dz) = (
115 (self.x - other.x).raw(),
116 (self.y - other.y).raw(),
117 (self.z - other.z).raw(),
118 );
119 <$len>::new::<meter>($sqrt(dx * dx + dy * dy + dz * dz))
120 }
121
122 pub fn lerp(self, other: Self, ratio: $scalar) -> Self {
125 Self::new(
126 self.x + (other.x - self.x) * ratio,
127 self.y + (other.y - self.y) * ratio,
128 self.z + (other.z - self.z) * ratio,
129 )
130 }
131 }
132
133 impl<const N: usize> Point2Soa<$len, N> {
134 pub fn distances_squared(&self, target: Point2<$len>, out: &mut [$area]) {
141 let n = self.len();
142 let (xs, ys, out) = (&self.x[..n], &self.y[..n], &mut out[..n]);
143 let (tx, ty) = (target.x.raw(), target.y.raw());
144 for i in 0..n {
145 let (dx, dy) = (xs[i].raw() - tx, ys[i].raw() - ty);
146 out[i] = <$area>::new::<square_meter>(dx * dx + dy * dy);
147 }
148 }
149
150 pub fn distances(&self, target: Point2<$len>, out: &mut [$len]) {
155 let n = self.len();
156 let (xs, ys, out) = (&self.x[..n], &self.y[..n], &mut out[..n]);
157 let (tx, ty) = (target.x.raw(), target.y.raw());
158 for i in 0..n {
159 let (dx, dy) = (xs[i].raw() - tx, ys[i].raw() - ty);
160 out[i] = <$len>::new::<meter>($sqrt(dx * dx + dy * dy));
161 }
162 }
163
164 pub fn lerp_toward(&mut self, target: Point2<$len>, ratio: $scalar) {
166 let n = self.len();
167 for i in 0..n {
168 self.x[i] = self.x[i] + (target.x - self.x[i]) * ratio;
169 self.y[i] = self.y[i] + (target.y - self.y[i]) * ratio;
170 }
171 }
172 }
173
174 impl<const N: usize> Point3Soa<$len, N> {
175 pub fn distances_squared(&self, target: Point3<$len>, out: &mut [$area]) {
182 let n = self.len();
183 let (xs, ys, zs) = (&self.x[..n], &self.y[..n], &self.z[..n]);
184 let out = &mut out[..n];
185 let (tx, ty, tz) = (target.x.raw(), target.y.raw(), target.z.raw());
186 for i in 0..n {
187 let (dx, dy, dz) = (xs[i].raw() - tx, ys[i].raw() - ty, zs[i].raw() - tz);
188 out[i] = <$area>::new::<square_meter>(dx * dx + dy * dy + dz * dz);
189 }
190 }
191
192 pub fn distances(&self, target: Point3<$len>, out: &mut [$len]) {
197 let n = self.len();
198 let (xs, ys, zs) = (&self.x[..n], &self.y[..n], &self.z[..n]);
199 let out = &mut out[..n];
200 let (tx, ty, tz) = (target.x.raw(), target.y.raw(), target.z.raw());
201 for i in 0..n {
202 let (dx, dy, dz) = (xs[i].raw() - tx, ys[i].raw() - ty, zs[i].raw() - tz);
203 out[i] = <$len>::new::<meter>($sqrt(dx * dx + dy * dy + dz * dz));
204 }
205 }
206
207 pub fn lerp_toward(&mut self, target: Point3<$len>, ratio: $scalar) {
209 let n = self.len();
210 for i in 0..n {
211 self.x[i] = self.x[i] + (target.x - self.x[i]) * ratio;
212 self.y[i] = self.y[i] + (target.y - self.y[i]) * ratio;
213 self.z[i] = self.z[i] + (target.z - self.z[i]) * ratio;
214 }
215 }
216 }
217 };
218}
219
220impl_point_metrics!(Length32, Area32, f32, libm::sqrtf);
221impl_point_metrics!(Length64, Area64, f64, libm::sqrt);
222
223#[derive(
228 Default, Debug, Clone, Copy, PartialEq, Encode, Decode, Serialize, Deserialize, Reflect,
229)]
230pub struct BBox<P: Copy + Debug + 'static> {
231 pub min: P,
232 pub max: P,
233}
234
235pub type BBox2f = BBox<Point2f>;
236pub type BBox2d = BBox<Point2d>;
237pub type BBox3f = BBox<Point3f>;
238pub type BBox3d = BBox<Point3d>;
239
240pub type BBox2u = BBox<Point2u>;
242pub type BBox2i = BBox<Point2i>;
244
245impl<P: Copy + Debug + 'static> BBox<P> {
246 pub const fn new(min: P, max: P) -> Self {
247 Self { min, max }
248 }
249}
250
251impl<L: Copy + Debug + PartialOrd + 'static> BBox<Point2<L>> {
252 pub fn contains(&self, p: Point2<L>) -> bool {
254 self.min.x <= p.x && p.x <= self.max.x && self.min.y <= p.y && p.y <= self.max.y
255 }
256
257 pub fn contains_points<const N: usize>(&self, points: &Point2Soa<L, N>, out: &mut [bool]) {
262 let n = points.len();
263 let (xs, ys, out) = (&points.x[..n], &points.y[..n], &mut out[..n]);
264 for i in 0..n {
265 out[i] = (self.min.x <= xs[i])
266 & (xs[i] <= self.max.x)
267 & (self.min.y <= ys[i])
268 & (ys[i] <= self.max.y);
269 }
270 }
271}
272
273impl<L: Copy + Debug + PartialOrd + 'static> BBox<Point3<L>> {
274 pub fn contains(&self, p: Point3<L>) -> bool {
276 self.min.x <= p.x
277 && p.x <= self.max.x
278 && self.min.y <= p.y
279 && p.y <= self.max.y
280 && self.min.z <= p.z
281 && p.z <= self.max.z
282 }
283
284 pub fn contains_points<const N: usize>(&self, points: &Point3Soa<L, N>, out: &mut [bool]) {
289 let n = points.len();
290 let (xs, ys, zs) = (&points.x[..n], &points.y[..n], &points.z[..n]);
291 let out = &mut out[..n];
292 for i in 0..n {
293 out[i] = (self.min.x <= xs[i])
294 & (xs[i] <= self.max.x)
295 & (self.min.y <= ys[i])
296 & (ys[i] <= self.max.y)
297 & (self.min.z <= zs[i])
298 & (zs[i] <= self.max.z);
299 }
300 }
301}
302
303#[cfg(test)]
304mod tests {
305 use super::*;
306
307 #[test]
308 fn point_distance_and_lerp() {
309 let a = Point2f::from_meters(1.0, 2.0);
310 let b = Point2f::from_meters(4.0, 6.0);
311 assert_eq!(a.distance(b).raw(), 5.0);
312 assert_eq!(a.lerp(b, 0.0), a);
314 assert!((a.lerp(b, 1.0).distance(b)).raw() <= 1e-6);
315 assert_eq!(a.lerp(b, 0.5), Point2f::from_meters(2.5, 4.0));
316
317 let a = Point3d::from_meters(1.0, 2.0, 3.0);
318 let b = Point3d::from_meters(3.0, 5.0, 9.0);
319 assert_eq!(a.distance(b).raw(), 7.0);
320 assert_eq!(a.lerp(b, 0.5), Point3d::from_meters(2.0, 3.5, 6.0));
321 }
322
323 #[test]
324 fn point_dimension_conversions() {
325 let p = Point3f::from_meters(1.0, 2.0, 3.0);
326 assert_eq!(p.xy(), Point2f::from_meters(1.0, 2.0));
327 assert_eq!(p.xy().with_z(p.z), p);
328 }
329
330 #[test]
331 fn bbox_contains_boundary_included() {
332 let b = BBox2f::new(
333 Point2f::from_meters(0.0, 0.0),
334 Point2f::from_meters(2.0, 2.0),
335 );
336 assert!(b.contains(Point2f::from_meters(1.0, 1.0)));
337 assert!(b.contains(Point2f::from_meters(0.0, 2.0)));
338 assert!(!b.contains(Point2f::from_meters(-0.1, 1.0)));
339 assert!(!b.contains(Point2f::from_meters(1.0, 2.1)));
340
341 let b = BBox3f::new(
342 Point3f::from_meters(0.0, 0.0, 0.0),
343 Point3f::from_meters(1.0, 1.0, 1.0),
344 );
345 assert!(b.contains(Point3f::from_meters(0.5, 0.5, 1.0)));
346 assert!(!b.contains(Point3f::from_meters(0.5, 0.5, 1.1)));
347 }
348
349 #[test]
350 fn pixel_bbox_contains() {
351 let b = BBox2u::new(Point2u::new(10, 20), Point2u::new(110, 220));
352 assert!(b.contains(Point2u::new(10, 220)));
353 assert!(b.contains(Point2u::new(60, 120)));
354 assert!(!b.contains(Point2u::new(9, 120)));
355 assert!(!b.contains(Point2u::new(60, 221)));
356 }
357
358 #[test]
359 fn soa_distances_match_scalar() {
360 let mut set = Point2fSoa::<8>::default();
361 let target = Point2f::from_meters(1.0, -2.0);
362 let points = [(0.0, 0.0), (3.0, 2.0), (-1.5, 4.0)];
363 for (x, y) in points {
364 set.push(Point2f::from_meters(x, y));
365 }
366
367 let mut d = [Length32::default(); 3];
368 let mut d2 = [Area32::default(); 3];
369 set.distances(target, &mut d);
370 set.distances_squared(target, &mut d2);
371 for i in 0..set.len() {
372 let scalar = set.get(i).distance(target);
373 assert!((d[i] - scalar).raw().abs() < 1e-6);
374 assert!((d2[i].raw() - scalar.raw() * scalar.raw()).abs() < 1e-5);
375 }
376
377 let mut set3 = Point3dSoa::<4>::default();
378 set3.push(Point3d::from_meters(1.0, 2.0, 3.0));
379 set3.push(Point3d::from_meters(-2.0, 0.5, 1.0));
380 let target3 = Point3d::from_meters(0.0, 1.0, -1.0);
381 let mut d3 = [Length64::default(); 2];
382 set3.distances(target3, &mut d3);
383 for (i, d) in d3.iter().enumerate() {
384 assert!((*d - set3.get(i).distance(target3)).raw().abs() < 1e-12);
385 }
386 }
387
388 #[test]
389 fn soa_lerp_toward_matches_scalar() {
390 let mut set = Point2fSoa::<4>::default();
391 set.push(Point2f::from_meters(0.0, 0.0));
392 set.push(Point2f::from_meters(4.0, -2.0));
393 let target = Point2f::from_meters(2.0, 2.0);
394
395 let expected: [Point2f; 2] = [set.get(0).lerp(target, 0.25), set.get(1).lerp(target, 0.25)];
396 set.lerp_toward(target, 0.25);
397 assert_eq!(set.get(0), expected[0]);
398 assert_eq!(set.get(1), expected[1]);
399 }
400
401 #[test]
402 fn bbox_contains_points_bulk() {
403 let b = BBox2f::new(
404 Point2f::from_meters(0.0, 0.0),
405 Point2f::from_meters(2.0, 2.0),
406 );
407 let mut set = Point2fSoa::<4>::default();
408 set.push(Point2f::from_meters(1.0, 1.0));
409 set.push(Point2f::from_meters(0.0, 2.0));
410 set.push(Point2f::from_meters(-0.1, 1.0));
411 let mut out = [false; 3];
412 b.contains_points(&set, &mut out);
413 assert_eq!(out, [true, true, false]);
414
415 let pix = BBox2u::new(Point2u::new(10, 20), Point2u::new(110, 220));
416 let mut pixels = Point2uSoa::<4>::default();
417 pixels.push(Point2u::new(60, 120));
418 pixels.push(Point2u::new(9, 120));
419 let mut out = [false; 2];
420 pix.contains_points(&pixels, &mut out);
421 assert_eq!(out, [true, false]);
422 }
423
424 #[test]
425 fn bbox3_contains_points_bulk_matches_scalar() {
426 let b = BBox3f::new(
427 Point3f::from_meters(0.0, 0.0, 0.0),
428 Point3f::from_meters(1.0, 1.0, 1.0),
429 );
430 let points = [
431 Point3f::from_meters(0.5, 0.5, 1.0), Point3f::from_meters(0.5, 0.5, 1.1), Point3f::from_meters(0.0, 0.0, 0.0), Point3f::from_meters(-0.1, 0.5, 0.5), ];
436 let mut set = Point3fSoa::<8>::default();
437 for p in points {
438 set.push(p);
439 }
440
441 let mut out = [true; 6];
443 b.contains_points(&set, &mut out);
444 assert_eq!(out, [true, false, true, false, true, true]);
445 for (i, p) in points.iter().enumerate() {
446 assert_eq!(out[i], b.contains(*p));
447 }
448 }
449
450 #[test]
451 fn bulk_kernels_handle_an_empty_set() {
452 let set = Point2fSoa::<4>::default();
453 assert!(set.is_empty());
454 set.distances(Point2f::from_meters(1.0, 1.0), &mut []);
455 set.distances_squared(Point2f::from_meters(1.0, 1.0), &mut []);
456 let b = BBox2f::new(
457 Point2f::from_meters(0.0, 0.0),
458 Point2f::from_meters(1.0, 1.0),
459 );
460 b.contains_points(&set, &mut []);
461 }
462
463 #[test]
465 fn decode_rejects_len_over_capacity() {
466 let mut wide = Point2fSoa::<8>::default();
467 for i in 0..8 {
468 wide.push(Point2f::from_meters(i as f32, -(i as f32)));
469 }
470 let cfg = cu29::bincode::config::standard();
471 let bytes = cu29::bincode::encode_to_vec(&wide, cfg).expect("encode");
472
473 let narrow: Result<(Point2fSoa<4>, usize), _> =
474 cu29::bincode::decode_from_slice(&bytes, cfg);
475 assert!(narrow.is_err(), "expected a capacity error");
476
477 let (same, _): (Point2fSoa<8>, _) =
478 cu29::bincode::decode_from_slice(&bytes, cfg).expect("decode");
479 assert_eq!(same.len(), 8);
480 assert_eq!(same.get(7), wide.get(7));
481 }
482}