#[derive(Clone, Copy, Debug, PartialEq)]
pub(crate) struct Tri {
pub lx: f64,
pub ly: f64,
pub lz: f64,
pub xy: f64,
pub xz: f64,
pub yz: f64,
pub inv_lx: f64,
pub inv_ly: f64,
pub inv_lz: f64,
}
impl Tri {
#[inline(always)]
pub(crate) fn wrap(self, dp: [f64; 3]) -> [f64; 3] {
let mut sz = dp[2] * self.inv_lz;
let mut sy = (dp[1] - self.yz * sz) * self.inv_ly;
let mut sx = (dp[0] - self.xy * sy - self.xz * sz) * self.inv_lx;
sx -= round_away(sx);
sy -= round_away(sy);
sz -= round_away(sz);
[
self.lx * sx + self.xy * sy + self.xz * sz,
self.ly * sy + self.yz * sz,
self.lz * sz,
]
}
}
#[cfg(target_arch = "x86_64")]
const INTEGRAL: f64 = 4503599627370496.0;
#[inline(always)]
pub(crate) fn round_away(x: f64) -> f64 {
#[cfg(target_arch = "x86_64")]
{
if x.abs() < INTEGRAL {
let t = x + 0.499_999_999_999_999_94_f64.copysign(x);
return unsafe { t.to_int_unchecked::<i64>() } as f64;
}
x
}
#[cfg(not(target_arch = "x86_64"))]
{
x.round()
}
}
#[inline(always)]
pub(crate) fn floor_fast(x: f64) -> f64 {
#[cfg(target_arch = "x86_64")]
{
if x.abs() < INTEGRAL {
let t = unsafe { x.to_int_unchecked::<i64>() } as f64;
return if t > x { t - 1.0 } else { t };
}
x
}
#[cfg(not(target_arch = "x86_64"))]
{
x.floor()
}
}
#[inline(always)]
pub(crate) fn wrap_half(d: f64, length: f64) -> f64 {
if d.abs() >= length {
return wrap_half_far(d, length);
}
wrap_one(d, length)
}
#[inline(always)]
pub(crate) fn wrap_one(d: f64, length: f64) -> f64 {
let half = 0.5 * length;
let w = d + if d < -half { length } else { 0.0 };
w - if w >= half { length } else { 0.0 }
}
#[inline(always)]
pub(crate) fn ortho_wrap(l: [f64; 3], dp: [f64; 3]) -> [f64; 3] {
let far = (dp[0].abs() >= l[0]) | (dp[1].abs() >= l[1]) | (dp[2].abs() >= l[2]);
if far {
return ortho_wrap_far(l[0], l[1], l[2], dp[0], dp[1], dp[2]);
}
[
wrap_one(dp[0], l[0]),
wrap_one(dp[1], l[1]),
wrap_one(dp[2], l[2]),
]
}
#[inline(always)]
pub(crate) fn ortho_dist2(l: [f64; 3], dp: [f64; 3]) -> f64 {
let ax = dp[0].abs();
let ay = dp[1].abs();
let az = dp[2].abs();
let bx = l[0] - ax;
let by = l[1] - ay;
let bz = l[2] - az;
if (bx < 0.0) | (by < 0.0) | (bz < 0.0) {
return n2(ortho_wrap_far(l[0], l[1], l[2], dp[0], dp[1], dp[2]));
}
let wx = if bx < ax { bx } else { ax };
let wy = if by < ay { by } else { ay };
let wz = if bz < az { bz } else { az };
wx * wx + wy * wy + wz * wz
}
#[cold]
#[inline(never)]
fn ortho_wrap_far(lx: f64, ly: f64, lz: f64, dx: f64, dy: f64, dz: f64) -> [f64; 3] {
[wrap_half(dx, lx), wrap_half(dy, ly), wrap_half(dz, lz)]
}
#[cold]
#[inline(never)]
fn wrap_half_far(d: f64, length: f64) -> f64 {
let w = d - length * floor_fast(d / length + 0.5);
let half = 0.5 * length;
let w = if w < -half { w + length } else { w };
if w >= half {
w - length
} else {
w
}
}
#[inline(always)]
pub(crate) fn general_wrap(h: [[f64; 3]; 3], hinv: [[f64; 3]; 3], dp: [f64; 3]) -> [f64; 3] {
let mut ds = mul(hinv, dp);
ds[0] -= round_away(ds[0]);
ds[1] -= round_away(ds[1]);
ds[2] -= round_away(ds[2]);
mul(h, ds)
}
#[inline]
pub(crate) fn mul(h: [[f64; 3]; 3], v: [f64; 3]) -> [f64; 3] {
[
h[0][0] * v[0] + h[1][0] * v[1] + h[2][0] * v[2],
h[0][1] * v[0] + h[1][1] * v[1] + h[2][1] * v[2],
h[0][2] * v[0] + h[1][2] * v[1] + h[2][2] * v[2],
]
}
#[inline]
pub(crate) fn invert_columns(h: [[f64; 3]; 3]) -> Option<([[f64; 3]; 3], f64)> {
let a = h[0];
let b = h[1];
let c = h[2];
let det = a[0] * (b[1] * c[2] - b[2] * c[1]) - a[1] * (b[0] * c[2] - b[2] * c[0])
+ a[2] * (b[0] * c[1] - b[1] * c[0]);
if !det.is_finite() || det.abs() < 1e-18 {
return None;
}
let invdet = 1.0 / det;
let inv = [
[
(b[1] * c[2] - b[2] * c[1]) * invdet,
(a[2] * c[1] - a[1] * c[2]) * invdet,
(a[1] * b[2] - a[2] * b[1]) * invdet,
],
[
(b[2] * c[0] - b[0] * c[2]) * invdet,
(a[0] * c[2] - a[2] * c[0]) * invdet,
(a[2] * b[0] - a[0] * b[2]) * invdet,
],
[
(b[0] * c[1] - b[1] * c[0]) * invdet,
(a[1] * c[0] - a[0] * c[1]) * invdet,
(a[0] * b[1] - a[1] * b[0]) * invdet,
],
];
Some((inv, det))
}
#[inline]
pub(crate) fn n2(v: [f64; 3]) -> f64 {
v[0] * v[0] + v[1] * v[1] + v[2] * v[2]
}
#[inline]
pub(crate) fn norm(v: [f64; 3]) -> f64 {
n2(v).sqrt()
}
#[inline]
pub(crate) fn dot(u: [f64; 3], v: [f64; 3]) -> f64 {
u[0] * v[0] + u[1] * v[1] + u[2] * v[2]
}
#[inline]
pub(crate) fn add(u: [f64; 3], v: [f64; 3]) -> [f64; 3] {
[u[0] + v[0], u[1] + v[1], u[2] + v[2]]
}
#[inline]
pub(crate) fn scale(s: f64, v: [f64; 3]) -> [f64; 3] {
[s * v[0], s * v[1], s * v[2]]
}
#[inline]
pub(crate) fn cross(u: [f64; 3], v: [f64; 3]) -> [f64; 3] {
[
u[1] * v[2] - u[2] * v[1],
u[2] * v[0] - u[0] * v[2],
u[0] * v[1] - u[1] * v[0],
]
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn wrap_matches_the_division_form() {
let l = [10.0, 11.0, 12.0];
let samples = [
[0.0, 0.0, 0.0],
[5.0, -5.5, 6.0],
[15.0, -18.0, 25.0],
[-15.0, 16.5, -30.0],
[6.0, 12.0, -6.0],
];
for dp in samples {
let via = ortho_wrap(l, dp);
for a in 0..3 {
let floor = dp[a] - l[a] * (dp[a] / l[a] + 0.5).floor();
assert!((wrap_half(dp[a], l[a]) - floor).abs() < 1e-12);
assert!((via[a] - floor).abs() < 1e-12);
}
}
}
fn awkward_doubles() -> Vec<f64> {
let mut xs = vec![
0.0,
-0.0,
0.5,
-0.5,
0.499_999_999_999_999_94,
-0.499_999_999_999_999_94,
1.5,
2.5,
-2.5,
4_503_599_627_370_495.5,
-4_503_599_627_370_495.5,
4_503_599_627_370_496.0,
9_007_199_254_740_993.0,
1e300,
-1e300,
f64::MIN_POSITIVE,
-f64::MIN_POSITIVE * 0.5,
f64::INFINITY,
f64::NEG_INFINITY,
];
for k in -40i32..=40 {
let h = f64::from(k) + 0.5;
let mut lo = h;
let mut hi = h;
for _ in 0..3 {
lo = f64::from_bits(lo.to_bits() - 1);
hi = f64::from_bits(hi.to_bits() + 1);
xs.extend([lo, hi, -lo, -hi]);
}
xs.extend([h, -h]);
}
let mut state = 0x9e37_79b9_7f4a_7c15u64;
for _ in 0..20_000 {
state ^= state << 13;
state ^= state >> 7;
state ^= state << 17;
let x = f64::from_bits(state);
if x.is_finite() {
xs.push(x);
xs.push(x * 1e-300);
}
xs.push((state >> 11) as f64 / (1u64 << 40) as f64 - 4096.0);
}
xs
}
#[test]
fn round_and_floor_match_libm() {
for x in awkward_doubles() {
assert_eq!(round_away(x), x.round(), "round {x:e}");
assert_eq!(floor_fast(x), x.floor(), "floor {x:e}");
}
assert!(round_away(f64::NAN).is_nan());
assert!(floor_fast(f64::NAN).is_nan());
}
#[test]
fn ortho_square_is_the_signed_wrap_square() {
let l = [10.0, 11.0, 12.0];
let mut state = 0x2545_f491_4f6c_dd1du64;
let mut unit = || {
state ^= state << 13;
state ^= state >> 7;
state ^= state << 17;
(state >> 11) as f64 / (1u64 << 53) as f64
};
let mut cases = vec![
[5.0, -5.5, 6.0],
[-5.0, 5.5, -6.0],
[10.0, -11.0, 12.0],
[15.0, -16.5, 18.0],
[-15.0, 16.5, -18.0],
[9.999_999_999_999_998, 0.0, 0.0],
];
for _ in 0..4000 {
cases.push([
(unit() - 0.5) * 6.0 * l[0],
(unit() - 0.5) * 2.0 * l[1],
(unit() - 0.5) * 2.0 * l[2],
]);
}
for dp in cases {
let signed = ortho_wrap(l, dp);
for a in 0..3 {
assert!(signed[a] >= -0.5 * l[a] && signed[a] < 0.5 * l[a], "{dp:?}");
}
assert_eq!(ortho_dist2(l, dp).to_bits(), n2(signed).to_bits(), "{dp:?}");
}
}
}