use super::source::WorldPos;
#[derive(Clone, Copy, Debug, Default, PartialEq)]
pub struct Split {
pub high: f32,
pub low: f32,
}
#[inline]
#[must_use]
pub fn split(v: f64) -> Split {
let high = v as f32;
let low = (v - high as f64) as f32;
Split { high, low }
}
#[inline]
#[must_use]
pub fn join(s: Split) -> f64 {
s.high as f64 + s.low as f64
}
#[derive(Clone, Copy, Debug, Default, PartialEq)]
pub struct RtePos {
pub high: [f32; 3],
pub low: [f32; 3],
}
impl RtePos {
#[must_use]
pub fn of(p: WorldPos) -> Self {
let (sx, sy, sz) = (split(p.x), split(p.y), split(p.z));
Self { high: [sx.high, sy.high, sz.high], low: [sx.low, sy.low, sz.low] }
}
#[must_use]
pub fn relative_to(self, eye: RtePos) -> [f32; 3] {
[
(self.high[0] - eye.high[0]) + (self.low[0] - eye.low[0]),
(self.high[1] - eye.high[1]) + (self.low[1] - eye.low[1]),
(self.high[2] - eye.high[2]) + (self.low[2] - eye.low[2]),
]
}
}
#[must_use]
pub fn naive_relative(p: WorldPos, eye: WorldPos) -> [f32; 3] {
[(p.x as f32) - (eye.x as f32), (p.y as f32) - (eye.y as f32), (p.z as f32) - (eye.z as f32)]
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn split_join_round_trips_to_f64_precision() {
for v in [0.0_f64, 1.0, -1.0, 0.123_456_789_012_345, 1e12, -3.75e-9] {
let back = join(split(v));
let err = (back - v).abs();
assert!(err <= v.abs() * 1e-14 + 1e-300, "v={v} back={back} err={err}");
}
}
#[test]
fn rte_beats_naive_f32_at_mercator_zoom_22() {
let eye = WorldPos::flat(0.554_321_098_765_432_1, 0.321_098_765_432_109_8);
let step = 1e-9_f64;
let p = WorldPos::flat(eye.x + step, eye.y);
let rte = RtePos::of(p).relative_to(RtePos::of(eye));
let naive = naive_relative(p, eye);
let rte_err = (rte[0] as f64 - step).abs();
let naive_err = (naive[0] as f64 - step).abs();
assert_eq!(naive[0], 0.0, "naive f32 was expected to lose the step entirely");
assert!(rte_err < step * 1e-3, "RTE error {rte_err} should be <0.1% of the {step} step");
assert!(
naive_err > rte_err * 1000.0,
"RTE must be >1000x better: rte_err={rte_err} naive_err={naive_err}"
);
}
#[test]
fn rte_is_exact_at_the_eye() {
let eye = WorldPos::new(1e9, -2e9, 3.5);
let r = RtePos::of(eye).relative_to(RtePos::of(eye));
assert_eq!(r, [0.0, 0.0, 0.0]);
}
}