1use super::sphere::ray_cast_sphere;
7use super::types::{Capsule, MassData, PlaneResult, RayCastInput, ShapeCastInput, Sphere};
8use crate::constants::{linear_slop, overlap_slop};
9use crate::distance::{
10 make_proxy, shape_cast, shape_distance, CastOutput, DistanceInput, ShapeCastPairInput,
11 ShapeProxy, SimplexCache,
12};
13use crate::math_functions::{
14 add, add_mm, clamp_float, compute_quat_between_unit_vectors, cylinder_inertia, distance, dot,
15 get_length_and_normalize, inv_mul_transforms, length_squared, make_matrix_from_quat, max, min,
16 mul_add, mul_mm, mul_sub, mul_sv, normalize, perp, segment_distance, sphere_inertia, sub,
17 transform_point, transpose, Aabb, Plane, Transform, Vec3, MAT3_IDENTITY, TRANSFORM_IDENTITY,
18 VEC3_AXIS_Y,
19};
20
21pub fn compute_capsule_mass(shape: &Capsule, density: f32) -> MassData {
23 let c1 = shape.center1;
24 let c2 = shape.center2;
25 let r = shape.radius;
26
27 let cylinder_height = distance(c1, c2);
29 let cylinder_volume = crate::math_functions::PI * r * r * cylinder_height;
30 let cylinder_mass = cylinder_volume * density;
31
32 let sphere_volume = (4.0 / 3.0) * crate::math_functions::PI * r * r * r;
34 let sphere_mass = sphere_volume * density;
35
36 let mut inertia = add_mm(
38 cylinder_inertia(cylinder_mass, r, cylinder_height),
39 sphere_inertia(sphere_mass, r),
40 );
41
42 let steiner = 0.125 * sphere_mass * (3.0 * r + 2.0 * cylinder_height) * cylinder_height;
43 inertia.cx.x += steiner;
44 inertia.cz.z += steiner;
45
46 let mut rotation = MAT3_IDENTITY;
48 if cylinder_height * cylinder_height > 1000.0 * f32::MIN_POSITIVE {
49 let direction = normalize(sub(c2, c1));
50 let q = compute_quat_between_unit_vectors(VEC3_AXIS_Y, direction);
51 rotation = make_matrix_from_quat(q);
52 }
53
54 let mass = sphere_mass + cylinder_mass;
55 let center = mul_sv(0.5, add(c1, c2));
56
57 MassData {
58 mass,
59 center,
60 inertia: mul_mm(rotation, mul_mm(inertia, transpose(rotation))),
62 }
63}
64
65pub fn compute_capsule_aabb(shape: &Capsule, transform: Transform) -> Aabb {
67 let r = shape.radius;
68
69 let center1 = transform_point(transform, shape.center1);
70 let center2 = transform_point(transform, shape.center2);
71 let extent = Vec3 { x: r, y: r, z: r };
72
73 Aabb {
74 lower_bound: sub(min(center1, center2), extent),
75 upper_bound: add(max(center1, center2), extent),
76 }
77}
78
79pub fn compute_swept_capsule_aabb(shape: &Capsule, xf1: Transform, xf2: Transform) -> Aabb {
82 let r = Vec3 {
83 x: shape.radius,
84 y: shape.radius,
85 z: shape.radius,
86 };
87 let a = transform_point(xf1, shape.center1);
88 let b = transform_point(xf1, shape.center2);
89 let c = transform_point(xf2, shape.center1);
90 let d = transform_point(xf2, shape.center2);
91
92 Aabb {
93 lower_bound: sub(min(min(a, b), min(c, d)), r),
94 upper_bound: add(max(max(a, b), max(c, d)), r),
95 }
96}
97
98pub fn overlap_capsule(shape: &Capsule, shape_transform: Transform, proxy: &ShapeProxy) -> bool {
100 let input = DistanceInput {
101 proxy_a: make_proxy(&[shape.center1, shape.center2], shape.radius),
102 proxy_b: *proxy,
103 transform: inv_mul_transforms(shape_transform, TRANSFORM_IDENTITY),
104 use_radii: true,
105 };
106
107 let mut cache = SimplexCache::default();
108 let output = shape_distance(&input, &mut cache, None);
109 output.distance < overlap_slop()
110}
111
112pub fn ray_cast_capsule(shape: &Capsule, input: &RayCastInput) -> CastOutput {
117 debug_assert!(super::is_valid_ray(input));
118
119 let c1 = shape.center1;
120 let c2 = shape.center2;
121 let r = shape.radius;
122
123 let mut output = CastOutput::default();
125
126 let d = sub(c2, c1);
127
128 let tol = 0.01 * linear_slop();
130 let length_squared_d = length_squared(d);
131 if length_squared_d < tol * tol {
132 let sphere_center = mul_sv(0.5, add(shape.center1, shape.center2));
133 let sphere = Sphere {
134 center: sphere_center,
135 radius: shape.radius,
136 };
137 return ray_cast_sphere(&sphere, input);
138 }
139
140 let s = sub(input.origin, c1);
142
143 let length = length_squared_d.sqrt();
145 let axis = mul_sv(1.0 / length, d);
146
147 let u = dot(s, axis);
149
150 let c = mul_sv(u, axis);
152
153 let sc = sub(s, c);
155
156 let sc2 = length_squared(sc);
158
159 if sc2 < r * r {
161 let u_clamped = clamp_float(u, 0.0, length);
163
164 let cp = mul_sv(u_clamped, axis);
166
167 let scp = sub(s, cp);
169
170 let scp2 = length_squared(scp);
172
173 if scp2 < r * r {
175 output.hit = true;
176 output.point = input.origin;
177 return output;
178 }
179
180 let sphere = Sphere {
182 center: add(c1, cp),
183 radius: r,
184 };
185
186 return ray_cast_sphere(&sphere, input);
187 }
188
189 let dr = input.translation;
192 let mut ray_length = 0.0;
193 let ray_axis = get_length_and_normalize(&mut ray_length, dr);
194 if ray_length == 0.0 {
195 return output;
196 }
197
198 let v = u + input.max_fraction * dot(dr, axis);
200
201 if (u < -r && v < -r) || (length + r < u && length + r < v) {
203 return output;
204 }
205
206 let a1 = axis;
210 let a2 = ray_axis;
211 let a12 = dot(a1, a2);
212
213 let tr;
215
216 let det = 1.0 - a12 * a12;
217 if det < f32::EPSILON {
218 let perp = mul_sub(a2, a12, a1);
224 let perp2 = length_squared(perp);
225
226 let beta = dot(sc, perp);
228
229 let gamma = sc2 - r * r;
231
232 let disc = beta * beta - perp2 * gamma;
234
235 if beta >= 0.0 || disc < 0.0 {
237 return output;
238 }
239
240 tr = gamma / (-beta + disc.sqrt());
243 } else {
244 let inv_det = 1.0 / det;
248 let sa1 = u;
249 let sa2 = dot(s, a2);
250
251 let t1 = (sa1 - a12 * sa2) * inv_det;
252 let t2 = (a12 * sa1 - sa2) * inv_det;
253
254 let p1 = mul_sv(t1, a1);
256 let p2 = mul_add(s, t2, a2);
257
258 let g = sub(p2, p1);
260
261 let g2 = length_squared(g);
262 if g2 > r * r {
263 return output;
265 }
266
267 let h = ((r * r - g2) * inv_det).sqrt();
271
272 tr = t2 - h;
273 }
274
275 if tr < 0.0 || input.max_fraction * ray_length < tr {
277 return output;
278 }
279
280 let tc = u + tr * a12;
282
283 if tc < 0.0 {
285 let sphere = Sphere {
287 center: c1,
288 radius: r,
289 };
290
291 return ray_cast_sphere(&sphere, input);
292 }
293
294 if length < tc {
296 let sphere = Sphere {
298 center: c2,
299 radius: r,
300 };
301
302 return ray_cast_sphere(&sphere, input);
303 }
304
305 let p = mul_add(s, tr, ray_axis);
307
308 let mut normal = mul_sub(p, tc, axis);
310 normal = normalize(normal);
311
312 output.point = add(c1, p);
313 output.normal = normal;
314 output.fraction = clamp_float(tr / ray_length, 0.0, input.max_fraction);
315 output.hit = true;
316 output
317}
318
319pub fn shape_cast_capsule(capsule: &Capsule, input: &ShapeCastInput) -> CastOutput {
321 let pair_input = ShapeCastPairInput {
322 proxy_a: make_proxy(&[capsule.center1, capsule.center2], capsule.radius),
323 proxy_b: input.proxy,
324 transform: TRANSFORM_IDENTITY,
325 translation_b: input.translation,
326 max_fraction: input.max_fraction,
327 can_encroach: input.can_encroach,
328 };
329
330 shape_cast(&pair_input)
331}
332
333pub fn collide_mover_and_capsule(
335 result: &mut PlaneResult,
336 shape: &Capsule,
337 mover: &Capsule,
338) -> i32 {
339 let total_radius = mover.radius + shape.radius;
340
341 let approach = segment_distance(shape.center1, shape.center2, mover.center1, mover.center2);
342
343 let mut distance = 0.0;
345 let mut normal = get_length_and_normalize(&mut distance, sub(approach.point2, approach.point1));
346
347 if distance > total_radius {
348 return 0;
349 }
350
351 let linear_slop = linear_slop();
352 if distance < linear_slop {
353 let mut mover_length = 0.0;
356 let mover_axis =
357 get_length_and_normalize(&mut mover_length, sub(mover.center2, mover.center1));
358 normal = if mover_length > linear_slop {
359 perp(mover_axis)
360 } else {
361 VEC3_AXIS_Y
362 };
363 distance = 0.0;
364 }
365
366 result.plane = Plane {
367 normal,
368 offset: total_radius - distance,
369 };
370 result.point = approach.point1;
371 1
372}