1use gizmo_math::{Quat, Vec3};
2use serde::{Deserialize, Serialize};
3
4use super::{CollisionLayer, PhysicsMaterial, Transform};
5
6#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
7#[non_exhaustive]
8pub struct Collider {
9 pub shape: ColliderShape,
10 pub is_trigger: bool,
11 pub material: PhysicsMaterial,
12 pub collision_layer: CollisionLayer,
13}
14
15impl Default for Collider {
16 fn default() -> Self {
17 Self {
18 shape: ColliderShape::Sphere(SphereShape { radius: 0.5 }),
19 is_trigger: false,
20 material: PhysicsMaterial::default(),
21 collision_layer: CollisionLayer::default(),
22 }
23 }
24}
25
26impl Collider {
27 pub fn from_shape(shape: ColliderShape) -> Self {
35 Self {
36 shape,
37 ..Default::default()
38 }
39 }
40
41 pub fn compute_aabb(&self, position: Vec3, rotation: Quat) -> gizmo_math::Aabb {
43 match &self.shape {
44 ColliderShape::Sphere(s) => {
45 let radius_vec = Vec3::splat(s.radius);
46 gizmo_math::Aabb::from_center_half_extents(position, radius_vec)
47 }
48 ColliderShape::Box(b) => {
49 let corners = [
51 Vec3::new(b.half_extents.x, b.half_extents.y, b.half_extents.z),
52 Vec3::new(-b.half_extents.x, b.half_extents.y, b.half_extents.z),
53 Vec3::new(b.half_extents.x, -b.half_extents.y, b.half_extents.z),
54 Vec3::new(b.half_extents.x, b.half_extents.y, -b.half_extents.z),
55 Vec3::new(-b.half_extents.x, -b.half_extents.y, b.half_extents.z),
56 Vec3::new(-b.half_extents.x, b.half_extents.y, -b.half_extents.z),
57 Vec3::new(b.half_extents.x, -b.half_extents.y, -b.half_extents.z),
58 Vec3::new(-b.half_extents.x, -b.half_extents.y, -b.half_extents.z),
59 ];
60
61 let mut min = Vec3::splat(f32::INFINITY);
62 let mut max = Vec3::splat(f32::NEG_INFINITY);
63
64 for corner in &corners {
65 let rotated = rotation * (*corner);
66 let world_pos = position + rotated;
67 min = min.min(world_pos);
68 max = max.max(world_pos);
69 }
70
71 gizmo_math::Aabb::new(min, max)
72 }
73 ColliderShape::Capsule(c) => {
74 let axis = rotation * Vec3::Y;
75 let half_height_vec = axis * c.half_height;
76 let radius_vec = Vec3::splat(c.radius);
77 let extent = half_height_vec.abs() + radius_vec;
78 gizmo_math::Aabb::from_center_half_extents(position, extent)
79 }
80 ColliderShape::Plane(_) => {
81 let large = 10000.0;
83 gizmo_math::Aabb::new(position - Vec3::splat(large), position + Vec3::splat(large))
84 }
85 ColliderShape::TriMesh(tm) => {
86 let mut min = Vec3::splat(f32::INFINITY);
87 let mut max = Vec3::splat(f32::NEG_INFINITY);
88 for v in tm.vertices.iter() {
89 let world_pos = position + rotation * (*v);
90 min = min.min(world_pos);
91 max = max.max(world_pos);
92 }
93 gizmo_math::Aabb::new(min, max)
94 }
95 ColliderShape::ConvexHull(ch) => {
96 let mut min = Vec3::splat(f32::INFINITY);
97 let mut max = Vec3::splat(f32::NEG_INFINITY);
98 for v in ch.vertices.iter() {
99 let world_pos = position + rotation * (*v);
100 min = min.min(world_pos);
101 max = max.max(world_pos);
102 }
103 gizmo_math::Aabb::new(min, max)
104 }
105 ColliderShape::Compound(shapes) => {
106 let mut min = Vec3::splat(f32::INFINITY);
107 let mut max = Vec3::splat(f32::NEG_INFINITY);
108 for (local_t, sub_shape) in shapes {
109 let world_pos = position + rotation.mul_vec3(local_t.position);
110 let world_rot = rotation * local_t.rotation;
111
112 let temp_col = Collider {
113 shape: (**sub_shape).clone(),
114 ..Default::default()
115 };
116 let sub_aabb = temp_col.compute_aabb(world_pos, world_rot);
117 min = min.min(sub_aabb.min.into());
118 max = max.max(sub_aabb.max.into());
119 }
120 gizmo_math::Aabb::new(min, max)
121 }
122 }
123 }
124
125 pub fn plane(normal: Vec3, distance: f32) -> Self {
126 Self {
127 shape: ColliderShape::Plane(PlaneShape { normal, distance }),
128 ..Default::default()
129 }
130 }
131
132 pub fn sphere(radius: f32) -> Self {
133 Self {
134 shape: ColliderShape::Sphere(SphereShape { radius }),
135 ..Default::default()
136 }
137 }
138
139 pub fn box_collider(half_extents: Vec3) -> Self {
140 Self {
141 shape: ColliderShape::Box(BoxShape { half_extents }),
142 ..Default::default()
143 }
144 }
145
146 pub fn offset_box(offset: Vec3, half_extents: Vec3) -> Self {
147 Self {
148 shape: ColliderShape::Compound(vec![(
149 Transform::new(offset),
150 Box::new(ColliderShape::Box(BoxShape { half_extents })),
151 )]),
152 ..Default::default()
153 }
154 }
155
156 pub fn capsule(radius: f32, half_height: f32) -> Self {
157 Self {
158 shape: ColliderShape::Capsule(CapsuleShape {
159 radius,
160 half_height,
161 }),
162 ..Default::default()
163 }
164 }
165
166 pub fn convex_hull(points: &[Vec3]) -> Self {
167 let hull = crate::quickhull::compute_convex_hull(points);
168 Self {
169 shape: ColliderShape::ConvexHull(ConvexHullShape {
170 vertices: std::sync::Arc::new(hull.vertices),
171 faces: std::sync::Arc::new(hull.faces),
172 }),
173 ..Default::default()
174 }
175 }
176
177 pub fn with_trigger(mut self, is_trigger: bool) -> Self {
178 self.is_trigger = is_trigger;
179 self
180 }
181
182 pub fn with_material(mut self, material: PhysicsMaterial) -> Self {
183 self.material = material;
184 self
185 }
186
187 pub fn with_restitution(mut self, restitution: f32) -> Self {
190 self.material.restitution = restitution.clamp(0.0, 1.0);
191 self
192 }
193
194 pub fn with_friction(mut self, friction: f32) -> Self {
196 let f = friction.max(0.0);
197 self.material.static_friction = f;
198 self.material.dynamic_friction = f;
199 self
200 }
201
202 pub fn aabb(half_extents: Vec3) -> Self {
204 Self::box_collider(half_extents)
205 }
206
207 pub fn new_sphere(radius: f32) -> Self {
208 Self::sphere(radius)
209 }
210
211 pub fn new_aabb(x: f32, y: f32, z: f32) -> Self {
212 Self::box_collider(Vec3::new(x, y, z))
213 }
214
215 pub fn new_capsule(radius: f32, half_height: f32) -> Self {
216 Self::capsule(radius, half_height)
217 }
218
219 pub fn with_layer(mut self, layer: CollisionLayer) -> Self {
220 self.collision_layer = layer;
221 self
222 }
223
224 pub fn volume(&self) -> f32 {
225 match &self.shape {
226 ColliderShape::Sphere(s) => (4.0 / 3.0) * std::f32::consts::PI * s.radius.powi(3),
227 ColliderShape::Box(b) => 8.0 * b.half_extents.x * b.half_extents.y * b.half_extents.z,
228 ColliderShape::Capsule(c) => {
229 let cylinder_vol = std::f32::consts::PI * c.radius.powi(2) * (c.half_height * 2.0);
230 let sphere_vol = (4.0 / 3.0) * std::f32::consts::PI * c.radius.powi(3);
231 cylinder_vol + sphere_vol
232 }
233 ColliderShape::Plane(_) => f32::MAX, ColliderShape::TriMesh(_)
235 | ColliderShape::ConvexHull(_)
236 | ColliderShape::Compound(_) => {
237 let aabb = self.compute_aabb(Vec3::ZERO, Quat::IDENTITY);
238 let e = aabb.max - aabb.min;
239 e.x * e.y * e.z * 0.5 }
241 }
242 }
243
244 pub fn extents_y(&self) -> f32 {
245 match &self.shape {
246 ColliderShape::Sphere(s) => s.radius,
247 ColliderShape::Box(b) => b.half_extents.y,
248 ColliderShape::Capsule(c) => c.half_height + c.radius,
249 ColliderShape::Plane(_) => 0.0,
250 ColliderShape::TriMesh(_)
251 | ColliderShape::ConvexHull(_)
252 | ColliderShape::Compound(_) => {
253 let aabb = self.compute_aabb(Vec3::ZERO, Quat::IDENTITY);
254 (aabb.max.y - aabb.min.y) * 0.5
255 }
256 }
257 }
258}
259
260#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
266pub enum ColliderShape {
267 Sphere(SphereShape),
268 Box(BoxShape),
269 Capsule(CapsuleShape),
270 Plane(PlaneShape),
271 TriMesh(TriMeshShape),
272 ConvexHull(ConvexHullShape),
273 Compound(Vec<(Transform, Box<ColliderShape>)>),
274}
275
276#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize)]
277pub struct SphereShape {
278 pub radius: f32,
279}
280
281#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize)]
282pub struct BoxShape {
283 pub half_extents: Vec3,
284}
285
286#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize)]
287pub struct CapsuleShape {
288 pub radius: f32,
289 pub half_height: f32, }
291
292#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize)]
293pub struct PlaneShape {
294 pub normal: Vec3,
295 pub distance: f32,
296}
297
298#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
299#[serde(into = "TriMeshShapeData", from = "TriMeshShapeData")]
300pub struct TriMeshShape {
301 pub vertices: std::sync::Arc<Vec<Vec3>>,
302 pub indices: std::sync::Arc<Vec<u32>>,
303 #[serde(skip)]
304 pub bvh: std::sync::Arc<crate::bvh::BvhTree>,
305}
306
307#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
308struct TriMeshShapeData {
309 vertices: Vec<Vec3>,
310 indices: Vec<u32>,
311}
312
313impl From<TriMeshShapeData> for TriMeshShape {
314 fn from(mut data: TriMeshShapeData) -> Self {
315 let bvh = crate::bvh::BvhTree::build(&data.vertices, &mut data.indices).unwrap_or_default();
316 Self {
317 vertices: std::sync::Arc::new(data.vertices),
318 indices: std::sync::Arc::new(data.indices),
319 bvh: std::sync::Arc::new(bvh),
320 }
321 }
322}
323
324impl From<TriMeshShape> for TriMeshShapeData {
325 fn from(shape: TriMeshShape) -> Self {
326 Self {
327 vertices: (*shape.vertices).clone(),
328 indices: (*shape.indices).clone(),
329 }
330 }
331}
332
333#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
334#[serde(into = "ConvexHullShapeData", from = "ConvexHullShapeData")]
335pub struct ConvexHullShape {
336 pub vertices: std::sync::Arc<Vec<Vec3>>,
337 pub faces: std::sync::Arc<Vec<[u32; 3]>>,
338}
339
340#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
341struct ConvexHullShapeData {
342 points: Vec<Vec3>, }
344
345impl From<ConvexHullShapeData> for ConvexHullShape {
346 fn from(data: ConvexHullShapeData) -> Self {
347 let hull = crate::quickhull::compute_convex_hull(&data.points);
348 Self {
349 vertices: std::sync::Arc::new(hull.vertices),
350 faces: std::sync::Arc::new(hull.faces),
351 }
352 }
353}
354
355impl From<ConvexHullShape> for ConvexHullShapeData {
356 fn from(shape: ConvexHullShape) -> Self {
357 Self {
358 points: (*shape.vertices).clone(),
359 }
360 }
361}
362
363gizmo_core::impl_component!(Collider);