use std::ops::{Add, Div, Mul, Neg, Sub};
#[repr(C, align(32))]
#[derive(Debug, Clone, Copy, PartialEq)]
#[cfg_attr(feature = "vulkan", derive(bytemuck::Pod, bytemuck::Zeroable))]
pub struct CgaVector5D {
pub x: f32, pub y: f32, pub z: f32, pub no: f32, pub ni: f32, pub _pad0: f32,
pub _pad1: f32,
pub _pad2: f32,
}
impl Default for CgaVector5D {
#[inline(always)]
fn default() -> Self {
Self::origin()
}
}
impl CgaVector5D {
#[inline(always)]
pub const fn zero() -> Self {
Self {
x: 0.0,
y: 0.0,
z: 0.0,
no: 0.0,
ni: 0.0,
_pad0: 0.0,
_pad1: 0.0,
_pad2: 0.0,
}
}
#[inline(always)]
pub const fn origin() -> Self {
Self {
x: 0.0,
y: 0.0,
z: 0.0,
no: 1.0,
ni: 0.0,
_pad0: 0.0,
_pad1: 0.0,
_pad2: 0.0,
}
}
#[inline(always)]
pub const fn infinity() -> Self {
Self {
x: 0.0,
y: 0.0,
z: 0.0,
no: 0.0,
ni: 1.0,
_pad0: 0.0,
_pad1: 0.0,
_pad2: 0.0,
}
}
#[inline]
pub fn point(x: f32, y: f32, z: f32) -> Self {
let sq = x * x + y * y + z * z;
Self {
x,
y,
z,
no: 1.0,
ni: 0.5 * sq,
_pad0: 0.0,
_pad1: 0.0,
_pad2: 0.0,
}
}
#[inline]
pub fn sphere(cx: f32, cy: f32, cz: f32, radius: f32) -> Self {
let sq = cx * cx + cy * cy + cz * cz;
Self {
x: cx,
y: cy,
z: cz,
no: 1.0,
ni: 0.5 * (sq - radius * radius),
_pad0: 0.0,
_pad1: 0.0,
_pad2: 0.0,
}
}
#[inline]
pub fn plane(nx: f32, ny: f32, nz: f32, d: f32) -> Self {
Self {
x: nx,
y: ny,
z: nz,
no: 0.0,
ni: d,
_pad0: 0.0,
_pad1: 0.0,
_pad2: 0.0,
}
}
#[inline(always)]
pub fn inner_product(&self, other: &Self) -> f32 {
(self.x * other.x + self.y * other.y + self.z * other.z)
- (self.no * other.ni + self.ni * other.no)
}
#[inline]
pub fn to_euclidean(&self) -> [f32; 3] {
if self.no.abs() > 1e-12 {
let inv = 1.0 / self.no;
[self.x * inv, self.y * inv, self.z * inv]
} else {
[self.x, self.y, self.z]
}
}
#[inline]
pub fn squared_distance(&self, other: &Self) -> f32 {
let dot = self.inner_product(other);
let weight = self.no * other.no;
if weight.abs() > 1e-12 {
(-2.0 * dot / weight).max(0.0)
} else {
0.0
}
}
#[inline(always)]
pub fn distance(&self, other: &Self) -> f32 {
self.squared_distance(other).sqrt()
}
#[inline]
pub fn sphere_radius(&self) -> Option<f32> {
if self.no.abs() > 1e-12 {
let inv_no = 1.0 / self.no;
let norm_s = *self * inv_no;
let dot = norm_s.inner_product(&norm_s);
if dot >= 0.0 {
Some(dot.sqrt())
} else {
None }
} else {
None }
}
#[inline]
pub fn invert_in_sphere(&self, sphere: &Self) -> Self {
let center = sphere.to_euclidean();
let r_sq = sphere.inner_product(sphere);
let pt = self.to_euclidean();
let dx = pt[0] - center[0];
let dy = pt[1] - center[1];
let dz = pt[2] - center[2];
let dist_sq = dx * dx + dy * dy + dz * dz;
if dist_sq < 1e-12 {
Self::infinity()
} else {
let scale = r_sq / dist_sq;
let new_x = center[0] + dx * scale;
let new_y = center[1] + dy * scale;
let new_z = center[2] + dz * scale;
Self::point(new_x, new_y, new_z)
}
}
#[inline]
pub fn translate(&self, dx: f32, dy: f32, dz: f32) -> Self {
let pt = self.to_euclidean();
Self::point(pt[0] + dx, pt[1] + dy, pt[2] + dz)
}
#[inline]
pub fn dilate(&self, lambda: f32) -> Self {
let pt = self.to_euclidean();
Self::point(pt[0] * lambda, pt[1] * lambda, pt[2] * lambda)
}
}
impl Add for CgaVector5D {
type Output = Self;
#[inline(always)]
fn add(self, rhs: Self) -> Self {
Self {
x: self.x + rhs.x,
y: self.y + rhs.y,
z: self.z + rhs.z,
no: self.no + rhs.no,
ni: self.ni + rhs.ni,
_pad0: 0.0,
_pad1: 0.0,
_pad2: 0.0,
}
}
}
impl Sub for CgaVector5D {
type Output = Self;
#[inline(always)]
fn sub(self, rhs: Self) -> Self {
Self {
x: self.x - rhs.x,
y: self.y - rhs.y,
z: self.z - rhs.z,
no: self.no - rhs.no,
ni: self.ni - rhs.ni,
_pad0: 0.0,
_pad1: 0.0,
_pad2: 0.0,
}
}
}
impl Neg for CgaVector5D {
type Output = Self;
#[inline(always)]
fn neg(self) -> Self {
Self {
x: -self.x,
y: -self.y,
z: -self.z,
no: -self.no,
ni: -self.ni,
_pad0: 0.0,
_pad1: 0.0,
_pad2: 0.0,
}
}
}
impl Mul<f32> for CgaVector5D {
type Output = Self;
#[inline(always)]
fn mul(self, rhs: f32) -> Self {
Self {
x: self.x * rhs,
y: self.y * rhs,
z: self.z * rhs,
no: self.no * rhs,
ni: self.ni * rhs,
_pad0: 0.0,
_pad1: 0.0,
_pad2: 0.0,
}
}
}
impl Div<f32> for CgaVector5D {
type Output = Self;
#[inline(always)]
fn div(self, rhs: f32) -> Self {
let inv = 1.0 / rhs;
Self {
x: self.x * inv,
y: self.y * inv,
z: self.z * inv,
no: self.no * inv,
ni: self.ni * inv,
_pad0: 0.0,
_pad1: 0.0,
_pad2: 0.0,
}
}
}
pub type CgaPoint = CgaVector5D;
pub type CgaSphere = CgaVector5D;
pub type CgaPlane = CgaVector5D;
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct CgaPointPair {
pub p1: CgaPoint,
pub p2: CgaPoint,
}
impl CgaPointPair {
#[inline]
pub fn new(p1: CgaPoint, p2: CgaPoint) -> Self {
Self { p1, p2 }
}
#[inline]
pub fn distance(&self) -> f32 {
self.p1.distance(&self.p2)
}
#[inline]
pub fn midpoint(&self) -> [f32; 3] {
let e1 = self.p1.to_euclidean();
let e2 = self.p2.to_euclidean();
[
(e1[0] + e2[0]) * 0.5,
(e1[1] + e2[1]) * 0.5,
(e1[2] + e2[2]) * 0.5,
]
}
}
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct CgaCircle {
pub center: [f32; 3],
pub normal: [f32; 3],
pub radius: f32,
}
impl CgaCircle {
#[inline]
pub fn new(center: [f32; 3], normal: [f32; 3], radius: f32) -> Self {
let n_norm = (normal[0] * normal[0] + normal[1] * normal[1] + normal[2] * normal[2]).sqrt();
let inv_n = if n_norm > 1e-12 { 1.0 / n_norm } else { 1.0 };
Self {
center,
normal: [normal[0] * inv_n, normal[1] * inv_n, normal[2] * inv_n],
radius: radius.max(0.0),
}
}
#[inline]
pub fn from_sphere_plane_intersection(sphere: &CgaSphere, plane: &CgaPlane) -> Option<Self> {
let sc = sphere.to_euclidean();
let r = sphere.sphere_radius()?;
let n = [plane.x, plane.y, plane.z];
let n_len = (n[0] * n[0] + n[1] * n[1] + n[2] * n[2]).sqrt();
if n_len < 1e-12 {
return None;
}
let inv_n = 1.0 / n_len;
let n_unit = [n[0] * inv_n, n[1] * inv_n, n[2] * inv_n];
let dist = n_unit[0] * sc[0] + n_unit[1] * sc[1] + n_unit[2] * sc[2] + plane.ni * inv_n;
if dist.abs() > r {
return None; }
let circle_r = (r * r - dist * dist).max(0.0).sqrt();
let circle_center = [
sc[0] - dist * n_unit[0],
sc[1] - dist * n_unit[1],
sc[2] - dist * n_unit[2],
];
Some(Self::new(circle_center, n_unit, circle_r))
}
#[inline]
pub fn area(&self) -> f32 {
std::f32::consts::PI * self.radius * self.radius
}
#[inline]
pub fn circumference(&self) -> f32 {
2.0 * std::f32::consts::PI * self.radius
}
}
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct CgaConformalMotor {
pub translation: [f32; 3],
pub rotation_axis: [f32; 3],
pub rotation_angle_rad: f32,
pub dilation_factor: f32,
}
impl Default for CgaConformalMotor {
#[inline(always)]
fn default() -> Self {
Self::identity()
}
}
impl CgaConformalMotor {
#[inline(always)]
pub const fn identity() -> Self {
Self {
translation: [0.0, 0.0, 0.0],
rotation_axis: [0.0, 0.0, 1.0],
rotation_angle_rad: 0.0,
dilation_factor: 1.0,
}
}
#[inline]
pub fn from_translation(dx: f32, dy: f32, dz: f32) -> Self {
Self {
translation: [dx, dy, dz],
rotation_axis: [0.0, 0.0, 1.0],
rotation_angle_rad: 0.0,
dilation_factor: 1.0,
}
}
#[inline]
pub fn from_rotation(axis: [f32; 3], angle_rad: f32) -> Self {
let norm = (axis[0] * axis[0] + axis[1] * axis[1] + axis[2] * axis[2]).sqrt();
let inv = if norm > 1e-12 { 1.0 / norm } else { 1.0 };
Self {
translation: [0.0, 0.0, 0.0],
rotation_axis: [axis[0] * inv, axis[1] * inv, axis[2] * inv],
rotation_angle_rad: angle_rad,
dilation_factor: 1.0,
}
}
#[inline]
pub fn from_dilation(lambda: f32) -> Self {
assert!(lambda > 0.0, "Dilation factor must be strictly positive");
Self {
translation: [0.0, 0.0, 0.0],
rotation_axis: [0.0, 0.0, 1.0],
rotation_angle_rad: 0.0,
dilation_factor: lambda,
}
}
#[inline]
pub fn transform_point(&self, pt: &CgaPoint) -> CgaPoint {
let [x0, y0, z0] = pt.to_euclidean();
let x1 = x0 * self.dilation_factor;
let y1 = y0 * self.dilation_factor;
let z1 = z0 * self.dilation_factor;
let theta = self.rotation_angle_rad;
let [nx, ny, nz] = self.rotation_axis;
let cos_t = theta.cos();
let sin_t = theta.sin();
let dot_nr = nx * x1 + ny * y1 + nz * z1;
let rx = x1 * cos_t + (ny * z1 - nz * y1) * sin_t + nx * dot_nr * (1.0 - cos_t);
let ry = y1 * cos_t + (nz * x1 - nx * z1) * sin_t + ny * dot_nr * (1.0 - cos_t);
let rz = z1 * cos_t + (nx * y1 - ny * x1) * sin_t + nz * dot_nr * (1.0 - cos_t);
let tx = rx + self.translation[0];
let ty = ry + self.translation[1];
let tz = rz + self.translation[2];
CgaPoint::point(tx, ty, tz)
}
}