1use bincode::Decode;
4use bincode::Encode;
5use cu_spatial_payloads::Transform3D;
6use cu29::prelude::*;
7use cu29::units::si::f64::Length as Length64;
8use cu29::units::si::length::meter;
9use serde::Deserialize;
10use serde::Serialize;
11
12#[derive(
14 Default, Debug, Clone, Copy, PartialEq, Encode, Decode, Serialize, Deserialize, Reflect,
15)]
16pub struct CuSphere {
17 pub center: [Length64; 3],
18 pub radius: Length64,
19}
20
21impl CuSphere {
22 pub fn new(center: [Length64; 3], radius: Length64) -> CuResult<Self> {
24 let sphere = Self { center, radius };
25 CuCollisionShape::Sphere(sphere).validate()?;
26 Ok(sphere)
27 }
28}
29
30#[derive(
32 Default, Debug, Clone, Copy, PartialEq, Encode, Decode, Serialize, Deserialize, Reflect,
33)]
34pub struct CuCapsule {
35 pub a: [Length64; 3],
36 pub b: [Length64; 3],
37 pub radius: Length64,
38}
39
40impl CuCapsule {
41 pub fn new(a: [Length64; 3], b: [Length64; 3], radius: Length64) -> CuResult<Self> {
43 let capsule = Self { a, b, radius };
44 CuCollisionShape::Capsule(capsule).validate()?;
45 Ok(capsule)
46 }
47}
48
49#[derive(Debug, Clone, Copy, PartialEq, Encode, Decode, Serialize, Deserialize, Reflect)]
54#[serde(rename_all = "snake_case")]
55pub enum CuCollisionShape {
56 Sphere(CuSphere),
57 Capsule(CuCapsule),
58}
59
60impl Default for CuCollisionShape {
61 fn default() -> Self {
62 Self::Sphere(CuSphere::default())
63 }
64}
65
66impl CuCollisionShape {
67 pub fn validate(&self) -> CuResult<()> {
69 let finite3 = |p: &[Length64; 3]| p.iter().all(|v| v.get::<meter>().is_finite());
70 let (finite, radius) = match self {
71 Self::Sphere(s) => (finite3(&s.center), s.radius),
72 Self::Capsule(c) => (finite3(&c.a) && finite3(&c.b), c.radius),
73 };
74 let r = radius.get::<meter>();
75 if finite && r.is_finite() && r >= 0.0 {
76 Ok(())
77 } else {
78 Err(CuError::from(
79 "repose_collision: invalid or non-finite collision shape geometry",
80 ))
81 }
82 }
83}
84
85#[derive(
92 Debug, Clone, Copy, Default, PartialEq, Encode, Decode, Serialize, Deserialize, Reflect,
93)]
94#[serde(deny_unknown_fields)]
95pub struct CuLinkShape {
96 pub shape: CuCollisionShape,
98 #[serde(default)]
100 pub frame: Option<usize>,
101}
102
103impl CuLinkShape {
104 pub const fn new(shape: CuCollisionShape, frame: Option<usize>) -> Self {
106 Self { shape, frame }
107 }
108}
109
110pub fn transform_shape(
112 frame: &Transform3D<f64>,
113 local: &CuCollisionShape,
114 out_world: &mut CuCollisionShape,
115) {
116 let mat = frame.to_matrix();
117 match local {
118 CuCollisionShape::Sphere(sph) => {
119 let c = transform_point_raw(&mat, sph.center);
120 *out_world = CuCollisionShape::Sphere(CuSphere {
121 center: c,
122 radius: sph.radius,
123 });
124 }
125 CuCollisionShape::Capsule(cap) => {
126 let a = transform_point_raw(&mat, cap.a);
127 let b = transform_point_raw(&mat, cap.b);
128 *out_world = CuCollisionShape::Capsule(CuCapsule {
129 a,
130 b,
131 radius: cap.radius,
132 });
133 }
134 }
135}
136
137pub fn sphere_sphere_signed_distance(a: &CuSphere, b: &CuSphere) -> Length64 {
141 let pa = to_meters(a.center);
142 let pb = to_meters(b.center);
143 let dist = norm3(sub3(pa, pb));
144 let r_sum = a.radius.get::<meter>() + b.radius.get::<meter>();
145 Length64::new::<meter>(dist - r_sum)
146}
147
148pub fn capsule_sphere_signed_distance(cap: &CuCapsule, sph: &CuSphere) -> Length64 {
152 let a = to_meters(cap.a);
153 let b = to_meters(cap.b);
154 let p = to_meters(sph.center);
155 let dist = point_segment_distance(p, a, b);
156 let r_sum = cap.radius.get::<meter>() + sph.radius.get::<meter>();
157 Length64::new::<meter>(dist - r_sum)
158}
159
160pub fn capsule_capsule_signed_distance(a: &CuCapsule, b: &CuCapsule) -> Length64 {
164 let p1 = to_meters(a.a);
165 let q1 = to_meters(a.b);
166 let p2 = to_meters(b.a);
167 let q2 = to_meters(b.b);
168 let dist = segment_segment_distance(p1, q1, p2, q2);
169 let r_sum = a.radius.get::<meter>() + b.radius.get::<meter>();
170 Length64::new::<meter>(dist - r_sum)
171}
172
173pub fn shape_signed_distance(a: &CuCollisionShape, b: &CuCollisionShape) -> Length64 {
175 match (a, b) {
176 (CuCollisionShape::Sphere(sa), CuCollisionShape::Sphere(sb)) => {
177 sphere_sphere_signed_distance(sa, sb)
178 }
179 (CuCollisionShape::Capsule(ca), CuCollisionShape::Sphere(sb)) => {
180 capsule_sphere_signed_distance(ca, sb)
181 }
182 (CuCollisionShape::Sphere(sa), CuCollisionShape::Capsule(cb)) => {
183 capsule_sphere_signed_distance(cb, sa)
184 }
185 (CuCollisionShape::Capsule(ca), CuCollisionShape::Capsule(cb)) => {
186 capsule_capsule_signed_distance(ca, cb)
187 }
188 }
189}
190
191pub fn shapes_violate_margin(
193 a: &CuCollisionShape,
194 b: &CuCollisionShape,
195 safety_margin: Length64,
196) -> bool {
197 shape_signed_distance(a, b).get::<meter>() < safety_margin.get::<meter>()
198}
199
200fn transform_point_raw(mat: &[[f64; 4]; 4], pt: [Length64; 3]) -> [Length64; 3] {
201 let x = pt[0].get::<meter>();
202 let y = pt[1].get::<meter>();
203 let z = pt[2].get::<meter>();
204 [
205 Length64::new::<meter>(mat[0][0] * x + mat[0][1] * y + mat[0][2] * z + mat[0][3]),
206 Length64::new::<meter>(mat[1][0] * x + mat[1][1] * y + mat[1][2] * z + mat[1][3]),
207 Length64::new::<meter>(mat[2][0] * x + mat[2][1] * y + mat[2][2] * z + mat[2][3]),
208 ]
209}
210
211fn to_meters(pt: [Length64; 3]) -> [f64; 3] {
212 [
213 pt[0].get::<meter>(),
214 pt[1].get::<meter>(),
215 pt[2].get::<meter>(),
216 ]
217}
218
219const fn sub3(a: [f64; 3], b: [f64; 3]) -> [f64; 3] {
220 [a[0] - b[0], a[1] - b[1], a[2] - b[2]]
221}
222
223const fn dot3(a: [f64; 3], b: [f64; 3]) -> f64 {
224 a[0] * b[0] + a[1] * b[1] + a[2] * b[2]
225}
226
227fn norm3(v: [f64; 3]) -> f64 {
228 libm::sqrt(dot3(v, v))
229}
230
231fn point_segment_distance(p: [f64; 3], a: [f64; 3], b: [f64; 3]) -> f64 {
232 let ab = sub3(b, a);
233 let ap = sub3(p, a);
234 let len_sq = dot3(ab, ab);
235 if len_sq <= 1e-18 {
236 return norm3(ap);
237 }
238 let t = (dot3(ap, ab) / len_sq).clamp(0.0, 1.0);
239 let closest = [a[0] + t * ab[0], a[1] + t * ab[1], a[2] + t * ab[2]];
240 norm3(sub3(p, closest))
241}
242
243fn segment_segment_distance(p1: [f64; 3], q1: [f64; 3], p2: [f64; 3], q2: [f64; 3]) -> f64 {
245 let d1 = sub3(q1, p1);
246 let d2 = sub3(q2, p2);
247 let r = sub3(p1, p2);
248 let a = dot3(d1, d1);
249 let e = dot3(d2, d2);
250 let f = dot3(d2, r);
251
252 let eps = 1e-18;
253 if a <= eps && e <= eps {
254 return norm3(r);
255 }
256 if a <= eps {
257 return point_segment_distance(p1, p2, q2);
258 }
259 if e <= eps {
260 return point_segment_distance(p2, p1, q1);
261 }
262
263 let c = dot3(d1, r);
264 let b = dot3(d1, d2);
265 let denom = a * e - b * b;
266
267 let mut s = if denom != 0.0 {
268 ((b * f - c * e) / denom).clamp(0.0, 1.0)
269 } else {
270 0.0
271 };
272
273 let mut t = (b * s + f) / e;
274 if t < 0.0 {
275 t = 0.0;
276 s = (-c / a).clamp(0.0, 1.0);
277 } else if t > 1.0 {
278 t = 1.0;
279 s = ((b - c) / a).clamp(0.0, 1.0);
280 }
281
282 let c1 = [p1[0] + s * d1[0], p1[1] + s * d1[1], p1[2] + s * d1[2]];
283 let c2 = [p2[0] + t * d2[0], p2[1] + t * d2[1], p2[2] + t * d2[2]];
284 norm3(sub3(c1, c2))
285}
286
287pub(crate) fn sphere_m(center: [f64; 3], radius: f64) -> CuSphere {
289 CuSphere {
290 center: center.map(Length64::new::<meter>),
291 radius: Length64::new::<meter>(radius),
292 }
293}
294
295pub(crate) fn capsule_m(a: [f64; 3], b: [f64; 3], radius: f64) -> CuCapsule {
297 CuCapsule {
298 a: a.map(Length64::new::<meter>),
299 b: b.map(Length64::new::<meter>),
300 radius: Length64::new::<meter>(radius),
301 }
302}