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}