use a5::LonLat;
use bevy_math::{DVec3, Vec3};
pub fn lonlat_to_dvec3(ll: &LonLat, radius: f64) -> DVec3 {
let lat = ll.latitude().to_radians();
let lng = ll.longitude().to_radians();
DVec3::new(
radius * lat.cos() * lng.cos(),
radius * lat.sin(),
radius * lat.cos() * lng.sin(),
)
}
pub fn lonlat_to_vec3(ll: &LonLat, radius: f64) -> Vec3 {
lonlat_to_dvec3(ll, radius).as_vec3()
}
pub fn dvec3_to_lonlat(pos: DVec3) -> Option<LonLat> {
let r = pos.length();
if r < 1e-12 {
return None;
}
let lat = (pos.y / r).asin().to_degrees();
let lng = pos.z.atan2(pos.x).to_degrees();
Some(LonLat::new(lng, lat))
}
pub fn tangent_frame(ll: &LonLat) -> (DVec3, DVec3, DVec3) {
let lat = ll.latitude().to_radians();
let lng = ll.longitude().to_radians();
let up = DVec3::new(lat.cos() * lng.cos(), lat.sin(), lat.cos() * lng.sin());
let north = DVec3::new(-lat.sin() * lng.cos(), lat.cos(), -lat.sin() * lng.sin()).normalize();
let east = DVec3::new(-lng.sin(), 0.0, lng.cos()).normalize();
(east, up, north)
}
pub fn cell_to_dvec3(cell: u64, radius: f64) -> Option<DVec3> {
let ll = a5::cell_to_lonlat(cell).ok()?;
Some(lonlat_to_dvec3(&ll, radius))
}
pub fn cell_to_vec3(cell: u64, radius: f64) -> Option<Vec3> {
cell_to_dvec3(cell, radius).map(|v| v.as_vec3())
}
pub fn world_pos_to_lonlat(pos: DVec3) -> Option<LonLat> {
dvec3_to_lonlat(pos)
}
pub fn world_pos_to_cell(pos: DVec3, resolution: i32) -> Option<u64> {
let ll = dvec3_to_lonlat(pos)?;
a5::lonlat_to_cell(ll, resolution).ok()
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn roundtrip_lonlat_to_dvec3() {
let ll = LonLat::new(90.0, 45.0);
let pos = lonlat_to_dvec3(&ll, 6371.0);
let ll2 = dvec3_to_lonlat(pos).unwrap();
assert!((ll.latitude() - ll2.latitude()).abs() < 1e-10);
assert!((ll.longitude() - ll2.longitude()).abs() < 1e-10);
}
#[test]
fn north_pole_up() {
let ll = LonLat::new(0.0, 90.0);
let (_, up, _) = tangent_frame(&ll);
assert!((up.y - 1.0).abs() < 1e-10);
assert!(up.x.abs() < 1e-10);
assert!(up.z.abs() < 1e-10);
}
}