Skip to main content

math_geometry_2d/
lib.rs

1#![doc = include_str!("../README.md")]
2
3pub mod surface;
4use std::ops::{Add, AddAssign, Div, Mul, Sub};
5
6use serde::{Deserialize, Serialize};
7
8#[derive(Debug, Clone, PartialEq, Eq)]
9/// Error type for validated 2D geometry contracts.
10pub enum GeometryError {
11    /// Invalid dimensions.
12    InvalidDimensions {
13        /// Width in pixels.
14        width: u32,
15        /// Height in pixels.
16        height: u32,
17    },
18    /// Invalid argument.
19    InvalidArgument(String),
20}
21
22impl std::fmt::Display for GeometryError {
23    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
24        match self {
25            Self::InvalidDimensions { width, height } => {
26                write!(f, "invalid dimensions: {width}x{height}")
27            }
28            Self::InvalidArgument(message) => f.write_str(message),
29        }
30    }
31}
32
33impl std::error::Error for GeometryError {}
34
35/// Result type for 2D geometry operations.
36pub type Result<T> = std::result::Result<T, GeometryError>;
37
38fn invalid_argument(message: impl Into<String>) -> GeometryError {
39    GeometryError::InvalidArgument(message.into())
40}
41
42#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize)]
43/// Data type for point2f.
44pub struct Point2f {
45    /// The x value.
46    pub x: f32,
47    /// The y value.
48    pub y: f32,
49}
50
51impl Point2f {
52    /// Creates a new value.
53    pub fn new(x: f32, y: f32) -> Result<Self> {
54        let point = Self { x, y };
55        point.validate()?;
56        Ok(point)
57    }
58
59    /// Validates this value.
60    pub fn validate(self) -> Result<()> {
61        if !self.x.is_finite() || !self.y.is_finite() {
62            return Err(invalid_argument("2D point coordinates must be finite"));
63        }
64        Ok(())
65    }
66
67    /// Returns translate.
68    pub fn translate(self, delta: Vector2f) -> Result<Self> {
69        delta.validate()?;
70        Self::new(self.x + delta.x, self.y + delta.y)
71    }
72
73    /// Converts this value to normalized.
74    pub fn to_normalized(self, size: Size2u) -> Result<NormalizedPoint2> {
75        size.validate()?;
76        NormalizedPoint2::new(self.x / size.width as f32, self.y / size.height as f32)
77    }
78}
79
80impl Add<Vector2f> for Point2f {
81    type Output = Self;
82
83    fn add(self, rhs: Vector2f) -> Self::Output {
84        Self {
85            x: self.x + rhs.x,
86            y: self.y + rhs.y,
87        }
88    }
89}
90
91impl Sub<Vector2f> for Point2f {
92    type Output = Self;
93
94    fn sub(self, rhs: Vector2f) -> Self::Output {
95        Self {
96            x: self.x - rhs.x,
97            y: self.y - rhs.y,
98        }
99    }
100}
101
102impl Sub<Point2f> for Point2f {
103    type Output = Vector2f;
104
105    fn sub(self, rhs: Point2f) -> Self::Output {
106        Vector2f {
107            x: self.x - rhs.x,
108            y: self.y - rhs.y,
109        }
110    }
111}
112
113#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
114/// Data type for point2i.
115pub struct Point2i {
116    /// The x value.
117    pub x: i32,
118    /// The y value.
119    pub y: i32,
120}
121
122impl Point2i {
123    /// Creates a new value.
124    pub const fn new(x: i32, y: i32) -> Self {
125        Self { x, y }
126    }
127}
128
129#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize)]
130/// Data type for vector2f.
131pub struct Vector2f {
132    /// The x value.
133    pub x: f32,
134    /// The y value.
135    pub y: f32,
136}
137
138impl Vector2f {
139    /// Creates a new value.
140    pub fn new(x: f32, y: f32) -> Result<Self> {
141        let vector = Self { x, y };
142        vector.validate()?;
143        Ok(vector)
144    }
145
146    /// Validates this value.
147    pub fn validate(self) -> Result<()> {
148        if !self.x.is_finite() || !self.y.is_finite() {
149            return Err(invalid_argument("2D vector components must be finite"));
150        }
151        Ok(())
152    }
153
154    /// Returns length.
155    pub fn length(self) -> Result<f32> {
156        self.validate()?;
157        Ok((self.x * self.x + self.y * self.y).sqrt())
158    }
159
160    /// Returns dot.
161    pub fn dot(self, rhs: Self) -> Result<f32> {
162        self.validate()?;
163        rhs.validate()?;
164        Ok(self.x.mul_add(rhs.x, self.y * rhs.y))
165    }
166
167    /// Returns cross.
168    pub fn cross(self, rhs: Self) -> Result<f32> {
169        self.validate()?;
170        rhs.validate()?;
171        Ok(self.x.mul_add(rhs.y, -(self.y * rhs.x)))
172    }
173
174    /// Normalizes this value.
175    pub fn normalize(self) -> Result<Self> {
176        let length = self.length()?;
177        if length <= f32::EPSILON {
178            return Err(invalid_argument(
179                "2D vector length must be greater than zero",
180            ));
181        }
182        Self::new(self.x / length, self.y / length)
183    }
184}
185
186impl Add for Vector2f {
187    type Output = Self;
188
189    fn add(self, rhs: Self) -> Self::Output {
190        Self {
191            x: self.x + rhs.x,
192            y: self.y + rhs.y,
193        }
194    }
195}
196
197impl AddAssign for Vector2f {
198    fn add_assign(&mut self, rhs: Self) {
199        self.x += rhs.x;
200        self.y += rhs.y;
201    }
202}
203
204impl Sub for Vector2f {
205    type Output = Self;
206
207    fn sub(self, rhs: Self) -> Self::Output {
208        Self {
209            x: self.x - rhs.x,
210            y: self.y - rhs.y,
211        }
212    }
213}
214
215impl Mul<f32> for Vector2f {
216    type Output = Self;
217
218    fn mul(self, rhs: f32) -> Self::Output {
219        Self {
220            x: self.x * rhs,
221            y: self.y * rhs,
222        }
223    }
224}
225
226impl Mul<Vector2f> for f32 {
227    type Output = Vector2f;
228
229    fn mul(self, rhs: Vector2f) -> Self::Output {
230        rhs * self
231    }
232}
233
234impl Div<f32> for Vector2f {
235    type Output = Self;
236
237    fn div(self, rhs: f32) -> Self::Output {
238        Self {
239            x: self.x / rhs,
240            y: self.y / rhs,
241        }
242    }
243}
244
245#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
246/// Data type for size2u.
247pub struct Size2u {
248    /// Width in pixels.
249    pub width: u32,
250    /// Height in pixels.
251    pub height: u32,
252}
253
254impl Size2u {
255    /// Creates a new value.
256    pub fn new(width: u32, height: u32) -> Result<Self> {
257        let size = Self { width, height };
258        size.validate()?;
259        Ok(size)
260    }
261
262    /// Validates this value.
263    pub fn validate(self) -> Result<()> {
264        if self.width == 0 || self.height == 0 {
265            return Err(GeometryError::InvalidDimensions {
266                width: self.width,
267                height: self.height,
268            });
269        }
270        Ok(())
271    }
272
273    /// Returns area.
274    pub fn area(self) -> Result<u64> {
275        self.validate()?;
276        Ok(self.width as u64 * self.height as u64)
277    }
278}
279
280#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
281/// Data type for rect u32.
282pub struct RectU32 {
283    /// The x value.
284    pub x: u32,
285    /// The y value.
286    pub y: u32,
287    /// Width in pixels.
288    pub width: u32,
289    /// Height in pixels.
290    pub height: u32,
291}
292
293impl RectU32 {
294    /// Creates a new value.
295    pub fn new(x: u32, y: u32, width: u32, height: u32) -> Result<Self> {
296        let rect = Self {
297            x,
298            y,
299            width,
300            height,
301        };
302        rect.validate()?;
303        Ok(rect)
304    }
305
306    /// Validates this value.
307    pub fn validate(self) -> Result<()> {
308        if self.width == 0 || self.height == 0 {
309            return Err(GeometryError::InvalidDimensions {
310                width: self.width,
311                height: self.height,
312            });
313        }
314        let _ = self.max_x()?;
315        let _ = self.max_y()?;
316        Ok(())
317    }
318
319    /// Returns max x.
320    pub fn max_x(self) -> Result<u32> {
321        self.x
322            .checked_add(self.width)
323            .ok_or_else(|| invalid_argument("rectangle x range overflows"))
324    }
325
326    /// Returns max y.
327    pub fn max_y(self) -> Result<u32> {
328        self.y
329            .checked_add(self.height)
330            .ok_or_else(|| invalid_argument("rectangle y range overflows"))
331    }
332
333    /// Returns contains.
334    pub fn contains(self, other: Self) -> Result<bool> {
335        self.validate()?;
336        other.validate()?;
337        Ok(self.x <= other.x
338            && self.y <= other.y
339            && self.max_x()? >= other.max_x()?
340            && self.max_y()? >= other.max_y()?)
341    }
342
343    /// Returns contains point.
344    pub fn contains_point(self, x: u32, y: u32) -> Result<bool> {
345        self.validate()?;
346        Ok(x >= self.x && x < self.max_x()? && y >= self.y && y < self.max_y()?)
347    }
348
349    /// Returns intersects.
350    pub fn intersects(self, other: Self) -> Result<bool> {
351        self.validate()?;
352        other.validate()?;
353        Ok(self.x < other.max_x()?
354            && other.x < self.max_x()?
355            && self.y < other.max_y()?
356            && other.y < self.max_y()?)
357    }
358
359    /// Returns intersection.
360    pub fn intersection(self, other: Self) -> Result<Option<Self>> {
361        if !self.intersects(other)? {
362            return Ok(None);
363        }
364        let x = self.x.max(other.x);
365        let y = self.y.max(other.y);
366        let max_x = self.max_x()?.min(other.max_x()?);
367        let max_y = self.max_y()?.min(other.max_y()?);
368        Self::new(x, y, max_x - x, max_y - y).map(Some)
369    }
370
371    /// Returns intersection-over-union for two rectangles.
372    pub fn iou(self, other: Self) -> Result<f32> {
373        let intersection_area = self
374            .intersection(other)?
375            .map(|rect| rect.area())
376            .transpose()?
377            .unwrap_or(0) as f32;
378        let union_area = self.area()? as f32 + other.area()? as f32 - intersection_area;
379        if union_area <= f32::EPSILON {
380            return Err(invalid_argument(
381                "rectangle union area must be greater than zero",
382            ));
383        }
384        Ok(intersection_area / union_area)
385    }
386
387    /// Returns the intersection area divided by this rectangle area.
388    pub fn overlap_ratio(self, other: Self) -> Result<f32> {
389        let intersection_area = self
390            .intersection(other)?
391            .map(|rect| rect.area())
392            .transpose()?
393            .unwrap_or(0) as f32;
394        Ok(intersection_area / self.area()? as f32)
395    }
396
397    /// Returns union.
398    pub fn union(self, other: Self) -> Result<Self> {
399        self.validate()?;
400        other.validate()?;
401        let x = self.x.min(other.x);
402        let y = self.y.min(other.y);
403        let max_x = self.max_x()?.max(other.max_x()?);
404        let max_y = self.max_y()?.max(other.max_y()?);
405        Self::new(x, y, max_x - x, max_y - y)
406    }
407
408    /// Returns clamp to.
409    pub fn clamp_to(self, bounds: Self) -> Result<Option<Self>> {
410        self.intersection(bounds)
411    }
412
413    /// Returns translate.
414    pub fn translate(self, dx: i32, dy: i32) -> Result<Self> {
415        self.validate()?;
416        let x = self.x as i64 + dx as i64;
417        let y = self.y as i64 + dy as i64;
418        if x < 0 || y < 0 || x > u32::MAX as i64 || y > u32::MAX as i64 {
419            return Err(invalid_argument(
420                "translated rectangle is out of u32 bounds",
421            ));
422        }
423        Self::new(x as u32, y as u32, self.width, self.height)
424    }
425
426    /// Returns scale.
427    pub fn scale(self, factor: f32) -> Result<Self> {
428        if !factor.is_finite() || factor <= 0.0 {
429            return Err(invalid_argument("scale factor must be finite and positive"));
430        }
431        let x = (self.x as f32 * factor).round();
432        let y = (self.y as f32 * factor).round();
433        let width = (self.width as f32 * factor).round();
434        let height = (self.height as f32 * factor).round();
435        if [x, y, width, height]
436            .iter()
437            .any(|value| *value < 0.0 || *value > u32::MAX as f32)
438        {
439            return Err(invalid_argument("scaled rectangle is out of u32 bounds"));
440        }
441        Self::new(x as u32, y as u32, width as u32, height as u32)
442    }
443
444    /// Returns inflate.
445    pub fn inflate(self, dx: i32, dy: i32) -> Result<Self> {
446        self.validate()?;
447        let x = self.x as i64 - dx as i64;
448        let y = self.y as i64 - dy as i64;
449        let width = self.width as i64 + 2 * dx as i64;
450        let height = self.height as i64 + 2 * dy as i64;
451        if x < 0 || y < 0 || width <= 0 || height <= 0 {
452            return Err(invalid_argument(
453                "inflated rectangle must stay within positive bounds",
454            ));
455        }
456        if x > u32::MAX as i64
457            || y > u32::MAX as i64
458            || width > u32::MAX as i64
459            || height > u32::MAX as i64
460        {
461            return Err(invalid_argument("inflated rectangle exceeds u32 bounds"));
462        }
463        Self::new(x as u32, y as u32, width as u32, height as u32)
464    }
465
466    /// Returns center f32.
467    pub fn center_f32(self) -> Point2f {
468        Point2f {
469            x: self.x as f32 + self.width as f32 / 2.0,
470            y: self.y as f32 + self.height as f32 / 2.0,
471        }
472    }
473
474    /// Returns area.
475    pub fn area(self) -> Result<u64> {
476        self.validate()?;
477        Ok(self.width as u64 * self.height as u64)
478    }
479
480    /// Returns size.
481    pub fn size(self) -> Size2u {
482        Size2u {
483            width: self.width,
484            height: self.height,
485        }
486    }
487}
488
489#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
490/// Data type for a broad-phase collision pair.
491pub struct CollisionPair {
492    /// Index of the first item.
493    pub left_index: usize,
494    /// Index of the second item.
495    pub right_index: usize,
496}
497
498impl CollisionPair {
499    /// Creates a new ordered same-set pair.
500    pub fn ordered(left_index: usize, right_index: usize) -> Self {
501        if left_index <= right_index {
502            Self {
503                left_index,
504                right_index,
505            }
506        } else {
507            Self {
508                left_index: right_index,
509                right_index: left_index,
510            }
511        }
512    }
513}
514
515#[derive(Debug, Clone, Copy, PartialEq, Eq)]
516/// Strategy used for broad-phase 2D collision detection.
517pub enum BroadPhase2Strategy {
518    /// Selects an implementation from the input shape.
519    Auto,
520    /// Checks every pair.
521    BruteForce,
522    /// Uses a spatial hash grid.
523    SpatialHashGrid,
524    /// Uses sweep and prune along the x axis.
525    SweepAndPrune,
526}
527
528#[derive(Debug, Clone, Copy, PartialEq, Eq)]
529/// Cell size selection for 2D spatial hashing.
530pub enum SpatialCellSize2 {
531    /// Uses median item dimensions.
532    Auto,
533    /// Uses a fixed cell size.
534    Fixed {
535        /// Cell width in pixels.
536        width: u32,
537        /// Cell height in pixels.
538        height: u32,
539    },
540}
541
542#[derive(Debug, Clone, Copy, PartialEq, Eq)]
543/// Options for 2D broad-phase collision detection.
544pub struct BroadPhase2Config {
545    /// Strategy to use.
546    pub strategy: BroadPhase2Strategy,
547    /// Maximum item count handled with brute force in auto mode.
548    pub brute_force_threshold: usize,
549    /// Maximum cells a single item may span before auto mode uses sweep and prune.
550    pub max_cells_per_item: usize,
551    /// Spatial hash grid cell size.
552    pub cell_size: SpatialCellSize2,
553}
554
555impl Default for BroadPhase2Config {
556    fn default() -> Self {
557        Self {
558            strategy: BroadPhase2Strategy::Auto,
559            brute_force_threshold: 128,
560            max_cells_per_item: 1024,
561            cell_size: SpatialCellSize2::Auto,
562        }
563    }
564}
565
566impl BroadPhase2Config {
567    /// Validates this value.
568    pub fn validate(self) -> Result<()> {
569        if self.max_cells_per_item == 0 {
570            return Err(invalid_argument(
571                "max_cells_per_item must be greater than zero",
572            ));
573        }
574        if let SpatialCellSize2::Fixed { width, height } = self.cell_size {
575            if width == 0 || height == 0 {
576                return Err(invalid_argument("fixed spatial cell size must be non-zero"));
577            }
578        }
579        Ok(())
580    }
581}
582
583#[derive(Debug, Clone, Copy, PartialEq, Eq)]
584/// Runtime statistics for broad-phase collision detection.
585pub struct BroadPhaseStats {
586    /// Number of objects indexed.
587    pub object_count: usize,
588    /// Number of occupied cells.
589    pub cell_count: usize,
590    /// Number of object-cell entries.
591    pub cell_entry_count: usize,
592    /// Number of candidate pairs emitted.
593    pub candidate_pair_count: usize,
594    /// Strategy selected after auto resolution.
595    pub selected_strategy: BroadPhase2Strategy,
596}
597
598impl Default for BroadPhaseStats {
599    fn default() -> Self {
600        Self {
601            object_count: 0,
602            cell_count: 0,
603            cell_entry_count: 0,
604            candidate_pair_count: 0,
605            selected_strategy: BroadPhase2Strategy::BruteForce,
606        }
607    }
608}
609
610#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
611struct GridCell2 {
612    x: u32,
613    y: u32,
614}
615
616#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
617struct GridEntry2 {
618    cell: GridCell2,
619    set: u8,
620    index: usize,
621}
622
623#[derive(Debug, Clone)]
624/// Reusable spatial hash grid for 2D broad-phase collision detection.
625pub struct SpatialHashGrid2 {
626    config: BroadPhase2Config,
627    cell_width: u32,
628    cell_height: u32,
629    left_bounds: Vec<RectU32>,
630    right_bounds: Vec<RectU32>,
631    entries: Vec<GridEntry2>,
632    pairs: Vec<CollisionPair>,
633    stats: BroadPhaseStats,
634}
635
636impl SpatialHashGrid2 {
637    /// Creates a new value.
638    pub fn new(config: BroadPhase2Config) -> Result<Self> {
639        config.validate()?;
640        Ok(Self {
641            config,
642            cell_width: 1,
643            cell_height: 1,
644            left_bounds: Vec::new(),
645            right_bounds: Vec::new(),
646            entries: Vec::new(),
647            pairs: Vec::new(),
648            stats: BroadPhaseStats {
649                selected_strategy: config.strategy,
650                ..BroadPhaseStats::default()
651            },
652        })
653    }
654
655    /// Rebuilds this grid for a single set of bounds.
656    pub fn rebuild(&mut self, bounds: &[RectU32]) -> Result<()> {
657        validate_rects(bounds)?;
658        let (cell_width, cell_height) = resolve_cell_size_2d(bounds, self.config.cell_size)?;
659        self.cell_width = cell_width;
660        self.cell_height = cell_height;
661        self.left_bounds.clear();
662        self.left_bounds.extend_from_slice(bounds);
663        self.right_bounds.clear();
664        self.entries.clear();
665        self.pairs.clear();
666        self.push_entries(bounds, 0)?;
667        self.stats = BroadPhaseStats {
668            object_count: bounds.len(),
669            cell_count: 0,
670            cell_entry_count: self.entries.len(),
671            candidate_pair_count: 0,
672            selected_strategy: BroadPhase2Strategy::SpatialHashGrid,
673        };
674        Ok(())
675    }
676
677    /// Returns candidate pairs for the most recently rebuilt single set.
678    pub fn candidate_pairs(&mut self) -> Result<&[CollisionPair]> {
679        self.entries.sort_unstable();
680        self.pairs.clear();
681        let mut cell_count = 0;
682        let mut start = 0;
683        while start < self.entries.len() {
684            let cell = self.entries[start].cell;
685            let mut end = start + 1;
686            while end < self.entries.len() && self.entries[end].cell == cell {
687                end += 1;
688            }
689            cell_count += 1;
690            for left in start..end {
691                for right in (left + 1)..end {
692                    let left_index = self.entries[left].index;
693                    let right_index = self.entries[right].index;
694                    if self.left_bounds[left_index].intersects(self.left_bounds[right_index])? {
695                        self.pairs
696                            .push(CollisionPair::ordered(left_index, right_index));
697                    }
698                }
699            }
700            start = end;
701        }
702        finish_pairs(&mut self.pairs);
703        self.stats.cell_count = cell_count;
704        self.stats.candidate_pair_count = self.pairs.len();
705        Ok(&self.pairs)
706    }
707
708    /// Returns candidate pairs between two independent sets.
709    pub fn candidate_pairs_between(
710        &mut self,
711        left: &[RectU32],
712        right: &[RectU32],
713    ) -> Result<&[CollisionPair]> {
714        validate_rects(left)?;
715        validate_rects(right)?;
716        let (cell_width, cell_height) =
717            resolve_cell_size_2d_for_sets(left, right, self.config.cell_size)?;
718        self.cell_width = cell_width;
719        self.cell_height = cell_height;
720        self.left_bounds.clear();
721        self.left_bounds.extend_from_slice(left);
722        self.right_bounds.clear();
723        self.right_bounds.extend_from_slice(right);
724        self.entries.clear();
725        self.pairs.clear();
726        self.push_entries(left, 0)?;
727        self.push_entries(right, 1)?;
728        self.entries.sort_unstable();
729
730        let mut cell_count = 0;
731        let mut start = 0;
732        while start < self.entries.len() {
733            let cell = self.entries[start].cell;
734            let mut end = start + 1;
735            while end < self.entries.len() && self.entries[end].cell == cell {
736                end += 1;
737            }
738            cell_count += 1;
739            for left_entry in start..end {
740                if self.entries[left_entry].set != 0 {
741                    continue;
742                }
743                for right_entry in start..end {
744                    if self.entries[right_entry].set == 1
745                        && self.left_bounds[self.entries[left_entry].index]
746                            .intersects(self.right_bounds[self.entries[right_entry].index])?
747                    {
748                        self.pairs.push(CollisionPair {
749                            left_index: self.entries[left_entry].index,
750                            right_index: self.entries[right_entry].index,
751                        });
752                    }
753                }
754            }
755            start = end;
756        }
757        finish_pairs(&mut self.pairs);
758        self.stats = BroadPhaseStats {
759            object_count: left.len() + right.len(),
760            cell_count,
761            cell_entry_count: self.entries.len(),
762            candidate_pair_count: self.pairs.len(),
763            selected_strategy: BroadPhase2Strategy::SpatialHashGrid,
764        };
765        Ok(&self.pairs)
766    }
767
768    /// Returns stats.
769    pub fn stats(&self) -> BroadPhaseStats {
770        self.stats
771    }
772
773    fn push_entries(&mut self, bounds: &[RectU32], set: u8) -> Result<()> {
774        for (index, rect) in bounds.iter().copied().enumerate() {
775            let (min_cell, max_cell) = rect_cell_range_2d(rect, self.cell_width, self.cell_height)?;
776            for y in min_cell.y..=max_cell.y {
777                for x in min_cell.x..=max_cell.x {
778                    self.entries.push(GridEntry2 {
779                        cell: GridCell2 { x, y },
780                        set,
781                        index,
782                    });
783                }
784            }
785        }
786        Ok(())
787    }
788}
789
790/// Returns broad-phase candidate pairs for 2D rectangles.
791pub fn broad_phase_pairs_2d(
792    bounds: &[RectU32],
793    config: BroadPhase2Config,
794) -> Result<Vec<CollisionPair>> {
795    config.validate()?;
796    validate_rects(bounds)?;
797    let (strategy, stats) = select_strategy_2d(bounds, config)?;
798    match strategy {
799        BroadPhase2Strategy::Auto => unreachable!("auto strategy must resolve before execution"),
800        BroadPhase2Strategy::BruteForce => Ok(brute_force_pairs_2d(bounds)),
801        BroadPhase2Strategy::SweepAndPrune => Ok(sweep_and_prune_pairs_2d(bounds)?),
802        BroadPhase2Strategy::SpatialHashGrid => {
803            let mut grid = SpatialHashGrid2::new(BroadPhase2Config { strategy, ..config })?;
804            grid.rebuild(bounds)?;
805            let pairs = grid.candidate_pairs()?.to_vec();
806            let _ = stats;
807            Ok(pairs)
808        }
809    }
810}
811
812fn select_strategy_2d(
813    bounds: &[RectU32],
814    config: BroadPhase2Config,
815) -> Result<(BroadPhase2Strategy, BroadPhaseStats)> {
816    let selected = match config.strategy {
817        BroadPhase2Strategy::Auto if bounds.len() <= config.brute_force_threshold => {
818            BroadPhase2Strategy::BruteForce
819        }
820        BroadPhase2Strategy::Auto => {
821            let (cell_width, cell_height) = resolve_cell_size_2d(bounds, config.cell_size)?;
822            if bounds.iter().copied().any(|rect| {
823                rect_cell_count_2d(rect, cell_width, cell_height)
824                    .map(|count| count > config.max_cells_per_item)
825                    .unwrap_or(true)
826            }) {
827                BroadPhase2Strategy::SweepAndPrune
828            } else {
829                BroadPhase2Strategy::SpatialHashGrid
830            }
831        }
832        strategy => strategy,
833    };
834    Ok((
835        selected,
836        BroadPhaseStats {
837            object_count: bounds.len(),
838            selected_strategy: selected,
839            ..BroadPhaseStats::default()
840        },
841    ))
842}
843
844fn brute_force_pairs_2d(bounds: &[RectU32]) -> Vec<CollisionPair> {
845    let mut pairs = Vec::new();
846    for left_index in 0..bounds.len() {
847        for right_index in (left_index + 1)..bounds.len() {
848            if bounds[left_index]
849                .intersects(bounds[right_index])
850                .unwrap_or(false)
851            {
852                pairs.push(CollisionPair {
853                    left_index,
854                    right_index,
855                });
856            }
857        }
858    }
859    pairs
860}
861
862fn sweep_and_prune_pairs_2d(bounds: &[RectU32]) -> Result<Vec<CollisionPair>> {
863    let mut ordered = bounds
864        .iter()
865        .copied()
866        .enumerate()
867        .map(|(index, rect)| Ok((index, rect.x, rect.max_x()?, rect)))
868        .collect::<Result<Vec<_>>>()?;
869    ordered.sort_unstable_by_key(|(index, min_x, _, _)| (*min_x, *index));
870
871    let mut pairs = Vec::new();
872    let mut active = Vec::<(usize, u32, RectU32)>::new();
873    for (index, min_x, max_x, rect) in ordered {
874        active.retain(|(_, active_max_x, _)| *active_max_x > min_x);
875        for (active_index, _, active_rect) in &active {
876            if active_rect.intersects(rect)? {
877                pairs.push(CollisionPair::ordered(*active_index, index));
878            }
879        }
880        active.push((index, max_x, rect));
881    }
882    finish_pairs(&mut pairs);
883    Ok(pairs)
884}
885
886fn resolve_cell_size_2d(bounds: &[RectU32], cell_size: SpatialCellSize2) -> Result<(u32, u32)> {
887    match cell_size {
888        SpatialCellSize2::Fixed { width, height } => {
889            if width == 0 || height == 0 {
890                return Err(invalid_argument("fixed spatial cell size must be non-zero"));
891            }
892            Ok((width, height))
893        }
894        SpatialCellSize2::Auto => {
895            if bounds.is_empty() {
896                return Ok((1, 1));
897            }
898            let mut widths = bounds.iter().map(|rect| rect.width).collect::<Vec<_>>();
899            let mut heights = bounds.iter().map(|rect| rect.height).collect::<Vec<_>>();
900            widths.sort_unstable();
901            heights.sort_unstable();
902            Ok((
903                widths[widths.len() / 2].max(1),
904                heights[heights.len() / 2].max(1),
905            ))
906        }
907    }
908}
909
910fn resolve_cell_size_2d_for_sets(
911    left: &[RectU32],
912    right: &[RectU32],
913    cell_size: SpatialCellSize2,
914) -> Result<(u32, u32)> {
915    match cell_size {
916        SpatialCellSize2::Fixed { .. } => resolve_cell_size_2d(left, cell_size),
917        SpatialCellSize2::Auto => {
918            let mut widths = left
919                .iter()
920                .chain(right.iter())
921                .map(|rect| rect.width)
922                .collect::<Vec<_>>();
923            let mut heights = left
924                .iter()
925                .chain(right.iter())
926                .map(|rect| rect.height)
927                .collect::<Vec<_>>();
928            if widths.is_empty() {
929                return Ok((1, 1));
930            }
931            widths.sort_unstable();
932            heights.sort_unstable();
933            Ok((
934                widths[widths.len() / 2].max(1),
935                heights[heights.len() / 2].max(1),
936            ))
937        }
938    }
939}
940
941fn rect_cell_range_2d(
942    rect: RectU32,
943    cell_width: u32,
944    cell_height: u32,
945) -> Result<(GridCell2, GridCell2)> {
946    let max_x = rect.max_x()?.saturating_sub(1);
947    let max_y = rect.max_y()?.saturating_sub(1);
948    Ok((
949        GridCell2 {
950            x: rect.x / cell_width,
951            y: rect.y / cell_height,
952        },
953        GridCell2 {
954            x: max_x / cell_width,
955            y: max_y / cell_height,
956        },
957    ))
958}
959
960fn rect_cell_count_2d(rect: RectU32, cell_width: u32, cell_height: u32) -> Result<usize> {
961    let (min, max) = rect_cell_range_2d(rect, cell_width, cell_height)?;
962    let width = (max.x - min.x + 1) as usize;
963    let height = (max.y - min.y + 1) as usize;
964    Ok(width.saturating_mul(height))
965}
966
967fn validate_rects(bounds: &[RectU32]) -> Result<()> {
968    for rect in bounds {
969        rect.validate()?;
970    }
971    Ok(())
972}
973
974fn finish_pairs(pairs: &mut Vec<CollisionPair>) {
975    pairs.sort_unstable();
976    pairs.dedup();
977}
978
979#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize)]
980/// Data type for rect f32.
981pub struct RectF32 {
982    /// The x value.
983    pub x: f32,
984    /// The y value.
985    pub y: f32,
986    /// Width in pixels.
987    pub width: f32,
988    /// Height in pixels.
989    pub height: f32,
990}
991
992impl RectF32 {
993    /// Creates a new value.
994    pub fn new(x: f32, y: f32, width: f32, height: f32) -> Result<Self> {
995        let rect = Self {
996            x,
997            y,
998            width,
999            height,
1000        };
1001        rect.validate()?;
1002        Ok(rect)
1003    }
1004
1005    /// Validates this value.
1006    pub fn validate(self) -> Result<()> {
1007        if [self.x, self.y, self.width, self.height]
1008            .iter()
1009            .any(|value| !value.is_finite())
1010        {
1011            return Err(invalid_argument("rectangle values must be finite"));
1012        }
1013        if self.width <= 0.0 || self.height <= 0.0 {
1014            return Err(invalid_argument(
1015                "rectangle width and height must be greater than zero",
1016            ));
1017        }
1018        Ok(())
1019    }
1020
1021    /// Returns max x.
1022    pub fn max_x(self) -> Result<f32> {
1023        self.validate()?;
1024        Ok(self.x + self.width)
1025    }
1026
1027    /// Returns max y.
1028    pub fn max_y(self) -> Result<f32> {
1029        self.validate()?;
1030        Ok(self.y + self.height)
1031    }
1032
1033    /// Returns contains point.
1034    pub fn contains_point(self, point: Point2f) -> Result<bool> {
1035        self.validate()?;
1036        point.validate()?;
1037        Ok(point.x >= self.x
1038            && point.x <= self.max_x()?
1039            && point.y >= self.y
1040            && point.y <= self.max_y()?)
1041    }
1042
1043    /// Returns intersects.
1044    pub fn intersects(self, other: Self) -> Result<bool> {
1045        self.validate()?;
1046        other.validate()?;
1047        Ok(self.x < other.max_x()?
1048            && other.x < self.max_x()?
1049            && self.y < other.max_y()?
1050            && other.y < self.max_y()?)
1051    }
1052
1053    /// Returns intersection.
1054    pub fn intersection(self, other: Self) -> Result<Option<Self>> {
1055        if !self.intersects(other)? {
1056            return Ok(None);
1057        }
1058        let x = self.x.max(other.x);
1059        let y = self.y.max(other.y);
1060        let max_x = self.max_x()?.min(other.max_x()?);
1061        let max_y = self.max_y()?.min(other.max_y()?);
1062        Self::new(x, y, max_x - x, max_y - y).map(Some)
1063    }
1064
1065    /// Returns intersection-over-union for two rectangles.
1066    pub fn iou(self, other: Self) -> Result<f32> {
1067        let intersection_area = self
1068            .intersection(other)?
1069            .map(|rect| rect.area())
1070            .transpose()?
1071            .unwrap_or(0.0);
1072        let union_area = self.area()? + other.area()? - intersection_area;
1073        if union_area <= f32::EPSILON {
1074            return Err(invalid_argument(
1075                "rectangle union area must be greater than zero",
1076            ));
1077        }
1078        Ok(intersection_area / union_area)
1079    }
1080
1081    /// Returns the intersection area divided by this rectangle area.
1082    pub fn overlap_ratio(self, other: Self) -> Result<f32> {
1083        let intersection_area = self
1084            .intersection(other)?
1085            .map(|rect| rect.area())
1086            .transpose()?
1087            .unwrap_or(0.0);
1088        Ok(intersection_area / self.area()?)
1089    }
1090
1091    /// Returns union.
1092    pub fn union(self, other: Self) -> Result<Self> {
1093        self.validate()?;
1094        other.validate()?;
1095        let x = self.x.min(other.x);
1096        let y = self.y.min(other.y);
1097        let max_x = self.max_x()?.max(other.max_x()?);
1098        let max_y = self.max_y()?.max(other.max_y()?);
1099        Self::new(x, y, max_x - x, max_y - y)
1100    }
1101
1102    /// Returns clamp to.
1103    pub fn clamp_to(self, bounds: Self) -> Result<Option<Self>> {
1104        self.intersection(bounds)
1105    }
1106
1107    /// Returns translate.
1108    pub fn translate(self, delta: Vector2f) -> Result<Self> {
1109        delta.validate()?;
1110        Self::new(self.x + delta.x, self.y + delta.y, self.width, self.height)
1111    }
1112
1113    /// Returns scale.
1114    pub fn scale(self, factor: f32) -> Result<Self> {
1115        if !factor.is_finite() || factor <= 0.0 {
1116            return Err(invalid_argument("scale factor must be finite and positive"));
1117        }
1118        Self::new(
1119            self.x * factor,
1120            self.y * factor,
1121            self.width * factor,
1122            self.height * factor,
1123        )
1124    }
1125
1126    /// Returns inflate.
1127    pub fn inflate(self, dx: f32, dy: f32) -> Result<Self> {
1128        if !dx.is_finite() || !dy.is_finite() {
1129            return Err(invalid_argument("inflate deltas must be finite"));
1130        }
1131        Self::new(
1132            self.x - dx,
1133            self.y - dy,
1134            self.width + 2.0 * dx,
1135            self.height + 2.0 * dy,
1136        )
1137    }
1138
1139    /// Returns center.
1140    pub fn center(self) -> Result<Point2f> {
1141        self.validate()?;
1142        Point2f::new(self.x + self.width / 2.0, self.y + self.height / 2.0)
1143    }
1144
1145    /// Returns area.
1146    pub fn area(self) -> Result<f32> {
1147        self.validate()?;
1148        Ok(self.width * self.height)
1149    }
1150}
1151
1152#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize)]
1153/// Data type for normalized point2.
1154pub struct NormalizedPoint2 {
1155    /// The x value.
1156    pub x: f32,
1157    /// The y value.
1158    pub y: f32,
1159}
1160
1161impl NormalizedPoint2 {
1162    /// Creates a new value.
1163    pub fn new(x: f32, y: f32) -> Result<Self> {
1164        let point = Self { x, y };
1165        point.validate()?;
1166        Ok(point)
1167    }
1168
1169    /// Validates this value.
1170    pub fn validate(self) -> Result<()> {
1171        if !self.x.is_finite() || !self.y.is_finite() {
1172            return Err(invalid_argument("normalized coordinates must be finite"));
1173        }
1174        if !(0.0..=1.0).contains(&self.x) || !(0.0..=1.0).contains(&self.y) {
1175            return Err(invalid_argument(
1176                "normalized coordinates must be within the inclusive range [0, 1]",
1177            ));
1178        }
1179        Ok(())
1180    }
1181
1182    /// Converts this value to pixel point.
1183    pub fn to_pixel_point(self, size: Size2u) -> Point2i {
1184        Point2i {
1185            x: (self.x * size.width as f32).round() as i32,
1186            y: (self.y * size.height as f32).round() as i32,
1187        }
1188    }
1189
1190    /// Converts this value to pixel point f32.
1191    pub fn to_pixel_point_f32(self, size: Size2u) -> Point2f {
1192        Point2f {
1193            x: self.x * size.width as f32,
1194            y: self.y * size.height as f32,
1195        }
1196    }
1197}
1198
1199#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize)]
1200/// Data type for affine2.
1201pub struct Affine2 {
1202    /// The m11 value.
1203    pub m11: f32,
1204    /// The m12 value.
1205    pub m12: f32,
1206    /// The m21 value.
1207    pub m21: f32,
1208    /// The m22 value.
1209    pub m22: f32,
1210    /// The tx value.
1211    pub tx: f32,
1212    /// The ty value.
1213    pub ty: f32,
1214}
1215
1216impl Affine2 {
1217    /// Creates a new value.
1218    pub const fn identity() -> Self {
1219        Self {
1220            m11: 1.0,
1221            m12: 0.0,
1222            m21: 0.0,
1223            m22: 1.0,
1224            tx: 0.0,
1225            ty: 0.0,
1226        }
1227    }
1228
1229    /// Creates a new value.
1230    pub const fn translation(tx: f32, ty: f32) -> Self {
1231        Self {
1232            tx,
1233            ty,
1234            ..Self::identity()
1235        }
1236    }
1237
1238    /// Creates a new value.
1239    pub const fn scaling(sx: f32, sy: f32) -> Self {
1240        Self {
1241            m11: sx,
1242            m12: 0.0,
1243            m21: 0.0,
1244            m22: sy,
1245            tx: 0.0,
1246            ty: 0.0,
1247        }
1248    }
1249
1250    /// Validates this value.
1251    pub fn validate(self) -> Result<()> {
1252        if [self.m11, self.m12, self.m21, self.m22, self.tx, self.ty]
1253            .iter()
1254            .any(|value| !value.is_finite())
1255        {
1256            return Err(invalid_argument("affine transform values must be finite"));
1257        }
1258        Ok(())
1259    }
1260
1261    /// Returns determinant.
1262    pub fn determinant(self) -> Result<f32> {
1263        self.validate()?;
1264        Ok(self.m11 * self.m22 - self.m12 * self.m21)
1265    }
1266
1267    /// Returns apply point.
1268    pub fn apply_point(self, point: Point2f) -> Point2f {
1269        Point2f {
1270            x: self.m11 * point.x + self.m12 * point.y + self.tx,
1271            y: self.m21 * point.x + self.m22 * point.y + self.ty,
1272        }
1273    }
1274
1275    /// Returns invert.
1276    pub fn invert(self) -> Result<Self> {
1277        self.validate()?;
1278        let det = self.determinant()?;
1279        if det.abs() <= f32::EPSILON {
1280            return Err(invalid_argument("affine transform is not invertible"));
1281        }
1282        let inv_det = 1.0 / det;
1283        let m11 = self.m22 * inv_det;
1284        let m12 = -self.m12 * inv_det;
1285        let m21 = -self.m21 * inv_det;
1286        let m22 = self.m11 * inv_det;
1287        let tx = -(m11 * self.tx + m12 * self.ty);
1288        let ty = -(m21 * self.tx + m22 * self.ty);
1289        Ok(Self {
1290            m11,
1291            m12,
1292            m21,
1293            m22,
1294            tx,
1295            ty,
1296        })
1297    }
1298
1299    /// Returns compose.
1300    pub fn compose(self, next: Self) -> Result<Self> {
1301        self.validate()?;
1302        next.validate()?;
1303        Ok(Self {
1304            m11: next.m11 * self.m11 + next.m12 * self.m21,
1305            m12: next.m11 * self.m12 + next.m12 * self.m22,
1306            m21: next.m21 * self.m11 + next.m22 * self.m21,
1307            m22: next.m21 * self.m12 + next.m22 * self.m22,
1308            tx: next.m11 * self.tx + next.m12 * self.ty + next.tx,
1309            ty: next.m21 * self.tx + next.m22 * self.ty + next.ty,
1310        })
1311    }
1312}
1313
1314#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize)]
1315/// Data type for line segment2.
1316pub struct LineSegment2 {
1317    /// The start value.
1318    pub start: Point2f,
1319    /// The end value.
1320    pub end: Point2f,
1321}
1322
1323#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize)]
1324/// Intersection details for two finite 2D line segments.
1325pub struct LineIntersection2 {
1326    /// Intersection point.
1327    pub point: Point2f,
1328    /// Parametric position on the left segment in `[0, 1]`.
1329    pub left_t: f32,
1330    /// Parametric position on the right segment in `[0, 1]`.
1331    pub right_t: f32,
1332}
1333
1334impl LineSegment2 {
1335    /// Creates a new value.
1336    pub fn new(start: Point2f, end: Point2f) -> Result<Self> {
1337        start.validate()?;
1338        end.validate()?;
1339        if start == end {
1340            return Err(invalid_argument(
1341                "line segment start and end must not be identical",
1342            ));
1343        }
1344        Ok(Self { start, end })
1345    }
1346
1347    /// Returns length.
1348    pub fn length(self) -> Result<f32> {
1349        (self.end - self.start).length()
1350    }
1351
1352    /// Returns midpoint.
1353    pub fn midpoint(self) -> Point2f {
1354        self.start + ((self.end - self.start) * 0.5)
1355    }
1356
1357    /// Returns direction.
1358    pub fn direction(self) -> Result<Vector2f> {
1359        (self.end - self.start).normalize()
1360    }
1361
1362    /// Returns the finite segment intersection, if present.
1363    pub fn intersection(self, other: Self) -> Result<Option<LineIntersection2>> {
1364        let p = self.start;
1365        let r = self.end - self.start;
1366        let q = other.start;
1367        let s = other.end - other.start;
1368        p.validate()?;
1369        q.validate()?;
1370        let denominator = r.cross(s)?;
1371        let q_minus_p = q - p;
1372        if denominator.abs() <= f32::EPSILON {
1373            return Ok(None);
1374        }
1375        let left_t = q_minus_p.cross(s)? / denominator;
1376        let right_t = q_minus_p.cross(r)? / denominator;
1377        if !(-f32::EPSILON..=1.0 + f32::EPSILON).contains(&left_t)
1378            || !(-f32::EPSILON..=1.0 + f32::EPSILON).contains(&right_t)
1379        {
1380            return Ok(None);
1381        }
1382        let point = p + r * left_t.clamp(0.0, 1.0);
1383        Ok(Some(LineIntersection2 {
1384            point,
1385            left_t,
1386            right_t,
1387        }))
1388    }
1389}
1390
1391#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize)]
1392/// Data type for circle2.
1393pub struct Circle2 {
1394    /// The center value.
1395    pub center: Point2f,
1396    /// The radius value.
1397    pub radius: f32,
1398}
1399
1400impl Circle2 {
1401    /// Creates a new value.
1402    pub fn new(center: Point2f, radius: f32) -> Result<Self> {
1403        center.validate()?;
1404        if !radius.is_finite() || radius <= 0.0 {
1405            return Err(invalid_argument(
1406                "circle radius must be finite and greater than zero",
1407            ));
1408        }
1409        Ok(Self { center, radius })
1410    }
1411
1412    /// Returns area.
1413    pub fn area(self) -> Result<f32> {
1414        self.center.validate()?;
1415        if !self.radius.is_finite() || self.radius <= 0.0 {
1416            return Err(invalid_argument(
1417                "circle radius must be finite and greater than zero",
1418            ));
1419        }
1420        Ok(std::f32::consts::PI * self.radius * self.radius)
1421    }
1422
1423    /// Returns contains point.
1424    pub fn contains_point(self, point: Point2f) -> Result<bool> {
1425        point.validate()?;
1426        Ok((point - self.center).length()? <= self.radius)
1427    }
1428}
1429
1430#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
1431/// Data type for polygon2f.
1432pub struct Polygon2f {
1433    points: Vec<Point2f>,
1434}
1435
1436impl Polygon2f {
1437    /// Creates a new value.
1438    pub fn new(points: impl Into<Vec<Point2f>>) -> Result<Self> {
1439        let polygon = Self {
1440            points: points.into(),
1441        };
1442        polygon.validate()?;
1443        Ok(polygon)
1444    }
1445
1446    /// Returns points.
1447    pub fn points(&self) -> &[Point2f] {
1448        &self.points
1449    }
1450
1451    /// Validates this value.
1452    pub fn validate(&self) -> Result<()> {
1453        if self.points.len() < 3 {
1454            return Err(invalid_argument(
1455                "polygon must contain at least three points",
1456            ));
1457        }
1458        for point in &self.points {
1459            point.validate()?;
1460        }
1461        Ok(())
1462    }
1463
1464    /// Returns bounds.
1465    pub fn bounds(&self) -> Result<Bounds2f> {
1466        self.validate()?;
1467        let mut min_x = self.points[0].x;
1468        let mut min_y = self.points[0].y;
1469        let mut max_x = self.points[0].x;
1470        let mut max_y = self.points[0].y;
1471        for point in &self.points[1..] {
1472            min_x = min_x.min(point.x);
1473            min_y = min_y.min(point.y);
1474            max_x = max_x.max(point.x);
1475            max_y = max_y.max(point.y);
1476        }
1477        Bounds2f::new(
1478            Point2f { x: min_x, y: min_y },
1479            Point2f { x: max_x, y: max_y },
1480        )
1481    }
1482
1483    /// Returns signed area.
1484    pub fn signed_area(&self) -> Result<f32> {
1485        self.validate()?;
1486        let mut area = 0.0_f32;
1487        for index in 0..self.points.len() {
1488            let current = self.points[index];
1489            let next = self.points[(index + 1) % self.points.len()];
1490            area += current.x * next.y - next.x * current.y;
1491        }
1492        Ok(area * 0.5)
1493    }
1494
1495    /// Returns area.
1496    pub fn area(&self) -> Result<f32> {
1497        Ok(self.signed_area()?.abs())
1498    }
1499
1500    /// Returns whether this polygon is clockwise.
1501    pub fn is_clockwise(&self) -> Result<bool> {
1502        Ok(self.signed_area()? < 0.0)
1503    }
1504
1505    /// Returns centroid.
1506    pub fn centroid(&self) -> Result<Point2f> {
1507        self.validate()?;
1508        let signed_area = self.signed_area()?;
1509        if signed_area.abs() <= f32::EPSILON {
1510            let sum = self
1511                .points
1512                .iter()
1513                .fold(Vector2f { x: 0.0, y: 0.0 }, |sum, point| {
1514                    sum + Vector2f {
1515                        x: point.x,
1516                        y: point.y,
1517                    }
1518                });
1519            return Point2f::new(
1520                sum.x / self.points.len() as f32,
1521                sum.y / self.points.len() as f32,
1522            );
1523        }
1524        let mut cx = 0.0_f32;
1525        let mut cy = 0.0_f32;
1526        for index in 0..self.points.len() {
1527            let current = self.points[index];
1528            let next = self.points[(index + 1) % self.points.len()];
1529            let cross = current.x * next.y - next.x * current.y;
1530            cx += (current.x + next.x) * cross;
1531            cy += (current.y + next.y) * cross;
1532        }
1533        let scale = 1.0 / (6.0 * signed_area);
1534        Point2f::new(cx * scale, cy * scale)
1535    }
1536
1537    /// Returns contains point.
1538    pub fn contains_point(&self, point: Point2f) -> Result<bool> {
1539        self.validate()?;
1540        point.validate()?;
1541        let mut inside = false;
1542        let mut previous = self.points.len() - 1;
1543        for current in 0..self.points.len() {
1544            let a = self.points[current];
1545            let b = self.points[previous];
1546            if ((a.y > point.y) != (b.y > point.y))
1547                && point.x < (b.x - a.x) * (point.y - a.y) / (b.y - a.y) + a.x
1548            {
1549                inside = !inside;
1550            }
1551            previous = current;
1552        }
1553        Ok(inside)
1554    }
1555}
1556
1557#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize)]
1558/// Data type for bounds2f.
1559pub struct Bounds2f {
1560    /// The min value.
1561    pub min: Point2f,
1562    /// The max value.
1563    pub max: Point2f,
1564}
1565
1566impl Bounds2f {
1567    /// Creates a new value.
1568    pub fn new(min: Point2f, max: Point2f) -> Result<Self> {
1569        min.validate()?;
1570        max.validate()?;
1571        if min.x > max.x || min.y > max.y {
1572            return Err(invalid_argument("bounds min must not exceed max"));
1573        }
1574        Ok(Self { min, max })
1575    }
1576
1577    /// Creates bounds from one or more finite points.
1578    pub fn from_points(points: &[Point2f]) -> Result<Self> {
1579        if points.is_empty() {
1580            return Err(invalid_argument("bounds require at least one point"));
1581        }
1582        let mut min_x = points[0].x;
1583        let mut min_y = points[0].y;
1584        let mut max_x = points[0].x;
1585        let mut max_y = points[0].y;
1586        for point in points {
1587            point.validate()?;
1588            min_x = min_x.min(point.x);
1589            min_y = min_y.min(point.y);
1590            max_x = max_x.max(point.x);
1591            max_y = max_y.max(point.y);
1592        }
1593        Self::new(Point2f::new(min_x, min_y)?, Point2f::new(max_x, max_y)?)
1594    }
1595
1596    /// Returns bounds width.
1597    pub fn width(self) -> f32 {
1598        self.max.x - self.min.x
1599    }
1600
1601    /// Returns bounds height.
1602    pub fn height(self) -> f32 {
1603        self.max.y - self.min.y
1604    }
1605
1606    /// Returns bounds center.
1607    pub fn center(self) -> Result<Point2f> {
1608        Point2f::new(
1609            self.min.x + self.width() / 2.0,
1610            self.min.y + self.height() / 2.0,
1611        )
1612    }
1613
1614    /// Expands bounds by a finite non-negative padding in all directions.
1615    pub fn expand(self, padding: f32) -> Result<Self> {
1616        if !padding.is_finite() || padding < 0.0 {
1617            return Err(invalid_argument(
1618                "bounds padding must be finite and non-negative",
1619            ));
1620        }
1621        Self::new(
1622            Point2f::new(self.min.x - padding, self.min.y - padding)?,
1623            Point2f::new(self.max.x + padding, self.max.y + padding)?,
1624        )
1625    }
1626
1627    /// Returns contains.
1628    pub fn contains(self, point: Point2f) -> Result<bool> {
1629        point.validate()?;
1630        Ok(point.x >= self.min.x
1631            && point.x <= self.max.x
1632            && point.y >= self.min.y
1633            && point.y <= self.max.y)
1634    }
1635
1636    /// Returns union.
1637    pub fn union(self, other: Self) -> Result<Self> {
1638        Self::new(
1639            Point2f {
1640                x: self.min.x.min(other.min.x),
1641                y: self.min.y.min(other.min.y),
1642            },
1643            Point2f {
1644                x: self.max.x.max(other.max.x),
1645                y: self.max.y.max(other.max.y),
1646            },
1647        )
1648    }
1649}
1650
1651#[cfg(test)]
1652mod tests {
1653    use super::*;
1654
1655    #[test]
1656    fn rejects_invalid_rectangles_and_non_finite_points() {
1657        assert!(RectU32::new(0, 0, 0, 2).is_err());
1658        assert!(RectF32::new(0.0, 0.0, f32::NAN, 1.0).is_err());
1659        assert!(Point2f::new(f32::INFINITY, 0.0).is_err());
1660    }
1661
1662    #[test]
1663    fn computes_intersection_and_union() {
1664        let a = RectU32::new(0, 0, 10, 10).unwrap();
1665        let b = RectU32::new(5, 5, 10, 10).unwrap();
1666        assert_eq!(
1667            a.intersection(b).unwrap(),
1668            Some(RectU32::new(5, 5, 5, 5).unwrap())
1669        );
1670        assert_eq!(a.union(b).unwrap(), RectU32::new(0, 0, 15, 15).unwrap());
1671    }
1672
1673    #[test]
1674    fn vector_and_shape_helpers_cover_common_2d_operations() {
1675        let a = Point2f::new(0.0, 0.0).unwrap();
1676        let b = Point2f::new(3.0, 4.0).unwrap();
1677        let segment = LineSegment2::new(a, b).unwrap();
1678        assert_eq!(segment.length().unwrap(), 5.0);
1679        assert_eq!(segment.midpoint(), Point2f::new(1.5, 2.0).unwrap());
1680
1681        let x = Vector2f::new(1.0, 0.0).unwrap();
1682        let y = Vector2f::new(0.0, 1.0).unwrap();
1683        assert_eq!(x.dot(y).unwrap(), 0.0);
1684        assert_eq!(x.cross(y).unwrap(), 1.0);
1685
1686        let circle = Circle2::new(a, 2.0).unwrap();
1687        assert!(circle
1688            .contains_point(Point2f::new(1.0, 1.0).unwrap())
1689            .unwrap());
1690    }
1691
1692    #[test]
1693    fn polygon_reports_area_centroid_winding_and_containment() {
1694        let polygon = Polygon2f::new([
1695            Point2f::new(0.0, 0.0).unwrap(),
1696            Point2f::new(2.0, 0.0).unwrap(),
1697            Point2f::new(2.0, 2.0).unwrap(),
1698            Point2f::new(0.0, 2.0).unwrap(),
1699        ])
1700        .unwrap();
1701        assert_eq!(polygon.area().unwrap(), 4.0);
1702        assert!(!polygon.is_clockwise().unwrap());
1703        assert_eq!(polygon.centroid().unwrap(), Point2f::new(1.0, 1.0).unwrap());
1704        assert!(polygon
1705            .contains_point(Point2f::new(1.0, 1.0).unwrap())
1706            .unwrap());
1707    }
1708
1709    #[test]
1710    fn affine_round_trip_recovers_point() {
1711        let point = Point2f::new(4.0, 5.0).unwrap();
1712        let affine = Affine2::translation(3.0, -2.0)
1713            .compose(Affine2::scaling(2.0, 0.5))
1714            .unwrap();
1715        let restored = affine
1716            .invert()
1717            .unwrap()
1718            .apply_point(affine.apply_point(point));
1719        assert!((restored.x - point.x).abs() < 1.0e-5);
1720        assert!((restored.y - point.y).abs() < 1.0e-5);
1721    }
1722
1723    #[test]
1724    fn converts_between_normalized_and_pixel_coordinates() {
1725        let point = NormalizedPoint2::new(0.25, 0.5).unwrap();
1726        let size = Size2u::new(200, 100).unwrap();
1727        assert_eq!(point.to_pixel_point(size), Point2i::new(50, 50));
1728        let normalized = Point2f::new(50.0, 50.0)
1729            .unwrap()
1730            .to_normalized(size)
1731            .unwrap();
1732        assert!((normalized.x - 0.25).abs() < 1.0e-6);
1733        assert!((normalized.y - 0.5).abs() < 1.0e-6);
1734    }
1735
1736    #[test]
1737    fn broad_phase_strategies_match_for_mixed_rectangles() {
1738        let rects = [
1739            RectU32::new(0, 0, 10, 10).unwrap(),
1740            RectU32::new(5, 5, 8, 8).unwrap(),
1741            RectU32::new(30, 30, 4, 4).unwrap(),
1742            RectU32::new(31, 31, 4, 4).unwrap(),
1743            RectU32::new(100, 0, 10, 10).unwrap(),
1744        ];
1745        let brute = broad_phase_pairs_2d(
1746            &rects,
1747            BroadPhase2Config {
1748                strategy: BroadPhase2Strategy::BruteForce,
1749                ..BroadPhase2Config::default()
1750            },
1751        )
1752        .unwrap();
1753        let sweep = broad_phase_pairs_2d(
1754            &rects,
1755            BroadPhase2Config {
1756                strategy: BroadPhase2Strategy::SweepAndPrune,
1757                ..BroadPhase2Config::default()
1758            },
1759        )
1760        .unwrap();
1761        let grid = broad_phase_pairs_2d(
1762            &rects,
1763            BroadPhase2Config {
1764                strategy: BroadPhase2Strategy::SpatialHashGrid,
1765                cell_size: SpatialCellSize2::Fixed {
1766                    width: 8,
1767                    height: 8,
1768                },
1769                ..BroadPhase2Config::default()
1770            },
1771        )
1772        .unwrap();
1773
1774        assert_eq!(brute, sweep);
1775        assert_eq!(brute, grid);
1776        assert_eq!(
1777            brute,
1778            vec![
1779                CollisionPair {
1780                    left_index: 0,
1781                    right_index: 1
1782                },
1783                CollisionPair {
1784                    left_index: 2,
1785                    right_index: 3
1786                }
1787            ]
1788        );
1789    }
1790
1791    #[test]
1792    fn broad_phase_handles_touching_nested_and_spanning_rectangles() {
1793        let touching = [
1794            RectU32::new(0, 0, 10, 10).unwrap(),
1795            RectU32::new(10, 0, 10, 10).unwrap(),
1796        ];
1797        assert!(
1798            broad_phase_pairs_2d(&touching, BroadPhase2Config::default())
1799                .unwrap()
1800                .is_empty()
1801        );
1802
1803        let nested = [
1804            RectU32::new(0, 0, 20, 20).unwrap(),
1805            RectU32::new(2, 2, 4, 4).unwrap(),
1806        ];
1807        assert_eq!(
1808            broad_phase_pairs_2d(
1809                &nested,
1810                BroadPhase2Config {
1811                    strategy: BroadPhase2Strategy::SpatialHashGrid,
1812                    cell_size: SpatialCellSize2::Fixed {
1813                        width: 2,
1814                        height: 2,
1815                    },
1816                    ..BroadPhase2Config::default()
1817                }
1818            )
1819            .unwrap(),
1820            vec![CollisionPair {
1821                left_index: 0,
1822                right_index: 1
1823            }]
1824        );
1825    }
1826
1827    #[test]
1828    fn spatial_hash_grid_reports_cross_set_pairs_and_stats() {
1829        let left = [
1830            RectU32::new(0, 0, 10, 10).unwrap(),
1831            RectU32::new(40, 40, 4, 4).unwrap(),
1832        ];
1833        let right = [
1834            RectU32::new(4, 4, 10, 10).unwrap(),
1835            RectU32::new(80, 80, 4, 4).unwrap(),
1836        ];
1837        let mut grid = SpatialHashGrid2::new(BroadPhase2Config {
1838            strategy: BroadPhase2Strategy::SpatialHashGrid,
1839            cell_size: SpatialCellSize2::Fixed {
1840                width: 8,
1841                height: 8,
1842            },
1843            ..BroadPhase2Config::default()
1844        })
1845        .unwrap();
1846        let pairs = grid.candidate_pairs_between(&left, &right).unwrap();
1847
1848        assert_eq!(
1849            pairs,
1850            &[CollisionPair {
1851                left_index: 0,
1852                right_index: 0
1853            }]
1854        );
1855        assert_eq!(grid.stats().object_count, 4);
1856        assert_eq!(grid.stats().candidate_pair_count, 1);
1857    }
1858
1859    #[test]
1860    fn broad_phase_rejects_invalid_fixed_cell_sizes() {
1861        assert!(BroadPhase2Config {
1862            cell_size: SpatialCellSize2::Fixed {
1863                width: 0,
1864                height: 8,
1865            },
1866            ..BroadPhase2Config::default()
1867        }
1868        .validate()
1869        .is_err());
1870    }
1871}