use std::f64::consts::{PI, FRAC_PI_2, FRAC_PI_4, FRAC_PI_8};
use super::{Customf64, TRANSITION_Z, ONE_OVER_TRANSITION_Z};
use super::sph_geom::coo3d::*;
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.pow2();
let r = 1.0 - eucl_cone_radius.pow2().half();
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.pow2();
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).half(); 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_small_circle(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.pow2(); let q = z / w; let n = r - z * z0;
(z0 - q * n) / (w0 * w - n.pow2()).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.pow2();
let q = z / w;
let n = r - z * z0;
let sqrt_d2_minus_n2 = (w0 * w - n.pow2()).sqrt();
let qn = q * n;
let dalphadz = (z0 - qn) / sqrt_d2_minus_n2;
let f = dalphadz - cte;
let df = (q * (z0.twice() - 3.0 * qn) - n * (1.0 / w + dalphadz.pow2())) / 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.pow2();
let r = 1.0 - eucl_cone_radius.pow2().half();
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_eucl(FRAC_PI_2) as u8;
let lon2_div_half_pi = p2.lon().div_eucl(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());
res_z2 = arc_special_point_in_pc_same_quarter(&intersect2, p2, z_eps_max, n_iter_max);
}
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());
let v2 = Coo3D::from_sph_coo(p2_mod_half_pi, p2.lat());
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).half(); 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.pow2();
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_small_circle(p1, p2, -z).unwrap();
Some(v.lonlat())
} else {
let v = intersect_small_circle(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.pow2();
let q = z / w2;
let n = r - z * z0;
let d2 = w0 * w2;
let sqrt_d2_minus_n2 = (d2 - n.pow2()).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.pow2();
let q = z / w2;
let n = r - z * z0;
let d2 = w0 * w2;
let sqrt_d2_minus_n2 = (d2 - n.pow2()).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.twice() - 3.0 * qn) - n * (1.0 / w2 + dalphadz.pow2()));
f / df
}
pub fn intersect_small_circle<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).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.pow2() + z.pow2())).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 {
let x0_y0 = x0 / y0;
let zz0_y0 = z * z0 / y0;
let a = 1.0 + x0_y0.pow2();
let b = 2.0 * x0_y0 * zz0_y0;
let c = zz0_y0.pow2() + z.pow2() - 1.0;
let sqrt_delta = (b.pow2() - 4.0 * a * c).sqrt();
let x1 = (-b + sqrt_delta) / a.twice();
let y1 = -x1 * x0_y0 - zz0_y0;
let x2 = (-b - sqrt_delta) / a.twice();
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_small_circle(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_small_circle(p1, p2, -TRANSITION_Z)
}