goud_engine/sdk/collision/mod.rs
1//! # SDK Collision Detection API
2//!
3//! Ergonomic Rust functions for 2D collision detection. Thin wrappers over
4//! [`crate::ecs::collision`] -- pure math, no engine state required.
5//!
6//! # Example
7//!
8//! ```rust
9//! use goud_engine::sdk::collision::{circle_circle, point_in_rect, distance};
10//! use goud_engine::core::math::Vec2;
11//!
12//! if let Some(contact) = circle_circle(
13//! Vec2::new(0.0, 0.0), 1.0,
14//! Vec2::new(1.5, 0.0), 1.0,
15//! ) {
16//! println!("Collision! Penetration: {}", contact.penetration);
17//! }
18//!
19//! assert!(point_in_rect(Vec2::new(5.0, 5.0), Vec2::new(0.0, 0.0), Vec2::new(10.0, 10.0)));
20//! assert!((distance(Vec2::new(0.0, 0.0), Vec2::new(3.0, 4.0)) - 5.0).abs() < 0.001);
21//! ```
22
23use crate::core::math::Vec2;
24
25// Re-export Contact from the internal collision module so SDK users
26// do not need to reach into `ecs::collision` directly.
27pub use crate::ecs::collision::Contact;
28
29// Also re-export CollisionResponse for physics resolution workflows.
30pub use crate::ecs::collision::CollisionResponse;
31
32// =============================================================================
33// Collision API struct (provides FFI generation via proc-macro)
34// =============================================================================
35
36/// Zero-sized type that hosts collision detection functions.
37///
38/// All methods are static (no `self` receiver) and are used by the
39/// `#[goud_api]` proc-macro to auto-generate `#[no_mangle] extern "C"`
40/// FFI wrappers. The free functions below delegate to these methods.
41pub struct Collision;
42
43// NOTE: FFI wrappers are hand-written in ffi/collision.rs. The `#[goud_api]`
44// attribute is omitted here to avoid duplicate `#[no_mangle]` symbol conflicts.
45impl Collision {
46 /// Tests collision between two axis-aligned bounding boxes.
47 pub fn aabb_aabb(
48 center_a: Vec2,
49 half_extents_a: Vec2,
50 center_b: Vec2,
51 half_extents_b: Vec2,
52 ) -> Option<Contact> {
53 crate::ecs::collision::aabb_aabb_collision(
54 center_a,
55 half_extents_a,
56 center_b,
57 half_extents_b,
58 )
59 }
60
61 /// Tests collision between two circles.
62 pub fn circle_circle(
63 center_a: Vec2,
64 radius_a: f32,
65 center_b: Vec2,
66 radius_b: f32,
67 ) -> Option<Contact> {
68 crate::ecs::collision::circle_circle_collision(center_a, radius_a, center_b, radius_b)
69 }
70
71 /// Tests collision between a circle and an AABB.
72 pub fn circle_aabb(
73 circle_center: Vec2,
74 circle_radius: f32,
75 box_center: Vec2,
76 box_half_extents: Vec2,
77 ) -> Option<Contact> {
78 crate::ecs::collision::circle_aabb_collision(
79 circle_center,
80 circle_radius,
81 box_center,
82 box_half_extents,
83 )
84 }
85
86 /// Checks whether a point lies inside a rectangle.
87 pub fn point_in_rect(point: Vec2, rect_origin: Vec2, rect_size: Vec2) -> bool {
88 point.x >= rect_origin.x
89 && point.x <= rect_origin.x + rect_size.x
90 && point.y >= rect_origin.y
91 && point.y <= rect_origin.y + rect_size.y
92 }
93
94 /// Checks whether a point lies inside a circle.
95 pub fn point_in_circle(point: Vec2, circle_center: Vec2, circle_radius: f32) -> bool {
96 let dx = point.x - circle_center.x;
97 let dy = point.y - circle_center.y;
98 (dx * dx + dy * dy) <= (circle_radius * circle_radius)
99 }
100
101 /// Fast boolean overlap test for two AABBs using min/max corners.
102 pub fn aabb_overlap(min_a: Vec2, max_a: Vec2, min_b: Vec2, max_b: Vec2) -> bool {
103 max_a.x >= min_b.x && min_a.x <= max_b.x && max_a.y >= min_b.y && min_a.y <= max_b.y
104 }
105
106 /// Fast boolean overlap test for two circles.
107 pub fn circle_overlap(center_a: Vec2, radius_a: f32, center_b: Vec2, radius_b: f32) -> bool {
108 let dx = center_b.x - center_a.x;
109 let dy = center_b.y - center_a.y;
110 let combined_radius = radius_a + radius_b;
111 (dx * dx + dy * dy) <= (combined_radius * combined_radius)
112 }
113
114 /// Returns the Euclidean distance between two points.
115 pub fn distance(a: Vec2, b: Vec2) -> f32 {
116 let dx = b.x - a.x;
117 let dy = b.y - a.y;
118 (dx * dx + dy * dy).sqrt()
119 }
120
121 /// Returns the squared Euclidean distance between two points.
122 pub fn distance_squared(a: Vec2, b: Vec2) -> f32 {
123 let dx = b.x - a.x;
124 let dy = b.y - a.y;
125 dx * dx + dy * dy
126 }
127}
128
129// =============================================================================
130// Public free-function API (delegates to Collision methods)
131// =============================================================================
132
133/// Tests collision between two axis-aligned bounding boxes.
134///
135/// Returns contact information (penetration depth, normal, contact point) if
136/// the boxes overlap, or `None` if they are separated.
137///
138/// # Example
139///
140/// ```rust
141/// use goud_engine::sdk::collision::aabb_aabb;
142/// use goud_engine::core::math::Vec2;
143///
144/// let contact = aabb_aabb(
145/// Vec2::new(0.0, 0.0), Vec2::new(1.0, 1.0),
146/// Vec2::new(1.5, 0.0), Vec2::new(1.0, 1.0),
147/// );
148/// assert!(contact.is_some());
149/// ```
150#[inline]
151pub fn aabb_aabb(
152 center_a: Vec2,
153 half_extents_a: Vec2,
154 center_b: Vec2,
155 half_extents_b: Vec2,
156) -> Option<Contact> {
157 Collision::aabb_aabb(center_a, half_extents_a, center_b, half_extents_b)
158}
159
160/// Tests collision between two circles (fastest collision test).
161///
162/// # Example
163///
164/// ```rust
165/// use goud_engine::sdk::collision::circle_circle;
166/// use goud_engine::core::math::Vec2;
167///
168/// let contact = circle_circle(
169/// Vec2::new(0.0, 0.0), 1.0,
170/// Vec2::new(1.5, 0.0), 1.0,
171/// );
172/// assert!(contact.is_some());
173/// ```
174#[inline]
175pub fn circle_circle(
176 center_a: Vec2,
177 radius_a: f32,
178 center_b: Vec2,
179 radius_b: f32,
180) -> Option<Contact> {
181 Collision::circle_circle(center_a, radius_a, center_b, radius_b)
182}
183
184/// Tests collision between a circle and an axis-aligned bounding box.
185///
186/// # Example
187///
188/// ```rust
189/// use goud_engine::sdk::collision::circle_aabb;
190/// use goud_engine::core::math::Vec2;
191///
192/// let contact = circle_aabb(
193/// Vec2::new(0.0, 0.0), 1.0,
194/// Vec2::new(2.0, 0.0), Vec2::new(1.0, 1.0),
195/// );
196/// // Circle at origin with radius 1, box centered at (2,0) with half-extents (1,1)
197/// ```
198#[inline]
199pub fn circle_aabb(
200 circle_center: Vec2,
201 circle_radius: f32,
202 box_center: Vec2,
203 box_half_extents: Vec2,
204) -> Option<Contact> {
205 Collision::circle_aabb(circle_center, circle_radius, box_center, box_half_extents)
206}
207
208/// Checks whether a point lies inside a rectangle.
209///
210/// # Example
211///
212/// ```rust
213/// use goud_engine::sdk::collision::point_in_rect;
214/// use goud_engine::core::math::Vec2;
215///
216/// assert!(point_in_rect(
217/// Vec2::new(5.0, 5.0),
218/// Vec2::new(0.0, 0.0),
219/// Vec2::new(10.0, 10.0),
220/// ));
221/// assert!(!point_in_rect(
222/// Vec2::new(15.0, 5.0),
223/// Vec2::new(0.0, 0.0),
224/// Vec2::new(10.0, 10.0),
225/// ));
226/// ```
227#[inline]
228pub fn point_in_rect(point: Vec2, rect_origin: Vec2, rect_size: Vec2) -> bool {
229 Collision::point_in_rect(point, rect_origin, rect_size)
230}
231
232/// Checks whether a point lies inside a circle.
233///
234/// # Example
235///
236/// ```rust
237/// use goud_engine::sdk::collision::point_in_circle;
238/// use goud_engine::core::math::Vec2;
239///
240/// assert!(point_in_circle(Vec2::new(0.5, 0.0), Vec2::new(0.0, 0.0), 1.0));
241/// assert!(!point_in_circle(Vec2::new(2.0, 0.0), Vec2::new(0.0, 0.0), 1.0));
242/// ```
243#[inline]
244pub fn point_in_circle(point: Vec2, circle_center: Vec2, circle_radius: f32) -> bool {
245 Collision::point_in_circle(point, circle_center, circle_radius)
246}
247
248/// Fast boolean overlap test for two AABBs using min/max corners.
249///
250/// # Example
251///
252/// ```rust
253/// use goud_engine::sdk::collision::aabb_overlap;
254/// use goud_engine::core::math::Vec2;
255///
256/// let overlapping = aabb_overlap(
257/// Vec2::new(0.0, 0.0), Vec2::new(2.0, 2.0),
258/// Vec2::new(1.0, 1.0), Vec2::new(3.0, 3.0),
259/// );
260/// assert!(overlapping);
261/// ```
262#[inline]
263pub fn aabb_overlap(min_a: Vec2, max_a: Vec2, min_b: Vec2, max_b: Vec2) -> bool {
264 Collision::aabb_overlap(min_a, max_a, min_b, max_b)
265}
266
267/// Fast boolean overlap test for two circles.
268///
269/// # Example
270///
271/// ```rust
272/// use goud_engine::sdk::collision::circle_overlap;
273/// use goud_engine::core::math::Vec2;
274///
275/// assert!(circle_overlap(Vec2::new(0.0, 0.0), 1.0, Vec2::new(1.5, 0.0), 1.0));
276/// assert!(!circle_overlap(Vec2::new(0.0, 0.0), 1.0, Vec2::new(5.0, 0.0), 1.0));
277/// ```
278#[inline]
279pub fn circle_overlap(center_a: Vec2, radius_a: f32, center_b: Vec2, radius_b: f32) -> bool {
280 Collision::circle_overlap(center_a, radius_a, center_b, radius_b)
281}
282
283/// Returns the Euclidean distance between two points.
284///
285/// # Example
286///
287/// ```rust
288/// use goud_engine::sdk::collision::distance;
289/// use goud_engine::core::math::Vec2;
290///
291/// let d = distance(Vec2::new(0.0, 0.0), Vec2::new(3.0, 4.0));
292/// assert!((d - 5.0).abs() < 0.001);
293/// ```
294#[inline]
295pub fn distance(a: Vec2, b: Vec2) -> f32 {
296 Collision::distance(a, b)
297}
298
299/// Returns the squared Euclidean distance between two points.
300///
301/// # Example
302///
303/// ```rust
304/// use goud_engine::sdk::collision::distance_squared;
305/// use goud_engine::core::math::Vec2;
306///
307/// let d2 = distance_squared(Vec2::new(0.0, 0.0), Vec2::new(3.0, 4.0));
308/// assert!((d2 - 25.0).abs() < 0.001);
309/// ```
310#[inline]
311pub fn distance_squared(a: Vec2, b: Vec2) -> f32 {
312 Collision::distance_squared(a, b)
313}
314
315#[cfg(test)]
316#[path = "tests.rs"]
317mod tests;