use core::cmp::Ordering;
use core::ops::{Add, Div, Mul, Neg, Sub};
#[cfg(not(feature = "std"))]
use alloc::vec;
#[cfg(not(feature = "std"))]
use alloc::vec::Vec;
#[cfg(all(feature = "simd", target_arch = "x86_64"))]
use core::arch::x86_64::*;
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Hash)]
#[repr(C)]
pub struct Fix128 {
pub hi: i64, pub lo: u64, }
impl Fix128 {
pub const ZERO: Self = Self { hi: 0, lo: 0 };
pub const ONE: Self = Self { hi: 1, lo: 0 };
pub const NEG_ONE: Self = Self { hi: -1, lo: 0 };
pub const PI: Self = Self {
hi: 3,
lo: 0x243F6A8885A308D3, };
pub const HALF_PI: Self = Self {
hi: 1,
lo: 0x921FB54442D18469, };
pub const TWO_PI: Self = Self {
hi: 6,
lo: 0x487ED5110B4611A6, };
#[inline]
#[must_use]
pub const fn from_int(n: i64) -> Self {
Self { hi: n, lo: 0 }
}
#[inline]
#[must_use]
pub const fn from_raw(hi: i64, lo: u64) -> Self {
Self { hi, lo }
}
#[must_use]
pub fn from_f64(f: f64) -> Self {
let hi = f as i64; let frac = f - (hi as f64);
let abs_frac = frac.abs();
let lo = (abs_frac * (1u128 << 64) as f64) as u64;
if f < 0.0 && lo != 0 {
Self {
hi: hi - 1,
lo: (!lo).wrapping_add(1),
}
} else {
Self { hi, lo }
}
}
#[must_use]
pub fn to_f64(self) -> f64 {
self.hi as f64 + (self.lo as f64 / (1u128 << 64) as f64)
}
#[must_use]
pub fn from_f32(f: f32) -> Self {
Self::from_f64(f as f64)
}
#[must_use]
pub fn to_f32(self) -> f32 {
self.to_f64() as f32
}
#[must_use]
pub fn from_ratio(num: i64, denom: i64) -> Self {
if denom == 0 {
return Self::ZERO;
}
let neg = (num < 0) != (denom < 0);
let num = num.unsigned_abs() as u128;
let denom = denom.unsigned_abs() as u128;
let scaled = (num << 64) / denom;
let hi = (scaled >> 64) as i64;
let lo = scaled as u64;
if neg {
Self { hi, lo }.neg()
} else {
Self { hi, lo }
}
}
#[inline]
#[must_use]
pub fn abs(self) -> Self {
if self.hi < 0 || (self.hi == 0 && self.lo == 0) {
self.neg()
} else {
self
}
}
#[inline]
#[must_use]
pub const fn is_negative(self) -> bool {
self.hi < 0
}
#[inline]
#[must_use]
pub const fn is_zero(self) -> bool {
self.hi == 0 && self.lo == 0
}
#[inline]
#[must_use]
pub const fn floor(self) -> Self {
Self { hi: self.hi, lo: 0 }
}
#[inline]
#[must_use]
pub const fn ceil(self) -> Self {
if self.lo == 0 {
self
} else {
Self {
hi: self.hi + 1,
lo: 0,
}
}
}
#[must_use]
pub fn sqrt(self) -> Self {
if self.is_negative() || self.is_zero() {
return Self::ZERO;
}
let sig_bits = if self.hi > 0 {
128 - (self.hi as u64).leading_zeros() as i64
} else if self.hi == 0 && self.lo > 0 {
64 - self.lo.leading_zeros() as i64
} else {
1
};
let result_bit = ((sig_bits + 63) / 2) as u32;
let mut x = if result_bit >= 64 {
Self {
hi: 1i64 << (result_bit - 64).min(62),
lo: 0,
}
} else {
Self {
hi: 0,
lo: 1u64 << result_bit,
}
};
for _ in 0..64 {
let div = self / x;
x = (x + div).half();
}
x
}
#[inline]
#[must_use]
pub const fn half(self) -> Self {
let hi = self.hi >> 1;
let lo = (self.lo >> 1) | ((self.hi as u64 & 1) << 63);
Self { hi, lo }
}
#[inline]
#[must_use]
pub const fn double(self) -> Self {
let hi = (self.hi << 1) | ((self.lo >> 63) as i64);
let lo = self.lo << 1;
Self { hi, lo }
}
#[must_use]
pub fn sin(self) -> Self {
cordic_sin_cos(self).0
}
#[must_use]
pub fn cos(self) -> Self {
cordic_sin_cos(self).1
}
#[must_use]
pub fn sin_cos(self) -> (Self, Self) {
cordic_sin_cos(self)
}
#[must_use]
pub fn atan(self) -> Self {
cordic_atan(self)
}
#[must_use]
pub fn atan2(y: Self, x: Self) -> Self {
cordic_atan2(y, x)
}
#[cfg(all(feature = "simd", target_arch = "x86_64"))]
#[inline]
#[target_feature(enable = "sse2")]
pub unsafe fn add_simd(self, rhs: Self) -> Self {
let a = _mm_set_epi64x(self.hi, self.lo as i64);
let b = _mm_set_epi64x(rhs.hi, rhs.lo as i64);
let lo_sum = (self.lo as u128) + (rhs.lo as u128);
let lo = lo_sum as u64;
let carry = (lo_sum >> 64) as i64;
let hi = self.hi.wrapping_add(rhs.hi).wrapping_add(carry);
let _ = (a, b);
Self { hi, lo }
}
#[cfg(all(feature = "simd", target_arch = "x86_64"))]
#[inline]
#[target_feature(enable = "sse2")]
pub unsafe fn sub_simd(self, rhs: Self) -> Self {
let (lo, borrow) = self.lo.overflowing_sub(rhs.lo);
let hi = self.hi.wrapping_sub(rhs.hi).wrapping_sub(borrow as i64);
Self { hi, lo }
}
}
impl Add for Fix128 {
type Output = Self;
#[inline]
fn add(self, rhs: Self) -> Self {
let (lo, carry) = self.lo.overflowing_add(rhs.lo);
let hi = self.hi.wrapping_add(rhs.hi).wrapping_add(carry as i64);
Self { hi, lo }
}
}
impl Sub for Fix128 {
type Output = Self;
#[inline]
fn sub(self, rhs: Self) -> Self {
let (lo, borrow) = self.lo.overflowing_sub(rhs.lo);
let hi = self.hi.wrapping_sub(rhs.hi).wrapping_sub(borrow as i64);
Self { hi, lo }
}
}
impl Mul for Fix128 {
type Output = Self;
fn mul(self, rhs: Self) -> Self {
let a_hi = self.hi as i128;
let a_lo = self.lo as u128;
let b_hi = rhs.hi as i128;
let b_lo = rhs.lo as u128;
let ll = a_lo.wrapping_mul(b_lo);
let hl = a_hi.wrapping_mul(b_lo as i128);
let lh = (a_lo as i128).wrapping_mul(b_hi);
let hh = a_hi.wrapping_mul(b_hi);
let ll_hi = (ll >> 64) as i128;
let mid = hl.wrapping_add(lh).wrapping_add(ll_hi);
let mid_lo = mid as u64;
let mid_hi = (mid >> 64) as i64;
let hi = (hh as i64).wrapping_add(mid_hi);
Self { hi, lo: mid_lo }
}
}
impl Div for Fix128 {
type Output = Self;
fn div(self, rhs: Self) -> Self {
if rhs.is_zero() {
return Self::ZERO;
}
let neg = self.is_negative() != rhs.is_negative();
let a = if self.is_negative() { self.neg() } else { self };
let b = if rhs.is_negative() { rhs.neg() } else { rhs };
let a_full = ((a.hi as u128) << 64) | (a.lo as u128);
let b_full = ((b.hi as u128) << 64) | (b.lo as u128);
if b_full == 0 {
return Self::ZERO;
}
let quot_hi = a_full / b_full;
let rem = a_full % b_full;
let mut r = rem;
let mut quot_lo: u64 = 0;
for i in (0..64).rev() {
let overflow_bit = r >> 127;
r <<= 1;
if overflow_bit != 0 || r >= b_full {
r = r.wrapping_sub(b_full);
quot_lo |= 1u64 << i;
}
}
let result = Self {
hi: quot_hi as i64,
lo: quot_lo,
};
if neg {
result.neg()
} else {
result
}
}
}
impl Neg for Fix128 {
type Output = Self;
#[inline]
fn neg(self) -> Self {
let (lo, carry) = (!self.lo).overflowing_add(1);
let hi = (!self.hi).wrapping_add(carry as i64);
Self { hi, lo }
}
}
impl PartialOrd for Fix128 {
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
Some(self.cmp(other))
}
}
impl Ord for Fix128 {
fn cmp(&self, other: &Self) -> Ordering {
match self.hi.cmp(&other.hi) {
Ordering::Equal => self.lo.cmp(&other.lo),
ord => ord,
}
}
}
impl core::fmt::Display for Fix128 {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
let val = self.to_f64();
write!(f, "{val:.4}")
}
}
impl From<i64> for Fix128 {
#[inline]
fn from(n: i64) -> Self {
Self::from_int(n)
}
}
impl From<i32> for Fix128 {
#[inline]
fn from(n: i32) -> Self {
Self::from_int(n as i64)
}
}
const CORDIC_ANGLES: [Fix128; 64] = compute_cordic_angles();
const CORDIC_K: Fix128 = Fix128 {
hi: 0,
lo: 0x9B74EDA8435E5DD7, };
const fn compute_cordic_angles() -> [Fix128; 64] {
const LO: [u64; 64] = [
0xC90FDAA22168C000, 0x76B19C1586ED4000, 0x3EB6EBF25901BA00, 0x1FD5BA9AAC2F6E00, 0x0FFAADDB967EF500, 0x07FF556EEA5D8940, 0x03FFEAAB776E5360, 0x01FFFD555BBBA970, 0x00FFFFAAAADDDDB8, 0x007FFFF55556EEF0, 0x003FFFFEAAAAB778, 0x001FFFFFD55555BC, 0x000FFFFFFAAAAAAE, 0x0007FFFFFF555555, 0x0003FFFFFFEAAAAA, 0x0001FFFFFFFD5555, 0x0000FFFFFFFFAAAA, 0x00007FFFFFFFF555, 0x00003FFFFFFFFEAA, 0x00001FFFFFFFFFD5, 0x00000FFFFFFFFFFA, 0x000007FFFFFFFFFF, 0x000003FFFFFFFFFF, 0x000001FFFFFFFFFF, 0x000000FFFFFFFFFF, 0x0000007FFFFFFFFF, 0x0000003FFFFFFFFF, 0x0000002000000000, 0x0000001000000000, 0x0000000800000000, 0x0000000400000000, 0x0000000200000000, 0x0000000100000000, 0x0000000080000000, 0x0000000040000000, 0x0000000020000000, 0x0000000010000000, 0x0000000008000000, 0x0000000004000000, 0x0000000002000000, 0x0000000001000000, 0x0000000000800000, 0x0000000000400000, 0x0000000000200000, 0x0000000000100000, 0x0000000000080000, 0x0000000000040000, 0x0000000000020000, 0x0000000000010000, 0x0000000000008000, 0x0000000000004000, 0x0000000000002000, 0x0000000000001000, 0x0000000000000800, 0x0000000000000400, 0x0000000000000200, 0x0000000000000100, 0x0000000000000080, 0x0000000000000040, 0x0000000000000020, 0x0000000000000010, 0x0000000000000008, 0x0000000000000004, 0x0000000000000002, ];
let mut angles = [Fix128::ZERO; 64];
let mut i = 0;
while i < 64 {
angles[i] = Fix128 { hi: 0, lo: LO[i] };
i += 1;
}
angles
}
fn cordic_sin_cos(angle: Fix128) -> (Fix128, Fix128) {
let mut theta = angle;
if theta > Fix128::PI || theta < Fix128::PI.neg() {
let shifted = theta + Fix128::PI;
let k = shifted / Fix128::TWO_PI;
let k_int = Fix128::from_int(k.hi);
theta = theta - Fix128::TWO_PI * k_int;
if theta > Fix128::PI {
theta = theta - Fix128::TWO_PI;
} else if theta < Fix128::PI.neg() {
theta = theta + Fix128::TWO_PI;
}
}
let negate_cos = if theta > Fix128::HALF_PI {
theta = Fix128::PI - theta;
true
} else if theta < Fix128::HALF_PI.neg() {
theta = Fix128::PI.neg() - theta;
true
} else {
false
};
let mut x = CORDIC_K;
let mut y = Fix128::ZERO;
let mut z = theta;
for (i, &angle) in CORDIC_ANGLES.iter().enumerate().take(48) {
let d = if z.is_negative() { -1i64 } else { 1i64 };
let x_shift = Fix128 {
hi: x.hi >> i.min(63),
lo: if i < 64 {
(x.lo >> i) | ((x.hi as u64) << (64 - i.max(1)))
} else {
0
},
};
let y_shift = Fix128 {
hi: y.hi >> i.min(63),
lo: if i < 64 {
(y.lo >> i) | ((y.hi as u64) << (64 - i.max(1)))
} else {
0
},
};
let new_x;
let new_y;
if d > 0 {
new_x = x - y_shift;
new_y = y + x_shift;
z = z - angle;
} else {
new_x = x + y_shift;
new_y = y - x_shift;
z = z + angle;
}
x = new_x;
y = new_y;
}
if negate_cos {
(y, x.neg()) } else {
(y, x) }
}
fn cordic_atan(v: Fix128) -> Fix128 {
let mut x = Fix128::ONE;
let mut y = v;
let mut z = Fix128::ZERO;
for (i, &angle) in CORDIC_ANGLES.iter().enumerate().take(48) {
let d = if y.is_negative() { 1i64 } else { -1i64 };
let x_shift = Fix128 {
hi: x.hi >> i.min(63),
lo: if i < 64 { x.lo >> i } else { 0 },
};
let y_shift = Fix128 {
hi: y.hi >> i.min(63),
lo: if i < 64 { y.lo >> i } else { 0 },
};
if d > 0 {
x = x - y_shift;
y = y + x_shift;
z = z - angle;
} else {
x = x + y_shift;
y = y - x_shift;
z = z + angle;
}
}
z
}
fn cordic_atan2(y: Fix128, x: Fix128) -> Fix128 {
if x.is_zero() && y.is_zero() {
return Fix128::ZERO;
}
if x.is_zero() {
return if y.is_negative() {
Fix128::HALF_PI.neg()
} else {
Fix128::HALF_PI
};
}
let ratio = y / x;
let base_atan = cordic_atan(ratio);
if x.is_negative() {
if y.is_negative() {
base_atan - Fix128::PI
} else {
base_atan + Fix128::PI
}
} else {
base_atan
}
}
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
#[repr(C)]
pub struct Vec3Fix {
pub x: Fix128,
pub y: Fix128,
pub z: Fix128,
}
impl Vec3Fix {
pub const ZERO: Self = Self {
x: Fix128::ZERO,
y: Fix128::ZERO,
z: Fix128::ZERO,
};
pub const UNIT_X: Self = Self {
x: Fix128::ONE,
y: Fix128::ZERO,
z: Fix128::ZERO,
};
pub const UNIT_Y: Self = Self {
x: Fix128::ZERO,
y: Fix128::ONE,
z: Fix128::ZERO,
};
pub const UNIT_Z: Self = Self {
x: Fix128::ZERO,
y: Fix128::ZERO,
z: Fix128::ONE,
};
#[inline]
#[must_use]
pub const fn new(x: Fix128, y: Fix128, z: Fix128) -> Self {
Self { x, y, z }
}
#[inline]
#[must_use]
pub const fn from_int(x: i64, y: i64, z: i64) -> Self {
Self {
x: Fix128::from_int(x),
y: Fix128::from_int(y),
z: Fix128::from_int(z),
}
}
#[inline]
#[must_use]
pub fn from_f32(x: f32, y: f32, z: f32) -> Self {
Self {
x: Fix128::from_f32(x),
y: Fix128::from_f32(y),
z: Fix128::from_f32(z),
}
}
#[inline]
#[must_use]
pub fn to_f32(self) -> (f32, f32, f32) {
(self.x.to_f32(), self.y.to_f32(), self.z.to_f32())
}
#[inline(always)]
#[must_use]
pub fn dot(self, rhs: Self) -> Fix128 {
self.x * rhs.x + self.y * rhs.y + self.z * rhs.z
}
#[inline(always)]
#[must_use]
pub fn cross(self, rhs: Self) -> Self {
Self {
x: self.y * rhs.z - self.z * rhs.y,
y: self.z * rhs.x - self.x * rhs.z,
z: self.x * rhs.y - self.y * rhs.x,
}
}
#[inline(always)]
#[must_use]
pub fn length_squared(self) -> Fix128 {
self.dot(self)
}
#[inline(always)]
#[must_use]
pub fn length(self) -> Fix128 {
self.length_squared().sqrt()
}
#[inline(always)]
#[must_use]
pub fn normalize(self) -> Self {
let len = self.length();
if len.is_zero() {
Self::ZERO
} else {
self / len
}
}
#[inline(always)]
#[must_use]
pub fn try_normalize(self) -> Option<Self> {
let len = self.length();
if len.is_zero() {
None
} else {
Some(self / len)
}
}
#[inline(always)]
#[must_use]
pub fn normalize_with_length(self) -> (Self, Fix128) {
let len = self.length();
if len.is_zero() {
(Self::ZERO, Fix128::ZERO)
} else {
let inv_len = Fix128::ONE / len;
(self * inv_len, len)
}
}
#[inline]
#[must_use]
pub fn scale(self, s: Fix128) -> Self {
Self {
x: self.x * s,
y: self.y * s,
z: self.z * s,
}
}
#[cfg(all(feature = "simd", target_arch = "x86_64"))]
#[target_feature(enable = "sse2")]
unsafe fn dot_simd_sse2(self, rhs: Self) -> Fix128 {
let px = self.x * rhs.x;
let py = self.y * rhs.y;
let pz = self.z * rhs.z;
let ab = px + py;
ab + pz
}
#[inline]
#[must_use]
pub fn dot_simd(self, rhs: Self) -> Fix128 {
#[cfg(all(feature = "simd", target_arch = "x86_64"))]
{
unsafe { self.dot_simd_sse2(rhs) }
}
#[cfg(not(all(feature = "simd", target_arch = "x86_64")))]
{
self.dot(rhs)
}
}
#[inline]
#[must_use]
pub fn length_squared_simd(self) -> Fix128 {
self.dot_simd(self)
}
#[cfg(all(feature = "simd", target_arch = "x86_64"))]
#[inline]
pub fn cross_simd(self, rhs: Self) -> Self {
let yz = self.y * rhs.z;
let zy = self.z * rhs.y;
let zx = self.z * rhs.x;
let xz = self.x * rhs.z;
let xy = self.x * rhs.y;
let yx = self.y * rhs.x;
Self {
x: yz - zy,
y: zx - xz,
z: xy - yx,
}
}
#[cfg(all(feature = "simd", target_arch = "x86_64"))]
pub fn dot_batch_4(a: [Self; 4], b: [Self; 4]) -> [Fix128; 4] {
[
a[0].dot(b[0]),
a[1].dot(b[1]),
a[2].dot(b[2]),
a[3].dot(b[3]),
]
}
}
impl Add for Vec3Fix {
type Output = Self;
#[inline]
fn add(self, rhs: Self) -> Self {
Self {
x: self.x + rhs.x,
y: self.y + rhs.y,
z: self.z + rhs.z,
}
}
}
impl Sub for Vec3Fix {
type Output = Self;
#[inline]
fn sub(self, rhs: Self) -> Self {
Self {
x: self.x - rhs.x,
y: self.y - rhs.y,
z: self.z - rhs.z,
}
}
}
impl Mul<Fix128> for Vec3Fix {
type Output = Self;
#[inline]
fn mul(self, rhs: Fix128) -> Self {
self.scale(rhs)
}
}
impl Div<Fix128> for Vec3Fix {
type Output = Self;
#[inline]
fn div(self, rhs: Fix128) -> Self {
Self {
x: self.x / rhs,
y: self.y / rhs,
z: self.z / rhs,
}
}
}
impl Neg for Vec3Fix {
type Output = Self;
#[inline]
fn neg(self) -> Self {
Self {
x: -self.x,
y: -self.y,
z: -self.z,
}
}
}
impl core::fmt::Display for Vec3Fix {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
write!(f, "({}, {}, {})", self.x, self.y, self.z)
}
}
impl From<[Fix128; 3]> for Vec3Fix {
#[inline]
fn from(arr: [Fix128; 3]) -> Self {
Self::new(arr[0], arr[1], arr[2])
}
}
impl From<Vec3Fix> for [Fix128; 3] {
#[inline]
fn from(v: Vec3Fix) -> Self {
[v.x, v.y, v.z]
}
}
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
#[repr(C)]
pub struct QuatFix {
pub x: Fix128,
pub y: Fix128,
pub z: Fix128,
pub w: Fix128,
}
impl QuatFix {
pub const IDENTITY: Self = Self {
x: Fix128::ZERO,
y: Fix128::ZERO,
z: Fix128::ZERO,
w: Fix128::ONE,
};
#[inline]
#[must_use]
pub const fn new(x: Fix128, y: Fix128, z: Fix128, w: Fix128) -> Self {
Self { x, y, z, w }
}
#[must_use]
pub fn from_axis_angle(axis: Vec3Fix, angle: Fix128) -> Self {
let half_angle = angle.half();
let (sin_ha, cos_ha) = half_angle.sin_cos();
let axis_norm = axis.normalize();
Self {
x: axis_norm.x * sin_ha,
y: axis_norm.y * sin_ha,
z: axis_norm.z * sin_ha,
w: cos_ha,
}
}
#[allow(clippy::should_implement_trait)]
#[must_use]
pub fn mul(self, rhs: Self) -> Self {
Self {
x: self.w * rhs.x + self.x * rhs.w + self.y * rhs.z - self.z * rhs.y,
y: self.w * rhs.y - self.x * rhs.z + self.y * rhs.w + self.z * rhs.x,
z: self.w * rhs.z + self.x * rhs.y - self.y * rhs.x + self.z * rhs.w,
w: self.w * rhs.w - self.x * rhs.x - self.y * rhs.y - self.z * rhs.z,
}
}
#[inline]
#[must_use]
pub fn conjugate(self) -> Self {
Self {
x: -self.x,
y: -self.y,
z: -self.z,
w: self.w,
}
}
#[inline]
#[must_use]
pub fn length_squared(self) -> Fix128 {
self.x * self.x + self.y * self.y + self.z * self.z + self.w * self.w
}
#[inline(always)]
#[must_use]
pub fn length(self) -> Fix128 {
self.length_squared().sqrt()
}
#[inline(always)]
#[must_use]
pub fn normalize(self) -> Self {
let len = self.length();
if len.is_zero() {
Self::IDENTITY
} else {
let inv_len = Fix128::ONE / len;
Self {
x: self.x * inv_len,
y: self.y * inv_len,
z: self.z * inv_len,
w: self.w * inv_len,
}
}
}
#[must_use]
pub fn rotate_vec(self, v: Vec3Fix) -> Vec3Fix {
let qv = Self::new(v.x, v.y, v.z, Fix128::ZERO);
let result = self.mul(qv).mul(self.conjugate());
Vec3Fix::new(result.x, result.y, result.z)
}
}
impl core::fmt::Display for QuatFix {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
write!(f, "({}, {}, {}, {})", self.x, self.y, self.z, self.w)
}
}
impl From<[Fix128; 4]> for QuatFix {
#[inline]
fn from(arr: [Fix128; 4]) -> Self {
Self::new(arr[0], arr[1], arr[2], arr[3])
}
}
impl From<QuatFix> for [Fix128; 4] {
#[inline]
fn from(q: QuatFix) -> Self {
[q.x, q.y, q.z, q.w]
}
}
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
#[repr(C)]
pub struct Mat3Fix {
pub col0: Vec3Fix,
pub col1: Vec3Fix,
pub col2: Vec3Fix,
}
impl Mat3Fix {
pub const IDENTITY: Self = Self {
col0: Vec3Fix::UNIT_X,
col1: Vec3Fix::UNIT_Y,
col2: Vec3Fix::UNIT_Z,
};
pub const ZERO: Self = Self {
col0: Vec3Fix::ZERO,
col1: Vec3Fix::ZERO,
col2: Vec3Fix::ZERO,
};
#[inline]
#[must_use]
pub const fn from_cols(col0: Vec3Fix, col1: Vec3Fix, col2: Vec3Fix) -> Self {
Self { col0, col1, col2 }
}
#[inline]
#[must_use]
pub const fn diagonal(x: Fix128, y: Fix128, z: Fix128) -> Self {
Self {
col0: Vec3Fix::new(x, Fix128::ZERO, Fix128::ZERO),
col1: Vec3Fix::new(Fix128::ZERO, y, Fix128::ZERO),
col2: Vec3Fix::new(Fix128::ZERO, Fix128::ZERO, z),
}
}
#[inline]
#[must_use]
pub fn mul_vec(self, v: Vec3Fix) -> Vec3Fix {
Vec3Fix::new(
self.col0.x * v.x + self.col1.x * v.y + self.col2.x * v.z,
self.col0.y * v.x + self.col1.y * v.y + self.col2.y * v.z,
self.col0.z * v.x + self.col1.z * v.y + self.col2.z * v.z,
)
}
#[inline]
#[must_use]
pub const fn transpose(self) -> Self {
Self {
col0: Vec3Fix::new(self.col0.x, self.col1.x, self.col2.x),
col1: Vec3Fix::new(self.col0.y, self.col1.y, self.col2.y),
col2: Vec3Fix::new(self.col0.z, self.col1.z, self.col2.z),
}
}
#[inline]
#[must_use]
pub fn scale(self, s: Fix128) -> Self {
Self {
col0: self.col0.scale(s),
col1: self.col1.scale(s),
col2: self.col2.scale(s),
}
}
#[inline]
#[must_use]
pub fn mul_mat(self, rhs: Self) -> Self {
Self {
col0: self.mul_vec(rhs.col0),
col1: self.mul_vec(rhs.col1),
col2: self.mul_vec(rhs.col2),
}
}
#[inline]
#[must_use]
pub fn determinant(self) -> Fix128 {
self.col0.x * (self.col1.y * self.col2.z - self.col1.z * self.col2.y)
- self.col1.x * (self.col0.y * self.col2.z - self.col0.z * self.col2.y)
+ self.col2.x * (self.col0.y * self.col1.z - self.col0.z * self.col1.y)
}
#[must_use]
pub fn inverse(self) -> Option<Self> {
let det = self.determinant();
if det.is_zero() {
return None;
}
let inv_det = Fix128::ONE / det;
let c00 = self.col1.y * self.col2.z - self.col1.z * self.col2.y;
let c01 = self.col0.z * self.col2.y - self.col0.y * self.col2.z;
let c02 = self.col0.y * self.col1.z - self.col0.z * self.col1.y;
let c10 = self.col1.z * self.col2.x - self.col1.x * self.col2.z;
let c11 = self.col0.x * self.col2.z - self.col0.z * self.col2.x;
let c12 = self.col0.z * self.col1.x - self.col0.x * self.col1.z;
let c20 = self.col1.x * self.col2.y - self.col1.y * self.col2.x;
let c21 = self.col0.y * self.col2.x - self.col0.x * self.col2.y;
let c22 = self.col0.x * self.col1.y - self.col0.y * self.col1.x;
Some(Self {
col0: Vec3Fix::new(c00 * inv_det, c01 * inv_det, c02 * inv_det),
col1: Vec3Fix::new(c10 * inv_det, c11 * inv_det, c12 * inv_det),
col2: Vec3Fix::new(c20 * inv_det, c21 * inv_det, c22 * inv_det),
})
}
}
#[inline(always)]
#[must_use]
pub const fn simd_width() -> usize {
#[cfg(all(feature = "simd", target_arch = "x86_64", target_feature = "avx2"))]
{
8
}
#[cfg(all(feature = "simd", target_arch = "x86_64", not(target_feature = "avx2")))]
{
4
}
#[cfg(all(feature = "simd", target_arch = "aarch64"))]
{
4
}
#[cfg(not(feature = "simd"))]
{
1
}
}
pub const SIMD_WIDTH: usize = simd_width();
#[inline(always)]
#[must_use]
pub(crate) const fn select_fix128(condition: bool, a: Fix128, b: Fix128) -> Fix128 {
let mask = -(condition as i64) as u64;
let inv_mask = !mask;
let hi = ((a.hi as u64 & mask) | (b.hi as u64 & inv_mask)) as i64;
let lo = (a.lo & mask) | (b.lo & inv_mask);
Fix128 { hi, lo }
}
#[inline(always)]
#[must_use]
pub(crate) const fn select_vec3(condition: bool, a: Vec3Fix, b: Vec3Fix) -> Vec3Fix {
Vec3Fix {
x: select_fix128(condition, a.x, b.x),
y: select_fix128(condition, a.y, b.y),
z: select_fix128(condition, a.z, b.z),
}
}
impl core::ops::Mul<Self> for Mat3Fix {
type Output = Self;
fn mul(self, rhs: Self) -> Self {
Self {
col0: self.mul_vec(rhs.col0),
col1: self.mul_vec(rhs.col1),
col2: self.mul_vec(rhs.col2),
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn fix128_mul_wraps_on_integer_overflow() {
let a = Fix128::from_int(1 << 62);
let b = Fix128::from_int(4);
assert_eq!(a * b, Fix128::ZERO);
assert_eq!(a * Fix128::from_int(3), Fix128::from_int(-(1 << 62)));
}
#[test]
fn fix128_mul_truncates_toward_negative_infinity() {
let tiny_neg = Fix128::from_raw(-1, u64::MAX);
let half = Fix128::from_ratio(1, 2);
assert_eq!(tiny_neg * half, tiny_neg);
let tiny_pos = Fix128::from_raw(0, 1);
assert_eq!(tiny_pos * half, Fix128::ZERO);
}
#[test]
fn test_fix128_basic_ops() {
let a = Fix128::from_int(5);
let b = Fix128::from_int(3);
let sum = a + b;
assert_eq!(sum.hi, 8);
assert_eq!(sum.lo, 0);
let diff = a - b;
assert_eq!(diff.hi, 2);
assert_eq!(diff.lo, 0);
}
#[test]
fn test_fix128_mul() {
let a = Fix128::from_int(6);
let b = Fix128::from_int(7);
let product = a * b;
assert_eq!(product.hi, 42);
}
#[test]
fn test_fix128_div() {
let a = Fix128::from_int(42);
let b = Fix128::from_int(6);
let quot = a / b;
assert_eq!(quot.hi, 7);
}
#[test]
fn test_fix128_neg() {
let a = Fix128::from_int(5);
let neg_a = -a;
assert_eq!(neg_a.hi, -5);
let sum = a + neg_a;
assert!(sum.is_zero());
}
#[test]
fn test_vec3_dot() {
let a = Vec3Fix::from_int(1, 2, 3);
let b = Vec3Fix::from_int(4, 5, 6);
let dot = a.dot(b);
assert_eq!(dot.hi, 32);
}
#[test]
fn test_vec3_cross() {
let a = Vec3Fix::UNIT_X;
let b = Vec3Fix::UNIT_Y;
let cross = a.cross(b);
assert_eq!(cross.z.hi, 1);
assert!(cross.x.is_zero());
assert!(cross.y.is_zero());
}
#[test]
fn test_quat_identity() {
let q = QuatFix::IDENTITY;
let v = Vec3Fix::from_int(1, 2, 3);
let rotated = q.rotate_vec(v);
assert_eq!(rotated.x.hi, 1);
assert_eq!(rotated.y.hi, 2);
assert_eq!(rotated.z.hi, 3);
}
#[test]
fn test_determinism() {
let a = Fix128::from_raw(12345, 0xABCDEF0123456789);
let b = Fix128::from_raw(67890, 0x9876543210FEDCBA);
let result1 = (a * b) + (a - b);
let result2 = (a * b) + (a - b);
assert_eq!(result1.hi, result2.hi);
assert_eq!(result1.lo, result2.lo);
}
#[test]
fn test_fix128_f32_roundtrip() {
let a = Fix128::from_f32(core::f32::consts::PI);
let back = a.to_f32();
assert!((back - core::f32::consts::PI).abs() < 0.001);
let b = Fix128::from_f32(-7.5);
let back_b = b.to_f32();
assert!((back_b - (-7.5)).abs() < 0.001);
}
#[test]
fn test_vec3fix_f32_roundtrip() {
let v = Vec3Fix::from_f32(1.0, -2.5, 3.75);
let (x, y, z) = v.to_f32();
assert!((x - 1.0).abs() < 0.001);
assert!((y - (-2.5)).abs() < 0.001);
assert!((z - 3.75).abs() < 0.001);
}
#[test]
fn test_dot_simd_integer_vectors_bit_exact() {
let a = Vec3Fix::from_int(1, 2, 3);
let b = Vec3Fix::from_int(4, 5, 6);
let scalar = a.dot(b);
let simd = a.dot_simd(b);
assert_eq!(scalar.hi, 32, "scalar dot hi mismatch");
assert_eq!(scalar.lo, 0, "scalar dot lo mismatch");
assert_eq!(simd.hi, scalar.hi, "dot_simd hi != scalar hi");
assert_eq!(simd.lo, scalar.lo, "dot_simd lo != scalar lo");
}
#[test]
fn test_dot_simd_fractional_bit_exact() {
let ax = Fix128::from_raw(3, 0xABCD_EF01_2345_6789);
let ay = Fix128::from_raw(-1, 0x1111_2222_3333_4444);
let az = Fix128::from_raw(7, 0xFEDC_BA98_7654_3210);
let bx = Fix128::from_raw(2, 0x9876_5432_10FE_DCBA);
let by = Fix128::from_raw(5, 0xAAAA_BBBB_CCCC_DDDD);
let bz = Fix128::from_raw(-3, 0x0F0F_0F0F_0F0F_0F0F);
let a = Vec3Fix::new(ax, ay, az);
let b = Vec3Fix::new(bx, by, bz);
let scalar = a.dot(b);
let simd = a.dot_simd(b);
assert_eq!(
simd.hi, scalar.hi,
"dot_simd hi={:#018x} != scalar hi={:#018x}",
simd.hi, scalar.hi
);
assert_eq!(
simd.lo, scalar.lo,
"dot_simd lo={:#018x} != scalar lo={:#018x}",
simd.lo, scalar.lo
);
}
#[test]
fn test_dot_simd_negative_components_bit_exact() {
let a = Vec3Fix::from_int(-3, 4, -5);
let b = Vec3Fix::from_int(6, -7, 8);
let scalar = a.dot(b);
let simd = a.dot_simd(b);
assert_eq!(scalar.hi, -86, "scalar dot should be -86");
assert_eq!(simd.hi, scalar.hi, "dot_simd hi mismatch on negatives");
assert_eq!(simd.lo, scalar.lo, "dot_simd lo mismatch on negatives");
}
#[test]
fn test_dot_simd_zero_vector_bit_exact() {
let a = Vec3Fix::ZERO;
let b = Vec3Fix::from_int(100, 200, 300);
let scalar = a.dot(b);
let simd = a.dot_simd(b);
assert!(scalar.is_zero(), "scalar dot with zero should be zero");
assert_eq!(simd.hi, scalar.hi, "dot_simd hi mismatch (zero vector)");
assert_eq!(simd.lo, scalar.lo, "dot_simd lo mismatch (zero vector)");
}
#[test]
fn test_dot_simd_unit_vectors_self_dot() {
for unit in [Vec3Fix::UNIT_X, Vec3Fix::UNIT_Y, Vec3Fix::UNIT_Z] {
let scalar = unit.dot(unit);
let simd = unit.dot_simd(unit);
assert_eq!(scalar.hi, 1, "unit self-dot should be 1");
assert_eq!(scalar.lo, 0, "unit self-dot lo should be 0");
assert_eq!(simd.hi, scalar.hi, "dot_simd hi mismatch on unit self-dot");
assert_eq!(simd.lo, scalar.lo, "dot_simd lo mismatch on unit self-dot");
}
}
#[test]
fn test_length_squared_simd_bit_exact() {
let v = Vec3Fix::from_int(3, 4, 0);
let scalar = v.length_squared();
let simd = v.length_squared_simd();
assert_eq!(scalar.hi, 25, "length_squared should be 25");
assert_eq!(simd.hi, scalar.hi, "length_squared_simd hi mismatch");
assert_eq!(simd.lo, scalar.lo, "length_squared_simd lo mismatch");
}
#[test]
fn test_length_squared_simd_fractional_bit_exact() {
let v = Vec3Fix::new(
Fix128::from_raw(1, 0x8000_0000_0000_0000), Fix128::from_raw(2, 0x0000_0000_0000_0000), Fix128::from_raw(0, 0x8000_0000_0000_0000), );
let scalar = v.length_squared();
let simd = v.length_squared_simd();
assert_eq!(
simd.hi, scalar.hi,
"length_squared_simd hi mismatch (fractional)"
);
assert_eq!(
simd.lo, scalar.lo,
"length_squared_simd lo mismatch (fractional)"
);
}
#[test]
fn test_dot_simd_exhaustive_raw_sweep() {
let raws: [(i64, u64); 8] = [
(0, 0x0000_0000_0000_0001),
(1, 0xFFFF_FFFF_FFFF_FFFF),
(-1, 0x0000_0000_0000_0000),
(42, 0x1234_5678_9ABC_DEF0),
(-7, 0xDEAD_BEEF_CAFE_BABE),
(100, 0xAAAA_AAAA_AAAA_AAAA),
(-100, 0x5555_5555_5555_5555),
(i64::MAX / 2, 0x8000_0000_0000_0000),
];
for &(ah, al) in &raws {
for &(bh, bl) in &raws {
let a = Vec3Fix::new(
Fix128::from_raw(ah, al),
Fix128::from_raw(bh, bl),
Fix128::from_raw(ah.wrapping_add(bh), al ^ bl),
);
let b = Vec3Fix::new(
Fix128::from_raw(bh, bl),
Fix128::from_raw(ah, al),
Fix128::from_raw(ah.wrapping_sub(bh), al.wrapping_add(bl)),
);
let scalar = a.dot(b);
let simd = a.dot_simd(b);
assert_eq!(
simd.hi, scalar.hi,
"sweep: dot_simd hi mismatch for a=({ah},{al:#x}) b=({bh},{bl:#x})"
);
assert_eq!(
simd.lo, scalar.lo,
"sweep: dot_simd lo mismatch for a=({ah},{al:#x}) b=({bh},{bl:#x})"
);
}
}
}
}