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