1use std::ops::{Add, Div, Mul, Neg, Sub};
7
8#[repr(C, align(32))]
15#[derive(Debug, Clone, Copy, PartialEq)]
16#[cfg_attr(feature = "vulkan", derive(bytemuck::Pod, bytemuck::Zeroable))]
17pub struct CgaVector5D {
18 pub x: f32, pub y: f32, pub z: f32, pub no: f32, pub ni: f32, pub _pad0: f32,
24 pub _pad1: f32,
25 pub _pad2: f32,
26}
27
28impl Default for CgaVector5D {
29 #[inline(always)]
30 fn default() -> Self {
31 Self::origin()
32 }
33}
34
35impl CgaVector5D {
36 #[inline(always)]
38 pub const fn zero() -> Self {
39 Self {
40 x: 0.0,
41 y: 0.0,
42 z: 0.0,
43 no: 0.0,
44 ni: 0.0,
45 _pad0: 0.0,
46 _pad1: 0.0,
47 _pad2: 0.0,
48 }
49 }
50
51 #[inline(always)]
53 pub const fn origin() -> Self {
54 Self {
55 x: 0.0,
56 y: 0.0,
57 z: 0.0,
58 no: 1.0,
59 ni: 0.0,
60 _pad0: 0.0,
61 _pad1: 0.0,
62 _pad2: 0.0,
63 }
64 }
65
66 #[inline(always)]
68 pub const fn infinity() -> Self {
69 Self {
70 x: 0.0,
71 y: 0.0,
72 z: 0.0,
73 no: 0.0,
74 ni: 1.0,
75 _pad0: 0.0,
76 _pad1: 0.0,
77 _pad2: 0.0,
78 }
79 }
80
81 #[inline]
84 pub fn point(x: f32, y: f32, z: f32) -> Self {
85 let sq = x * x + y * y + z * z;
86 Self {
87 x,
88 y,
89 z,
90 no: 1.0,
91 ni: 0.5 * sq,
92 _pad0: 0.0,
93 _pad1: 0.0,
94 _pad2: 0.0,
95 }
96 }
97
98 #[inline]
101 pub fn sphere(cx: f32, cy: f32, cz: f32, radius: f32) -> Self {
102 let sq = cx * cx + cy * cy + cz * cz;
103 Self {
104 x: cx,
105 y: cy,
106 z: cz,
107 no: 1.0,
108 ni: 0.5 * (sq - radius * radius),
109 _pad0: 0.0,
110 _pad1: 0.0,
111 _pad2: 0.0,
112 }
113 }
114
115 #[inline]
118 pub fn plane(nx: f32, ny: f32, nz: f32, d: f32) -> Self {
119 Self {
120 x: nx,
121 y: ny,
122 z: nz,
123 no: 0.0,
124 ni: d,
125 _pad0: 0.0,
126 _pad1: 0.0,
127 _pad2: 0.0,
128 }
129 }
130
131 #[inline(always)]
134 pub fn inner_product(&self, other: &Self) -> f32 {
135 (self.x * other.x + self.y * other.y + self.z * other.z)
136 - (self.no * other.ni + self.ni * other.no)
137 }
138
139 #[inline]
141 pub fn to_euclidean(&self) -> [f32; 3] {
142 if self.no.abs() > 1e-12 {
143 let inv = 1.0 / self.no;
144 [self.x * inv, self.y * inv, self.z * inv]
145 } else {
146 [self.x, self.y, self.z]
147 }
148 }
149
150 #[inline]
153 pub fn squared_distance(&self, other: &Self) -> f32 {
154 let dot = self.inner_product(other);
155 let weight = self.no * other.no;
156 if weight.abs() > 1e-12 {
157 (-2.0 * dot / weight).max(0.0)
158 } else {
159 0.0
160 }
161 }
162
163 #[inline(always)]
165 pub fn distance(&self, other: &Self) -> f32 {
166 self.squared_distance(other).sqrt()
167 }
168
169 #[inline]
171 pub fn sphere_radius(&self) -> Option<f32> {
172 if self.no.abs() > 1e-12 {
173 let inv_no = 1.0 / self.no;
174 let norm_s = *self * inv_no;
175 let dot = norm_s.inner_product(&norm_s);
176 if dot >= 0.0 {
177 Some(dot.sqrt())
178 } else {
179 None }
181 } else {
182 None }
184 }
185
186 #[inline]
189 pub fn invert_in_sphere(&self, sphere: &Self) -> Self {
190 let center = sphere.to_euclidean();
191 let r_sq = sphere.inner_product(sphere);
192 let pt = self.to_euclidean();
193
194 let dx = pt[0] - center[0];
195 let dy = pt[1] - center[1];
196 let dz = pt[2] - center[2];
197
198 let dist_sq = dx * dx + dy * dy + dz * dz;
199 if dist_sq < 1e-12 {
200 Self::infinity()
202 } else {
203 let scale = r_sq / dist_sq;
204 let new_x = center[0] + dx * scale;
205 let new_y = center[1] + dy * scale;
206 let new_z = center[2] + dz * scale;
207 Self::point(new_x, new_y, new_z)
208 }
209 }
210
211 #[inline]
213 pub fn translate(&self, dx: f32, dy: f32, dz: f32) -> Self {
214 let pt = self.to_euclidean();
215 Self::point(pt[0] + dx, pt[1] + dy, pt[2] + dz)
216 }
217
218 #[inline]
220 pub fn dilate(&self, lambda: f32) -> Self {
221 let pt = self.to_euclidean();
222 Self::point(pt[0] * lambda, pt[1] * lambda, pt[2] * lambda)
223 }
224}
225
226impl Add for CgaVector5D {
227 type Output = Self;
228 #[inline(always)]
229 fn add(self, rhs: Self) -> Self {
230 Self {
231 x: self.x + rhs.x,
232 y: self.y + rhs.y,
233 z: self.z + rhs.z,
234 no: self.no + rhs.no,
235 ni: self.ni + rhs.ni,
236 _pad0: 0.0,
237 _pad1: 0.0,
238 _pad2: 0.0,
239 }
240 }
241}
242
243impl Sub for CgaVector5D {
244 type Output = Self;
245 #[inline(always)]
246 fn sub(self, rhs: Self) -> Self {
247 Self {
248 x: self.x - rhs.x,
249 y: self.y - rhs.y,
250 z: self.z - rhs.z,
251 no: self.no - rhs.no,
252 ni: self.ni - rhs.ni,
253 _pad0: 0.0,
254 _pad1: 0.0,
255 _pad2: 0.0,
256 }
257 }
258}
259
260impl Neg for CgaVector5D {
261 type Output = Self;
262 #[inline(always)]
263 fn neg(self) -> Self {
264 Self {
265 x: -self.x,
266 y: -self.y,
267 z: -self.z,
268 no: -self.no,
269 ni: -self.ni,
270 _pad0: 0.0,
271 _pad1: 0.0,
272 _pad2: 0.0,
273 }
274 }
275}
276
277impl Mul<f32> for CgaVector5D {
278 type Output = Self;
279 #[inline(always)]
280 fn mul(self, rhs: f32) -> Self {
281 Self {
282 x: self.x * rhs,
283 y: self.y * rhs,
284 z: self.z * rhs,
285 no: self.no * rhs,
286 ni: self.ni * rhs,
287 _pad0: 0.0,
288 _pad1: 0.0,
289 _pad2: 0.0,
290 }
291 }
292}
293
294impl Div<f32> for CgaVector5D {
295 type Output = Self;
296 #[inline(always)]
297 fn div(self, rhs: f32) -> Self {
298 let inv = 1.0 / rhs;
299 Self {
300 x: self.x * inv,
301 y: self.y * inv,
302 z: self.z * inv,
303 no: self.no * inv,
304 ni: self.ni * inv,
305 _pad0: 0.0,
306 _pad1: 0.0,
307 _pad2: 0.0,
308 }
309 }
310}
311
312pub type CgaPoint = CgaVector5D;
314pub type CgaSphere = CgaVector5D;
315pub type CgaPlane = CgaVector5D;
316
317#[derive(Debug, Clone, Copy, PartialEq)]
320pub struct CgaPointPair {
321 pub p1: CgaPoint,
322 pub p2: CgaPoint,
323}
324
325impl CgaPointPair {
326 #[inline]
327 pub fn new(p1: CgaPoint, p2: CgaPoint) -> Self {
328 Self { p1, p2 }
329 }
330
331 #[inline]
333 pub fn distance(&self) -> f32 {
334 self.p1.distance(&self.p2)
335 }
336
337 #[inline]
339 pub fn midpoint(&self) -> [f32; 3] {
340 let e1 = self.p1.to_euclidean();
341 let e2 = self.p2.to_euclidean();
342 [
343 (e1[0] + e2[0]) * 0.5,
344 (e1[1] + e2[1]) * 0.5,
345 (e1[2] + e2[2]) * 0.5,
346 ]
347 }
348}
349
350#[derive(Debug, Clone, Copy, PartialEq)]
353pub struct CgaCircle {
354 pub center: [f32; 3],
355 pub normal: [f32; 3],
356 pub radius: f32,
357}
358
359impl CgaCircle {
360 #[inline]
361 pub fn new(center: [f32; 3], normal: [f32; 3], radius: f32) -> Self {
362 let n_norm = (normal[0] * normal[0] + normal[1] * normal[1] + normal[2] * normal[2]).sqrt();
363 let inv_n = if n_norm > 1e-12 { 1.0 / n_norm } else { 1.0 };
364 Self {
365 center,
366 normal: [normal[0] * inv_n, normal[1] * inv_n, normal[2] * inv_n],
367 radius: radius.max(0.0),
368 }
369 }
370
371 #[inline]
373 pub fn from_sphere_plane_intersection(sphere: &CgaSphere, plane: &CgaPlane) -> Option<Self> {
374 let sc = sphere.to_euclidean();
375 let r = sphere.sphere_radius()?;
376 let n = [plane.x, plane.y, plane.z];
377 let n_len = (n[0] * n[0] + n[1] * n[1] + n[2] * n[2]).sqrt();
378 if n_len < 1e-12 {
379 return None;
380 }
381 let inv_n = 1.0 / n_len;
382 let n_unit = [n[0] * inv_n, n[1] * inv_n, n[2] * inv_n];
383
384 let dist = n_unit[0] * sc[0] + n_unit[1] * sc[1] + n_unit[2] * sc[2] + plane.ni * inv_n;
386 if dist.abs() > r {
387 return None; }
389
390 let circle_r = (r * r - dist * dist).max(0.0).sqrt();
391 let circle_center = [
392 sc[0] - dist * n_unit[0],
393 sc[1] - dist * n_unit[1],
394 sc[2] - dist * n_unit[2],
395 ];
396
397 Some(Self::new(circle_center, n_unit, circle_r))
398 }
399
400 #[inline]
402 pub fn area(&self) -> f32 {
403 std::f32::consts::PI * self.radius * self.radius
404 }
405
406 #[inline]
408 pub fn circumference(&self) -> f32 {
409 2.0 * std::f32::consts::PI * self.radius
410 }
411}
412
413#[derive(Debug, Clone, Copy, PartialEq)]
417pub struct CgaConformalMotor {
418 pub translation: [f32; 3],
419 pub rotation_axis: [f32; 3],
420 pub rotation_angle_rad: f32,
421 pub dilation_factor: f32,
422}
423
424impl Default for CgaConformalMotor {
425 #[inline(always)]
426 fn default() -> Self {
427 Self::identity()
428 }
429}
430
431impl CgaConformalMotor {
432 #[inline(always)]
433 pub const fn identity() -> Self {
434 Self {
435 translation: [0.0, 0.0, 0.0],
436 rotation_axis: [0.0, 0.0, 1.0],
437 rotation_angle_rad: 0.0,
438 dilation_factor: 1.0,
439 }
440 }
441
442 #[inline]
443 pub fn from_translation(dx: f32, dy: f32, dz: f32) -> Self {
444 Self {
445 translation: [dx, dy, dz],
446 rotation_axis: [0.0, 0.0, 1.0],
447 rotation_angle_rad: 0.0,
448 dilation_factor: 1.0,
449 }
450 }
451
452 #[inline]
453 pub fn from_rotation(axis: [f32; 3], angle_rad: f32) -> Self {
454 let norm = (axis[0] * axis[0] + axis[1] * axis[1] + axis[2] * axis[2]).sqrt();
455 let inv = if norm > 1e-12 { 1.0 / norm } else { 1.0 };
456 Self {
457 translation: [0.0, 0.0, 0.0],
458 rotation_axis: [axis[0] * inv, axis[1] * inv, axis[2] * inv],
459 rotation_angle_rad: angle_rad,
460 dilation_factor: 1.0,
461 }
462 }
463
464 #[inline]
465 pub fn from_dilation(lambda: f32) -> Self {
466 assert!(lambda > 0.0, "Dilation factor must be strictly positive");
467 Self {
468 translation: [0.0, 0.0, 0.0],
469 rotation_axis: [0.0, 0.0, 1.0],
470 rotation_angle_rad: 0.0,
471 dilation_factor: lambda,
472 }
473 }
474
475 #[inline]
477 pub fn transform_point(&self, pt: &CgaPoint) -> CgaPoint {
478 let [x0, y0, z0] = pt.to_euclidean();
479
480 let x1 = x0 * self.dilation_factor;
482 let y1 = y0 * self.dilation_factor;
483 let z1 = z0 * self.dilation_factor;
484
485 let theta = self.rotation_angle_rad;
487 let [nx, ny, nz] = self.rotation_axis;
488 let cos_t = theta.cos();
489 let sin_t = theta.sin();
490 let dot_nr = nx * x1 + ny * y1 + nz * z1;
491
492 let rx = x1 * cos_t + (ny * z1 - nz * y1) * sin_t + nx * dot_nr * (1.0 - cos_t);
493 let ry = y1 * cos_t + (nz * x1 - nx * z1) * sin_t + ny * dot_nr * (1.0 - cos_t);
494 let rz = z1 * cos_t + (nx * y1 - ny * x1) * sin_t + nz * dot_nr * (1.0 - cos_t);
495
496 let tx = rx + self.translation[0];
498 let ty = ry + self.translation[1];
499 let tz = rz + self.translation[2];
500
501 CgaPoint::point(tx, ty, tz)
502 }
503}