From bb7513a959b896984d09a20ac1303e91ebd852d4 Mon Sep 17 00:00:00 2001 From: Matthieu Baumann Date: Sat, 24 Jun 2023 14:30:42 +0200 Subject: [PATCH] wip perimeter moc draw --- examples/al-eso-catalogs.html | 23 + examples/al-gw.html | 8 +- src/core/Cargo.toml | 2 +- src/core/al-api/src/color.rs | 2 + src/core/al-api/src/grid.rs | 6 + src/core/al-api/src/moc.rs | 23 +- src/core/al-core/src/lib.rs | 2 - src/core/al-core/src/text.rs | 171 ---- src/core/src/app.rs | 44 +- src/core/src/async_task.rs | 47 +- src/core/src/camera/viewport.rs | 46 +- src/core/src/grid/label.rs | 153 ++++ src/core/src/grid/meridian.rs | 237 ++++++ src/core/src/grid/mod.rs | 327 +++++++ src/core/src/grid/parallel.rs | 159 ++++ src/core/src/lib.rs | 1 + src/core/src/math/lonlat.rs | 4 - src/core/src/math/sph_geom/region.rs | 7 +- src/core/src/renderable/catalog/index.rs | 78 +- src/core/src/renderable/catalog/manager.rs | 39 +- src/core/src/renderable/catalog/mod.rs | 2 - src/core/src/renderable/catalog/source.rs | 64 -- src/core/src/renderable/grid/mod.rs | 842 ------------------- src/core/src/renderable/labels.rs | 339 -------- src/core/src/renderable/line/mod.rs | 205 +++-- src/core/src/renderable/line/parallel_arc.rs | 133 +-- src/core/src/renderable/moc.rs | 141 +++- src/core/src/renderable/mod.rs | 11 +- src/core/src/renderable/text.rs | 113 +++ src/core/src/renderable/utils.rs | 2 +- src/core/src/survey/config.rs | 21 +- src/css/aladin.css | 7 + src/glsl/webgl2/line/line_frag.glsl | 2 + src/glsl/webgl2/line/line_vertex.glsl | 1 - src/glsl/webgl2/text/text_frag.glsl | 20 - src/glsl/webgl2/text/text_vertex.glsl | 29 - src/js/Aladin.js | 2 +- src/js/View.js | 10 +- src/js/WebGL.js | 5 - src/js/gui/CooGrid.js | 10 +- 40 files changed, 1538 insertions(+), 1800 deletions(-) create mode 100644 examples/al-eso-catalogs.html delete mode 100644 src/core/al-core/src/text.rs create mode 100644 src/core/src/grid/label.rs create mode 100644 src/core/src/grid/meridian.rs create mode 100644 src/core/src/grid/mod.rs create mode 100644 src/core/src/grid/parallel.rs delete mode 100644 src/core/src/renderable/catalog/source.rs delete mode 100644 src/core/src/renderable/grid/mod.rs delete mode 100644 src/core/src/renderable/labels.rs create mode 100644 src/core/src/renderable/text.rs delete mode 100644 src/glsl/webgl2/text/text_frag.glsl delete mode 100644 src/glsl/webgl2/text/text_vertex.glsl diff --git a/examples/al-eso-catalogs.html b/examples/al-eso-catalogs.html new file mode 100644 index 00000000..bb68b081 --- /dev/null +++ b/examples/al-eso-catalogs.html @@ -0,0 +1,23 @@ + + + + + + +
+ + + + diff --git a/examples/al-gw.html b/examples/al-gw.html index e187d52a..687af9dd 100644 --- a/examples/al-gw.html +++ b/examples/al-gw.html @@ -10,10 +10,10 @@ A.init.then(() => { aladin = A.aladin('#aladin-lite-div', {projection: "TAN", target: '15 16 57.636 -60 55 7.49', showCooGrid: true, fov: 90}); - var moc_0_99 = A.MOCFromURL("./gw/gw_0.9.fits",{ name: "GW 90%", color: "#ff0000", opacity: 0.5, lineWidth: 1, adaptativeDisplay: true}); - var moc_0_95 = A.MOCFromURL("./gw/gw_0.6.fits",{ name: "GW 60%", color: "#00ff00", opacity: 0.5, lineWidth: 1, adaptativeDisplay: true}); - var moc_0_5 = A.MOCFromURL("./gw/gw_0.3.fits",{ name: "GW 30%", color: "#00ffff", opacity: 0.5, lineWidth: 1, adaptativeDisplay: false}); - var moc_0_2 = A.MOCFromURL("./gw/gw_0.1.fits",{ name: "GW 10%", color: "#ff00ff", opacity: 0.5, lineWidth: 1, adaptativeDisplay: false}); + var moc_0_99 = A.MOCFromURL("./gw/gw_0.9.fits",{ name: "GW 90%", color: "#ff0000", opacity: 1.0, lineWidth: 1, adaptativeDisplay: true}); + var moc_0_95 = A.MOCFromURL("./gw/gw_0.6.fits",{ name: "GW 60%", color: "#00ff00", opacity: 1.0, lineWidth: 1, adaptativeDisplay: true}); + var moc_0_5 = A.MOCFromURL("./gw/gw_0.3.fits",{ name: "GW 30%", color: "#00ffff", opacity: 1.0, lineWidth: 1, adaptativeDisplay: false}); + var moc_0_2 = A.MOCFromURL("./gw/gw_0.1.fits",{ name: "GW 10%", color: "#ff00ff", opacity: 1.0, lineWidth: 1, adaptativeDisplay: false}); aladin.addMOC(moc_0_99); aladin.addMOC(moc_0_95); diff --git a/src/core/Cargo.toml b/src/core/Cargo.toml index 57613f60..c21e3d23 100644 --- a/src/core/Cargo.toml +++ b/src/core/Cargo.toml @@ -23,7 +23,7 @@ js-sys = "0.3.47" wasm-bindgen-futures = "0.4.20" cgmath = "*" cdshealpix = { path = "../../../cds-healpix-rust", version = "0.6.4" } -moclib = { package = "moc", version = "0.10.1" } +moclib = { package = "moc", path = "../../../cds-moc-rust" } serde = { version = "^1.0.59", features = ["derive"] } serde_json = "1.0" serde-wasm-bindgen = "0.4" diff --git a/src/core/al-api/src/color.rs b/src/core/al-api/src/color.rs index c3db39d2..eee133f7 100644 --- a/src/core/al-api/src/color.rs +++ b/src/core/al-api/src/color.rs @@ -8,6 +8,8 @@ extern "C" { pub fn hexToRgb(hex: String) -> JsValue; #[wasm_bindgen(static_method_of = Color)] pub fn hexToRgba(hex: String) -> JsValue; + #[wasm_bindgen(static_method_of = Color)] + pub fn rgbToHex(r: u8, g: u8, b: u8) -> String; } #[derive(Debug, Clone, Copy)] diff --git a/src/core/al-api/src/grid.rs b/src/core/al-api/src/grid.rs index b0085dcc..41b65f6e 100644 --- a/src/core/al-api/src/grid.rs +++ b/src/core/al-api/src/grid.rs @@ -12,6 +12,8 @@ use super::color::ColorRGB; pub struct GridCfg { #[serde(default = "default_color")] pub color: Option, + #[serde(default = "default_thickness")] + pub thickness: Option, pub opacity: Option, #[serde(default = "default_labels")] pub show_labels: Option, @@ -39,6 +41,10 @@ fn default_label_size() -> Option { None } +fn default_thickness() -> Option { + None +} + fn default_fmt() -> Option { None } diff --git a/src/core/al-api/src/moc.rs b/src/core/al-api/src/moc.rs index 55352494..9904400f 100644 --- a/src/core/al-api/src/moc.rs +++ b/src/core/al-api/src/moc.rs @@ -1,30 +1,30 @@ use wasm_bindgen::prelude::wasm_bindgen; -use super::color::{Color, ColorRGB}; +use super::color::{Color, ColorRGBA}; #[derive(Clone, Debug)] #[wasm_bindgen] pub struct MOC { uuid: String, - opacity: f32, line_width: f32, is_showing: bool, - color: ColorRGB, + color: ColorRGBA, adaptative_display: bool, } use std::convert::TryInto; -use crate::Abort; +use crate::{Abort, color::ColorRGB}; #[wasm_bindgen] impl MOC { #[wasm_bindgen(constructor)] pub fn new(uuid: String, opacity: f32, line_width: f32, is_showing: bool, hex_color: String, adaptative_display: bool) -> Self { let color = Color::hexToRgb(hex_color); - let color = color.try_into().unwrap_abort(); + let rgb: ColorRGB = color.try_into().unwrap_abort(); + let rgba = ColorRGBA { r: rgb.r, g: rgb.g, b: rgb.b, a: opacity }; + Self { uuid, - opacity, line_width, - color, + color: rgba, is_showing, adaptative_display } @@ -41,13 +41,9 @@ impl MOC { &self.uuid } - pub fn get_color(&self) -> &ColorRGB { + pub fn get_color(&self) -> &ColorRGBA { &self.color } - - pub fn get_opacity(&self) -> f32 { - self.opacity - } pub fn get_line_width(&self) -> f32 { self.line_width @@ -66,10 +62,9 @@ impl Default for MOC { fn default() -> Self { Self { uuid: String::from("moc"), - opacity: 1.0, line_width: 1.0, is_showing: true, - color: ColorRGB {r: 1.0, g: 0.0, b: 0.0}, + color: ColorRGBA {r: 1.0, g: 0.0, b: 0.0, a: 1.0}, adaptative_display: true, } } diff --git a/src/core/al-core/src/lib.rs b/src/core/al-core/src/lib.rs index 368d505d..152455c1 100644 --- a/src/core/al-core/src/lib.rs +++ b/src/core/al-core/src/lib.rs @@ -4,8 +4,6 @@ extern crate serde_json; extern crate futures; extern crate wasm_streams; -pub mod text; - pub mod image; mod object; pub mod shader; diff --git a/src/core/al-core/src/text.rs b/src/core/al-core/src/text.rs deleted file mode 100644 index fbfa693b..00000000 --- a/src/core/al-core/src/text.rs +++ /dev/null @@ -1,171 +0,0 @@ -#[derive(Serialize, Deserialize)] -pub struct LetterTexPosition { - pub x_min: u32, - pub x_max: u32, - pub y_min: u32, - pub y_max: u32, - pub x_advance: u32, - pub y_advance: u32, - pub w: u32, - pub h: u32, - pub bound_xmin: f32, - pub bound_ymin: f32, -} - -use std::collections::HashMap; -use serde::{Serialize, Deserialize}; -pub struct Font { - pub bitmap: Vec, - pub letters: HashMap, -} - -pub const TEX_SIZE: usize = 256; - -mod tests { - #[test] - pub fn rasterize_font() { - #[derive(PartialEq)] - struct Letter { - pub l: char, - pub w: u32, - pub h: u32, - pub x_advance: u32, - pub y_advance: u32, - pub bitmap: Vec, - pub bounds: fontdue::OutlineBounds, - } - use std::cmp::Ordering; - impl PartialOrd for Letter { - fn partial_cmp(&self, other: &Self) -> Option { - let w_cmp = other.w.cmp(&self.w); - - if Ordering::Equal == w_cmp { - Some(other.h.cmp(&self.h)) - } else { - Some(w_cmp) - } - } - } - - use super::TEX_SIZE; - - use super::LetterTexPosition; - use std::collections::HashMap; - use std::io::Write; - - - // Read the font data. - let font = include_bytes!("../resources/arial.ttf") as &[u8]; - // Parse it into the font type. - let font = fontdue::Font::from_bytes(font, fontdue::FontSettings::default()).unwrap(); - - // Rasterize and get the layout metrics for the letter 'g' at 17px. - let mut w = 0; - let mut h = 0; - let mut letters = Vec::new(); - for c in 0_u8..255_u8 { - let (metrics, bitmap) = font.rasterize(c as char, 16.0); - - letters.push(Letter { - w: metrics.width as u32, - h: metrics.height as u32, - x_advance: metrics.advance_width as u32, - y_advance: metrics.advance_height as u32, - bounds: metrics.bounds, - l: c as char, - bitmap, - }); - - h += metrics.height; - w = std::cmp::max(w, metrics.width); - } - letters.sort_unstable_by(|l, r| { - let w_cmp = r.w.cmp(&l.w); - - if Ordering::Equal == w_cmp { - r.h.cmp(&l.h) - } else { - w_cmp - } - }); - - let mut letters_tex = HashMap::new(); - let mut x_min = 0; - let mut y_min = 0; - let mut size_col = letters[0].w; - let mut img = vec![0; TEX_SIZE * TEX_SIZE * 4]; - - for Letter { - l, - w, - h, - x_advance, - y_advance, - bitmap, - bounds, - } in letters.into_iter() - { - let mut i = 0; - - let mut y_max = y_min + h; - if y_max >= TEX_SIZE as u32 { - y_min = 0; - y_max = h; - x_min += size_col; - - size_col = w; - } - - // Draw here the letter in the tex - let x_max = x_min + w; - letters_tex.insert( - l, - LetterTexPosition { - x_min, - x_max, - y_min, - y_max, - x_advance, - y_advance, - w: x_max - x_min, - h: y_max - y_min, - bound_xmin: bounds.xmin, - bound_ymin: bounds.ymin, - }, - ); - for y in (y_min as usize)..(y_max as usize) { - for x in (x_min as usize)..(x_max as usize) { - img[4 * (x + TEX_SIZE * y)] = bitmap[i]; - img[4 * (x + TEX_SIZE * y) + 1] = bitmap[i]; - img[4 * (x + TEX_SIZE * y) + 2] = bitmap[i]; - img[4 * (x + TEX_SIZE * y) + 3] = bitmap[i]; - - i += 1; - } - } - - y_min += h; - } - - /* Save the jpeg file */ - use std::fs::File; - use std::io::BufWriter; - - let file = File::create("letters.png").unwrap(); - let ref mut w = BufWriter::new(file); - - let mut encoder = png::Encoder::new(w, TEX_SIZE as u32, TEX_SIZE as u32); // Width is 2 pixels and height is 1. - encoder.set_color(png::ColorType::Rgba); - encoder.set_depth(png::BitDepth::Eight); - - let mut writer = encoder.write_header().unwrap(); - - writer.write_image_data(&img).unwrap(); // Save - - /* Save the letters position */ - let letters_tex_serialized = serde_json::to_string(&letters_tex).unwrap(); - - let mut file = File::create("letters.json").unwrap(); - write!(file, "{}", letters_tex_serialized).unwrap(); - } -} diff --git a/src/core/src/app.rs b/src/core/src/app.rs index bc3e814e..32ea709d 100644 --- a/src/core/src/app.rs +++ b/src/core/src/app.rs @@ -4,15 +4,17 @@ use crate::{ downloader::Downloader, math::{ self, - angle::{Angle, ArcDeg}, + angle::{Angle, ArcDeg, ToAngle}, lonlat::{LonLat, LonLatT}, }, renderable::{ - catalog::{Manager, Source}, - grid::ProjetedGrid, + catalog::Manager, moc::MOC, ImageCfg, + line::RasterizedLineRenderer, + Renderer, }, + grid::ProjetedGrid, healpix::coverage::HEALPixCoverage, shader::ShaderManager, renderable::Layers, @@ -21,6 +23,8 @@ use crate::{ inertia::Inertia, }; +use crate::coo_space::XYNDC; + use wasm_bindgen::prelude::*; use al_core::WebGlContext; @@ -29,7 +33,7 @@ use al_core::colormap::{Colormap, Colormaps}; use al_api::{ coo_system::CooSystem, grid::GridCfg, - hips::{ImageMetadata, HiPSCfg, FITSCfg}, + hips::{ImageMetadata, HiPSCfg, FITSCfg}, color::ColorRGBA, }; use wasm_bindgen_futures::JsFuture; use fitsrs::{fits::AsyncFits, hdu::{extension::AsyncXtensionHDU}}; @@ -88,6 +92,7 @@ pub struct App { _final_rendering_pass: RenderPass, _fbo_view: FrameBufferObject, _fbo_ui: FrameBufferObject, + line_renderer: RasterizedLineRenderer, colormaps: Colormaps, @@ -192,6 +197,8 @@ impl App { let (fits_send, fits_recv) = async_channel::unbounded::(); let (ack_send, ack_recv) = async_channel::unbounded::(); + let line_renderer = RasterizedLineRenderer::new(&gl)?; + Ok(App { gl, start_time_frame, @@ -221,6 +228,8 @@ impl App { _fbo_ui, _final_rendering_pass, + line_renderer, + inertia, disable_inertia, prev_cam_position, @@ -439,6 +448,7 @@ impl App { pub(crate) fn add_moc(&mut self, params: al_api::moc::MOC, moc: HEALPixCoverage) -> Result<(), JsValue> { self.moc.insert(moc, params, &self.camera, &self.projection); + self.request_redraw = true; Ok(()) } @@ -447,11 +457,13 @@ impl App { self.moc.remove(params, &self.camera) .ok_or_else(|| JsValue::from_str("MOC not found"))?; + self.request_redraw = true; + Ok(()) } pub(crate) fn set_moc_params(&mut self, params: al_api::moc::MOC) -> Result<(), JsValue> { - self.moc.set_params(params, &self.camera, &self.projection) + self.moc.set_params(params, &self.camera, &self.projection, &mut self.line_renderer) .ok_or_else(|| JsValue::from_str("MOC not found"))?; self.request_redraw = true; @@ -689,15 +701,12 @@ impl App { self.request_redraw = false; // Finally update the camera that reset the flag camera changed - if has_camera_moved { + //if has_camera_moved { // Catalogues update /*if let Some(view) = self.layers.get_view() { self.manager.update(&self.camera, view); }*/ - self.grid.update(&self.camera, &self.projection); - // MOCs update - self.moc.update(&self.camera, &self.projection); - } + //} // Check for async retrieval if let Ok(fits) = self.fits_recv.try_recv() { @@ -815,13 +824,17 @@ impl App { self.gl.clear(web_sys::WebGl2RenderingContext::COLOR_BUFFER_BIT); self.layers.draw(&self.camera, shaders, &self.colormaps, &self.projection)?; - self.moc.draw(shaders, &self.camera); // Draw the catalog //let fbo_view = &self.fbo_view; //catalogs.draw(&gl, shaders, camera, colormaps, fbo_view)?; //catalogs.draw(&gl, shaders, camera, colormaps, None, self.projection)?; - self.grid.draw(&self.camera, shaders)?; + self.line_renderer.begin(); + self.grid.draw(&self.camera, shaders, &self.projection, &mut self.line_renderer)?; + self.moc.draw(shaders, &self.camera, &self.projection, &mut self.line_renderer); + self.line_renderer.end(); + + self.line_renderer.draw(&self.camera)?; //let dpi = self.camera.get_dpi(); //ui.draw(&gl, dpi)?; @@ -1149,11 +1162,10 @@ impl App { .spawner() .spawn(TaskType::ParseTableTask, async move { let mut stream = ParseTableTask::<[f32; 2]>::new(table); - let mut results: Vec = vec![]; + let mut results: Vec> = vec![]; while let Some(item) = stream.next().await { - let item: &[f32] = item.as_ref(); - results.push(item.into()); + results.push(LonLatT::new(item[0].to_angle(), item[1].to_angle())); } let mut stream_sort = BuildCatalogIndex::new(results); @@ -1213,7 +1225,7 @@ impl App { } pub(crate) fn set_grid_cfg(&mut self, cfg: GridCfg) -> Result<(), JsValue> { - self.grid.set_cfg(cfg, &self.camera, &self.projection)?; + self.grid.set_cfg(cfg, &self.camera, &self.projection, &mut self.line_renderer)?; self.request_redraw = true; Ok(()) diff --git a/src/core/src/async_task.rs b/src/core/src/async_task.rs index 09930637..40cf2638 100644 --- a/src/core/src/async_task.rs +++ b/src/core/src/async_task.rs @@ -6,11 +6,13 @@ use al_task_exec::Executor; pub type TaskExecutor = Executor; -pub use crate::renderable::catalog::Source; +use crate::math::lonlat::LonLat; +use crate::math::lonlat::LonLatT; + pub enum TaskResult { TableParsed { name: String, - sources: Box<[Source]>, + sources: Box<[LonLatT]>, }, /*TileSentToGPU { tile: Tile, @@ -55,6 +57,7 @@ where } use serde::de::DeserializeOwned; +use std::ops::DerefMut; use std::pin::Pin; use std::task::{Context, Poll}; impl Stream for ParseTableTask @@ -93,19 +96,25 @@ where /*use rand::rngs::StdRng; use rand::Rng; use rand::SeedableRng;*/ -pub struct BuildCatalogIndex { - pub sources: Vec, +pub struct BuildCatalogIndex +where + T: LonLat + Clone +{ + pub sources: Vec, num_sorted_sources: usize, i: usize, j: usize, merging: bool, - new_sorted_sources: Vec, + new_sorted_sources: Vec, ready: bool, chunk_size: usize, prev_num_sorted_sources: usize, } -impl BuildCatalogIndex { - pub fn new(sources: Vec) -> Self { +impl BuildCatalogIndex +where + T: LonLat + Clone +{ + pub fn new(sources: Vec) -> Self { let num_sorted_sources = 0; let merging = false; let new_sorted_sources = vec![]; @@ -130,7 +139,15 @@ impl BuildCatalogIndex { const CHUNK_OF_SOURCES_TO_SORT: usize = 1000; const CHUNK_OF_SORTED_SOURCES_TO_MERGE: usize = 20000; use crate::Abort; -impl Stream for BuildCatalogIndex { +impl Unpin for BuildCatalogIndex +where + T: LonLat + Clone +{} + +impl Stream for BuildCatalogIndex +where + T: LonLat + Clone +{ type Item = (); /// Attempt to resolve the next item in the stream. @@ -149,8 +166,11 @@ impl Stream for BuildCatalogIndex { //let mut rng = StdRng::seed_from_u64(0); // Get the chunk to sort (&mut self.sources[a..b]).sort_unstable_by(|s1, s2| { - let (s1_lon, s1_lat) = s1.lonlat(); - let (s2_lon, s2_lat) = s2.lonlat(); + let s1_lonlat = s1.lonlat(); + let s2_lonlat = s2.lonlat(); + + let (s1_lon, s1_lat) = (s1_lonlat.lon().to_radians(), s1_lonlat.lat().to_radians()); + let (s2_lon, s2_lat) = (s2_lonlat.lon().to_radians(), s2_lonlat.lat().to_radians()); let idx1 = cdshealpix::nested::hash(7, s1_lon as f64, s1_lat as f64); let idx2 = cdshealpix::nested::hash(7, s2_lon as f64, s2_lat as f64); @@ -197,8 +217,11 @@ impl Stream for BuildCatalogIndex { } else { let s1 = &self.sources[self.j]; let s2 = &self.sources[self.i]; - let (s1_lon, s1_lat) = s1.lonlat(); - let (s2_lon, s2_lat) = s2.lonlat(); + let s1_lonlat = s1.lonlat(); + let s2_lonlat = s2.lonlat(); + + let (s1_lon, s1_lat) = (s1_lonlat.lon().to_radians(), s1_lonlat.lat().to_radians()); + let (s2_lon, s2_lat) = (s2_lonlat.lon().to_radians(), s2_lonlat.lat().to_radians()); let p1 = cdshealpix::nested::hash(7, s1_lon as f64, s1_lat as f64); let p2 = cdshealpix::nested::hash(7, s2_lon as f64, s2_lat as f64); diff --git a/src/core/src/camera/viewport.rs b/src/core/src/camera/viewport.rs index 099f3652..acb5bec2 100644 --- a/src/core/src/camera/viewport.rs +++ b/src/core/src/camera/viewport.rs @@ -175,7 +175,7 @@ impl CameraViewPort { // a flag telling if the viewport has a reversed longitude axis reversed_longitude, }; - camera.set_canvas_size(); + //camera.set_canvas_size(); camera } @@ -212,30 +212,23 @@ impl CameraViewPort { self.gl.scissor((tl_s.x as i32).max(0), (tl_s.y as i32).max(0), w as i32, h as i32); } - fn set_canvas_size(&self) { + fn set_canvas_size(&self, width: f32, height: f32) { let canvas = self.gl .canvas() .unwrap_abort() .dyn_into::() .unwrap_abort(); - canvas.set_width(self.width as u32); - canvas.set_height(self.height as u32); - // Once the canvas size is changed, we have to set the viewport as well - self.gl.viewport(0, 0, self.width as i32, self.height as i32); - } - - pub fn set_screen_size(&mut self, width: f32, height: f32, projection: &ProjectionType) { - let canvas = self - .gl - .canvas() + // grid canvas + let document = web_sys::window().unwrap_abort().document().unwrap_abort(); + let grid_canvas = document + // Inside it, retrieve the canvas + .get_elements_by_class_name("aladin-gridCanvas") + .get_with_index(0) .unwrap_abort() .dyn_into::() .unwrap_abort(); - self.width = (width as f32) * self.dpi; - self.height = (height as f32) * self.dpi; - canvas .style() .set_property("width", &format!("{}px", width)) @@ -244,6 +237,27 @@ impl CameraViewPort { .style() .set_property("height", &format!("{}px", height)) .unwrap_abort(); + grid_canvas + .style() + .set_property("width", &format!("{}px", width)) + .unwrap_abort(); + grid_canvas + .style() + .set_property("height", &format!("{}px", height)) + .unwrap_abort(); + + canvas.set_width(self.width as u32); + canvas.set_height(self.height as u32); + grid_canvas.set_width(self.width as u32); + grid_canvas.set_height(self.height as u32); + + // Once the canvas size is changed, we have to set the viewport as well + self.gl.viewport(0, 0, self.width as i32, self.height as i32); + } + + pub fn set_screen_size(&mut self, width: f32, height: f32, projection: &ProjectionType) { + self.width = (width as f32) * self.dpi; + self.height = (height as f32) * self.dpi; self.aspect = width / height; @@ -262,7 +276,7 @@ impl CameraViewPort { self, )); // Update the size of the canvas - self.set_canvas_size(); + self.set_canvas_size(width, height); // Once it is done, recompute the scissor self.recompute_scissor(); } diff --git a/src/core/src/grid/label.rs b/src/core/src/grid/label.rs new file mode 100644 index 00000000..82cdfde7 --- /dev/null +++ b/src/core/src/grid/label.rs @@ -0,0 +1,153 @@ + +use crate::math::HALF_PI; +use crate::math::PI; +use cgmath::Vector3; +use crate::ProjectionType; +use crate::CameraViewPort; +use crate::LonLatT; +use cgmath::InnerSpace; +use crate::Abort; +use crate::math::angle::SerializeFmt; +use crate::math::TWICE_PI; +use crate::grid::XYScreen; +use crate::math::lonlat::LonLat; +use wasm_bindgen::JsValue; +use crate::math::angle::ToAngle; +use core::ops::Range; +use cgmath::Vector2; + +const OFF_TANGENT: f64 = 35.0; +const OFF_BI_TANGENT: f64 = 5.0; + +pub enum LabelOptions { + Centered, + OnSide, +} + +#[derive(Debug)] +pub struct Label { + // The position + pub position: XYScreen, + // the string content + pub content: String, + // in radians + pub rot: f64, +} +impl Label { + pub fn from_meridian( + lon: f64, + lat: &Range, + options: LabelOptions, + camera: &CameraViewPort, + projection: &ProjectionType, + fmt: &SerializeFmt + ) -> Option { + 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() { + Vector3::new(0.0, -1.0, 0.0) + } else { + Vector3::new(0.0, 1.0, 0.0) + }; + + let lonlat = match options { + LabelOptions::Centered => { + let mut lat = camera.get_center().lat().to_radians(); + if lat.abs() > 70.0_f64.to_radians() { + lat = lat.signum() * 70.0_f64.to_radians(); + } + + LonLatT::new(lon.to_angle(), lat.to_angle()) + } + LabelOptions::OnSide => LonLatT::new(lon.to_angle(), lat.start.to_angle()) + }; + + let m1: Vector3<_> = lonlat.vector(); + let m2 = (m1 + d * 1e-3).normalize(); + + //let s1 = projection.model_to_screen_space(&(system.to_icrs_j2000::() * m1), camera, reversed_longitude)?; + let d1 = projection.model_to_screen_space(&m1.extend(1.0), camera)?; + let d2 = projection.model_to_screen_space(&m2.extend(1.0), camera)?; + + //let s2 = projection.model_to_screen_space(&(system.to_icrs_j2000::() * m2), camera, reversed_longitude)?; + let dt = (d2 - d1).normalize(); + let db = Vector2::new(dt.y.abs(), dt.x.abs()); + + let mut lon = m1.lon().to_radians(); + if lon < 0.0 { + lon += TWICE_PI; + } + + let content = fmt.to_string(lon.to_angle()); + let position = if !fov.is_allsky() { + d1 + OFF_TANGENT * dt - OFF_BI_TANGENT * db + } else { + d1 + }; + + // rot is between -PI and +PI + let rot = dt.y.signum() * dt.x.acos(); + + Some(Label { + position, + content, + rot, + }) + } + + pub fn from_parallel( + lat: f64, + lon: &Range, + options: LabelOptions, + camera: &CameraViewPort, + projection: &ProjectionType, + ) -> Option { + let lonlat = match options { + LabelOptions::Centered => { + let lon = camera.get_center().lon(); + LonLatT::new(lon, lat.to_angle()) + } + LabelOptions::OnSide => LonLatT::new(lon.start.to_angle(), lat.to_angle()) + }; + + let m1: Vector3<_> = lonlat.vector(); + + 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 d1 = projection.model_to_screen_space(&m1.extend(1.0), camera)?; + let d2 = projection.model_to_screen_space(&m2.extend(1.0), camera)?; + + let dt = (d2 - d1).normalize(); + let db = Vector2::new(dt.y.abs(), dt.x.abs()); + + let content = SerializeFmt::DMS.to_string(lonlat.lat()); + + let fov = camera.get_field_of_view(); + let position = if !fov.is_allsky() && !fov.contains_pole() { + d1 + OFF_TANGENT * dt - OFF_BI_TANGENT * db + } else { + d1 + }; + + // rot is between -PI and +PI + let rot = dt.y.signum() * dt.x.acos() + PI; + + + Some(Label { + position, + content, + rot, + }) + } +} diff --git a/src/core/src/grid/meridian.rs b/src/core/src/grid/meridian.rs new file mode 100644 index 00000000..b0b9d095 --- /dev/null +++ b/src/core/src/grid/meridian.rs @@ -0,0 +1,237 @@ +use crate::math::angle::ToAngle; +use crate::renderable::utils::Triangle; +use cgmath::Vector4; +use crate::LonLatT; +use super::label::{Label, LabelOptions}; +use crate::CameraViewPort; +use crate::math::lonlat::LonLat; +use crate::math::sph_geom::region::Intersection; +use core::ops::Range; +use crate::grid::XYNDC; +use crate::math::{PI, TWICE_PI, MINUS_HALF_PI}; +use crate::ProjectionType; +use wasm_bindgen::JsValue; +use crate::grid::angle::SerializeFmt; +use crate::math::HALF_PI; +use al_core::{log, info, inforec}; + +pub fn get_intersecting_meridian(lon: f64, camera: &CameraViewPort, projection: &ProjectionType, fmt: &SerializeFmt) -> Option { + let fov = camera.get_field_of_view(); + if fov.contains_both_poles() { + let meridian = Meridian::new(lon, &(-HALF_PI..HALF_PI), LabelOptions::Centered, camera, projection, fmt); + Some(meridian) + } else { + let i = fov.intersects_meridian(lon); + match i { + Intersection::Included => { + // Longitude fov >= PI + let meridian = Meridian::new(lon, &(-HALF_PI..HALF_PI), LabelOptions::Centered, camera, projection, fmt); + Some(meridian) + }, + Intersection::Intersect { vertices } => { + let num_intersections = vertices.len(); + let meridian = match num_intersections { + 1 => { + let v1 = &vertices[0]; + let lonlat1 = v1.lonlat(); + let lat1 = lonlat1.lat().to_radians(); + + let lat = if fov.contains_north_pole() { + lat1..HALF_PI + } else { + lat1..MINUS_HALF_PI + }; + + Meridian::new(lon, &lat, LabelOptions::OnSide, camera, projection, fmt) + }, + 2 => { + // full intersection + let v1 = &vertices[0]; + let v2 = &vertices[1]; + + let lat1 = v1.lat().to_radians(); + let lat2 = v2.lat().to_radians(); + + Meridian::new(lon, &(lat1..lat2), LabelOptions::OnSide, camera, projection, fmt) + }, + _ => { + /*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::>(); + + let label = Label::from_meridian(&v1.lonlat(), camera, projection, fmt); + */ + Meridian::new(lon, &(-HALF_PI..HALF_PI), LabelOptions::OnSide, camera, projection, fmt) + } + }; + + Some(meridian) + }, + Intersection::Empty => { + None + }, + } + } +} + +pub struct Meridian { + // longitude of the meridian (in radians) + lon: f64, + // latitudes ranges (in radians) + lat: Range, + // List of vertices + vertices: Vec<[f32; 2]>, + // Line vertices indices + indices: Vec>, + label: Option