use super::{
AP7_ROT_RADS, CoordIJK, EPSILON, INV_SQRT7_POWERS, RES0_U_GNOMONIC,
to_positive_angle,
};
use crate::{
Face, LatLng, face,
math::{abs, atan, atan2, hypot, mul_add},
resolution::ExtendedResolution,
};
use float_eq::float_eq;
const RSIN60: f64 = 1.1547005383792515;
const ONE_THIRD: f64 = 0.3333333333333333;
#[derive(Debug, Clone, Copy)]
pub struct Vec2d {
pub x: f64,
pub y: f64,
}
impl PartialEq for Vec2d {
fn eq(&self, other: &Self) -> bool {
float_eq!(self.x, other.x, abs <= f64::from(f32::EPSILON))
&& float_eq!(self.y, other.y, abs <= f64::from(f32::EPSILON))
}
}
impl Eq for Vec2d {}
impl Vec2d {
pub const fn new(x: f64, y: f64) -> Self {
Self { x, y }
}
pub fn magnitude(self) -> f64 {
hypot(self.x, self.y)
}
pub fn intersection(line1: (Self, Self), line2: (Self, Self)) -> Self {
let s1 = Self {
x: line1.1.x - line1.0.x,
y: line1.1.y - line1.0.y,
};
let s2 = Self {
x: line2.1.x - line2.0.x,
y: line2.1.y - line2.0.y,
};
let t = mul_add(
s2.x,
line1.0.y - line2.0.y,
-s2.y * (line1.0.x - line2.0.x),
) / mul_add(-s2.x, s1.y, s1.x * s2.y);
Self {
x: mul_add(t, s1.x, line1.0.x),
y: mul_add(t, s1.y, line1.0.y),
}
}
pub fn to_latlng(
self,
face: Face,
resolution: ExtendedResolution,
is_substrate: bool,
) -> LatLng {
let face = usize::from(face);
let r = {
let mut r = self.magnitude();
if r < EPSILON {
return face::CENTER_GEO[face];
}
r *= INV_SQRT7_POWERS[usize::from(resolution)];
if is_substrate {
r *= ONE_THIRD;
debug_assert!(!resolution.is_class3());
}
atan(r * RES0_U_GNOMONIC)
};
let theta = {
let mut theta = atan2(self.y, self.x);
if !is_substrate && resolution.is_class3() {
theta = to_positive_angle(theta + AP7_ROT_RADS);
}
to_positive_angle(face::AXES_AZ_RADS_CII[face][0] - theta)
};
face::CENTER_GEO[face].coord_at(theta, r)
}
}
impl From<Vec2d> for CoordIJK {
fn from(value: Vec2d) -> Self {
let k = 0;
let a1 = abs(value.x);
let a2 = abs(value.y);
let x2 = a2 * RSIN60;
let x1 = a1 + x2 / 2.;
#[expect(clippy::cast_possible_truncation, reason = "on purpose")]
let m1 = x1 as i32;
#[expect(clippy::cast_possible_truncation, reason = "on purpose")]
let m2 = x2 as i32;
let r1 = x1 - f64::from(m1);
let r2 = x2 - f64::from(m2);
let (mut i, mut j) = if r1 < 0.5 {
if r1 < 1. / 3. {
let i = m1;
let j = m2 + i32::from(r2 >= f64::midpoint(1., r1));
(i, j)
} else {
let i = m1 + i32::from((1. - r1) <= r2 && r2 < (2. * r1));
let j = m2 + i32::from(r2 >= (1. - r1));
(i, j)
}
} else if r1 < 2. / 3. {
let j = m2 + i32::from(r2 >= (1. - r1));
let i =
m1 + i32::from(mul_add(2.0, r1, -1.) >= r2 || r2 >= (1. - r1));
(i, j)
} else {
let i = m1 + 1;
let j = m2 + i32::from(r2 >= (r1 / 2.));
(i, j)
};
if value.x < 0. {
let offset = j % 2;
let axis_i = i32::midpoint(j, offset);
let diff = i - axis_i;
i -= 2 * diff + offset;
}
if value.y < 0. {
i -= (2 * j + 1) / 2;
j = -j;
}
Self::new(i, j, k).normalize()
}
}
#[cfg(test)]
#[path = "./vec2d_tests.rs"]
mod tests;