use crate::sph_geom::coo3d::*;
use crate::{LonLat, LonLatT};
use crate::{ONE_OVER_TRANSITION_Z, TRANSITION_Z};
use std::f64::consts::{FRAC_PI_2, FRAC_PI_4, FRAC_PI_8, PI};
pub fn arc_special_points<'a>(
mut p1: &'a Coo3D,
mut p2: &'a Coo3D,
z_eps_max: f64,
n_iter_max: u8,
) -> Box<[LonLat]> {
if p1.z() > p2.z() {
std::mem::swap(&mut p1, &mut p2);
}
if TRANSITION_Z <= p1.z() || p2.z() <= -TRANSITION_Z {
match arc_special_point_in_pc(p1, p2, z_eps_max, n_iter_max) {
Some(lonlat) => Box::new([lonlat; 1]),
None => Vec::new().into_boxed_slice(),
}
} else if -TRANSITION_Z <= p1.z() && p2.z() <= TRANSITION_Z {
match arc_special_point_in_eqr(p1, p2, z_eps_max, n_iter_max) {
Some(lonlat) => Box::new([lonlat; 1]),
None => Vec::new().into_boxed_slice(),
}
} else if p1.z() < -TRANSITION_Z {
let mut res: Vec<LonLat> = Vec::with_capacity(3);
let v_eqr_south = Coo3D::from(intersect_with_transition_lat_spc(p1, p2).unwrap());
if let Some(lonlat) = arc_special_point_in_pc(p1, &v_eqr_south, z_eps_max, n_iter_max) {
res.push(lonlat);
}
if p2.z() <= TRANSITION_Z {
if let Some(lonlat) = arc_special_point_in_eqr(&v_eqr_south, p2, z_eps_max, n_iter_max)
{
res.push(lonlat);
}
} else {
let v_eqr_north = Coo3D::from(intersect_with_transition_lat_npc(p1, p2).unwrap());
if let Some(lonlat) =
arc_special_point_in_eqr(&v_eqr_south, &v_eqr_north, z_eps_max, n_iter_max)
{
res.push(lonlat);
}
if let Some(lonlat) = arc_special_point_in_pc(&v_eqr_north, p2, z_eps_max, n_iter_max) {
res.push(lonlat);
}
}
res.into_boxed_slice()
} else {
let mut res: Vec<LonLat> = Vec::with_capacity(2);
let v_eqr_north = Coo3D::from(intersect_with_transition_lat_npc(p1, p2).unwrap());
if let Some(lonlat) = arc_special_point_in_eqr(p1, &v_eqr_north, z_eps_max, n_iter_max) {
res.push(lonlat);
}
if let Some(lonlat) = arc_special_point_in_pc(&v_eqr_north, p2, z_eps_max, n_iter_max) {
res.push(lonlat);
}
res.into_boxed_slice()
}
}
#[allow(dead_code)]
pub fn cone_special_point_in_eqr(
mut z: f64,
z0: f64,
eucl_cone_radius: f64,
north_point: bool,
z_eps_max: f64,
n_iter_max: u8,
) -> Option<f64> {
let cte = if north_point {
-ONE_OVER_TRANSITION_Z * FRAC_PI_4
} else {
ONE_OVER_TRANSITION_Z * FRAC_PI_4
};
let w0 = 1.0 - z0 * z0;
let r = 1.0 - eucl_cone_radius * eucl_cone_radius * 0.5;
let z_eps_max = z_eps_max.min(0.2e-1 * eucl_cone_radius).max(1.0e-15);
let mut z_eps = 1.0_f64;
let mut n_iter = 0_u8;
while n_iter < n_iter_max && z_eps.abs() > z_eps_max {
z_eps = f_over_df_eqr(z, z0, w0, cte, r);
z -= z_eps;
n_iter += 1;
}
debug_assert!(z.is_finite());
if z.abs() < TRANSITION_Z {
debug_assert!((north_point && z >= z0) || (!north_point && z <= z0));
Some(z)
} else {
None
}
}
pub fn arc_special_point_in_eqr(
p1: &Coo3D,
p2: &Coo3D,
z_eps_max: f64,
n_iter_max: u8,
) -> Option<LonLat> {
let cone_center = cross_product(p1, p2).normalized();
let z0 = cone_center.z();
let z1 = p1.z();
let z2 = p2.z();
let north_point = z0 < 0.0;
let cte = if north_point {
-ONE_OVER_TRANSITION_Z * FRAC_PI_4
} else {
ONE_OVER_TRANSITION_Z * FRAC_PI_4
};
let w0 = 1.0 - z0 * z0;
let d1 = f_eqr(z1, z0, w0, cte, 0.0);
let d2 = f_eqr(z2, z0, w0, cte, 0.0);
if have_same_sign(d1, d2) {
return None;
}
let z_eps_max = z_eps_max.min(0.2e-1 * (z2 - z1).abs()).max(1.0e-15);
let mut z = (z1 + z2) * 0.5; let mut z_eps = 1.0_f64;
let mut n_iter = 0_u8;
while n_iter < n_iter_max && z_eps.abs() > z_eps_max {
z_eps = f_over_df_eqr(z, z0, w0, cte, 0.0);
z -= z_eps;
n_iter += 1;
}
if z.abs() < TRANSITION_Z
&& ((z1 <= z2 && z1 <= z && z <= z2) || (z2 < z1 && z2 <= z && z <= z1))
{
intersect_parallel(p1, p2, z).map(|v| v.lonlat())
} else {
None
}
}
#[inline]
#[allow(clippy::many_single_char_names)]
fn f_eqr(z: f64, z0: f64, w0: f64, cte: f64, r: f64) -> f64 {
let w = 1.0 - z * z; let q = z / w; let n = r - z * z0;
(z0 - q * n) / (w0 * w - n * n).sqrt() - cte
}
#[inline]
#[allow(clippy::many_single_char_names)]
fn f_over_df_eqr(z: f64, z0: f64, w0: f64, cte: f64, r: f64) -> f64 {
let w = 1.0 - z * z;
let q = z / w;
let n = r - z * z0;
let sqrt_d2_minus_n2 = (w0 * w - n * n).sqrt();
let qn = q * n;
let dalphadz = (z0 - qn) / sqrt_d2_minus_n2;
let f = dalphadz - cte;
let df = (q * (z0 * 2.0 - 3.0 * qn) - n * (1.0 / w + dalphadz * dalphadz)) / sqrt_d2_minus_n2;
f / df
}
#[allow(dead_code)]
#[allow(clippy::too_many_arguments)]
pub fn cone_special_point_in_pc(
mut z: f64,
cone_center_lon_mod_half_pi: f64,
mut z0: f64,
eucl_cone_radius: f64,
east_value: bool,
mut north_value: bool,
z_eps_max: f64,
n_iter_max: u8,
) -> Option<f64> {
let spc = z < 0.0; if spc {
z = -z;
z0 = -z0;
north_value = !north_value;
}
let cte = if north_value { -FRAC_PI_8 } else { FRAC_PI_8 };
let w0 = 1.0 - z0 * z0;
let r = 1.0 - eucl_cone_radius * eucl_cone_radius * 0.5;
let direction = if east_value { 1.0 } else { -1.0 };
let z_eps_max = z_eps_max.min(0.2e-1 * eucl_cone_radius).max(1.0e-15);
let mut n_iter = 0_u8;
let mut z_eps = 1.0_f64;
while n_iter < n_iter_max && z_eps.abs() > z_eps_max {
z_eps = f_over_df_npc(z, cone_center_lon_mod_half_pi, z0, w0, cte, direction, r);
z -= z_eps;
n_iter += 1;
}
if z.is_finite() && z > TRANSITION_Z {
Some(if spc { -z } else { z })
} else {
None
}
}
pub fn arc_special_point_in_pc<'a>(
mut p1: &'a Coo3D,
mut p2: &'a Coo3D,
z_eps_max: f64,
n_iter_max: u8,
) -> Option<LonLat> {
if p1.lon() > p2.lon() {
std::mem::swap(&mut p1, &mut p2);
}
debug_assert!(p1.lon() % FRAC_PI_2 >= 0.0);
debug_assert!(p2.lon() % FRAC_PI_2 >= 0.0);
let lon1_div_half_pi = p1.lon().div_euclid(FRAC_PI_2) as u8;
let lon2_div_half_pi = p2.lon().div_euclid(FRAC_PI_2) as u8;
debug_assert!(lon1_div_half_pi < 4);
debug_assert!(lon2_div_half_pi < 4);
debug_assert!(lon1_div_half_pi <= lon2_div_half_pi);
if lon1_div_half_pi != lon2_div_half_pi {
let mut res_z1 = None;
let mut res_z2 = None;
if p2.lon() - p1.lon() > PI {
let p2p1_n = cross_product(p2, p1).normalized();
if p2.lon() % FRAC_PI_2 > 0.0 {
debug_assert!(lon2_div_half_pi > 0);
let n2_y = lon2_div_half_pi & 1;
let n2 = Coo3D::from_vec3((n2_y ^ 1) as f64, n2_y as f64, 0.0);
let intersect2 = Coo3D::from(intersect_point_pc(p1, p2, &p2p1_n, &n2));
debug_assert!(
p2.lon() < intersect2.lon() || intersect2.lon() < p1.lon(),
"p1.lon: {}, p2.lon: {}, intersect.lon: {}",
p1.lon(),
p2.lon(),
intersect2.lon()
);
if p2.lon() < intersect2.lon() {
res_z2 = arc_special_point_in_pc_same_quarter(
p2,
&intersect2,
z_eps_max,
n_iter_max,
);
}
}
debug_assert!(lon1_div_half_pi < 3);
let n1_x = lon1_div_half_pi & 1;
let n1 = Coo3D::from_vec3(n1_x as f64, (n1_x ^ 1) as f64, 0.0);
let intersect1 = Coo3D::from(intersect_point_pc(p1, p2, &p2p1_n, &n1));
debug_assert!(
intersect1.lon() < p1.lon(),
"p1: ({}, {}); p2: ({}, {}); intersect: ({}, {}); n; ({}, {})",
p1.lon().to_degrees(),
p1.lat().to_degrees(),
p2.lon().to_degrees(),
p2.lat().to_degrees(),
intersect1.lon().to_degrees(),
intersect1.lat().to_degrees(),
n1.lon().to_degrees(),
n1.lat().to_degrees()
);
res_z1 = arc_special_point_in_pc_same_quarter(&intersect1, p1, z_eps_max, n_iter_max);
} else {
let p1p2_n = cross_product(p1, p2).normalized();
if p1.lon() % FRAC_PI_2 > 0.0 {
debug_assert!(lon1_div_half_pi < 3);
let n1_y = lon1_div_half_pi & 1;
let n1 = Coo3D::from_vec3((n1_y ^ 1) as f64, n1_y as f64, 0.0);
let intersect1 = Coo3D::from(intersect_point_pc(p1, p2, &p1p2_n, &n1));
debug_assert!(p1.lon() < intersect1.lon());
res_z1 =
arc_special_point_in_pc_same_quarter(p1, &intersect1, z_eps_max, n_iter_max);
}
debug_assert!(lon2_div_half_pi > 0);
let n2_x = lon2_div_half_pi & 1;
let n2 = Coo3D::from_vec3(n2_x as f64, (n2_x ^ 1) as f64, 0.0);
let intersect2 = Coo3D::from(intersect_point_pc(p1, p2, &p1p2_n, &n2));
debug_assert!(
intersect2.lon() <= p2.lon(),
"Failed: {} < {}",
intersect2.lon(),
p2.lon()
);
if intersect2.lon() < p2.lon() {
res_z2 =
arc_special_point_in_pc_same_quarter(&intersect2, p2, z_eps_max, n_iter_max);
} else {
res_z2 = None
}
}
if res_z1.is_some() { res_z1 } else { res_z2 }
} else {
arc_special_point_in_pc_same_quarter(p1, p2, z_eps_max, n_iter_max)
}
}
fn arc_special_point_in_pc_same_quarter(
p1: &Coo3D,
p2: &Coo3D,
z_eps_max: f64,
n_iter_max: u8,
) -> Option<LonLat> {
debug_assert!(
p1.lon() < p2.lon(),
"p1: ({}, {}); p2: ({}, {})",
p1.lon().to_degrees(),
p1.lat().to_degrees(),
p2.lon().to_degrees(),
p2.lat().to_degrees()
);
let mut p2_mod_half_pi = p2.lon() % FRAC_PI_2;
if p2_mod_half_pi == 0.0 {
p2_mod_half_pi = FRAC_PI_2;
}
let v1 = Coo3D::from_sph_coo([p1.lon() % FRAC_PI_2, p1.lat()].as_lonlat());
let v2 = Coo3D::from_sph_coo([p2_mod_half_pi, p2.lat()].as_lonlat());
let mut cone_center = cross_product(v1, v2).normalized();
let lonlat = cone_center.lonlat();
if lonlat.lon() > PI {
cone_center = cone_center.opposite();
}
let cone_center_lon = cone_center.lonlat().lon();
let mut z0 = cone_center.z();
let mut z1 = v1.z();
let mut z2 = v2.z();
let mut north_value = z0 < 0.0;
let east_value = ((v1.lat() > v2.lat()) ^ (v1.lon() > v2.lon())) ^ !north_value;
let mut z = (z1 + z2) * 0.5; let spc = z < 0.0; if spc {
z = -z;
z1 = -z1;
z2 = -z2;
z0 = -z0;
north_value = !north_value;
}
let cte = if north_value { -FRAC_PI_8 } else { FRAC_PI_8 };
let w0 = 1.0 - z0 * z0;
let direction = if east_value { 1.0 } else { -1.0 };
let d1 = f_npc(z1, cone_center_lon, z0, w0, cte, direction, 0.0);
let d2 = f_npc(z2, cone_center_lon, z0, w0, cte, direction, 0.0);
if have_same_sign(d1, d2) {
return None;
}
let dz = f_over_df_npc(z, cone_center_lon, z0, w0, cte, direction, 0.0);
z -= dz;
if !((z1 < z && z < z2) || (z2 < z && z < z1)) {
z = z2 - f_over_df_npc(z2, cone_center_lon, z0, w0, cte, direction, 0.0);
if !((z1 < z && z < z2) || (z2 < z && z < z1)) {
z = z1 - f_over_df_npc(z1, cone_center_lon, z0, w0, cte, direction, 0.0);
}
}
let z_eps_max = z_eps_max.min(0.2e-1 * (z2 - z1).abs()).max(1.0e-15);
let mut n_iter = 0_u8;
let mut z_eps = 1.0_f64;
while n_iter < n_iter_max && z_eps.abs() > z_eps_max {
z_eps = f_over_df_npc(z, cone_center_lon, z0, w0, cte, direction, 0.0);
z -= z_eps;
n_iter += 1;
}
if z.is_finite() && z > TRANSITION_Z && ((z1 < z && z < z2) || (z2 < z && z < z1)) {
if spc {
let v = intersect_parallel(p1, p2, -z).unwrap();
Some(v.lonlat())
} else {
let v = intersect_parallel(p1, p2, z).unwrap();
Some(v.lonlat())
}
} else {
None
}
}
#[inline]
fn intersect_point_pc(p1: &Coo3D, p2: &Coo3D, p1_x_p2: &UnitVect3, n: &Coo3D) -> UnitVect3 {
debug_assert!(p1.z().abs() >= TRANSITION_Z && p2.z().abs() >= TRANSITION_Z);
debug_assert_eq!(p1.z() > 0.0, p2.z() > 0.0);
let intersect = cross_product(p1_x_p2, n).normalized();
if !have_same_sign(intersect.z(), p1.z()) {
intersect.opposite()
} else {
intersect
}
}
#[inline]
#[allow(clippy::many_single_char_names)]
fn f_npc(
z: f64,
cone_center_lon_mod_half_pi: f64,
z0: f64,
w0: f64,
cte: f64,
direction: f64,
r: f64,
) -> f64 {
let w = 1.0 - z;
let w2 = 1.0 - z * z;
let q = z / w2;
let n = r - z * z0;
let d2 = w0 * w2;
let sqrt_d2_minus_n2 = (d2 - n * n).sqrt();
let qn = q * n;
let arccos = (n / d2.sqrt()).acos();
let dalphadz = (z0 - qn) / sqrt_d2_minus_n2;
direction * w * dalphadz - 0.5 * (direction * arccos + cone_center_lon_mod_half_pi - FRAC_PI_4)
+ cte
}
#[inline]
#[allow(clippy::many_single_char_names)]
fn f_over_df_npc(
z: f64,
cone_center_lon_mod_half_pi: f64,
z0: f64,
w0: f64,
cte: f64,
direction: f64,
r: f64,
) -> f64 {
let w = 1.0 - z;
let w2 = 1.0 - z * z;
let q = z / w2;
let n = r - z * z0;
let d2 = w0 * w2;
let sqrt_d2_minus_n2 = (d2 - n * n).sqrt();
let qn = q * n;
let arccos = (n / d2.sqrt()).acos();
let dalphadz = (z0 - qn) / sqrt_d2_minus_n2;
let f = direction * w * dalphadz
- 0.5 * (direction * arccos + cone_center_lon_mod_half_pi - FRAC_PI_4)
+ cte;
let df = -ONE_OVER_TRANSITION_Z * direction * dalphadz
+ (direction * w / sqrt_d2_minus_n2)
* (q * (z0 * 2.0 - 3.0 * qn) - n * (1.0 / w2 + dalphadz * dalphadz));
f / df
}
pub fn intersect_parallel<T1, T2>(p1: &T1, p2: &T2, z: f64) -> Option<UnitVect3>
where
T1: UnitVec3,
T2: UnitVec3,
{
debug_assert!((-1.0..=1.0).contains(&z));
if (p1.z() < z && z < p2.z()) || (p2.z() < z && z < p1.z()) {
let p1_dot_p2 = dot_product(p1, p2);
let p1_x_p2 = cross_product(p1, p2);
let p1_x_p2_norm = p1_x_p2.norm();
let ang_dist = p1_x_p2_norm.atan2(p1_dot_p2);
let p1_x_p2 = p1_x_p2.normalized();
let x0 = p1_x_p2.x();
let y0 = p1_x_p2.y();
let z0 = p1_x_p2.z();
if y0.abs() <= 1e-14 {
let x = -(z * z0) / x0;
let y1 = (1.0 - (x * x + z * z)).sqrt();
let y2 = -y1;
if p1.x() * x + p1.y() * y1 + p1.z() * z >= p1_dot_p2
&& p2.x() * x + p2.y() * y1 + p2.z() * z >= p1_dot_p2
{
Some(UnitVect3::new_unsafe(x, y1, z))
} else if p1.x() * x + p1.y() * y2 + p1.z() * z >= p1_dot_p2
&& p2.x() * x + p2.y() * y2 + p2.z() * z >= p1_dot_p2
{
Some(UnitVect3::new_unsafe(x, y2, z))
} else {
unreachable!();
}
} else if ang_dist < (1_f64 / 3600.0).to_degrees() {
let t = (z - p1.z()) / (p2.z() - p1.z());
let x = (p2.x() - p1.x()) * t + p1.x();
let y = (p2.y() - p1.y()) * t + p1.y();
Some(UnitVect3::new(x, y, z))
} else {
let x0_y0 = x0 / y0;
let zz0_y0 = z * z0 / y0;
let a = 1.0 + x0_y0 * x0_y0;
let b = 2.0 * x0_y0 * zz0_y0;
let c = zz0_y0 * zz0_y0 + z * z - 1.0;
let delta = b * b - 4.0 * a * c;
let sqrt_delta = delta.sqrt();
let x1 = (-b + sqrt_delta) / a * 0.5;
let y1 = -x1 * x0_y0 - zz0_y0;
let x2 = (-b - sqrt_delta) / a * 0.5;
let y2 = -x2 * x0_y0 - zz0_y0;
if p1.x() * x1 + p1.y() * y1 + p1.z() * z >= p1_dot_p2
&& p2.x() * x1 + p2.y() * y1 + p2.z() * z >= p1_dot_p2
{
Some(UnitVect3::new_unsafe(x1, y1, z))
} else if p1.x() * x2 + p1.y() * y2 + p1.z() * z >= p1_dot_p2
&& p2.x() * x2 + p2.y() * y2 + p2.z() * z >= p1_dot_p2
{
Some(UnitVect3::new_unsafe(x2, y2, z))
} else {
unreachable!();
}
}
} else {
None
}
}
#[inline]
fn have_same_sign(d1: f64, d2: f64) -> bool {
d1 == 0.0 || d2 == 0.0 || ((d1 > 0.0) == (d2 > 0.0))
}
#[inline]
fn intersect_with_transition_lat_npc<T1, T2>(p1: &T1, p2: &T2) -> Option<UnitVect3>
where
T1: UnitVec3,
T2: UnitVec3,
{
intersect_parallel(p1, p2, TRANSITION_Z)
}
#[inline]
fn intersect_with_transition_lat_spc<T1, T2>(p1: &T1, p2: &T2) -> Option<UnitVect3>
where
T1: UnitVec3,
T2: UnitVec3,
{
intersect_parallel(p1, p2, -TRANSITION_Z)
}