#[cfg(all(feature = "libm", not(feature = "std"), not(test)))]
use crate::math::Float as _;
use core::ops::{Add, Div, Mul, Sub};
use crate::angle::two_sum;
#[inline(always)]
pub(crate) fn fma(a: f64, b: f64, c: f64) -> f64 {
if cfg!(target_feature = "fma") {
a.mul_add(b, c)
} else {
a * b + c
}
}
#[inline]
pub(crate) fn two_prod(a: f64, b: f64) -> (f64, f64) {
let p = a * b;
if cfg!(target_feature = "fma") {
(p, a.mul_add(b, -p))
} else {
let split = |x: f64| {
let c = 134_217_729.0 * x; let hi = c - (c - x);
(hi, x - hi)
};
let ((ah, al), (bh, bl)) = (split(a), split(b));
(p, ((ah * bh - p) + ah * bl + al * bh) + al * bl)
}
}
#[derive(Clone, Copy, Debug)]
pub(crate) struct Dd(pub f64, pub f64);
impl Dd {
#[inline]
pub fn prod(a: f64, b: f64) -> Dd {
let (hi, lo) = two_prod(a, b);
Dd(hi, lo)
}
#[inline]
pub fn dot(x: Vec3, y: Vec3) -> Dd {
Dd::prod(x.x, y.x) + Dd::prod(x.y, y.y) + Dd::prod(x.z, y.z)
}
#[inline]
pub fn f64(self) -> f64 {
self.0 + self.1
}
}
impl From<f64> for Dd {
#[inline]
fn from(x: f64) -> Dd {
Dd(x, 0.0)
}
}
impl Add for Dd {
type Output = Dd;
#[inline]
fn add(self, o: Dd) -> Dd {
let (s, e) = two_sum(self.0, o.0);
let e = e + self.1 + o.1;
let hi = s + e;
Dd(hi, e - (hi - s))
}
}
impl Sub for Dd {
type Output = Dd;
#[inline]
fn sub(self, o: Dd) -> Dd {
self + Dd(-o.0, -o.1)
}
}
impl Mul for Dd {
type Output = Dd;
#[inline]
fn mul(self, o: Dd) -> Dd {
let (p, e) = two_prod(self.0, o.0);
let e = e + self.0 * o.1 + self.1 * o.0;
let hi = p + e;
Dd(hi, e - (hi - p))
}
}
macro_rules! vector_ops {
($t:ident { $a:ident, $b:ident, $c:ident }) => {
impl core::ops::Add for $t {
type Output = $t;
#[inline]
fn add(self, o: $t) -> $t {
$t::new(self.$a + o.$a, self.$b + o.$b, self.$c + o.$c)
}
}
impl core::ops::Sub for $t {
type Output = $t;
#[inline]
fn sub(self, o: $t) -> $t {
$t::new(self.$a - o.$a, self.$b - o.$b, self.$c - o.$c)
}
}
impl core::ops::Mul<f64> for $t {
type Output = $t;
#[inline]
fn mul(self, k: f64) -> $t {
$t::new(self.$a * k, self.$b * k, self.$c * k)
}
}
impl core::ops::Neg for $t {
type Output = $t;
#[inline]
fn neg(self) -> $t {
$t::new(-self.$a, -self.$b, -self.$c)
}
}
impl From<[f64; 3]> for $t {
#[inline]
fn from([a, b, c]: [f64; 3]) -> Self {
$t::new(a, b, c)
}
}
impl From<$t> for [f64; 3] {
#[inline]
fn from(v: $t) -> Self {
[v.$a, v.$b, v.$c]
}
}
};
}
pub(crate) use vector_ops;
#[derive(Clone, Copy, Debug, Default, PartialEq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct Vec3 {
pub x: f64,
pub y: f64,
pub z: f64,
}
impl Vec3 {
pub const ZERO: Vec3 = Vec3 { x: 0.0, y: 0.0, z: 0.0 };
#[inline]
pub const fn new(x: f64, y: f64, z: f64) -> Self {
Vec3 { x, y, z }
}
#[inline]
pub fn dot(self, o: Vec3) -> f64 {
fma(self.x, o.x, fma(self.y, o.y, self.z * o.z))
}
#[inline]
pub fn cross(self, o: Vec3) -> Vec3 {
Vec3::new(
fma(self.y, o.z, -(self.z * o.y)),
fma(self.z, o.x, -(self.x * o.z)),
fma(self.x, o.y, -(self.y * o.x)),
)
}
#[inline]
pub fn norm(self) -> f64 {
self.dot(self).sqrt()
}
#[inline]
pub fn normalized(self) -> Option<Vec3> {
let n = self.norm();
(n > 0.0).then(|| self * (1.0 / n))
}
#[inline]
pub fn distance_to(self, o: Vec3) -> f64 {
(self - o).norm()
}
#[inline]
pub fn angle_to(self, o: Vec3) -> f64 {
self.cross(o).norm().atan2(self.dot(o))
}
#[inline]
pub fn project_onto(self, onto: Vec3) -> Vec3 {
let d = onto.dot(onto);
if d == 0.0 {
Vec3::ZERO
} else {
onto * (self.dot(onto) / d)
}
}
#[inline]
pub const fn to_array(self) -> [f64; 3] {
[self.x, self.y, self.z]
}
}
vector_ops!(Vec3 { x, y, z });
impl Div<f64> for Vec3 {
type Output = Vec3;
#[inline]
fn div(self, k: f64) -> Vec3 {
Vec3::new(self.x / k, self.y / k, self.z / k)
}
}
#[cfg(test)]
mod tests {
#[test]
fn exact_products_and_double_doubles() {
let mut s: u64 = 0x2545_f491_4f6c_dd1d;
for _ in 0..100_000 {
let mut r = || {
s ^= s << 13;
s ^= s >> 7;
s ^= s << 17;
let m = (s >> 11) as f64 / (1u64 << 53) as f64 - 0.5;
m * 10f64.powi((s % 61) as i32 - 30)
};
let (a, b) = (r(), r());
let (hi, lo) = two_prod(a, b);
assert_eq!(hi, a * b);
assert_eq!(lo, a.mul_add(b, -hi), "{a} × {b}");
}
let x = 1.0 + 2f64.powi(-30);
assert_eq!(x * x - 1.0 - 2f64.powi(-29), 0.0);
let dd = Dd::prod(x, x) - Dd(1.0, 0.0) - Dd(2f64.powi(-29), 0.0);
assert_eq!(dd.f64(), 2f64.powi(-60));
assert_eq!((Dd(3.0, 0.0) * Dd(5.0, 0.0)).f64(), 15.0);
let v = Vec3::new(1e8, 1.0, 1e-8);
let d = Dd::dot(v, v); assert_eq!((d.0, d.1), (1e16, 1.0));
}
use super::*;
use crate::test_util::close;
#[test]
fn small_angles_keep_their_precision() {
let a = Vec3::new(1.0, 0.0, 0.0);
let b = Vec3::new(1.0, 1e-10, 0.0);
assert!(close(a.angle_to(b), 1e-10, 1e-24), "{}", a.angle_to(b));
assert!(close(a.angle_to(-b), core::f64::consts::PI - 1e-10, 1e-15));
assert!(close(
a.angle_to(Vec3::new(0.0, 3.0, 0.0)),
core::f64::consts::FRAC_PI_2,
1e-16
));
}
#[test]
fn vector_math() {
let a = Vec3::new(1.0, 0.0, 0.0);
let b = Vec3::new(0.0, 2.0, 0.0);
assert!(close(a.angle_to(b), core::f64::consts::FRAC_PI_2, 1e-12));
assert_eq!(a.angle_to(Vec3::ZERO), 0.0);
assert_eq!(Vec3::new(3.0, 4.0, 0.0).project_onto(a), Vec3::new(3.0, 0.0, 0.0));
assert_eq!(Vec3::new(1.0, 1.0, 1.0).project_onto(Vec3::ZERO), Vec3::ZERO);
assert_eq!(Vec3::new(2.0, 4.0, 6.0) / 2.0, Vec3::new(1.0, 2.0, 3.0));
assert!(close(Vec3::new(3.0, 4.0, 0.0).distance_to(Vec3::ZERO), 5.0, 1e-12));
assert!(close(Vec3::new(1.0, 2.0, 2.0).normalized().unwrap().norm(), 1.0, 1e-15));
assert!(Vec3::ZERO.normalized().is_none());
assert_eq!(<[f64; 3]>::from(Vec3::new(1.0, 2.0, 3.0)), [1.0, 2.0, 3.0]);
assert_eq!(a.cross(b), Vec3::new(0.0, 0.0, 2.0));
assert_eq!(a + b - b, a);
assert_eq!(-(a * 2.0), Vec3::from([-2.0, 0.0, 0.0]));
}
}