1#![deny(missing_docs)]
2
3use collide::{Collider, CollisionInfo};
8use num_traits::{real::Real, One, Zero};
9use vector_space::{InnerSpace, VectorSpace};
10
11trait ClampedVector: InnerSpace {
12 fn clamped_project(self, dis: Self) -> Self {
13 let mag = dis.magnitude();
14 let dir = dis / mag;
15 let new_mag = self.scalar(dir);
16 if new_mag <= Self::Scalar::zero() {
17 Self::zero()
18 } else if new_mag >= mag {
19 dis
20 } else {
21 dir * new_mag
22 }
23 }
24}
25
26impl<V: InnerSpace> ClampedVector for V {}
27
28#[derive(Copy, Clone)]
29pub struct Capsule<V: VectorSpace> {
31 pub start: V,
33 pub end: V,
35 pub rad: V::Scalar,
37}
38
39impl<V: InnerSpace> Capsule<V> {
40 pub fn new(rad: V::Scalar, start: V, end: V) -> Self {
42 Self { start, end, rad }
43 }
44
45 pub fn point(pos: V) -> Self {
47 Self {
48 start: pos,
49 end: pos,
50 rad: V::Scalar::zero(),
51 }
52 }
53
54 pub fn line(start: V, end: V) -> Self {
56 Self {
57 start,
58 end,
59 rad: V::Scalar::zero(),
60 }
61 }
62
63 pub fn sphere(pos: V, rad: V::Scalar) -> Self {
65 Self {
66 start: pos,
67 end: pos,
68 rad,
69 }
70 }
71
72 fn points(&self, other: &Self) -> (V, V) {
73 let p = self.end - self.start;
74 let q = other.end - other.start;
75 let d = self.start - other.start;
76
77 match (p.is_zero(), q.is_zero()) {
78 (false, false) => {
79 let p_mag2 = p.magnitude2();
80 let q_mag2 = q.magnitude2();
81 let angle = p.scalar(q);
82
83 let div = angle * angle - p_mag2 * q_mag2;
84 if div.is_zero() {
85 let add = d.reject(p);
86 (self.start, self.start - add)
87 } else {
88 let a = (p * q_mag2 - q * angle).scalar(d) / div;
89 let b = (p * angle - q * p_mag2).scalar(d) / div;
90
91 let zero = V::Scalar::zero();
92 let one = V::Scalar::one();
93
94 let a_neg = a < zero;
95 let b_neg = b < zero;
96
97 let a_centered = !a_neg && a <= one;
98 let b_centered = !b_neg && b <= one;
99
100 if a_centered && b_centered {
101 (self.start + p * a, other.start + q * b)
102 } else {
103 if !a_centered {
104 let a = if a_neg { zero } else { one };
105 let b = (angle * a + q.scalar(d)) / q_mag2;
106 if zero <= b && b <= one {
107 return (self.start + p * a, other.start + q * b);
108 }
109 }
110 if !b_centered {
111 let b = if b_neg { zero } else { one };
112 let a = (angle * b - p.scalar(d)) / p_mag2;
113 if zero <= a && a <= one {
114 return (self.start + p * a, other.start + q * b);
115 }
116 }
117 (
118 if a_neg { self.start } else { self.end },
119 if b_neg { other.start } else { other.end },
120 )
121 }
122 }
123 }
124
125 (false, true) => (self.start + (-d).clamped_project(p), other.start),
126 (true, false) => (self.start, other.start + d.clamped_project(q)),
127 (true, true) => (self.start, other.start),
128 }
129 }
130}
131
132impl<V: InnerSpace> Collider for Capsule<V> {
133 type Vector = V;
134
135 fn collision_info(&self, other: &Self) -> Option<CollisionInfo<Self::Vector>> {
136 let (point, other_point) = self.points(other);
137
138 let dis = other_point - point;
139 let mag2 = dis.magnitude2();
140 let rad = self.rad + other.rad;
141 if mag2 <= rad * rad {
142 let mag = mag2.sqrt();
143 let dir = dis / mag;
144 Some(CollisionInfo {
145 self_contact: point + dir * self.rad,
146 other_contact: other_point - dir * other.rad,
147 vector: dir * (mag - rad),
148 })
149 } else {
150 None
151 }
152 }
153}