ycraft/
collision.rs

1//! Implement collision shapes and handle collisions between them
2
3use sdl2::rect::Rect;
4
5/// Colliders that attach to GameObjects. Support Circle and Rect colliders
6#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord)]
7pub enum CollisionShape {
8    Circle {
9        center: (i32, i32),
10        radius: u32
11    }, Rect {
12        center: (i32, i32),
13        size: (u32, u32)
14    }, Polygon {
15        center: (i32, i32),
16        points: Vec<(i32, i32)>
17    }
18}
19
20impl CollisionShape {
21    pub fn collides_with(&self, other: &CollisionShape) -> bool {
22        match self {
23            CollisionShape::Circle { center, radius } => {
24                match other {
25                    CollisionShape::Circle { center: other_center, radius: other_radius } => {
26                        let a = other_center.0 - center.0;
27                        let b = other_center.1 - center.1;
28                        let c = other_radius + radius;
29                        ((a * a) + (b * b)) as u32 <= (c * c)
30                    }, CollisionShape::Rect { center: other_center, size: other_size } => {
31                        let mut test = center.clone();
32                        let rect = Rect::new(
33                            other_center.0 - other_size.0 as i32 / 2,
34                            other_center.1 - other_size.1 as i32 / 2,
35                            other_size.0,
36                            other_size.1
37                        );
38                        if center.0 < rect.x {
39                            test.0 = rect.x;
40                        } else if center.0 > rect.x + rect.w {
41                            test.0 = rect.x + rect.w;
42                        }
43                        if center.1 < rect.y {
44                            test.1 = rect.y;
45                        } else if center.1 > rect.y + rect.h {
46                            test.1 = rect.y + rect.h;
47                        }
48                        let dist_lat = (center.0 - test.0, center.1 - test.1);
49                        let dist_sqrd = (dist_lat.0 * dist_lat.0) + (dist_lat.1 * dist_lat.1);
50                        dist_sqrd as u32 <= radius * radius
51                    }, CollisionShape::Polygon { center: other_center, points } => {
52                        for point in points.iter() {
53                            let dists = (
54                                other_center.0 + point.0 - center.0,
55                                other_center.1 + point.1 - center.1
56                            );
57                            let dist_sqrd = dists.0 * dists.0 + dists.1 + dists.1;
58                            if (dist_sqrd as u32) < radius * radius {
59                                return true;
60                            }
61                        }
62                        false
63                    }
64                }
65            }, CollisionShape::Rect { center, size } => {
66                match other {
67                    CollisionShape::Circle { center: other_center, radius: other_radius } => {
68                        let mut test = other_center.clone();
69                        let rect = Rect::new(
70                            center.0 - size.0 as i32 / 2,
71                            center.1 - size.1 as i32 / 2,
72                            size.0,
73                            size.1
74                        );
75                        if other_center.0 < rect.x {
76                            test.0 = rect.x;
77                        } else if other_center.0 > rect.x + rect.w {
78                            test.0 = rect.x + rect.w;
79                        }
80                        if other_center.1 < rect.y {
81                            test.1 = rect.y;
82                        } else if other_center.1 > rect.y + rect.h {
83                            test.1 = rect.y + rect.h;
84                        }
85                        let dist_lat = (other_center.0 - test.0, other_center.1 - test.1);
86                        let dist_sqrd = (dist_lat.0 * dist_lat.0) + (dist_lat.1 * dist_lat.1);
87                        dist_sqrd as u32 <= other_radius * other_radius
88                    }, CollisionShape::Rect { center: other_center, size: other_size } => {
89                        let r1 = Rect::new(
90                            center.0 - size.0 as i32 / 2,
91                            center.1 - size.1 as i32 / 2,
92                            size.0,
93                            size.1
94                        );
95                        let r2 = Rect::new(
96                            other_center.0 - other_size.1 as i32 / 2,
97                            other_center.1 - other_size.1 as i32 / 2,
98                            other_size.0,
99                            other_size.1
100                        );
101                        r1.x + r1.w >= r2.x
102                            && r1.x <= r2.x + r2.w
103                            && r1.y + r1.h >= r2.y
104                            && r1.y <= r2.y + r2.h
105                    }, CollisionShape::Polygon { center: other_center, points } => {
106                        for point in points.iter() {
107                            let transformed_point = (
108                                other_center.0 + point.0,
109                                other_center.1 + point.1
110                            );
111                            let (left, right) = (
112                                center.0 - size.0 as i32 / 2,
113                                center.0 + size.0 as i32 / 2
114                            );
115                            let (top, bottom) = (
116                                center.1 - size.1 as i32 / 2,
117                                center.1 + size.1 as i32 / 2
118                            );
119                            if transformed_point.0 >= left
120                                    && transformed_point.0 <= right
121                                    && transformed_point.1 >= top
122                                    && transformed_point.1 <= bottom {
123                                return true;
124                            }
125                        }
126                        false
127                    }
128                }
129            }, CollisionShape::Polygon { center, points } => {
130                match other {
131                    CollisionShape::Circle { center: other_center, radius } => {
132                        for point in points.iter() {
133                            let dists = (
134                                center.0 + point.0 - other_center.0,
135                                center.1 + point.1 - other_center.1
136                            );
137                            let dist_sqrd = dists.0 * dists.0 + dists.1 + dists.1;
138                            if (dist_sqrd as u32) < radius * radius {
139                                return true;
140                            }
141                        }
142                        false
143                    }, CollisionShape::Rect { center: other_center, size } => {
144                        for point in points.iter() {
145                            let transformed_point = (
146                                center.0 + point.0,
147                                center.1 + point.1
148                            );
149                            let (left, right) = (
150                                other_center.0 - size.0 as i32 / 2,
151                                other_center.0 + size.0 as i32 / 2
152                            );
153                            let (top, bottom) = (
154                                other_center.1 - size.1 as i32 / 2,
155                                other_center.1 + size.1 as i32 / 2
156                            );
157                            if transformed_point.0 >= left
158                                    && transformed_point.0 <= right
159                                    && transformed_point.1 >= top
160                                    && transformed_point.1 <= bottom {
161                                return true;
162                            }
163                        }
164                        false
165                    }, CollisionShape::Polygon { center: other_center, points: other_points } => {
166                        // Iterate over each edge of the first polygon
167                        for i in 0..points.len() {
168                            let p1 = points[i];
169                            let p2 = points[(i + 1) % points.len()];
170
171                            // Calculate the normal of the edge
172                            let normal = ((p2.1 - p1.1), -(p2.0 - p1.0));
173
174                            // Project both polygons onto the normal
175                            let (min1, max1) = project_polygon(&points, &normal, &center);
176                            let (min2, max2) = project_polygon(
177                                &other_points, &normal, &other_center
178                            );
179
180                            // Check for overlap on the projected axis
181                            if max1 < min2 || max2 < min1 {
182                                return false;
183                            }
184                        }
185                        true
186                    }
187                }
188            }
189        }
190    }
191}
192
193fn project_polygon(points: &[(i32, i32)], axis: &(i32, i32), center: &(i32, i32)) -> (i32, i32) {
194    let mut min = i32::MAX;
195    let mut max = i32::MIN;
196    for point in points.iter() {
197        let proj_point = (point.0 + center.0, point.1 + center.1);
198        let dot_prod = proj_point.0 * axis.0 + proj_point.1 * axis.1;
199        if dot_prod < min {
200            min = dot_prod;
201        }
202        if dot_prod > max {
203            max = dot_prod;
204        }
205    }
206    (min, max)
207}
208