mirror of
https://github.com/cds-astro/aladin-lite.git
synced 2025-12-12 15:49:18 -08:00
enhance grid wip
This commit is contained in:
@@ -9,4 +9,5 @@ pub type XYNDC = Vector2<f64>;
|
||||
pub type XYClip = Vector2<f64>;
|
||||
pub type XYZWorld = Vector3<f64>;
|
||||
pub type XYZWWorld = Vector4<f64>;
|
||||
pub type XYZWModel = Vector4<f64>;
|
||||
pub type XYZWModel = Vector4<f64>;
|
||||
pub type XYZModel = Vector3<f64>;
|
||||
@@ -1,2 +1,60 @@
|
||||
pub mod bbox;
|
||||
pub mod region;
|
||||
|
||||
use super::{PI, TWICE_PI};
|
||||
|
||||
#[inline]
|
||||
pub fn is_in_lon_range(lon0: f64, lon1: f64, lon2: f64) -> bool {
|
||||
// First version of the code:
|
||||
// ((v2.lon() - v1.lon()).abs() > PI) != ((v2.lon() > coo.lon()) != (v1.lon() > coo.lon()))
|
||||
//
|
||||
// Lets note
|
||||
// - lonA = v1.lon()
|
||||
// - lonB = v2.lon()
|
||||
// - lon0 = coo.lon()
|
||||
// When (lonB - lonA).abs() <= PI
|
||||
// => lonB > lon0 != lonA > lon0 like in PNPOLY
|
||||
// A B lonA <= lon0 && lon0 < lonB
|
||||
// --[++++[--
|
||||
// B A lonB <= lon0 && lon0 < lonA
|
||||
//
|
||||
// But when (lonB - lonA).abs() > PI, then the test should be
|
||||
// => lonA >= lon0 == lonB >= lon0
|
||||
// <=> !(lonA >= lon0 != lonB >= lon0)
|
||||
// A | B (lon0 < lonB) || (lonA <= lon0)
|
||||
// --[++|++[--
|
||||
// B | A (lon0 < lonA) || (lonB <= lon0)
|
||||
//
|
||||
// Instead of lonA > lon0 == lonB > lon0,
|
||||
// i.e. !(lonA > lon0 != lonB > lon0).
|
||||
// A | B (lon0 <= lonB) || (lonA < lon0)
|
||||
// --]++|++]--
|
||||
// B | A (lon0 <= lonA) || (lonB < lon0)
|
||||
//
|
||||
// So the previous code was bugged in this very specific case:
|
||||
// - `lon0` has the same value as a vertex being part of:
|
||||
// - one segment that do not cross RA=0
|
||||
// - plus one segment crossing RA=0.
|
||||
// - the point have an odd number of intersections with the polygon
|
||||
// (since it will be counted 0 or 2 times instead of 1).
|
||||
let dlon = lon2 - lon1;
|
||||
if dlon < 0.0 {
|
||||
(dlon >= -PI) == (lon2 <= lon0 && lon0 < lon1)
|
||||
} else {
|
||||
(dlon <= PI) == (lon1 <= lon0 && lon0 < lon2)
|
||||
}
|
||||
}
|
||||
|
||||
// Returns the longitude length between two longitudes
|
||||
// lon1 lies in [0; 2\pi[
|
||||
// lon2 lies in [0; 2\pi[
|
||||
#[inline]
|
||||
pub fn distance_from_two_lon(lon1: f64, lon2: f64) -> f64 {
|
||||
// Cross the primary meridian
|
||||
if lon1 > lon2 {
|
||||
lon2 + TWICE_PI - lon1
|
||||
} else {
|
||||
lon2 - lon1
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -6,6 +6,7 @@ use cdshealpix::sph_geom::coo3d::Coo3D;
|
||||
use cdshealpix::sph_geom::coo3d::Vec3;
|
||||
use cdshealpix::sph_geom::ContainsSouthPoleMethod;
|
||||
use crate::math::angle::ToAngle;
|
||||
use al_core::{inforec, info, log};
|
||||
|
||||
pub enum Region {
|
||||
AllSky,
|
||||
@@ -30,6 +31,7 @@ pub enum PoleContained {
|
||||
}
|
||||
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum Intersection {
|
||||
// The segment is fully included into the region
|
||||
Included,
|
||||
@@ -87,13 +89,19 @@ impl Region {
|
||||
}
|
||||
|
||||
pub fn intersects_parallel(&self, lat: f64) -> Intersection {
|
||||
use crate::math::lonlat::LonLat;
|
||||
match self {
|
||||
// The polygon is included inside the region
|
||||
Region::AllSky => Intersection::Included,
|
||||
Region::Polygon { polygon, .. } => {
|
||||
let vertices = polygon.intersect_parallel(lat)
|
||||
.iter()
|
||||
.map(|v| XYZWModel::new(v.y(), v.z(), v.x(), 1.0))
|
||||
.map(|v| {
|
||||
let v = XYZWModel::new(v.y(), v.z(), v.x(), 1.0);
|
||||
let l = v.lonlat();
|
||||
al_core::info!(l);
|
||||
v
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
if !vertices.is_empty() {
|
||||
@@ -134,8 +142,8 @@ impl Region {
|
||||
}
|
||||
|
||||
pub fn intersects_meridian(&self, lon: f64) -> Intersection {
|
||||
let n_pole_lonlat = LonLatT::new(HALF_PI.to_angle(), lon.to_angle());
|
||||
let s_pole_lonlat = LonLatT::new(MINUS_HALF_PI.to_angle(), lon.to_angle());
|
||||
let n_pole_lonlat = LonLatT::new(lon.to_angle(), (HALF_PI - 1e-4).to_angle());
|
||||
let s_pole_lonlat = LonLatT::new(lon.to_angle(), (MINUS_HALF_PI + 1e-4).to_angle());
|
||||
|
||||
self.intersects_great_circle_arc(&s_pole_lonlat, &n_pole_lonlat)
|
||||
}
|
||||
|
||||
@@ -1,9 +1,13 @@
|
||||
use crate::math::MINUS_HALF_PI;
|
||||
use crate::math::lonlat::LonLat;
|
||||
use crate::math::projection::coo_space::XYNDC;
|
||||
use crate::math::projection::coo_space::XYScreen;
|
||||
use crate::math::projection::coo_space::XYZWModel;
|
||||
use crate::math::sph_geom::region::Intersection;
|
||||
use cdshealpix::nested::center;
|
||||
use web_sys::WebGl2RenderingContext;
|
||||
use cdshealpix::sph_geom::coo3d::{Coo3D};
|
||||
use crate::renderable::line;
|
||||
|
||||
use al_core::{log, inforec, info};
|
||||
|
||||
@@ -52,7 +56,6 @@ use wasm_bindgen::JsValue;
|
||||
use super::labels::RenderManager;
|
||||
|
||||
use super::TextRenderManager;
|
||||
use super::line;
|
||||
|
||||
use al_api::resources::Resources;
|
||||
impl ProjetedGrid {
|
||||
@@ -184,7 +187,7 @@ impl ProjetedGrid {
|
||||
fn force_update(&mut self, camera: &CameraViewPort, projection: &ProjectionType) {
|
||||
self.text_renderer.begin_frame();
|
||||
//let text_height = text_renderer.text_size();
|
||||
let lines = lines(camera, &self.text_renderer, projection, &self.fmt);
|
||||
let lines = lines(camera, projection, &self.fmt);
|
||||
|
||||
self.offsets.clear();
|
||||
self.sizes.clear();
|
||||
@@ -217,7 +220,7 @@ impl ProjetedGrid {
|
||||
.flatten()
|
||||
.flat_map(|v| [v.x as f32, v.y as f32])
|
||||
.collect::<Vec<_>>();
|
||||
//self.lines = lines;
|
||||
|
||||
self.num_vertices = vertices.len() >> 1;
|
||||
|
||||
#[cfg(feature = "webgl2")]
|
||||
@@ -296,6 +299,7 @@ impl ProjetedGrid {
|
||||
|
||||
use crate::shader::ShaderId;
|
||||
|
||||
use core::num;
|
||||
use std::borrow::Cow;
|
||||
use std::path::is_separator;
|
||||
|
||||
@@ -309,34 +313,21 @@ use core::ops::Range;
|
||||
|
||||
#[derive(Debug)]
|
||||
struct Label {
|
||||
position: Vector2<f64>,
|
||||
// The position
|
||||
position: XYScreen,
|
||||
// the string content
|
||||
content: String,
|
||||
// in radians
|
||||
rot: f64,
|
||||
}
|
||||
impl Label {
|
||||
fn meridian(
|
||||
fov: &FieldOfView,
|
||||
mut lon: f64,
|
||||
m1: &Vector3<f64>,
|
||||
fn from_meridian(
|
||||
lonlat: &LonLatT<f64>,
|
||||
camera: &CameraViewPort,
|
||||
sp: Option<&Vector2<f64>>,
|
||||
text_renderer: &TextRenderManager,
|
||||
projection: &ProjectionType,
|
||||
fmt: &angle::SerializeFmt
|
||||
) -> Option<Self> {
|
||||
let lat = camera.get_center().lonlat().lat();
|
||||
// Do not plot meridian labels when the center of fov
|
||||
// is above 80deg
|
||||
if fov.is_allsky() {
|
||||
// If allsky label plotting mode
|
||||
// check if we are not too near of a pole
|
||||
// If so, do not plot the meridian labels because
|
||||
// they can overlap
|
||||
if lat.abs() > ArcDeg(80.0) {
|
||||
return None;
|
||||
}
|
||||
}
|
||||
|
||||
let fov = camera.get_field_of_view();
|
||||
let d = if fov.contains_north_pole() {
|
||||
Vector3::new(0.0, 1.0, 0.0)
|
||||
} else if fov.contains_south_pole() {
|
||||
@@ -345,23 +336,26 @@ impl Label {
|
||||
Vector3::new(0.0, 1.0, 0.0)
|
||||
};
|
||||
|
||||
let m2 = ((m1 + d * 1e-3).normalize()).extend(1.0);
|
||||
let m1: Vector3<_> = lonlat.vector();
|
||||
let m2 = (m1 + d * 1e-3).normalize();
|
||||
|
||||
//let s1 = projection.model_to_screen_space(&(system.to_icrs_j2000::<f64>() * m1), camera, reversed_longitude)?;
|
||||
let s1 = projection.model_to_screen_space(&m1.extend(1.0), camera)?;
|
||||
let s2 = projection.model_to_screen_space(&m2, camera)?;
|
||||
let s2 = projection.model_to_screen_space(&m2.extend(1.0), camera)?;
|
||||
|
||||
//let s2 = projection.model_to_screen_space(&(system.to_icrs_j2000::<f64>() * m2), camera, reversed_longitude)?;
|
||||
|
||||
let ds = (s2 - s1).normalize();
|
||||
|
||||
let mut lon = m1.lon().to_radians();
|
||||
if lon < 0.0 {
|
||||
lon += TWICE_PI;
|
||||
}
|
||||
let content = fmt.to_string(Angle(lon));
|
||||
|
||||
let content = fmt.to_string(lon.to_angle());
|
||||
let position = if !fov.is_allsky() {
|
||||
//let dim = ctx2d.measure_text(&content).unwrap_abort();
|
||||
let dim = text_renderer.get_width_pixel_size(&content);
|
||||
let dim = 100.0;
|
||||
let k = ds * (dim * 0.5 + 10.0);
|
||||
|
||||
s1 + k
|
||||
@@ -369,8 +363,6 @@ impl Label {
|
||||
s1
|
||||
};
|
||||
|
||||
//position += dv * text_height * 0.5;
|
||||
|
||||
// rot is between -PI and +PI
|
||||
let rot = if ds.y > 0.0 {
|
||||
ds.x.acos()
|
||||
@@ -396,44 +388,41 @@ impl Label {
|
||||
})
|
||||
}
|
||||
|
||||
fn parallel(
|
||||
fov: &FieldOfView,
|
||||
lat: f64,
|
||||
m1: &Vector3<f64>,
|
||||
fn from_parallel(
|
||||
lonlat: &LonLatT<f64>,
|
||||
camera: &CameraViewPort,
|
||||
// in pixels
|
||||
text_renderer: &TextRenderManager,
|
||||
projection: &ProjectionType,
|
||||
) -> Option<Self> {
|
||||
let mut d = Vector3::new(-m1.z, 0.0, m1.x).normalize();
|
||||
let _system = camera.get_system();
|
||||
let center = camera.get_center().truncate();
|
||||
//let center = (system.to_gal::<f64>() * camera.get_center()).truncate();
|
||||
if center.dot(d) < 0.0 {
|
||||
d = -d;
|
||||
}
|
||||
let m2 = (m1 + d * 1e-3).normalize();
|
||||
let m1: Vector3<_> = lonlat.vector();
|
||||
|
||||
let s1 =
|
||||
//projection.model_to_screen_space(&(system.to_icrs_j2000::<f64>() * m1.extend(1.0)), camera, reversed_longitude)?;
|
||||
projection.model_to_screen_space(&m1.extend(1.0), camera)?;
|
||||
let s2 =
|
||||
//projection.model_to_screen_space(&(system.to_icrs_j2000::<f64>() * m2.extend(1.0)), camera, reversed_longitude)?;
|
||||
projection.model_to_screen_space(&m2.extend(1.0), camera)?;
|
||||
let mut t = Vector3::new(-m1.z, 0.0, m1.x).normalize();
|
||||
let center = camera.get_center().truncate();
|
||||
|
||||
let dot_t_center = center.dot(t);
|
||||
if dot_t_center.abs() < 1e-4 {
|
||||
t = -t;
|
||||
} else {
|
||||
t = dot_t_center.signum() * t;
|
||||
}
|
||||
|
||||
let m2 = (m1 + t * 1e-3).normalize();
|
||||
|
||||
let s1 = projection.model_to_screen_space(&m1.extend(1.0), camera)?;
|
||||
let s2 = projection.model_to_screen_space(&m2.extend(1.0), camera)?;
|
||||
|
||||
let ds = (s2 - s1).normalize();
|
||||
|
||||
let content = angle::SerializeFmt::DMS.to_string(Angle(lat));
|
||||
let content = angle::SerializeFmt::DMS.to_string(lonlat.lat());
|
||||
|
||||
let fov = camera.get_field_of_view();
|
||||
let position = if !fov.is_allsky() && !fov.contains_pole() {
|
||||
let dim = text_renderer.get_width_pixel_size(&content);
|
||||
let dim = 100.0;
|
||||
let k = ds * (dim * 0.5 + 10.0);
|
||||
//let k = Vector2::new(0.0, 0.0);
|
||||
|
||||
s1 + k
|
||||
} else {
|
||||
s1
|
||||
};
|
||||
//position += dv * text_height * 0.5;
|
||||
|
||||
// rot is between -PI and +PI
|
||||
let rot = if ds.y > 0.0 {
|
||||
@@ -442,13 +431,9 @@ impl Label {
|
||||
-ds.x.acos()
|
||||
};
|
||||
|
||||
let rot = if ds.y > 0.0 {
|
||||
if rot > HALF_PI {
|
||||
-PI + rot
|
||||
} else {
|
||||
rot
|
||||
}
|
||||
} else if rot < -HALF_PI {
|
||||
let rot = if ds.y > 0.0 && rot > HALF_PI {
|
||||
-PI + rot
|
||||
} else if ds.y <= 0.0 && rot < -HALF_PI {
|
||||
PI + rot
|
||||
} else {
|
||||
rot
|
||||
@@ -476,112 +461,162 @@ use crate::math::{
|
||||
};
|
||||
|
||||
impl GridLine {
|
||||
/*fn meridian(
|
||||
fn meridian(
|
||||
lon: f64,
|
||||
lat: &Range<f64>,
|
||||
sp: Option<&Vector2<f64>>,
|
||||
camera: &CameraViewPort,
|
||||
//text_height: f64,
|
||||
text_renderer: &TextRenderManager,
|
||||
projection: &ProjectionType,
|
||||
fmt: &angle::SerializeFmt
|
||||
) -> Option<Self> {
|
||||
let fov = camera.get_field_of_view();
|
||||
match fov {
|
||||
FieldOfViewType::Polygon { poly, .. } => {
|
||||
// Polygon case
|
||||
let lon = if lon < 0.0 { lon + TWICE_PI } else { lon };
|
||||
if fov.contains_both_poles() {
|
||||
let camera_center = camera.get_center();
|
||||
|
||||
// The arc length must be < PI, so we create an arc from [(lon, -PI/2); (lon, PI/2)[
|
||||
// see the cdshealpix doc:
|
||||
// https://docs.rs/cdshealpix/latest/cdshealpix/sph_geom/struct.Polygon.html#method.intersect_great_circle_arc
|
||||
let a = Coo3D::from_sph_coo(lon, -HALF_PI);
|
||||
let b = Coo3D::from_sph_coo(lon, HALF_PI - 1e-6);
|
||||
let center_lat = camera_center.lat().to_radians();
|
||||
let label_lat = if center_lat > 70.0_f64.to_radians() {
|
||||
70.0_f64.to_radians()
|
||||
} else if center_lat < -70.0_f64.to_radians() {
|
||||
(-70.0_f64).to_radians()
|
||||
} else {
|
||||
center_lat
|
||||
};
|
||||
|
||||
// For those intersecting, perform the intersection
|
||||
let inter_vertices = poly.intersect_great_circle_arc(&a, &b)
|
||||
.iter()
|
||||
.map(|v| Vector3::new(v.y(), v.z(), v.x()))
|
||||
.collect::<Vec<_>>();
|
||||
let lonlat = LonLatT::new(
|
||||
lon.to_angle(),
|
||||
label_lat.to_angle()
|
||||
);
|
||||
|
||||
if fov.contains_both_poles() {
|
||||
// Like the allsky case, we draw all the meridians
|
||||
let n_pole_lonlat = LonLatT::new(Angle(lon), Angle(HALF_PI));
|
||||
let s_pole_lonlat = LonLatT::new(Angle(lon), Angle(-HALF_PI));
|
||||
|
||||
let vertices = SmallCircle::new(&n_pole_lonlat, &s_pole_lonlat).project(camera, projection);
|
||||
|
||||
// Allsky case
|
||||
// We do an approx saying allsky fovs intersect all meridian
|
||||
// but this is not true for example for the orthographic projection
|
||||
// Some meridians may not be visible
|
||||
let center_lat = camera.get_center().lat();
|
||||
let pos_label: Vector3<f64> = LonLatT::new(Angle(lon), center_lat).vector();
|
||||
let label = Label::meridian(fov, lon, &pos_label, camera, sp, text_renderer, projection, fmt);
|
||||
|
||||
let label = Label::from_meridian(&lonlat, camera, projection, fmt);
|
||||
|
||||
// Draw the full parallel
|
||||
let vertices = line::great_circle_arc::project(lon, -HALF_PI, lon, HALF_PI, camera, projection);
|
||||
Some(GridLine { vertices, label })
|
||||
} else {
|
||||
let i = fov.intersects_meridian(lon);
|
||||
match i {
|
||||
Intersection::Included => {
|
||||
// Longitude fov >= PI
|
||||
let camera_center = camera.get_center();
|
||||
|
||||
let center_lat = camera_center.lat().to_radians();
|
||||
|
||||
let lonlat = LonLatT::new(
|
||||
lon.to_angle(),
|
||||
center_lat.to_angle()
|
||||
);
|
||||
|
||||
let label = Label::from_meridian(&lonlat, camera, projection, fmt);
|
||||
|
||||
// Draw the full parallel
|
||||
let vertices = line::great_circle_arc::project(lon, -HALF_PI, lon, HALF_PI, camera, projection);
|
||||
Some(GridLine { vertices, label })
|
||||
} else if fov.contains_north_pole() {
|
||||
// Only one intersection
|
||||
let n_pole_lonlat = LonLatT::new(Angle(lon), Angle(HALF_PI));
|
||||
let i_lonlat = inter_vertices[0].lonlat();
|
||||
},
|
||||
Intersection::Intersect { vertices } => {
|
||||
let num_intersections = vertices.len();
|
||||
let (vertices, label) = match num_intersections {
|
||||
1 => {
|
||||
let v1 = &vertices[0];
|
||||
let lonlat1 = v1.lonlat();
|
||||
let lat1 = lonlat1.lat().to_radians();
|
||||
|
||||
let vertices = SmallCircle::new(&n_pole_lonlat, &i_lonlat).project(camera, projection);
|
||||
let line_vertices = if fov.contains_north_pole() {
|
||||
line::great_circle_arc::project(
|
||||
lon,
|
||||
lat1,
|
||||
lon,
|
||||
HALF_PI,
|
||||
camera,
|
||||
projection
|
||||
)
|
||||
} else {
|
||||
line::great_circle_arc::project(
|
||||
lon,
|
||||
lat1,
|
||||
lon,
|
||||
MINUS_HALF_PI,
|
||||
camera,
|
||||
projection
|
||||
)
|
||||
};
|
||||
|
||||
let pos_label = &inter_vertices[0];
|
||||
let label = Label::meridian(fov, lon, pos_label, camera, sp, text_renderer, projection, fmt);
|
||||
let label = Label::from_meridian(&lonlat1, camera, projection, fmt);
|
||||
(line_vertices, label)
|
||||
},
|
||||
2 => {
|
||||
// full intersection
|
||||
let v1 = &vertices[0];
|
||||
let v2 = &vertices[1];
|
||||
|
||||
let lat1 = v1.lat().to_radians();
|
||||
let lat2 = v2.lat().to_radians();
|
||||
|
||||
let line_vertices = line::great_circle_arc::project(
|
||||
lon,
|
||||
lat1,
|
||||
lon,
|
||||
lat2,
|
||||
camera,
|
||||
projection
|
||||
);
|
||||
|
||||
let label = Label::from_meridian(&v1.lonlat(), camera, projection, fmt);
|
||||
(line_vertices, label)
|
||||
},
|
||||
_ => {
|
||||
let mut vertices = vertices.into_vec();
|
||||
// One segment over two will be in the field of view
|
||||
vertices.push(Vector4::new(0.0, 1.0, 0.0, 1.0));
|
||||
vertices.push(Vector4::new(0.0, -1.0, 0.0, 1.0));
|
||||
|
||||
vertices.sort_by(|i1, i2| {
|
||||
i1.y.total_cmp(&i2.y)
|
||||
});
|
||||
|
||||
let v1 = &vertices[0];
|
||||
let v2 = &vertices[1];
|
||||
|
||||
// meridian are part of great circles so the mean between v1 & v2 also lies on it
|
||||
let vm = (v1 + v2).truncate().normalize();
|
||||
|
||||
let vertices = if !fov.contains_south_pole() {
|
||||
&vertices[1..]
|
||||
} else {
|
||||
&vertices
|
||||
};
|
||||
|
||||
let line_vertices = vertices.iter().zip(vertices.iter().skip(1))
|
||||
.step_by(2)
|
||||
.map(|(i1, i2)| {
|
||||
line::great_circle_arc::project(
|
||||
lon,
|
||||
i1.lat().to_radians(),
|
||||
lon,
|
||||
i2.lat().to_radians(),
|
||||
camera,
|
||||
projection
|
||||
)
|
||||
})
|
||||
.flatten()
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
let label = Label::from_meridian(&v1.lonlat(), camera, projection, fmt);
|
||||
(line_vertices, label)
|
||||
}
|
||||
};
|
||||
|
||||
Some(GridLine { vertices, label })
|
||||
} else if fov.contains_south_pole() {
|
||||
// Only one intersection
|
||||
let i_lonlat = inter_vertices[0].lonlat();
|
||||
let s_pole_lonlat = LonLatT::new(Angle(lon), Angle(-HALF_PI));
|
||||
|
||||
let vertices = SmallCircle::new(&i_lonlat, &s_pole_lonlat).project(camera, projection);
|
||||
|
||||
let pos_label = &inter_vertices[0];
|
||||
let label = Label::meridian(fov, lon, pos_label, camera, sp, text_renderer, projection, fmt);
|
||||
|
||||
Some(GridLine { vertices, label })
|
||||
} else {
|
||||
if inter_vertices.len() >= 2 {
|
||||
// There must be at least 2 points intersecting the fov
|
||||
let i1_lonlat = inter_vertices[0].lonlat();
|
||||
let i2_lonlat = inter_vertices[1].lonlat();
|
||||
|
||||
let vertices = SmallCircle::new(&i1_lonlat, &i2_lonlat).project(camera, projection);
|
||||
|
||||
let pos_label = &inter_vertices[0];
|
||||
let label = Label::meridian(fov, lon, pos_label, camera, sp, text_renderer, projection, fmt);
|
||||
|
||||
Some(GridLine { vertices, label })
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
},
|
||||
FieldOfViewType::Allsky => {
|
||||
let n_pole_lonlat = LonLatT::new(Angle(lon), Angle(HALF_PI));
|
||||
let s_pole_lonlat = LonLatT::new(Angle(lon), Angle(-HALF_PI));
|
||||
|
||||
let vertices = SmallCircle::new(&s_pole_lonlat, &n_pole_lonlat).project(camera, projection);
|
||||
|
||||
// Allsky case
|
||||
// We do an approx saying allsky fovs intersect all meridian
|
||||
// but this is not true for example for the orthographic projection
|
||||
// Some meridians may not be visible
|
||||
let center_lat = camera.get_center().lat();
|
||||
let pos_label: Vector3<f64> = LonLatT::new(Angle(lon), center_lat).vector();
|
||||
let label = Label::meridian(fov, lon, &pos_label, camera, sp, text_renderer, projection, fmt);
|
||||
|
||||
Some(GridLine { vertices, label })
|
||||
},
|
||||
Intersection::Empty => {
|
||||
None
|
||||
},
|
||||
}
|
||||
}
|
||||
}*/
|
||||
}
|
||||
|
||||
fn parallel(
|
||||
lat: f64,
|
||||
camera: &CameraViewPort,
|
||||
text_renderer: &TextRenderManager,
|
||||
projection: &ProjectionType,
|
||||
) -> Option<Self> {
|
||||
let fov = camera.get_field_of_view();
|
||||
@@ -589,15 +624,16 @@ impl GridLine {
|
||||
// Longitude fov >= PI
|
||||
let camera_center = camera.get_center();
|
||||
let center_lon = camera_center.lon();
|
||||
let label_vertex = LonLatT::new(
|
||||
let lonlat = LonLatT::new(
|
||||
center_lon,
|
||||
lat.to_angle()
|
||||
).vector();
|
||||
);
|
||||
|
||||
let label = Label::parallel(fov, lat, &label_vertex, camera, text_renderer, projection);
|
||||
let label = Label::from_parallel(&lonlat, camera, projection);
|
||||
|
||||
// Draw the full parallel
|
||||
let vertices = crate::renderable::line::parallel::project(lat, center_lon.to_radians(), center_lon.to_radians() + TWICE_PI, camera, projection);
|
||||
let mut vertices = line::parallel_arc::project(lat, center_lon.to_radians(), center_lon.to_radians() + PI, camera, projection);
|
||||
vertices.append(&mut line::parallel_arc::project(lat, center_lon.to_radians() + PI, center_lon.to_radians() + TWICE_PI, camera, projection));
|
||||
Some(GridLine { vertices, label })
|
||||
} else {
|
||||
// Longitude fov < PI
|
||||
@@ -606,46 +642,35 @@ impl GridLine {
|
||||
Intersection::Included => {
|
||||
let camera_center = camera.get_center();
|
||||
let center_lon = camera_center.lon();
|
||||
let label_vertex = LonLatT::new(
|
||||
let lonlat = LonLatT::new(
|
||||
center_lon,
|
||||
lat.to_angle()
|
||||
).vector();
|
||||
);
|
||||
|
||||
let label = Label::parallel(fov, lat, &label_vertex, camera, text_renderer, projection);
|
||||
let label = Label::from_parallel(&lonlat, camera, projection);
|
||||
|
||||
// Draw the full parallel
|
||||
let vertices = crate::renderable::line::parallel::project(lat, center_lon.to_radians(), center_lon.to_radians() + TWICE_PI, camera, projection);
|
||||
let vertices = line::parallel_arc::project(lat, center_lon.to_radians(), center_lon.to_radians() + TWICE_PI, camera, projection);
|
||||
Some(GridLine { vertices, label })
|
||||
},
|
||||
Intersection::Intersect { vertices: inter_vertices } => {
|
||||
let is_intersecting_zero_meridian = fov.is_intersecting_zero_meridian();
|
||||
Intersection::Intersect { vertices } => {
|
||||
let v1 = &vertices[0];
|
||||
let v2 = &vertices[1];
|
||||
|
||||
let lonlat1 = inter_vertices[0].lonlat();
|
||||
let lonlat2 = inter_vertices[1].lonlat();
|
||||
let mut lon1 = v1.lon().to_radians();
|
||||
let mut lon2 = v2.lon().to_radians();
|
||||
|
||||
let center_lonlat = camera.get_center().lonlat();
|
||||
let lon0 = center_lonlat.lon().to_radians();
|
||||
let mut lon1 = lonlat1.lon().to_radians();
|
||||
let mut lon2 = lonlat2.lon().to_radians();
|
||||
|
||||
let lon_len = if lon1 > lon2 {
|
||||
lon2 + TWICE_PI - lon1
|
||||
} else {
|
||||
lon2 - lon1
|
||||
};
|
||||
let lon_len = crate::math::sph_geom::distance_from_two_lon(lon1, lon2);
|
||||
|
||||
// The fov should be contained into PI length
|
||||
if lon_len >= PI {
|
||||
std::mem::swap(&mut lon1, &mut lon2);
|
||||
}
|
||||
|
||||
let lat_deg = lat.to_degrees();
|
||||
al_core::info!(lat_deg, ":", lon0, lon1, lon2);
|
||||
|
||||
let vertices = crate::renderable::line::parallel::project(lat, lon1, lon2, camera, projection);
|
||||
let label = Label::parallel(fov, lat, &inter_vertices[0].truncate(), camera, text_renderer, projection);
|
||||
let line_vertices = line::parallel_arc::project(lat, lon1, lon2, camera, projection);
|
||||
let label = Label::from_parallel(&v1.lonlat(), camera, projection);
|
||||
|
||||
Some(GridLine { vertices, label })
|
||||
Some(GridLine { vertices: line_vertices, label })
|
||||
},
|
||||
Intersection::Empty => {
|
||||
None
|
||||
@@ -656,52 +681,51 @@ impl GridLine {
|
||||
}
|
||||
|
||||
const GRID_STEPS: &[f64] = &[
|
||||
0.0000000000048481366,
|
||||
0.000000000009696273,
|
||||
0.000000000024240684,
|
||||
0.000000000048481368,
|
||||
0.000000000096962736,
|
||||
0.00000000024240682,
|
||||
0.00000000048481363,
|
||||
0.0000000009696273,
|
||||
0.0000000024240685,
|
||||
0.000000004848137,
|
||||
0.000000009696274,
|
||||
0.000000024240684,
|
||||
0.00000004848137,
|
||||
0.00000009696274,
|
||||
0.00000024240686,
|
||||
0.0000004848137,
|
||||
0.0000009696274,
|
||||
0.0000024240685,
|
||||
0.000004848137,
|
||||
0.000009696274,
|
||||
0.000024240684,
|
||||
0.000048481368,
|
||||
0.000072722054,
|
||||
0.00014544411,
|
||||
0.00029088822,
|
||||
0.00058177643,
|
||||
0.0014544411,
|
||||
0.0029088822,
|
||||
0.004363323,
|
||||
0.008726646,
|
||||
0.017453292,
|
||||
0.034906585,
|
||||
0.08726646,
|
||||
0.17453292,
|
||||
0.34906584,
|
||||
0.0000000000048481367,
|
||||
0.000000000009696274,
|
||||
0.000000000024240685,
|
||||
0.000000000048481369,
|
||||
0.000000000096962737,
|
||||
0.00000000024240683,
|
||||
0.00000000048481364,
|
||||
0.0000000009696274,
|
||||
0.0000000024240686,
|
||||
0.000000004848138,
|
||||
0.000000009696275,
|
||||
0.000000024240685,
|
||||
0.00000004848138,
|
||||
0.00000009696275,
|
||||
0.00000024240687,
|
||||
0.0000004848138,
|
||||
0.0000009696275,
|
||||
0.0000024240686,
|
||||
0.000004848138,
|
||||
0.000009696275,
|
||||
0.000024240685,
|
||||
0.000048481369,
|
||||
0.000072722055,
|
||||
0.00014544412,
|
||||
0.00029088823,
|
||||
0.00058177644,
|
||||
0.0014544412,
|
||||
0.0029088823,
|
||||
0.004363324,
|
||||
0.008726647,
|
||||
0.017453293,
|
||||
0.034906586,
|
||||
0.08726647,
|
||||
0.17453293,
|
||||
0.34906585,
|
||||
std::f64::consts::FRAC_PI_4,
|
||||
];
|
||||
|
||||
fn lines(
|
||||
camera: &CameraViewPort,
|
||||
//text_height: f64,
|
||||
text_renderer: &TextRenderManager,
|
||||
projection: &ProjectionType,
|
||||
fmt: &angle::SerializeFmt,
|
||||
) -> Vec<GridLine> {
|
||||
// Get the screen position of the nearest pole
|
||||
let _system = camera.get_system();
|
||||
let fov = camera.get_field_of_view();
|
||||
let sp = if fov.contains_pole() {
|
||||
if fov.contains_north_pole() {
|
||||
@@ -746,28 +770,23 @@ fn lines(
|
||||
stop_theta -= 1e-3;
|
||||
}
|
||||
|
||||
/*while theta < stop_theta {
|
||||
while theta < stop_theta {
|
||||
if let Some(line) =
|
||||
GridLine::meridian(theta, &bbox.get_lat(), sp.as_ref(), camera, text_renderer, projection, fmt)
|
||||
GridLine::meridian(theta, &bbox.get_lat(), sp.as_ref(), camera, projection, fmt)
|
||||
{
|
||||
lines.push(line);
|
||||
}
|
||||
theta += step_lon;
|
||||
}*/
|
||||
|
||||
// Add parallels
|
||||
//let step_lat = select_grid_step(bbox, bbox.get_lat_size() as f64, NUM_LINES);
|
||||
}
|
||||
|
||||
let mut alpha = bbox.lat_min() - (bbox.lat_min() % step_lat);
|
||||
if alpha == -HALF_PI {
|
||||
alpha += step_lat;
|
||||
}
|
||||
let stop_alpha = bbox.lat_max();
|
||||
/*if stop_alpha == HALF_PI {
|
||||
stop_alpha -= 1e-3;
|
||||
}*/
|
||||
|
||||
while alpha < stop_alpha {
|
||||
if let Some(line) = GridLine::parallel(alpha, camera, text_renderer, projection) {
|
||||
if let Some(line) = GridLine::parallel(alpha, camera, projection) {
|
||||
lines.push(line);
|
||||
}
|
||||
alpha += step_lat;
|
||||
|
||||
@@ -1,140 +1,217 @@
|
||||
use crate::{math::{lonlat::LonLatT, projection::{ProjectionType, coo_space::XYNDC}}, camera::CameraViewPort};
|
||||
use crate::math::angle::Angle;
|
||||
use crate::math::projection::coo_space::XYZWModel;
|
||||
use cgmath::Vector2;
|
||||
use cgmath::Vector3;
|
||||
use crate::ProjectionType;
|
||||
use crate::CameraViewPort;
|
||||
use cgmath::Zero;
|
||||
use cgmath::InnerSpace;
|
||||
use crate::math::angle::ToAngle;
|
||||
use crate::math::lonlat::LonLat;
|
||||
use al_core::{log, info, inforec};
|
||||
use crate::coo_space::XYZWModel;
|
||||
use crate::coo_space::XYNDC;
|
||||
use crate::coo_space::XYZModel;
|
||||
use crate::math::{TWICE_PI, PI};
|
||||
|
||||
use crate::ArcDeg;
|
||||
const MAX_ANGLE_BEFORE_SUBDIVISION: Angle<f64> = Angle(0.10943951023); // 12 degrees
|
||||
const MAX_ITERATION: usize = 3;
|
||||
use crate::LonLatT;
|
||||
const MAX_ANGLE_BEFORE_SUBDIVISION: f64 = 0.1;
|
||||
const MAX_ITERATION: usize = 5;
|
||||
|
||||
pub fn project(lonlat1: &LonLatT<f64>, lonlat2: &LonLatT<f64>, camera: &CameraViewPort, projection: &ProjectionType) -> Vec<XYNDC> {
|
||||
// First longitude between [0; 2*pi[
|
||||
let mut lon1 = lonlat1.lon().to_radians();
|
||||
// Parallel latitude between [-0.5*pi; 0.5*pi]
|
||||
let lat1 = lonlat1.lat().to_radians();
|
||||
// Second longitude between [0; 2*pi[
|
||||
let mut lon2 = lonlat2.lon().to_radians();
|
||||
// Parallel latitude between [-0.5*pi; 0.5*pi]
|
||||
let lat2 = lonlat2.lat().to_radians();
|
||||
let is_intersecting_zero_meridian = lon1 > lon2;
|
||||
// Requirement:
|
||||
// * Latitudes between [-0.5*pi; 0.5*pi]
|
||||
// * Longitudes between [0; 2\pi[
|
||||
// * (lon1 - lon2).abs() < PI so that is can only either cross the preimary meridian or opposite primary meridian
|
||||
// (the latest is handled because of the longitudes intervals)
|
||||
pub fn project(lon1: f64, lat1: f64, lon2: f64, lat2: f64, camera: &CameraViewPort, projection: &ProjectionType) -> Vec<XYNDC> {
|
||||
let mut vertices = vec![];
|
||||
|
||||
if is_intersecting_zero_meridian {
|
||||
// Make the longitudes lie between [-PI; PI];
|
||||
if lon1 > PI {
|
||||
lon1 -= TWICE_PI;
|
||||
}
|
||||
let lonlat1 = LonLatT::new(lon1.to_angle(), lat1.to_angle());
|
||||
let lonlat2 = LonLatT::new(lon2.to_angle(), lat2.to_angle());
|
||||
|
||||
if lon2 > PI {
|
||||
lon2 -= TWICE_PI;
|
||||
let v1: Vector3<_> = lonlat1.vector();
|
||||
let v2: Vector3<_> = lonlat2.vector();
|
||||
|
||||
let p1 = projection.model_to_normalized_device_space(&v1.extend(1.0), camera);
|
||||
let p2 = projection.model_to_normalized_device_space(&v2.extend(1.0), camera);
|
||||
|
||||
match (p1, p2) {
|
||||
(Some(_), Some(_)) => {
|
||||
project_line(&mut vertices, &v1, &v2, camera, projection, 0);
|
||||
},
|
||||
(None, Some(_)) => {
|
||||
let (v1, v2) = sub_valid_domain(v2, v1, projection, camera);
|
||||
project_line(&mut vertices, &v1, &v2, camera, projection, 0);
|
||||
},
|
||||
(Some(_), None) => {
|
||||
let (v1, v2) = sub_valid_domain(v1, v2, projection, camera);
|
||||
project_line(&mut vertices, &v1, &v2, camera, projection, 0);
|
||||
},
|
||||
(None, None) => {
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
let mut ndc_vertices: Vec<XYNDC> = vec![];
|
||||
|
||||
let start_world_vertex = LonLatT::new(lon1.to_angle(), lat1.to_angle()).vector();
|
||||
let end_world_vertex = LonLatT::new(lon2.to_angle(), lat2.to_angle()).vector();
|
||||
|
||||
let ndc_v1 = projection.model_to_normalized_device_space(&start_world_vertex, camera);
|
||||
let ndc_v2 = projection.model_to_normalized_device_space(&end_world_vertex, camera);
|
||||
|
||||
if let (Some(start_ndc_vertex), Some(end_ndc_vertex)) = (ndc_v1, ndc_v2) {
|
||||
subdivide(
|
||||
&mut ndc_vertices,
|
||||
start_world_vertex,
|
||||
start_ndc_vertex,
|
||||
end_world_vertex,
|
||||
end_ndc_vertex,
|
||||
camera,
|
||||
projection,
|
||||
0
|
||||
);
|
||||
}
|
||||
|
||||
ndc_vertices
|
||||
vertices
|
||||
}
|
||||
|
||||
fn subdivide(
|
||||
ndc_vertices: &mut Vec<XYNDC>,
|
||||
start_world_vertex: XYZWModel,
|
||||
start_ndc_vertex: XYNDC,
|
||||
// Precondition:
|
||||
// * angular distance between valid_lon and invalid_lon is < PI
|
||||
// * valid_lon and invalid_lon are well defined, i.e. they can be between [-PI; PI] or [0, 2PI] depending
|
||||
// whether they cross or not the zero meridian
|
||||
fn sub_valid_domain(valid_v: XYZModel, invalid_v: XYZModel, projection: &ProjectionType, camera: &CameraViewPort) -> (XYZModel, XYZModel) {
|
||||
let d_alpha = camera.get_aperture().to_radians() * 0.02;
|
||||
|
||||
end_world_vertex: XYZWModel,
|
||||
end_ndc_vertex: XYNDC,
|
||||
|
||||
camera: &CameraViewPort,
|
||||
projection: &ProjectionType,
|
||||
|
||||
iter: usize,
|
||||
) {
|
||||
if iter > MAX_ITERATION {
|
||||
ndc_vertices.push(start_ndc_vertex);
|
||||
ndc_vertices.push(end_ndc_vertex);
|
||||
return;
|
||||
}
|
||||
|
||||
// Project them. We are always facing the camera
|
||||
let mid_world_vertex = (start_world_vertex + end_world_vertex).normalize();
|
||||
|
||||
if let Some(mid_ndc_vertex) = projection.model_to_normalized_device_space(&mid_world_vertex, camera) {
|
||||
let ab = mid_ndc_vertex - start_ndc_vertex;
|
||||
let bc = end_ndc_vertex - mid_ndc_vertex;
|
||||
|
||||
let ab_l = ab.magnitude2();
|
||||
let bc_l = bc.magnitude2();
|
||||
|
||||
let ab = ab.normalize();
|
||||
let bc = bc.normalize();
|
||||
let theta = crate::math::vector::angle2(&ab, &bc);
|
||||
|
||||
// nearly colinear vectors
|
||||
if theta.abs() < MAX_ANGLE_BEFORE_SUBDIVISION {
|
||||
if crate::math::vector::det(&ab, &bc).abs() < 1e-2 {
|
||||
ndc_vertices.push(start_ndc_vertex);
|
||||
ndc_vertices.push(end_ndc_vertex);
|
||||
} else {
|
||||
// not colinear
|
||||
ndc_vertices.push(start_ndc_vertex);
|
||||
ndc_vertices.push(mid_ndc_vertex);
|
||||
|
||||
ndc_vertices.push(mid_ndc_vertex);
|
||||
ndc_vertices.push(end_ndc_vertex);
|
||||
}
|
||||
} else if ab_l.min(bc_l) / ab_l.max(bc_l) < 0.1 {
|
||||
if ab_l < bc_l {
|
||||
ndc_vertices.push(start_ndc_vertex);
|
||||
ndc_vertices.push(mid_ndc_vertex);
|
||||
} else {
|
||||
ndc_vertices.push(mid_ndc_vertex);
|
||||
ndc_vertices.push(end_ndc_vertex);
|
||||
}
|
||||
let mut vv = valid_v;
|
||||
let mut vi = invalid_v;
|
||||
while crate::math::vector::angle3(&vv, &vi).to_radians() > d_alpha {
|
||||
let vm = (vv + vi).normalize();
|
||||
// check whether is it defined or not
|
||||
if let Some(_) = projection.model_to_normalized_device_space(&vm.extend(1.0), camera) {
|
||||
vv = vm;
|
||||
} else {
|
||||
// Subdivide a->b and b->c
|
||||
subdivide(
|
||||
ndc_vertices,
|
||||
start_world_vertex,
|
||||
start_ndc_vertex,
|
||||
mid_world_vertex,
|
||||
mid_ndc_vertex,
|
||||
camera,
|
||||
projection,
|
||||
iter + 1
|
||||
);
|
||||
|
||||
subdivide(
|
||||
ndc_vertices,
|
||||
mid_world_vertex,
|
||||
mid_ndc_vertex,
|
||||
end_world_vertex,
|
||||
end_ndc_vertex,
|
||||
camera,
|
||||
projection,
|
||||
iter + 1
|
||||
);
|
||||
vi = vm;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Return the valid interval found by dichotomy
|
||||
(vv, valid_v)
|
||||
}
|
||||
|
||||
fn project_line(
|
||||
vertices: &mut Vec<XYNDC>,
|
||||
v1: &XYZModel,
|
||||
v2: &XYZModel,
|
||||
camera: &CameraViewPort,
|
||||
projection: &ProjectionType,
|
||||
iter: usize,
|
||||
) -> bool {
|
||||
let p1 = projection.model_to_normalized_device_space(&v1.extend(1.0), camera);
|
||||
let p2 = projection.model_to_normalized_device_space(&v2.extend(1.0), camera);
|
||||
|
||||
if iter < MAX_ITERATION {
|
||||
// Project them. We are always facing the camera
|
||||
let vm = (v1 + v2).normalize();
|
||||
let pm = projection.model_to_normalized_device_space(&vm.extend(1.0), camera);
|
||||
|
||||
match (p1, pm, p2) {
|
||||
(Some(p1), Some(pm), Some(p2)) => {
|
||||
let d12 = crate::math::vector::angle3(v1, v2).to_radians();
|
||||
|
||||
// Subdivide when until it is > 30 degrees
|
||||
if d12 > 30.0_f64.to_radians() {
|
||||
subdivide(
|
||||
vertices,
|
||||
v1,
|
||||
v2,
|
||||
&vm,
|
||||
p1,
|
||||
p2,
|
||||
pm,
|
||||
camera,
|
||||
projection,
|
||||
iter
|
||||
);
|
||||
} else {
|
||||
// enough to stop the recursion
|
||||
let ab = pm - p1;
|
||||
let bc = p2 - pm;
|
||||
|
||||
let ab_u = ab.normalize();
|
||||
let bc_u = bc.normalize();
|
||||
|
||||
let dot_abbc = crate::math::vector::dot(&ab_u, &bc_u);
|
||||
let theta_abbc = dot_abbc.acos();
|
||||
|
||||
if theta_abbc.abs() < 5.0_f64.to_radians() {
|
||||
let det_abbc = crate::math::vector::det(&ab_u, &bc_u);
|
||||
|
||||
if det_abbc.abs() < 1e-2 {
|
||||
vertices.push(p1);
|
||||
vertices.push(p2);
|
||||
} else {
|
||||
// not colinear but enough to stop
|
||||
vertices.push(p1);
|
||||
vertices.push(pm);
|
||||
|
||||
vertices.push(pm);
|
||||
vertices.push(p2);
|
||||
}
|
||||
} else {
|
||||
let ab_l = ab.magnitude2();
|
||||
let bc_l = bc.magnitude2();
|
||||
|
||||
let r = (ab_l - bc_l).abs() / (ab_l + bc_l);
|
||||
|
||||
if r > 0.8 {
|
||||
if ab_l < bc_l {
|
||||
vertices.push(p1);
|
||||
vertices.push(pm);
|
||||
} else {
|
||||
vertices.push(pm);
|
||||
vertices.push(p2);
|
||||
}
|
||||
} else {
|
||||
// Subdivide a->b and b->c
|
||||
subdivide(
|
||||
vertices,
|
||||
v1,
|
||||
v2,
|
||||
&vm,
|
||||
p1,
|
||||
p2,
|
||||
pm,
|
||||
camera,
|
||||
projection,
|
||||
iter
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
true
|
||||
},
|
||||
_ => false
|
||||
}
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
fn subdivide(
|
||||
vertices: &mut Vec<XYNDC>,
|
||||
v1: &XYZModel,
|
||||
v2: &XYZModel,
|
||||
vm: &XYZModel,
|
||||
p1: XYNDC,
|
||||
p2: XYNDC,
|
||||
pm: XYNDC,
|
||||
camera: &CameraViewPort,
|
||||
projection: &ProjectionType,
|
||||
iter: usize
|
||||
) {
|
||||
// Subdivide a->b and b->c
|
||||
if !project_line(
|
||||
vertices,
|
||||
v1,
|
||||
vm,
|
||||
camera,
|
||||
projection,
|
||||
iter + 1
|
||||
) {
|
||||
vertices.push(p1);
|
||||
vertices.push(pm);
|
||||
}
|
||||
|
||||
if !project_line(
|
||||
vertices,
|
||||
vm,
|
||||
v2,
|
||||
camera,
|
||||
projection,
|
||||
iter + 1
|
||||
) {
|
||||
vertices.push(pm);
|
||||
vertices.push(p2);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
/// This module handles the lines rendering code
|
||||
pub mod meridian;
|
||||
pub mod parallel;
|
||||
pub mod great_circle_arc;
|
||||
pub mod parallel_arc;
|
||||
|
||||
use al_core::WebGlContext;
|
||||
use al_core::VertexArrayObject;
|
||||
|
||||
@@ -7,117 +7,52 @@ use cgmath::Zero;
|
||||
use cgmath::InnerSpace;
|
||||
use crate::math::angle::ToAngle;
|
||||
use crate::math::lonlat::LonLat;
|
||||
use al_core::{log, info, inforec};
|
||||
use crate::coo_space::XYNDC;
|
||||
use crate::math::{TWICE_PI, PI};
|
||||
use crate::ArcDeg;
|
||||
use crate::LonLatT;
|
||||
const MAX_ANGLE_BEFORE_SUBDIVISION: Angle<f64> = Angle(0.174533); // 12 degrees
|
||||
const MAX_ANGLE_BEFORE_SUBDIVISION: Angle<f64> = Angle(0.10);
|
||||
const MAX_ITERATION: usize = 4;
|
||||
|
||||
// Requirement:
|
||||
// * Parallel latitude between [-0.5*pi; 0.5*pi]
|
||||
// * First longitude between [0; 2\pi[
|
||||
// * Second lon length between [0; 2\pi[
|
||||
// * Either (lon1, lat) or (lon2, lat) is defined
|
||||
pub fn project(lat: f64, mut lon1: f64, lon2: f64, camera: &CameraViewPort, projection: &ProjectionType) -> Vec<XYNDC> {
|
||||
let lon_len = if lon1 > lon2 {
|
||||
lon2 + TWICE_PI - lon1
|
||||
} else {
|
||||
lon2 - lon1
|
||||
};
|
||||
// * (lon1 - lon2).abs() < PI
|
||||
pub fn project(lat: f64, mut lon1: f64, mut lon2: f64, camera: &CameraViewPort, projection: &ProjectionType) -> Vec<XYNDC> {
|
||||
let mut vertices = vec![];
|
||||
|
||||
if lon_len > PI + 1e-6 {
|
||||
// lon mid lies in [0; 2\pi[
|
||||
let lon_mid = if lon1 + PI >= TWICE_PI {
|
||||
lon1 -= TWICE_PI;
|
||||
lon1 + PI
|
||||
} else {
|
||||
lon1 + PI
|
||||
};
|
||||
let lon_len = crate::math::sph_geom::distance_from_two_lon(lon1, lon2);
|
||||
let mut lon2 = lon1 + lon_len;
|
||||
|
||||
let first_half_meridian = project(lat, lon1, lon_mid, camera, projection);
|
||||
let mut second_half_meridian = project(lat, lon_mid, lon2, camera, projection);
|
||||
|
||||
let mut vertices = first_half_meridian;
|
||||
vertices.append(&mut second_half_meridian);
|
||||
vertices
|
||||
} else {
|
||||
let mut lon2 = lon1 + lon_len;
|
||||
|
||||
let mut vertices = vec![];
|
||||
|
||||
// Can only cross the 0 meridian but not 0 and 180 ones
|
||||
if lon2 > TWICE_PI {
|
||||
// it crosses the zero meridian
|
||||
lon2 -= TWICE_PI;
|
||||
// lon1 is > PI because the lon len is <= PI
|
||||
lon1 -= TWICE_PI;
|
||||
}
|
||||
|
||||
// We know (lon1, lat) can be projected as it is a requirement of that method
|
||||
let v1 = crate::math::lonlat::proj(&LonLatT::new(lon1.to_angle(), lat.to_angle()), projection, camera);
|
||||
let v2 = crate::math::lonlat::proj(&LonLatT::new(lon2.to_angle(), lat.to_angle()), projection, camera);
|
||||
|
||||
match (v1, v2) {
|
||||
(Some(v1), Some(v2)) => {
|
||||
subdivide_multi(&mut vertices, lat, lon1, lon2, camera, projection);
|
||||
},
|
||||
(None, Some(v2)) => {
|
||||
let (lon1, lon2) = sub_valid_domain(lat, lon2, lon1, projection, camera);
|
||||
subdivide_multi(&mut vertices, lat, lon1, lon2, camera, projection);
|
||||
},
|
||||
(Some(v1), None) => {
|
||||
let (lon1, lon2) = sub_valid_domain(lat, lon1, lon2, projection, camera);
|
||||
subdivide_multi(&mut vertices, lat, lon1, lon2, camera, projection);
|
||||
},
|
||||
(None, None) => {}
|
||||
}
|
||||
|
||||
vertices
|
||||
// Can only cross the 0 meridian but not 0 and 180 ones
|
||||
if lon2 > TWICE_PI {
|
||||
// it crosses the zero meridian
|
||||
lon2 -= TWICE_PI;
|
||||
// lon1 is > PI because the lon len is <= PI
|
||||
lon1 -= TWICE_PI;
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn is_in_lon_range(lon0: f64, lon1: f64, lon2: f64) -> bool {
|
||||
// First version of the code:
|
||||
// ((v2.lon() - v1.lon()).abs() > PI) != ((v2.lon() > coo.lon()) != (v1.lon() > coo.lon()))
|
||||
//
|
||||
// Lets note
|
||||
// - lonA = v1.lon()
|
||||
// - lonB = v2.lon()
|
||||
// - lon0 = coo.lon()
|
||||
// When (lonB - lonA).abs() <= PI
|
||||
// => lonB > lon0 != lonA > lon0 like in PNPOLY
|
||||
// A B lonA <= lon0 && lon0 < lonB
|
||||
// --[++++[--
|
||||
// B A lonB <= lon0 && lon0 < lonA
|
||||
//
|
||||
// But when (lonB - lonA).abs() > PI, then the test should be
|
||||
// => lonA >= lon0 == lonB >= lon0
|
||||
// <=> !(lonA >= lon0 != lonB >= lon0)
|
||||
// A | B (lon0 < lonB) || (lonA <= lon0)
|
||||
// --[++|++[--
|
||||
// B | A (lon0 < lonA) || (lonB <= lon0)
|
||||
//
|
||||
// Instead of lonA > lon0 == lonB > lon0,
|
||||
// i.e. !(lonA > lon0 != lonB > lon0).
|
||||
// A | B (lon0 <= lonB) || (lonA < lon0)
|
||||
// --]++|++]--
|
||||
// B | A (lon0 <= lonA) || (lonB < lon0)
|
||||
//
|
||||
// So the previous code was bugged in this very specific case:
|
||||
// - `lon0` has the same value as a vertex being part of:
|
||||
// - one segment that do not cross RA=0
|
||||
// - plus one segment crossing RA=0.
|
||||
// - the point have an odd number of intersections with the polygon
|
||||
// (since it will be counted 0 or 2 times instead of 1).
|
||||
let dlon = lon2 - lon1;
|
||||
if dlon < 0.0 {
|
||||
(dlon >= -PI) == (lon2 <= lon0 && lon0 < lon1)
|
||||
} else {
|
||||
(dlon <= PI) == (lon1 <= lon0 && lon0 < lon2)
|
||||
// We know (lon1, lat) can be projected as it is a requirement of that method
|
||||
let v1 = crate::math::lonlat::proj(&LonLatT::new(lon1.to_angle(), lat.to_angle()), projection, camera);
|
||||
let v2 = crate::math::lonlat::proj(&LonLatT::new(lon2.to_angle(), lat.to_angle()), projection, camera);
|
||||
|
||||
match (v1, v2) {
|
||||
(Some(v1), Some(v2)) => {
|
||||
subdivide_multi(&mut vertices, lat, lon1, lon2, camera, projection);
|
||||
},
|
||||
(None, Some(v2)) => {
|
||||
let (lon1, lon2) = sub_valid_domain(lat, lon2, lon1, projection, camera);
|
||||
subdivide_multi(&mut vertices, lat, lon1, lon2, camera, projection);
|
||||
},
|
||||
(Some(v1), None) => {
|
||||
let (lon1, lon2) = sub_valid_domain(lat, lon1, lon2, projection, camera);
|
||||
subdivide_multi(&mut vertices, lat, lon1, lon2, camera, projection);
|
||||
},
|
||||
(None, None) => {}
|
||||
}
|
||||
|
||||
vertices
|
||||
}
|
||||
|
||||
// Precondition:
|
||||
@@ -179,11 +114,11 @@ fn subdivide(
|
||||
camera: &CameraViewPort,
|
||||
projection: &ProjectionType,
|
||||
iter: usize,
|
||||
) {
|
||||
if iter < MAX_ITERATION {
|
||||
let p1 = crate::math::lonlat::proj(&LonLatT::new(lon1.to_angle(), lat.to_angle()), projection, camera);
|
||||
let p2 = crate::math::lonlat::proj(&LonLatT::new(lon2.to_angle(), lat.to_angle()), projection, camera);
|
||||
) -> bool {
|
||||
let p1 = crate::math::lonlat::proj(&LonLatT::new(lon1.to_angle(), lat.to_angle()), projection, camera);
|
||||
let p2 = crate::math::lonlat::proj(&LonLatT::new(lon2.to_angle(), lat.to_angle()), projection, camera);
|
||||
|
||||
if iter < MAX_ITERATION {
|
||||
// Project them. We are always facing the camera
|
||||
let lon0 = (lon1 + lon2)*0.5;
|
||||
let pm = crate::math::lonlat::proj(&LonLatT::new(lon0.to_angle(), lat.to_angle()), projection, camera);
|
||||
@@ -223,7 +158,7 @@ fn subdivide(
|
||||
}
|
||||
} else {
|
||||
// Subdivide a->b and b->c
|
||||
subdivide(
|
||||
if !subdivide(
|
||||
vertices,
|
||||
lat,
|
||||
lon1,
|
||||
@@ -231,9 +166,12 @@ fn subdivide(
|
||||
camera,
|
||||
projection,
|
||||
iter + 1
|
||||
);
|
||||
) {
|
||||
vertices.push(p1);
|
||||
vertices.push(pm);
|
||||
}
|
||||
|
||||
subdivide(
|
||||
if !subdivide(
|
||||
vertices,
|
||||
lat,
|
||||
lon0,
|
||||
@@ -241,10 +179,17 @@ fn subdivide(
|
||||
camera,
|
||||
projection,
|
||||
iter + 1
|
||||
);
|
||||
) {
|
||||
vertices.push(pm);
|
||||
vertices.push(p2);
|
||||
}
|
||||
}
|
||||
|
||||
true
|
||||
},
|
||||
_ => {}
|
||||
_ => false
|
||||
}
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user