Skip to main content

goud_engine/ecs/components/collider/
shape.rs

1//! Collision shape definitions.
2//!
3//! Defines the [`ColliderShape`] enum and associated geometry methods used by
4//! the [`Collider`](super::Collider) component.
5
6use crate::core::math::{Rect, Vec2};
7
8// =============================================================================
9// ColliderShape Enum
10// =============================================================================
11
12/// The geometric shape of a collider.
13///
14/// Each shape type has different performance characteristics and use cases:
15///
16/// - **Circle**: Fastest collision detection, best for balls, projectiles
17/// - **Box**: Axis-aligned (AABB) or oriented (OBB), good for walls and platforms
18/// - **Capsule**: Good for characters, combines efficiency with smooth edges
19/// - **Polygon**: Most flexible but slowest, use sparingly for complex shapes
20#[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize)]
21pub enum ColliderShape {
22    /// Circle collider defined by radius.
23    ///
24    /// Center is at the entity's position. Fastest collision detection.
25    Circle {
26        /// Radius of the circle in world units
27        radius: f32,
28    },
29
30    /// Axis-Aligned Bounding Box (AABB).
31    ///
32    /// Defined by half-extents (half-width, half-height) from the center.
33    /// Fast collision detection, no rotation support.
34    Aabb {
35        /// Half-extents (half-width, half-height)
36        half_extents: Vec2,
37    },
38
39    /// Oriented Bounding Box (OBB).
40    ///
41    /// Similar to AABB but can be rotated. Slightly slower than AABB.
42    Obb {
43        /// Half-extents (half-width, half-height)
44        half_extents: Vec2,
45    },
46
47    /// Capsule collider (rounded rectangle).
48    ///
49    /// Defined by half-height and radius. Good for character controllers.
50    /// The capsule extends vertically from the center.
51    Capsule {
52        /// Half-height of the capsule's cylindrical section
53        half_height: f32,
54        /// Radius of the capsule's rounded ends
55        radius: f32,
56    },
57
58    /// Convex polygon collider.
59    ///
60    /// Vertices must be in counter-clockwise order and form a convex hull.
61    /// Slowest collision detection, use sparingly.
62    Polygon {
63        /// Vertices in local space, counter-clockwise order
64        vertices: Vec<Vec2>,
65    },
66}
67
68impl ColliderShape {
69    /// Returns the type name of this shape.
70    pub fn type_name(&self) -> &'static str {
71        match self {
72            ColliderShape::Circle { .. } => "Circle",
73            ColliderShape::Aabb { .. } => "AABB",
74            ColliderShape::Obb { .. } => "OBB",
75            ColliderShape::Capsule { .. } => "Capsule",
76            ColliderShape::Polygon { .. } => "Polygon",
77        }
78    }
79
80    /// Computes the axis-aligned bounding box (AABB) for this shape.
81    ///
82    /// Returns a rectangle in local space that fully contains the shape.
83    /// For rotated shapes (OBB, Polygon), this is a conservative bound.
84    pub fn compute_aabb(&self) -> Rect {
85        match self {
86            ColliderShape::Circle { radius } => {
87                Rect::new(-radius, -radius, radius * 2.0, radius * 2.0)
88            }
89            ColliderShape::Aabb { half_extents } | ColliderShape::Obb { half_extents } => {
90                Rect::new(
91                    -half_extents.x,
92                    -half_extents.y,
93                    half_extents.x * 2.0,
94                    half_extents.y * 2.0,
95                )
96            }
97            ColliderShape::Capsule {
98                half_height,
99                radius,
100            } => {
101                let width = radius * 2.0;
102                let height = (half_height + radius) * 2.0;
103                Rect::new(-radius, -(half_height + radius), width, height)
104            }
105            ColliderShape::Polygon { vertices } => {
106                if vertices.is_empty() {
107                    return Rect::unit();
108                }
109
110                let mut min_x = vertices[0].x;
111                let mut min_y = vertices[0].y;
112                let mut max_x = vertices[0].x;
113                let mut max_y = vertices[0].y;
114
115                for v in vertices.iter().skip(1) {
116                    min_x = min_x.min(v.x);
117                    min_y = min_y.min(v.y);
118                    max_x = max_x.max(v.x);
119                    max_y = max_y.max(v.y);
120                }
121
122                Rect::new(min_x, min_y, max_x - min_x, max_y - min_y)
123            }
124        }
125    }
126
127    /// Returns true if this shape is a circle.
128    pub fn is_circle(&self) -> bool {
129        matches!(self, ColliderShape::Circle { .. })
130    }
131
132    /// Returns true if this shape is an axis-aligned box (AABB).
133    pub fn is_aabb(&self) -> bool {
134        matches!(self, ColliderShape::Aabb { .. })
135    }
136
137    /// Returns true if this shape is an oriented box (OBB).
138    pub fn is_obb(&self) -> bool {
139        matches!(self, ColliderShape::Obb { .. })
140    }
141
142    /// Returns true if this shape is a capsule.
143    pub fn is_capsule(&self) -> bool {
144        matches!(self, ColliderShape::Capsule { .. })
145    }
146
147    /// Returns true if this shape is a polygon.
148    pub fn is_polygon(&self) -> bool {
149        matches!(self, ColliderShape::Polygon { .. })
150    }
151
152    /// Validates that the shape is well-formed.
153    ///
154    /// Returns `true` if:
155    /// - Radii and extents are positive
156    /// - Polygons have at least 3 vertices
157    /// - Polygon vertices form a convex hull (not checked, assumed by user)
158    pub fn is_valid(&self) -> bool {
159        match self {
160            ColliderShape::Circle { radius } => *radius > 0.0,
161            ColliderShape::Aabb { half_extents } | ColliderShape::Obb { half_extents } => {
162                half_extents.x > 0.0 && half_extents.y > 0.0
163            }
164            ColliderShape::Capsule {
165                half_height,
166                radius,
167            } => *half_height > 0.0 && *radius > 0.0,
168            ColliderShape::Polygon { vertices } => vertices.len() >= 3,
169        }
170    }
171}
172
173impl Default for ColliderShape {
174    /// Returns a unit circle (radius 1.0) as the default shape.
175    fn default() -> Self {
176        ColliderShape::Circle { radius: 1.0 }
177    }
178}