ferravex_core/
collision.rs1use ferravex_math::Vec2;
3
4#[derive(Debug, Clone, Copy, PartialEq)]
5pub struct Aabb2<V = Vec2> {
6 pub min: V,
7 pub max: V,
8}
9
10impl<V: Copy + Into<[f32; 2]> + From<[f32; 2]>> Aabb2<V> {
11 pub fn from_min_size(min: V, size: V) -> Self {
12 let a: [f32; 2] = min.into();
13 let b: [f32; 2] = size.into();
14 Self {
15 min,
16 max: [a[0] + b[0], a[1] + b[1]].into(),
17 }
18 }
19
20 fn owned(self) -> Aabb2 {
21 Aabb2 {
22 min: Vec2::from(self.min.into()),
23 max: Vec2::from(self.max.into()),
24 }
25 }
26 fn valid(self) -> bool {
27 let a = self.owned();
28 a.min.is_finite() && a.max.is_finite() && a.max.cmpge(a.min).all()
29 }
30
31 pub fn overlaps(self, other: Self) -> bool {
34 let (this, other) = (self.owned(), other.owned());
35 this.valid()
36 && other.valid()
37 && this.min.cmplt(this.max).all()
38 && other.min.cmplt(other.max).all()
39 && this.min.cmplt(other.max).all()
40 && this.max.cmpgt(other.min).all()
41 }
42
43 pub fn cast_segment(self, start: V, end: V) -> Option<f32> {
46 let this = self.owned();
47 let (start, end) = (Vec2::from(start.into()), Vec2::from(end.into()));
48 if !this.valid() || !start.is_finite() || !end.is_finite() {
49 return None;
50 }
51 let delta = end - start;
52 if !delta.is_finite() {
53 return None;
54 }
55 let mut near: f32 = 0.0;
56 let mut far: f32 = 1.0;
57 for axis in 0..2 {
58 if delta[axis] == 0.0 {
59 if start[axis] < this.min[axis] || start[axis] > this.max[axis] {
60 return None;
61 }
62 } else {
63 let a = (this.min[axis] - start[axis]) / delta[axis];
64 let b = (this.max[axis] - start[axis]) / delta[axis];
65 near = near.max(a.min(b));
66 far = far.min(a.max(b));
67 if near > far {
68 return None;
69 }
70 }
71 }
72 Some(near)
73 }
74}
75
76pub fn segment_circle<V: Copy + Into<[f32; 2]>>(
79 start: V,
80 end: V,
81 center: V,
82 radius: f32,
83) -> Option<f32> {
84 let (start, end, center) = (
85 Vec2::from(start.into()),
86 Vec2::from(end.into()),
87 Vec2::from(center.into()),
88 );
89 if !start.is_finite()
90 || !end.is_finite()
91 || !center.is_finite()
92 || !radius.is_finite()
93 || radius < 0.0
94 {
95 return None;
96 }
97 let delta = end - start;
98 let relative = start - center;
99 let c = relative.length_squared() - radius * radius;
100 if c <= 0.0 {
101 return Some(0.0);
102 }
103 let a = delta.length_squared();
104 if a == 0.0 || !a.is_finite() || !c.is_finite() {
105 return None;
106 }
107 let b = relative.dot(delta);
108 let discriminant = b * b - a * c;
109 if discriminant < 0.0 || !discriminant.is_finite() {
110 return None;
111 }
112 let t = (-b - discriminant.sqrt()) / a;
113 (0.0..=1.0).contains(&t).then_some(t)
114}
115
116#[cfg(test)]
117mod tests {
118 use super::*;
119 #[test]
120 fn resting_edges_do_not_push_a_platformer_sideways() {
121 let floor = Aabb2::from_min_size(Vec2::new(0.0, 10.0), Vec2::new(100.0, 10.0));
122 assert!(!floor.overlaps(Aabb2::from_min_size(Vec2::new(4.0, 0.0), Vec2::splat(10.0))));
123 assert!(floor.overlaps(Aabb2::from_min_size(Vec2::new(4.0, 0.1), Vec2::splat(10.0))));
124 assert!(!floor.overlaps(Aabb2::from_min_size(Vec2::new(4.0, 11.0), Vec2::ZERO)));
125 }
126 #[test]
127 fn segment_queries_cover_tunneling_tangency_and_stationary_input() {
128 let box_ = Aabb2::from_min_size(Vec2::splat(5.0), Vec2::splat(2.0));
129 assert_eq!(
130 box_.cast_segment(Vec2::new(0.0, 6.0), Vec2::new(10.0, 6.0)),
131 Some(0.5)
132 );
133 assert_eq!(
134 box_.cast_segment(Vec2::splat(6.0), Vec2::splat(6.0)),
135 Some(0.0)
136 );
137 assert_eq!(box_.cast_segment(Vec2::ZERO, Vec2::X), None);
138 assert_eq!(
139 segment_circle(
140 Vec2::ZERO,
141 Vec2::new(100.0, 0.0),
142 Vec2::new(50.0, 0.0),
143 10.0
144 ),
145 Some(0.4)
146 );
147 assert_eq!(
148 segment_circle(
149 Vec2::ZERO,
150 Vec2::new(100.0, 0.0),
151 Vec2::new(50.0, 10.0),
152 10.0
153 ),
154 Some(0.5)
155 );
156 assert_eq!(
157 segment_circle(Vec2::ZERO, Vec2::ZERO, Vec2::ZERO, 1.0),
158 Some(0.0)
159 );
160 assert_eq!(segment_circle(Vec2::ZERO, Vec2::ZERO, Vec2::X, 0.1), None);
161 assert_eq!(segment_circle(Vec2::ZERO, Vec2::X, Vec2::X, f32::NAN), None);
162 }
163}