Skip to main content

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;