wip perimeter moc draw

This commit is contained in:
Matthieu Baumann
2023-06-24 14:30:42 +02:00
parent 526cf51c4c
commit bb7513a959
40 changed files with 1538 additions and 1800 deletions

View File

@@ -0,0 +1,23 @@
<!doctype html>
<html>
<head>
</head>
<body>
<div id="aladin-lite-div" style="width: 500px; height: 400px"></div>
<script type="module">
import A from '../src/js/A.js';
var vmc_cepheids = 'https://archive.eso.org/tap_cat/sync?REQUEST=doQuery&LANG=ADQL&MAXREC=401&FORMAT=votable&QUERY=SELECT%20*%20from%20vmc_er4_yjks_cepheidCatMetaData_fits_V3%20where%20%20CONTAINS(POINT(%27%27,RA2000,DEC2000),%20CIRCLE(%27%27,80.894167,-69.756111,2.7))=1';
var pessto = 'https://archive.eso.org/tap_cat/sync?REQUEST=doQuery&LANG=ADQL&MAXREC=3&FORMAT=votable&QUERY=SELECT%20*%20from%20safcat.PESSTO_TRAN_CAT_V3%20where%20CONTAINS(POINT(%27%27,TRANSIENT_RAJ2000,TRANSIENT_DECJ2000),%20CIRCLE(%27%27,80.894167,-69.756111,2.7))=1';
var aladin;
A.init.then(() => {
aladin = A.aladin('#aladin-lite-div', {survey: 'P/DSS2/red', target: 'LMC', fov: 5});
aladin.addCatalog(A.catalogFromURL('https://vizier.u-strasbg.fr/viz-bin/votable?-source=HIP2&-c=LMC&-out.add=_RAJ,_DEJ&-oc.form=dm&-out.meta=DhuL&-out.max=9999&-c.rm=180', {sourceSize:12, color: '#f08080'}));
aladin.addCatalog(A.catalogFromURL(vmc_cepheids, {onClick: 'showTable', sourceSize:14, color: '#fff080'}));
aladin.addCatalog(A.catalogFromURL(pessto, {onClick: 'showPopup', sourceSize:14, color: '#00f080'}), undefined, true);
});
</script>
</body>
</html>

View File

@@ -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);

View File

@@ -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"

View File

@@ -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)]

View File

@@ -12,6 +12,8 @@ use super::color::ColorRGB;
pub struct GridCfg {
#[serde(default = "default_color")]
pub color: Option<ColorRGB>,
#[serde(default = "default_thickness")]
pub thickness: Option<f32>,
pub opacity: Option<f32>,
#[serde(default = "default_labels")]
pub show_labels: Option<bool>,
@@ -39,6 +41,10 @@ fn default_label_size() -> Option<f32> {
None
}
fn default_thickness() -> Option<f32> {
None
}
fn default_fmt() -> Option<AngleSerializeFmt> {
None
}

View File

@@ -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,
}
}

View File

@@ -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;

View File

@@ -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<u8>,
pub letters: HashMap<char, LetterTexPosition>,
}
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<u8>,
pub bounds: fontdue::OutlineBounds,
}
use std::cmp::Ordering;
impl PartialOrd for Letter {
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
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();
}
}

View File

@@ -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::<ImageCfg>();
let (ack_send, ack_recv) = async_channel::unbounded::<ImageParams>();
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<Source> = vec![];
let mut results: Vec<LonLatT<f32>> = 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(())

View File

@@ -6,11 +6,13 @@
use al_task_exec::Executor;
pub type TaskExecutor = Executor<TaskType, TaskResult>;
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<f32>]>,
},
/*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<T> Stream for ParseTableTask<T>
@@ -93,19 +96,25 @@ where
/*use rand::rngs::StdRng;
use rand::Rng;
use rand::SeedableRng;*/
pub struct BuildCatalogIndex {
pub sources: Vec<Source>,
pub struct BuildCatalogIndex<T>
where
T: LonLat<f32> + Clone
{
pub sources: Vec<T>,
num_sorted_sources: usize,
i: usize,
j: usize,
merging: bool,
new_sorted_sources: Vec<Source>,
new_sorted_sources: Vec<T>,
ready: bool,
chunk_size: usize,
prev_num_sorted_sources: usize,
}
impl BuildCatalogIndex {
pub fn new(sources: Vec<Source>) -> Self {
impl<T> BuildCatalogIndex<T>
where
T: LonLat<f32> + Clone
{
pub fn new(sources: Vec<T>) -> 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<T> Unpin for BuildCatalogIndex<T>
where
T: LonLat<f32> + Clone
{}
impl<T> Stream for BuildCatalogIndex<T>
where
T: LonLat<f32> + 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);

View File

@@ -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::<web_sys::HtmlCanvasElement>()
.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::<web_sys::HtmlCanvasElement>()
.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();
}

153
src/core/src/grid/label.rs Normal file
View File

@@ -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<f64>,
options: LabelOptions,
camera: &CameraViewPort,
projection: &ProjectionType,
fmt: &SerializeFmt
) -> Option<Self> {
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::<f64>() * 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::<f64>() * 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<f64>,
options: LabelOptions,
camera: &CameraViewPort,
projection: &ProjectionType,
) -> Option<Self> {
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,
})
}
}

View File

@@ -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<Meridian> {
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::<Vec<_>>();
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<f64>,
// List of vertices
vertices: Vec<[f32; 2]>,
// Line vertices indices
indices: Vec<Range<usize>>,
label: Option<Label>,
}
use cgmath::{Rad, Vector3};
use crate::math::{
angle::ArcDeg,
};
impl Meridian {
pub fn new(
lon: f64,
lat: &Range<f64>,
label_options: LabelOptions,
camera: &CameraViewPort,
projection: &ProjectionType,
fmt: &SerializeFmt
) -> Self {
let label = Label::from_meridian(lon, lat, label_options, camera, projection, fmt);
// Draw the full parallel
let vertices = crate::renderable::line::great_circle_arc::project(lon, lat.start, lon, lat.end, camera, projection)
.into_iter()
.map(|v| [v.x as f32, v.y as f32])
.collect::<Vec<_>>();
let mut start_idx = 0;
let mut indices = if vertices.len() >= 3 {
let v_iter = (1..(vertices.len() - 1))
.map(|i| &vertices[i]);
v_iter.clone()
.zip(v_iter.skip(1))
.enumerate()
.step_by(2)
.filter_map(|(i, (v1, v2))| {
if v1 == v2 {
None
} else {
let res = Some(start_idx..(i + 2));
start_idx = i + 2;
res
}
})
.collect()
} else {
vec![]
};
indices.push(start_idx..vertices.len());
/*let mut prev_v = [vertices[0].x as f32, vertices[0].y as f32];
let vertices: Vec<_> = std::iter::once(prev_v)
.chain(
vertices.into_iter().skip(1)
.filter_map(|v| {
let cur_v = [v.x as f32, v.y as f32];
if cur_v == prev_v {
None
} else {
prev_v = cur_v;
Some(cur_v)
}
})
)
.collect();
// Create subsets of vertices referring to different lines
let indices = if vertices.len() >= 3 {
let mut indices = vec![];
let mut v0 = 0;
let mut v1 = 1;
let mut v2 = 2;
let mut s = 0;
let n_segment = vertices.len() - 1;
for i in 0..n_segment {
if Triangle::new(&vertices[v0], &vertices[v1], &vertices[v2]).is_valid(camera) {
indices.push(s..(i+1));
s = i;
}
v0 = v1;
v1 = v2;
v2 = (v2 + 1) % vertices.len();
}
//indices.push(start_line_i..vertices.len());
al_core::info!(indices);
//vec![0..vertices.len()]
vec![0..2]
} else {
vec![0..vertices.len()]
};*/
Self {
lat: lat.clone(),
vertices,
indices,
label,
lon
}
}
#[inline]
pub fn get_lines_vertices(&self) -> Vec<&[[f32; 2]]> {
self.indices
.iter()
.map(|r| &self.vertices[r.start..r.end])
.collect()
}
#[inline]
pub fn get_label(&self) -> Option<&Label> {
self.label.as_ref()
}
}

327
src/core/src/grid/mod.rs Normal file
View File

@@ -0,0 +1,327 @@
pub mod parallel;
pub mod meridian;
pub mod label;
use crate::Abort;
use crate::camera;
use meridian::Meridian;
use parallel::Parallel;
use serde::Serialize;
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 crate::renderable::Renderer;
use al_api::color::ColorRGBA;
use crate::math::HALF_PI;
use al_core::{log, inforec, info};
use crate::math::TWICE_PI;
use crate::math::angle;
use cgmath::Vector4;
use crate::camera::CameraViewPort;
use crate::ProjectionType;
use crate::LonLatT;
use crate::math::angle::ToAngle;
use al_api::grid::GridCfg;
use al_core::VertexArrayObject;
use al_api::color::ColorRGB;
use crate::grid::label::Label;
pub struct ProjetedGrid {
// Properties
pub color: ColorRGBA,
pub show_labels: bool,
pub enabled: bool,
pub label_scale: f32,
thickness: f32,
gl: WebGlContext,
// Render Text Manager
text_renderer: TextRenderManager,
fmt: angle::SerializeFmt,
line_style: line::Style,
}
use crate::shader::ShaderManager;
use al_core::VecData;
use al_core::WebGlContext;
use wasm_bindgen::JsValue;
use crate::renderable::text::TextRenderManager;
use crate::renderable::line::RasterizedLineRenderer;
use al_api::resources::Resources;
impl ProjetedGrid {
pub fn new(
gl: &WebGlContext,
camera: &CameraViewPort,
resources: &Resources,
projection: &ProjectionType,
) -> Result<ProjetedGrid, JsValue> {
let gl = gl.clone();
let text_renderer = TextRenderManager::new(&gl, camera)?;
let color = ColorRGBA { r: 0.0, g: 1.0, b: 0.0, a: 0.5 };
let show_labels = true;
let enabled = false;
let label_scale = 1.0;
let line_style = line::Style::None;
let fmt = angle::SerializeFmt::DMS;
let thickness = 3.0;
let grid = ProjetedGrid {
color,
line_style,
show_labels,
enabled,
label_scale,
thickness,
gl,
text_renderer,
fmt,
};
// Initialize the vertices & labels
//grid.force_update(camera, projection, line_renderer);
Ok(grid)
}
pub fn set_cfg(&mut self, new_cfg: GridCfg, camera: &CameraViewPort, projection: &ProjectionType, line_renderer: &mut RasterizedLineRenderer) -> Result<(), JsValue> {
let GridCfg {
color,
opacity,
thickness,
show_labels,
label_size,
enabled,
fmt,
} = new_cfg;
if let Some(color) = color {
self.color = ColorRGBA {r: color.r, g: color.g, b: color.b, a: self.color.a};
self.text_renderer.set_color(&color);
}
if let Some(opacity) = opacity {
self.color.a = opacity;
}
if let Some(thickness) = thickness {
// convert thickness in pixels to ndc
self.thickness = thickness;
}
if let Some(show_labels) = show_labels {
self.show_labels = show_labels;
}
if let Some(fmt) = fmt {
self.fmt = fmt.into();
}
if let Some(label_size) = label_size {
self.label_scale = label_size;
self.text_renderer.set_font_size(label_size as u32);
}
if let Some(enabled) = enabled {
self.enabled = enabled;
if !self.enabled {
self.text_renderer.clear_text_canvas();
}
}
Ok(())
}
// Update the grid whenever the camera moved
fn update(&mut self, camera: &CameraViewPort, projection: &ProjectionType, line_renderer: &mut RasterizedLineRenderer) -> Result<(), JsValue> {
let fov = camera.get_field_of_view();
let bbox = fov.get_bounding_box();
let max_dim_px = camera.get_width().max(camera.get_height()) as f64;
let step_line_px = max_dim_px * 0.2;
// update meridians
let meridians = {
// Select the good step with a binary search
let step_lon_precised = (bbox.get_lon_size() as f64) * step_line_px / (camera.get_width() as f64);
let step_lon = select_fixed_step(step_lon_precised);
// Add meridians
let start_lon = bbox.lon_min() - (bbox.lon_min() % step_lon);
let mut stop_lon = bbox.lon_max();
if bbox.all_lon() {
stop_lon -= 1e-3;
}
let mut meridians = vec![];
let mut lon = start_lon;
while lon < stop_lon {
if let Some(p) = meridian::get_intersecting_meridian(lon, camera, projection, &self.fmt) {
meridians.push(p);
}
lon += step_lon;
}
meridians
};
let parallels = {
let step_lat_precised = (bbox.get_lat_size() as f64) * step_line_px / (camera.get_height() as f64);
let step_lat = select_fixed_step(step_lat_precised);
let mut start_lat = bbox.lat_min() - (bbox.lat_min() % step_lat);
if start_lat == -HALF_PI {
start_lat += step_lat;
}
let stop_lat = bbox.lat_max();
let mut lat = start_lat;
let mut parallels = vec![];
while lat < stop_lat {
if let Some(p) = parallel::get_intersecting_parallel(lat, camera, projection) {
parallels.push(p);
}
lat += step_lat;
}
parallels
};
// update the line buffers
let paths = meridians.iter()
.map(|meridian| meridian.get_lines_vertices())
.chain(
parallels.iter().map(|parallel| parallel.get_lines_vertices())
)
.flatten();
line_renderer.add_paths(paths, self.thickness * 2.0 / camera.get_width(), &self.color, &self.line_style);
// update labels
{
let labels = meridians.iter().filter_map(|m| m.get_label())
.chain(parallels.iter().filter_map(|p| p.get_label()));
let dpi = camera.get_dpi();
self.text_renderer.begin();
for Label { content, position, rot } in labels {
let position = position.cast::<f32>().unwrap_abort() * dpi;
self.text_renderer.add_label(
&content,
&position,
cgmath::Rad(*rot as f32),
)?;
}
self.text_renderer.end();
}
Ok(())
}
pub fn draw(
&mut self,
camera: &CameraViewPort,
shaders: &mut ShaderManager,
projection: &ProjectionType,
line_renderer: &mut RasterizedLineRenderer
) -> Result<(), JsValue> {
if self.enabled {
self.update(camera, projection, line_renderer)?;
}
Ok(())
}
}
use crate::shader::ShaderId;
use core::num;
use std::borrow::Cow;
use std::path::is_separator;
use crate::math::{
angle::Angle,
};
use crate::camera::fov::FieldOfView;
use cgmath::InnerSpace;
use cgmath::Vector2;
use core::ops::Range;
const GRID_STEPS: &[f64] = &[
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 select_fixed_step(fov: f64) -> f64 {
match GRID_STEPS.binary_search_by(|v| {
v.partial_cmp(&fov).expect("Couldn't compare values, maybe because the fov given is NaN")
}) {
Ok(idx) => GRID_STEPS[idx],
Err(idx) => {
if idx == 0 {
GRID_STEPS[0]
} else if idx == GRID_STEPS.len() {
GRID_STEPS[idx - 1]
} else {
let a = GRID_STEPS[idx];
let b = GRID_STEPS[idx - 1];
if a - fov > fov - b {
b
} else {
a
}
}
}
}
}

View File

@@ -0,0 +1,159 @@
use crate::math::angle::ToAngle;
use crate::math::projection::ProjectionType;
use super::label::Label;
use crate::{CameraViewPort, camera};
use crate::math::sph_geom::region::Intersection;
use crate::math::lonlat::LonLat;
use crate::math::{PI, TWICE_PI};
use crate::LonLatT;
use crate::renderable::line;
use crate::grid::XYNDC;
use wasm_bindgen::JsValue;
use core::ops::Range;
pub fn get_intersecting_parallel(lat: f64, camera: &CameraViewPort, projection: &ProjectionType) -> Option<Parallel> {
let fov = camera.get_field_of_view();
if fov.get_bounding_box().get_lon_size() > PI {
// Longitude fov >= PI
let camera_center = camera.get_center();
let lon_start = camera_center.lon().to_radians();
Some(
Parallel::new(lat, &(lon_start..(lon_start + TWICE_PI)), camera, LabelOptions::Centered, projection)
)
} else {
// Longitude fov < PI
let i = fov.intersects_parallel(lat);
match i {
Intersection::Included => {
let camera_center = camera.get_center();
let lon_start = camera_center.lon().to_radians();
Some(Parallel::new(lat, &(lon_start..(lon_start + TWICE_PI)), camera, LabelOptions::Centered, projection))
},
Intersection::Intersect { vertices } => {
let v1 = &vertices[0];
let v2 = &vertices[1];
let mut lon1 = v1.lon().to_radians();
let mut lon2 = v2.lon().to_radians();
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);
}
Some(Parallel::new(lat, &(lon1..lon2), camera, LabelOptions::OnSide, projection))
},
Intersection::Empty => {
None
},
}
}
}
pub struct Parallel {
// latitude of the parallel (in radians)
lat: f64,
// longitude ranges (in radians)
lon: Range<f64>,
// List of vertices
vertices: Vec<[f32; 2]>,
// Line vertices indices
indices: Vec<Range<usize>>,
label: Option<Label>,
}
use super::label::LabelOptions;
impl Parallel {
pub fn new(
lat: f64,
lon: &Range<f64>,
camera: &CameraViewPort,
label_options: LabelOptions,
projection: &ProjectionType
) -> Self {
let label = Label::from_parallel(lat, lon, label_options, camera, projection);
// Draw the full parallel
let vertices = if lon.end - lon.start > PI {
let mut vertices = line::parallel_arc::project(lat, lon.start, lon.start + PI, camera, projection);
vertices.append(&mut line::parallel_arc::project(lat, lon.start + PI, lon.end, camera, projection));
vertices
} else {
line::parallel_arc::project(lat, lon.start, lon.end, camera, projection)
};
/*let mut prev_v = [vertices[0].x as f32, vertices[0].y as f32];
let vertices: Vec<_> = std::iter::once(prev_v)
.chain(
vertices.into_iter().skip(1)
.filter_map(|v| {
let cur_v = [v.x as f32, v.y as f32];
if cur_v == prev_v {
None
} else {
prev_v = cur_v;
Some(cur_v)
}
})
)
.collect();
let indices = vec![0..vertices.len()];
*/
let mut start_idx = 0;
let mut indices = if vertices.len() >= 3 {
let v_iter = (1..(vertices.len() - 1))
.map(|i| &vertices[i]);
v_iter.clone()
.zip(v_iter.skip(1))
.enumerate()
.step_by(2)
.filter_map(|(i, (v1, v2))| {
if v1 == v2 {
None
} else {
let res = Some(start_idx..(i + 2));
start_idx = i + 2;
res
}
})
.collect()
} else {
vec![]
};
indices.push(start_idx..vertices.len());
Self {
vertices,
indices,
label,
lat,
lon: lon.clone()
}
}
#[inline]
pub fn get_lines_vertices(&self) -> Vec<&[[f32; 2]]> {
self.indices
.iter()
.map(|range| &self.vertices[range.start..range.end])
.collect()
}
#[inline]
pub fn get_label(&self) -> Option<&Label> {
self.label.as_ref()
}
}

View File

@@ -89,6 +89,7 @@ mod tile_fetcher;
mod time;
mod fifo_cache;
mod inertia;
mod grid;
use crate::{
camera::CameraViewPort, math::lonlat::LonLatT, shader::ShaderManager, time::DeltaTime,

View File

@@ -31,10 +31,6 @@ where
LonLatT(lon, lat)
}
pub fn from_radians(lon: Rad<S>, lat: Rad<S>) -> LonLatT<S> {
LonLatT::new(lon.into(), lat.into())
}
#[inline]
pub fn lon(&self) -> Angle<S> {
self.0

View File

@@ -96,12 +96,7 @@ impl Region {
Region::Polygon { polygon, .. } => {
let vertices = polygon.intersect_parallel(lat)
.iter()
.map(|v| {
let v = XYZWModel::new(v.y(), v.z(), v.x(), 1.0);
let l = v.lonlat();
al_core::info!(l);
v
})
.map(|v| XYZWModel::new(v.y(), v.z(), v.x(), 1.0))
.collect::<Vec<_>>();
if !vertices.is_empty() {

View File

@@ -1,40 +1,42 @@
use cgmath::BaseFloat;
use crate::healpix::cell::HEALPixCell;
use std::ops::Range;
pub struct SourceIndices(Box<[Range<u32>]>);
pub struct CooIdxVec([(u32, u32); 196608]);
use super::source::Source;
use crate::math::lonlat::LonLat;
impl CooIdxVec {
/// Build a coordinate index vector from a list of sky coordinates sorted by HEALPix value
pub fn new<T>(coo: &[T]) -> Self
where
T: LonLat<f32>
{
let mut coo_idx_vector: [(u32, u32); 196608] = [(u32::MAX, u32::MAX); 196608];
impl SourceIndices {
pub fn new(sources: &[Source]) -> Self {
let mut healpix_idx: Box<[Option<Range<u32>>]> = vec![None; 196608].into_boxed_slice();
for (idx, s) in coo.iter().enumerate() {
let lonlat = s.lonlat();
let hash = cdshealpix::nested::hash(7, lonlat.lon().to_radians() as f64, lonlat.lat().to_radians() as f64) as usize;
for (idx_source, s) in sources.iter().enumerate() {
let (lon, lat) = s.lonlat();
let idx = cdshealpix::nested::hash(7, lon as f64, lat as f64) as usize;
if let Some(ref mut healpix_idx) = &mut healpix_idx[idx] {
healpix_idx.end += 1;
if coo_idx_vector[hash].0 == u32::MAX {
let idx_u32 = idx as u32;
coo_idx_vector[hash] = (idx_u32, idx_u32 + 1);
} else {
healpix_idx[idx] = Some((idx_source as u32)..((idx_source + 1) as u32));
coo_idx_vector[hash].1 += 1;
}
}
let mut idx_source = 0;
let healpix_idx = healpix_idx
.iter()
.map(|idx| {
if let Some(r) = idx {
idx_source = r.end;
for coo_idx in coo_idx_vector.iter_mut() {
if coo_idx.0 == u32::MAX {
*coo_idx = (idx_source, idx_source);
} else {
idx_source = coo_idx.1;
}
}
r.start..r.end
} else {
idx_source..idx_source
}
})
.collect::<Vec<_>>();
SourceIndices(healpix_idx.into_boxed_slice())
CooIdxVec(coo_idx_vector)
}
pub fn get_source_indices(&self, cell: &HEALPixCell) -> Range<u32> {
@@ -46,8 +48,8 @@ impl SourceIndices {
let healpix_idx_start = (idx << off) as usize;
let healpix_idx_end = ((idx + 1) << off) as usize;
let idx_start_sources = self.0[healpix_idx_start].start;
let idx_end_sources = self.0[healpix_idx_end - 1].end;
let idx_start_sources = self.0[healpix_idx_start].0;
let idx_end_sources = self.0[healpix_idx_end - 1].1;
idx_start_sources..idx_end_sources
} else {
@@ -56,21 +58,25 @@ impl SourceIndices {
let off = 2 * (depth - 7);
let idx_start = (idx >> off) as usize;
let idx_start_sources = self.0[idx_start].start;
let idx_end_sources = self.0[idx_start].end;
let idx_start_sources = self.0[idx_start].0;
let idx_end_sources = self.0[idx_start].1;
idx_start_sources..idx_end_sources
}
}
// Returns k sources from a cell having depth <= 7
pub fn get_k_sources<'a>(
pub fn get_k_sources<'a, S, T>(
&self,
sources: &'a [f32],
sources: &'a [T],
cell: &HEALPixCell,
k: usize,
offset: usize,
) -> &'a [f32] {
) -> &'a [T]
where
S: BaseFloat,
T: LonLat<S>
{
let HEALPixCell(depth, idx) = *cell;
debug_assert!(depth <= 7);
@@ -79,8 +85,8 @@ impl SourceIndices {
let healpix_idx_start = (idx << off) as usize;
let healpix_idx_end = ((idx + 1) << off) as usize;
let idx_start_sources = self.0[healpix_idx_start].start as usize;
let idx_end_sources = self.0[healpix_idx_end - 1].end as usize;
let idx_start_sources = self.0[healpix_idx_start].0 as usize;
let idx_end_sources = self.0[healpix_idx_end - 1].1 as usize;
let num_sources = idx_end_sources - idx_start_sources;
@@ -90,8 +96,6 @@ impl SourceIndices {
idx_start_sources..idx_end_sources
};
let idx_f32 =
(idx_sources.start * Source::num_f32())..(idx_sources.end * Source::num_f32());
&sources[idx_f32]
&sources[idx_sources]
}
}

View File

@@ -1,4 +1,3 @@
use super::source::Source;
use crate::ShaderManager;
use al_api::resources::Resources;
@@ -163,7 +162,7 @@ impl Manager {
pub fn add_catalog<P: Projection>(
&mut self,
name: String,
sources: Box<[Source]>,
sources: Box<[LonLatT<f32>]>,
colormap: Colormap,
_shaders: &mut ShaderManager,
_camera: &CameraViewPort,
@@ -248,16 +247,16 @@ impl Manager {
}
}
use super::index::SourceIndices;
use super::index::CooIdxVec;
use crate::LonLatT;
pub struct Catalog {
colormap: Colormap,
num_instances: i32,
indices: SourceIndices,
indices: CooIdxVec,
alpha: f32,
strength: f32,
current_sources: Vec<f32>,
sources: Box<[f32]>,
sources: Box<[LonLatT<f32>]>,
vertex_array_object_catalog: VertexArrayObject,
}
use crate::healpix::cell::HEALPixCell;
@@ -273,14 +272,14 @@ impl Catalog {
fn new<P: Projection>(
gl: &WebGlContext,
colormap: Colormap,
sources: Box<[Source]>,
sources: Box<[LonLatT<f32>]>,
) -> Catalog {
let alpha = 1_f32;
let strength = 1_f32;
let indices = SourceIndices::new(&sources);
let indices = CooIdxVec::new(&sources);
let num_instances = sources.len() as i32;
let sources = unsafe { utils::transmute_boxed_slice(sources) };
//let sources = unsafe { utils::transmute_boxed_slice(sources) };
let vertex_array_object_catalog = {
#[cfg(feature = "webgl2")]
@@ -320,7 +319,7 @@ impl Catalog {
&[3],
&[0],
WebGl2RenderingContext::DYNAMIC_DRAW,
SliceData(sources.as_ref()),
SliceData(&[]),
)
// Set the element buffer
.add_element_buffer(
@@ -335,7 +334,7 @@ impl Catalog {
3,
"center",
WebGl2RenderingContext::DYNAMIC_DRAW,
SliceData(sources.as_ref()),
SliceData(&[]),
)
// Store the UV and the offsets of the billboard in a VBO
.add_array_buffer(
@@ -361,14 +360,12 @@ impl Catalog {
vao
};
let current_sources = vec![];
Self {
alpha,
strength,
colormap,
num_instances,
indices,
current_sources,
sources,
vertex_array_object_catalog,
@@ -402,7 +399,7 @@ impl Catalog {
fn update(&mut self, cells: &[HEALPixCell]) {
let num_sources_in_fov = self.get_total_num_sources_in_fov(cells) as f32;
// reset the sources in the frame
self.current_sources.clear();
let mut sources: Vec<_> = vec![];
// depth < 7
for cell in cells {
let delta_depth = (7_i8 - cell.depth() as i8).max(0);
@@ -416,26 +413,24 @@ impl Catalog {
let num_sources = ((num_sources_in_kernel_cell as f32) / num_sources_in_fov)
* MAX_SOURCES_PER_CATALOG;
let sources =
self.indices
.get_k_sources(&self.sources, &c, num_sources as usize, 0);
self.current_sources.extend(sources);
let k_sources = self.indices.get_k_sources(&self.sources, &c, num_sources as usize, 0);
sources.extend(k_sources);
}
}
}
//self.current_sources.shrink_to_fit();
self.num_instances = sources.len() as i32;
let sources = unsafe { utils::transmute_vec::<LonLatT<f32>, f32>(sources).unwrap() };
// Update the vertex buffer
self.num_instances = (self.current_sources.len() / Source::num_f32()) as i32;
#[cfg(feature = "webgl1")]
self.vertex_array_object_catalog
.bind_for_update()
.update_instanced_array("center", VecData(&self.current_sources));
.update_instanced_array("center", VecData(&sources));
#[cfg(feature = "webgl2")]
self.vertex_array_object_catalog
.bind_for_update()
.update_instanced_array("center", VecData(&self.current_sources));
.update_instanced_array("center", VecData(&sources));
}
fn draw(

View File

@@ -1,5 +1,3 @@
mod manager;
pub use manager::{Catalog, Manager};
mod source;
pub use source::Source;
mod index;

View File

@@ -1,64 +0,0 @@
#[repr(C, packed)]
pub struct Source {
pub x: f32,
pub y: f32,
pub z: f32,
}
impl Source {
pub const fn num_f32() -> usize {
std::mem::size_of::<Self>() / std::mem::size_of::<f32>()
}
}
impl PartialEq for Source {
fn eq(&self, other: &Self) -> bool {
self.x == other.x && self.y == other.y && self.z == other.z
}
}
impl Eq for Source {}
impl Clone for Source {
fn clone(&self) -> Self {
Source { x: self.x, y: self.y, z: self.z }
}
}
use cgmath::Vector3;
use crate::math::{self, angle::Angle, lonlat::LonLat};
impl Source {
pub fn new(lon: Angle<f32>, lat: Angle<f32> /*, mag: f32*/) -> Source {
let world_pos = math::lonlat::radec_to_xyz(lon, lat);
let x = world_pos.x;
let y = world_pos.y;
let z = world_pos.z;
Source {
x,
y,
z,
//lon,
//lat,
//mag
}
}
pub fn lonlat(&self) -> (f32, f32) {
let lonlat = Vector3::new(self.x, self.y, self.z).lonlat();
(lonlat.lon().to_radians(), lonlat.lat().to_radians())
}
}
use crate::math::angle::ArcDeg;
impl From<&[f32]> for Source {
fn from(data: &[f32]) -> Source {
let lon = ArcDeg(data[0]).into();
let lat = ArcDeg(data[1]).into();
//let mag = data[3];
Source::new(lon, lat /*, mag*/)
}
}

View File

@@ -1,842 +0,0 @@
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};
use crate::math::TWICE_PI;
use crate::math::angle;
use cgmath::Vector4;
use crate::camera::CameraViewPort;
use crate::ProjectionType;
use crate::LonLatT;
use crate::math::angle::ToAngle;
use al_api::grid::GridCfg;
use al_core::VertexArrayObject;
use al_api::color::ColorRGB;
use crate::Abort;
pub struct ProjetedGrid {
// Properties
pub color: ColorRGB,
pub opacity: f32,
pub show_labels: bool,
pub enabled: bool,
pub label_scale: f32,
// The vertex array object of the screen in NDC
vao: VertexArrayObject,
labels: Vec<Option<Label>>,
sizes: Vec<usize>,
offsets: Vec<usize>,
num_vertices: usize,
gl: WebGlContext,
// Render Text Manager
text_renderer: TextRenderManager,
fmt: angle::SerializeFmt,
}
use crate::shader::ShaderManager;
use al_core::VecData;
use al_core::WebGlContext;
use wasm_bindgen::JsValue;
use super::labels::RenderManager;
use super::TextRenderManager;
use al_api::resources::Resources;
impl ProjetedGrid {
pub fn new(
gl: &WebGlContext,
camera: &CameraViewPort,
resources: &Resources,
projection: &ProjectionType
) -> Result<ProjetedGrid, JsValue> {
let vao = {
let mut vao = VertexArrayObject::new(gl);
let vertices = vec![];
// layout (location = 0) in vec2 ndc_pos;
#[cfg(feature = "webgl2")]
vao.bind_for_update().add_array_buffer(
"ndc_pos",
2 * std::mem::size_of::<f32>(),
&[2],
&[0],
WebGl2RenderingContext::DYNAMIC_DRAW,
VecData::<f32>(&vertices),
);
#[cfg(feature = "webgl1")]
vao.bind_for_update().add_array_buffer(
2,
"ndc_pos",
WebGl2RenderingContext::DYNAMIC_DRAW,
VecData::<f32>(&vertices),
);
vao
};
let num_vertices = 0;
let labels = vec![];
let gl = gl.clone();
let sizes = vec![];
let offsets = vec![];
let text_renderer = TextRenderManager::new(gl.clone(), &resources)?;
let color = ColorRGB { r: 0.0, g: 1.0, b: 0.0 };
let opacity = 1.0;
let show_labels = true;
let enabled = false;
let label_scale = 1.0;
let fmt = angle::SerializeFmt::DMS;
let mut grid = ProjetedGrid {
color,
opacity,
show_labels,
enabled,
label_scale,
vao,
//vbo,
labels,
num_vertices,
sizes,
offsets,
gl,
text_renderer,
fmt,
};
// Initialize the vertices & labels
grid.force_update(camera, projection);
Ok(grid)
}
pub fn set_cfg(&mut self, new_cfg: GridCfg, camera: &CameraViewPort, projection: &ProjectionType) -> Result<(), JsValue> {
let GridCfg {
color,
opacity,
show_labels,
label_size,
enabled,
fmt,
} = new_cfg;
if let Some(color) = color {
self.color = color;
}
if let Some(opacity) = opacity {
self.opacity = opacity;
}
if let Some(show_labels) = show_labels {
self.show_labels = show_labels;
}
if let Some(fmt) = fmt {
self.fmt = fmt.into();
}
if let Some(enabled) = enabled {
self.enabled = enabled;
if enabled {
self.force_update(camera, projection);
}
}
if let Some(label_size) = label_size {
self.label_scale = label_size;
}
self.text_renderer.begin_frame();
for label in self.labels.iter().flatten() {
self.text_renderer.add_label(
&label.content,
&label.position.cast::<f32>().unwrap_abort(),
cgmath::Rad(label.rot as f32),
);
}
self.text_renderer.end_frame();
Ok(())
}
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, projection, &self.fmt);
self.offsets.clear();
self.sizes.clear();
let (vertices, labels): (Vec<Vec<Vector2<f64>>>, Vec<Option<Label>>) = lines
.into_iter()
.map(|line| {
if self.sizes.is_empty() {
self.offsets.push(0);
} else {
let last_offset = *self.offsets.last().unwrap_abort();
self.offsets.push(last_offset + self.sizes.last().unwrap_abort());
}
self.sizes.push(line.vertices.len());
(line.vertices, line.label)
})
.unzip();
self.labels = labels;
for label in self.labels.iter().flatten() {
self.text_renderer.add_label(
&label.content,
&label.position.cast::<f32>().unwrap_abort(),
cgmath::Rad(label.rot as f32),
);
}
let vertices = vertices
.into_iter()
.flatten()
.flat_map(|v| [v.x as f32, v.y as f32])
.collect::<Vec<_>>();
self.num_vertices = vertices.len() >> 1;
#[cfg(feature = "webgl2")]
self.vao.bind_for_update().update_array(
"ndc_pos",
WebGl2RenderingContext::DYNAMIC_DRAW,
VecData(&vertices),
);
#[cfg(feature = "webgl1")]
self.vao.bind_for_update().update_array(
"ndc_pos",
WebGl2RenderingContext::DYNAMIC_DRAW,
VecData(&vertices),
);
self.text_renderer.end_frame();
}
// Update the grid whenever the camera moved
pub fn update(&mut self, camera: &CameraViewPort, projection: &ProjectionType) {
if !self.enabled {
return;
}
self.force_update(camera, projection);
}
fn draw_lines_cpu(&self, camera: &CameraViewPort, shaders: &mut ShaderManager) {
self.gl.blend_func_separate(
WebGl2RenderingContext::SRC_ALPHA,
WebGl2RenderingContext::ONE_MINUS_SRC_ALPHA,
WebGl2RenderingContext::ONE,
WebGl2RenderingContext::ONE,
);
let shader = shaders
.get(
&self.gl,
&ShaderId(Cow::Borrowed("GridVS_CPU"), Cow::Borrowed("GridFS_CPU")),
)
.unwrap_abort();
let shader = shader.bind(&self.gl);
shader
.attach_uniforms_from(camera)
.attach_uniform("opacity", &self.opacity)
.attach_uniform("color", &self.color);
// The raster vao is bound at the lib.rs level
let drawer = shader.bind_vertex_array_object_ref(&self.vao);
for (offset, size) in self.offsets.iter().zip(self.sizes.iter()) {
if *size > 0 {
drawer.draw_arrays(WebGl2RenderingContext::LINES, *offset as i32, *size as i32);
}
}
}
pub fn draw(
&mut self,
camera: &CameraViewPort,
shaders: &mut ShaderManager,
) -> Result<(), JsValue> {
if self.enabled {
self.gl.enable(WebGl2RenderingContext::BLEND);
self.draw_lines_cpu(camera, shaders);
self.gl.disable(WebGl2RenderingContext::BLEND);
if self.show_labels {
self.text_renderer.draw(camera, &self.color, self.opacity, self.label_scale)?;
}
}
Ok(())
}
}
use crate::shader::ShaderId;
use core::num;
use std::borrow::Cow;
use std::path::is_separator;
use crate::math::{
angle::Angle,
};
use crate::camera::fov::FieldOfView;
use cgmath::InnerSpace;
use cgmath::Vector2;
use core::ops::Range;
#[derive(Debug)]
struct Label {
// The position
position: XYScreen,
// the string content
content: String,
// in radians
rot: f64,
}
impl Label {
fn from_meridian(
lonlat: &LonLatT<f64>,
camera: &CameraViewPort,
projection: &ProjectionType,
fmt: &angle::SerializeFmt
) -> Option<Self> {
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 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.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(lon.to_angle());
let position = if !fov.is_allsky() {
//let dim = ctx2d.measure_text(&content).unwrap_abort();
let dim = 100.0;
let k = ds * (dim * 0.5 + 10.0);
s1 + k
} else {
s1
};
// rot is between -PI and +PI
let rot = if ds.y > 0.0 {
ds.x.acos()
} else {
-ds.x.acos()
};
let rot = if ds.y > 0.0 {
if rot > HALF_PI {
-PI + rot
} else {
rot
}
} else if rot < -HALF_PI {
PI + rot
} else {
rot
};
Some(Label {
position,
content,
rot,
})
}
fn from_parallel(
lonlat: &LonLatT<f64>,
camera: &CameraViewPort,
projection: &ProjectionType,
) -> Option<Self> {
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 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(lonlat.lat());
let fov = camera.get_field_of_view();
let position = if !fov.is_allsky() && !fov.contains_pole() {
let dim = 100.0;
let k = ds * (dim * 0.5 + 10.0);
s1 + k
} else {
s1
};
// rot is between -PI and +PI
let rot = if ds.y > 0.0 {
ds.x.acos()
} else {
-ds.x.acos()
};
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
};
Some(Label {
position,
content,
rot,
})
}
}
#[derive(Debug)]
struct GridLine {
vertices: Vec<XYNDC>,
label: Option<Label>,
}
use cgmath::{Rad, Vector3};
//use math::angle::SerializeToString;
const PI: f64 = std::f64::consts::PI;
const HALF_PI: f64 = 0.5 * PI;
use crate::math::{
angle::ArcDeg,
};
impl GridLine {
fn meridian(
lon: f64,
lat: &Range<f64>,
sp: Option<&Vector2<f64>>,
camera: &CameraViewPort,
projection: &ProjectionType,
fmt: &angle::SerializeFmt
) -> Option<Self> {
let fov = camera.get_field_of_view();
if fov.contains_both_poles() {
let camera_center = camera.get_center();
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
};
let lonlat = LonLatT::new(
lon.to_angle(),
label_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 {
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 })
},
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 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 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 })
},
Intersection::Empty => {
None
},
}
}
}
fn parallel(
lat: f64,
camera: &CameraViewPort,
projection: &ProjectionType,
) -> Option<Self> {
let fov = camera.get_field_of_view();
if fov.get_bounding_box().get_lon_size() > PI {
// Longitude fov >= PI
let camera_center = camera.get_center();
let center_lon = camera_center.lon();
let lonlat = LonLatT::new(
center_lon,
lat.to_angle()
);
let label = Label::from_parallel(&lonlat, camera, projection);
// Draw the full parallel
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
let i = fov.intersects_parallel(lat);
match i {
Intersection::Included => {
let camera_center = camera.get_center();
let center_lon = camera_center.lon();
let lonlat = LonLatT::new(
center_lon,
lat.to_angle()
);
let label = Label::from_parallel(&lonlat, camera, projection);
// Draw the full parallel
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 } => {
let v1 = &vertices[0];
let v2 = &vertices[1];
let mut lon1 = v1.lon().to_radians();
let mut lon2 = v2.lon().to_radians();
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 line_vertices = line::parallel_arc::project(lat, lon1, lon2, camera, projection);
let label = Label::from_parallel(&v1.lonlat(), camera, projection);
Some(GridLine { vertices: line_vertices, label })
},
Intersection::Empty => {
None
},
}
}
}
}
const GRID_STEPS: &[f64] = &[
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,
projection: &ProjectionType,
fmt: &angle::SerializeFmt,
) -> Vec<GridLine> {
// Get the screen position of the nearest pole
let fov = camera.get_field_of_view();
let sp = if fov.contains_pole() {
if fov.contains_north_pole() {
// Project the pole into the screen
// This is an information needed
// for plotting labels
// screen north pole
projection.view_to_screen_space(
//&(system.to_icrs_j2000::<f64>() * Vector4::new(0.0, 1.0, 0.0, 1.0)),
&Vector4::new(0.0, 1.0, 0.0, 1.0),
camera,
)
} else {
// screen south pole
projection.view_to_screen_space(
//&(system.to_icrs_j2000::<f64>() * Vector4::new(0.0, -1.0, 0.0, 1.0)),
&Vector4::new(0.0, -1.0, 0.0, 1.0),
camera,
)
}
} else {
None
};
let bbox = camera.get_field_of_view().get_bounding_box();
let max_dim_px = camera.get_width().max(camera.get_height()) as f64;
let step_line_px = max_dim_px * 0.2;
let step_lon_precised = (bbox.get_lon_size() as f64) * step_line_px / (camera.get_width() as f64);
let step_lat_precised = (bbox.get_lat_size() as f64) * step_line_px / (camera.get_height() as f64);
// Select the good step with a binary search
let step_lon = select_fixed_step(step_lon_precised);
let step_lat = select_fixed_step(step_lat_precised);
let mut lines = vec![];
// Add meridians
let mut theta = bbox.lon_min() - (bbox.lon_min() % step_lon);
let mut stop_theta = bbox.lon_max();
if bbox.all_lon() {
stop_theta -= 1e-3;
}
while theta < stop_theta {
if let Some(line) =
GridLine::meridian(theta, &bbox.get_lat(), sp.as_ref(), camera, projection, fmt)
{
lines.push(line);
}
theta += step_lon;
}
let mut alpha = bbox.lat_min() - (bbox.lat_min() % step_lat);
if alpha == -HALF_PI {
alpha += step_lat;
}
let stop_alpha = bbox.lat_max();
while alpha < stop_alpha {
if let Some(line) = GridLine::parallel(alpha, camera, projection) {
lines.push(line);
}
alpha += step_lat;
}
lines
}
/*fn select_grid_step(fov: f64, max_lines: usize) -> f64 {
// Select the best meridian grid step
let mut i = 0;
let mut step = GRID_STEPS[0];
while i < GRID_STEPS.len() {
if fov >= GRID_STEPS[i] {
let num_meridians_in_fov = (fov / GRID_STEPS[i]) as usize;
if num_meridians_in_fov >= max_lines - 1 {
//let idx_grid = if i == 0 { 0 } else { i - 1 };
//step = GRID_STEPS[idx_grid];
step = GRID_STEPS[i];
break;
}
}
step = GRID_STEPS[i];
i += 1;
}
step
}*/
fn select_fixed_step(fov: f64) -> f64 {
match GRID_STEPS.binary_search_by(|v| {
v.partial_cmp(&fov).expect("Couldn't compare values, maybe because the fov given is NaN")
}) {
Ok(idx) => GRID_STEPS[idx],
Err(idx) => {
if idx == 0 {
GRID_STEPS[0]
} else if idx == GRID_STEPS.len() {
GRID_STEPS[idx - 1]
} else {
let a = GRID_STEPS[idx];
let b = GRID_STEPS[idx - 1];
if a - fov > fov - b {
b
} else {
a
}
}
}
}
}

View File

@@ -1,339 +0,0 @@
use al_core::shader::Shader;
use al_core::text::LetterTexPosition;
use al_core::texture::Texture2D;
use al_core::webgl_ctx::WebGlContext;
use al_core::VertexArrayObject;
use std::collections::HashMap;
pub trait RenderManager {
fn begin_frame(&mut self);
fn end_frame(&mut self);
fn draw(&mut self, camera: &CameraViewPort, color: &ColorRGB, opacity: f32, scale: f32) -> Result<(), JsValue>;
}
pub struct TextRenderManager {
gl: WebGlContext,
shader: Shader,
vao: VertexArrayObject,
font_texture: Texture2D,
letters: HashMap<char, LetterTexPosition>,
#[cfg(feature = "webgl2")]
vertices: Vec<f32>,
#[cfg(feature = "webgl1")]
pos: Vec<f32>,
#[cfg(feature = "webgl1")]
tx: Vec<f32>,
indices: Vec<u16>,
}
use al_core::VecData;
use cgmath::{Rad, Vector2};
use wasm_bindgen::JsValue;
use crate::camera::CameraViewPort;
use al_api::color::ColorRGB;
use web_sys::WebGl2RenderingContext;
use al_api::resources::Resources;
impl TextRenderManager {
/// Init the buffers, VAO and shader
pub fn new(gl: WebGlContext, resources: &Resources) -> Result<Self, JsValue> {
// Create the VAO for the screen
#[cfg(feature = "webgl1")]
let shader = Shader::new(
&gl,
include_str!("../../../glsl/webgl1/text/text_vertex.glsl"),
include_str!("../../../glsl/webgl1/text/text_frag.glsl"),
)?;
#[cfg(feature = "webgl2")]
let shader = Shader::new(
&gl,
include_str!("../../../glsl/webgl2/text/text_vertex.glsl"),
include_str!("../../../glsl/webgl2/text/text_frag.glsl"),
)?;
let mut vao = VertexArrayObject::new(&gl);
#[cfg(feature = "webgl2")]
let vertices = vec![];
#[cfg(feature = "webgl1")]
let pos = vec![];
#[cfg(feature = "webgl1")]
let tx = vec![];
let indices = vec![];
#[cfg(feature = "webgl2")]
vao.bind_for_update()
.add_array_buffer(
"vertices",
7 * std::mem::size_of::<f32>(),
&[2, 2, 2, 1],
&[0, 2 * std::mem::size_of::<f32>(), 4 * std::mem::size_of::<f32>(), 6 * std::mem::size_of::<f32>()],
WebGl2RenderingContext::DYNAMIC_DRAW,
VecData::<f32>(&vertices),
)
// Set the element buffer
.add_element_buffer(
WebGl2RenderingContext::DYNAMIC_DRAW,
VecData::<u16>(&indices),
);
#[cfg(feature = "webgl1")]
vao.bind_for_update()
.add_array_buffer(
2,
"pos",
WebGl2RenderingContext::DYNAMIC_DRAW,
VecData::<f32>(&pos),
)
.add_array_buffer(
2,
"tx",
WebGl2RenderingContext::DYNAMIC_DRAW,
VecData::<f32>(&tx),
)
// Set the element buffer
.add_element_buffer(
WebGl2RenderingContext::DYNAMIC_DRAW,
VecData::<u16>(&indices),
);
/*let al_core::text::Font {
bitmap,
letters,
..
} = al_core::text::rasterize_font(text_size);*/
let letters_filename = resources.get_filename("letters").ok_or(JsValue::from_str("letters loading failed"))?;
let letters_content = resources.get_filename("letters_metadata").ok_or(JsValue::from_str("letters metadata loading failed"))?;
let letters = serde_json::from_str(&letters_content).map_err(|_| JsValue::from_str("serde json failed"))?;
let font_texture = Texture2D::create_from_path::<_, al_core::image::format::RGBA8U>(
&gl,
"letters",
letters_filename,
&[
(
WebGl2RenderingContext::TEXTURE_MIN_FILTER,
WebGl2RenderingContext::LINEAR,
),
(
WebGl2RenderingContext::TEXTURE_MAG_FILTER,
WebGl2RenderingContext::LINEAR,
),
// Prevents s-coordinate wrapping (repeating)
(
WebGl2RenderingContext::TEXTURE_WRAP_S,
WebGl2RenderingContext::CLAMP_TO_EDGE,
),
// Prevents t-coordinate wrapping (repeating)
(
WebGl2RenderingContext::TEXTURE_WRAP_T,
WebGl2RenderingContext::CLAMP_TO_EDGE,
),
],
)?;
Ok(Self {
gl,
shader,
vao,
letters,
font_texture,
#[cfg(feature = "webgl2")]
vertices: vec![],
#[cfg(feature = "webgl1")]
pos: vec![],
#[cfg(feature = "webgl1")]
tx: vec![],
indices: vec![],
})
}
pub fn add_label<A: Into<Rad<f32>>>(
&mut self,
text: &str,
screen_pos: &Vector2<f32>,
angle_rot: A,
) {
// 1. Loop over the text chars to compute the size of the text to plot
let (mut w, mut h) = (0, 0);
for c in text.chars() {
if let Some(l) = self.letters.get(&c) {
w += l.x_advance;
h = std::cmp::max(h, l.h);
}
}
let x_pos = -(w as f32) * 0.5;
let y_pos = -(h as f32) * 0.5;
let f_tex_size = &self.font_texture.get_size();
let mut x_offset = 0.0;
let rot: Rad<_> = angle_rot.into();
for c in text.chars() {
if let Some(l) = self.letters.get(&c) {
let u1 = (l.x_min as f32) / (f_tex_size.0 as f32);
let v1 = (l.y_min as f32) / (f_tex_size.1 as f32);
let u2 = (l.x_min as f32 + l.w as f32) / (f_tex_size.0 as f32);
let v2 = (l.y_min as f32) / (f_tex_size.1 as f32);
let u3 = (l.x_min as f32 + l.w as f32) / (f_tex_size.0 as f32);
let v3 = (l.y_min as f32 + l.h as f32) / (f_tex_size.1 as f32);
let u4 = (l.x_min as f32) / (f_tex_size.0 as f32);
let v4 = (l.y_min as f32 + l.h as f32) / (f_tex_size.1 as f32);
#[cfg(feature = "webgl2")]
let num_vertices = (self.vertices.len() / 7) as u16;
#[cfg(feature = "webgl1")]
let num_vertices = (self.pos.len() / 2) as u16;
let xmin = l.bound_xmin;
let ymin = l.bound_ymin + (l.h as f32);
#[cfg(feature = "webgl2")]
self.vertices.extend([
x_pos + x_offset + xmin,
y_pos - ymin,
u1,
v1,
screen_pos.x,
screen_pos.y,
rot.0,
x_pos + x_offset + (l.w as f32) + xmin,
y_pos - ymin,
u2,
v2,
screen_pos.x,
screen_pos.y,
rot.0,
x_pos + x_offset + (l.w as f32) + xmin,
y_pos + (l.h as f32) - ymin,
u3,
v3,
screen_pos.x,
screen_pos.y,
rot.0,
x_pos + x_offset + xmin,
y_pos + (l.h as f32) - ymin,
u4,
v4,
screen_pos.x,
screen_pos.y,
rot.0,
]);
#[cfg(feature = "webgl1")]
self.pos.extend([
x_pos + x_offset + xmin,
y_pos - ymin,
x_pos + x_offset + (l.w as f32) + xmin,
y_pos - ymin,
x_pos + x_offset + (l.w as f32) + xmin,
y_pos + (l.h as f32) - ymin,
x_pos + x_offset + xmin,
y_pos + (l.h as f32) - ymin,
]);
#[cfg(feature = "webgl1")]
self.tx.extend([u1, v1, u2, v2, u3, v3, u4, v4]);
self.indices.extend([
num_vertices,
num_vertices + 2,
num_vertices + 1,
num_vertices,
num_vertices + 3,
num_vertices + 2,
]);
x_offset += l.x_advance as f32;
}
}
}
pub fn get_width_pixel_size(&self, content: &str) -> f64 {
let mut w = 0;
for c in content.chars() {
if let Some(l) = self.letters.get(&c) {
w += l.x_advance;
}
}
w as f64
}
}
impl RenderManager for TextRenderManager {
fn begin_frame(&mut self) {
#[cfg(feature = "webgl2")]
self.vertices.clear();
#[cfg(feature = "webgl1")]
self.pos.clear();
#[cfg(feature = "webgl1")]
self.tx.clear();
self.indices.clear();
}
fn end_frame(&mut self) {
// update to the GPU
#[cfg(feature = "webgl2")]
self.vao
.bind_for_update()
.update_array(
"vertices",
WebGl2RenderingContext::DYNAMIC_DRAW,
VecData(&self.vertices),
)
.update_element_array(WebGl2RenderingContext::DYNAMIC_DRAW, VecData(&self.indices));
#[cfg(feature = "webgl1")]
self.vao
.bind_for_update()
.update_array(
"pos",
WebGl2RenderingContext::DYNAMIC_DRAW,
VecData(&self.pos),
)
.update_array(
"tx",
WebGl2RenderingContext::DYNAMIC_DRAW,
VecData(&self.tx),
)
.update_element_array(WebGl2RenderingContext::DYNAMIC_DRAW, VecData(&self.indices));
}
fn draw(&mut self, camera: &CameraViewPort, color: &ColorRGB, opacity: f32, scale: f32) -> Result<(), JsValue> {
self.gl.enable(WebGl2RenderingContext::BLEND);
self.gl.blend_func_separate(
WebGl2RenderingContext::SRC_ALPHA,
WebGl2RenderingContext::ONE_MINUS_SRC_ALPHA,
WebGl2RenderingContext::ONE,
WebGl2RenderingContext::ONE,
); // premultiplied alpha
self.gl.disable(WebGl2RenderingContext::CULL_FACE);
{
let shader = self.shader.bind(&self.gl);
shader.attach_uniform("u_sampler_font", &self.font_texture) // Font letters texture
.attach_uniform("u_screen_size", &camera.get_screen_size())
.attach_uniform("u_dpi", &camera.get_dpi())
.attach_uniform("u_color", &color)
.attach_uniform("u_opacity", &opacity)
.attach_uniform("u_scale", &scale)
.bind_vertex_array_object_ref(&self.vao)
.draw_elements_with_i32(
WebGl2RenderingContext::TRIANGLES,
Some(self.indices.len() as i32),
WebGl2RenderingContext::UNSIGNED_SHORT,
0,
);
}
self.gl.enable(WebGl2RenderingContext::CULL_FACE);
self.gl.disable(WebGl2RenderingContext::BLEND);
Ok(())
}
}

View File

@@ -8,22 +8,40 @@ use al_core::shader::Shader;
use crate::Abort;
use crate::math::projection::coo_space::XYNDC;
use al_api::color::ColorRGBA;
use super::Renderer;
struct LineMeta {
use lyon::algorithms::{
math::point,
path::Path,
length::approximate_length,
measure::{PathMeasurements, SampleType},
};
use al_core::{info, inforec, log};
struct Meta {
color: ColorRGBA,
style: Style,
thickness: f32,
off_idx: usize,
num_idx: usize,
off_indices: usize,
num_indices: usize,
}
pub struct RasterizedLinesRenderManager {
#[derive(Clone)]
pub enum Style {
None,
Dashed,
Dotted
}
pub struct RasterizedLineRenderer {
gl: WebGlContext,
shader: Shader,
vao: VertexArrayObject,
vertices: Vec<f32>,
indices: Vec<u16>,
meta: Vec<LineMeta>,
meta: Vec<Meta>,
}
use wasm_bindgen::JsValue;
use cgmath::Vector2;
@@ -32,13 +50,11 @@ use web_sys::WebGl2RenderingContext;
use crate::Color;
use crate::camera::CameraViewPort;
use lyon::math::point;
use lyon::path::Path;
use lyon::tessellation::*;
impl RasterizedLinesRenderManager {
impl RasterizedLineRenderer {
/// Init the buffers, VAO and shader
pub fn new(gl: WebGlContext, camera: &CameraViewPort) -> Result<Self, JsValue> {
pub fn new(gl: &WebGlContext) -> Result<Self, JsValue> {
let vertices = vec![];
let indices = vec![];
// Create the VAO for the screen
@@ -67,6 +83,7 @@ impl RasterizedLinesRenderManager {
.unbind();
let meta = vec![];
let gl = gl.clone();
Ok(Self {
gl,
shader,
@@ -77,92 +94,152 @@ impl RasterizedLinesRenderManager {
})
}
pub fn add_path(&mut self, path: &[XYNDC], thickness: f32, color: &ColorRGBA) {
let mut builder = Path::builder();
if path.is_empty() {
return;
pub fn add_paths<'a>(&mut self, paths: impl Iterator<Item=&'a [[f32; 2]]>, thickness: f32, color: &ColorRGBA, style: &Style) {
let mut path_builder = Path::builder();
let clamp_ndc_vertex = |v: &[f32; 2]| -> [f32; 2] {
let x = v[0].clamp(-2.0, 2.0);
let y = v[1].clamp(-2.0, 2.0);
[x, y]
};
match &style {
Style::None => {
for line in paths {
if !line.is_empty() {
let v = clamp_ndc_vertex(&line[0]);
path_builder.begin(point(v[0], v[1]));
for v in line.iter().skip(1) {
let v = clamp_ndc_vertex(v);
path_builder.line_to(point(v[0], v[1]));
}
path_builder.end(false);
}
}
},
Style::Dashed => {
for line in paths {
if !line.is_empty() {
let mut line_path_builder = Path::builder();
let v = clamp_ndc_vertex(&line[0]);
line_path_builder.begin(point(v[0], v[1]));
for v in line.iter().skip(1) {
let v = clamp_ndc_vertex(v);
line_path_builder.line_to(point(v[0], v[1]));
}
line_path_builder.end(false);
let path = line_path_builder.build();
// Build the acceleration structure.
let measurements = PathMeasurements::from_path(&path, 1e-2);
let mut sampler = measurements.create_sampler(&path, SampleType::Normalized);
let path_len = sampler.length();
let step = 1e-2 / path_len;
for i in (0..((1.0/step) as usize)).step_by(2) {
let start = (i as f32) * step;
let end = (i as f32 + 1.0) * step;
sampler.split_range(start..end, &mut path_builder);
}
}
}
},
Style::Dotted => {
}
}
builder.begin(point(path[0].x as f32, path[0].y as f32));
for p in path.iter().skip(1) {
builder.line_to(point(p.x as f32, p.y as f32));
}
builder.end(true);
let path = builder.build();
let p = path_builder.build();
// Let's use our own custom vertex type instead of the default one.
// Will contain the result of the tessellation.
let mut geometry: VertexBuffers<[f32; 2], u16> = VertexBuffers::new();
let mut tessellator = FillTessellator::new();
{
let mut tessellator = StrokeTessellator::new();
// Compute the tessellation.
tessellator.tessellate_path(
&path,
&FillOptions::default(),
&mut BuffersBuilder::new(&mut geometry, |vertex: FillVertex| {
tessellator.tessellate(
&p,
&StrokeOptions::default()
.with_line_width(thickness),
&mut BuffersBuilder::new(&mut geometry, |vertex: StrokeVertex| {
vertex.position().to_array()
}),
).unwrap_abort();
}
let num_vertices = (self.vertices.len() / 2) as u16;
self.vertices.extend(geometry.vertices.iter().flatten());
for i in geometry.indices.iter_mut() {
*i += num_vertices;
let VertexBuffers {vertices, mut indices} = geometry;
let num_vertices = (self.vertices.len() / 2) as u16;
for idx in indices.iter_mut() {
*idx += num_vertices;
}
let num_idx = geometry.indices.len();
let off_idx = self.indices.len();
self.indices.extend(geometry.indices);
let num_indices = indices.len();
let off_indices = self.indices.len();
self.vertices.extend(vertices.iter().flatten());
self.indices.extend(indices.iter());
self.meta.push(
LineMeta {
off_idx,
num_idx,
Meta {
off_indices,
num_indices,
thickness,
color: color.clone(),
style: style.clone()
}
);
}
pub fn draw(&mut self, camera: &CameraViewPort) -> Result<(), JsValue> {
self.gl.enable(WebGl2RenderingContext::BLEND);
self.gl.blend_func_separate(
WebGl2RenderingContext::SRC_ALPHA,
WebGl2RenderingContext::ONE_MINUS_SRC_ALPHA,
WebGl2RenderingContext::ONE,
WebGl2RenderingContext::ONE,
);
self.gl.disable(WebGl2RenderingContext::CULL_FACE);
fn begin_frame(&mut self) {
let shader = self.shader.bind(&self.gl);
for meta in self.meta.iter() {
shader
.attach_uniform("u_color", &meta.color) // Strengh of the kernel
.bind_vertex_array_object_ref(&self.vao)
.draw_elements_with_i32(
WebGl2RenderingContext::TRIANGLES,
Some(meta.num_indices as i32),
WebGl2RenderingContext::UNSIGNED_SHORT,
((meta.off_indices as usize) * std::mem::size_of::<u16>()) as i32
);
}
self.gl.enable(WebGl2RenderingContext::CULL_FACE);
self.gl.disable(WebGl2RenderingContext::BLEND);
Ok(())
}
}
impl Renderer for RasterizedLineRenderer {
fn begin(&mut self) {
self.vertices.clear();
self.indices.clear();
self.meta.clear();
}
fn end_frame(&mut self) {
fn end(&mut self) {
// update to the GPU
self.vao.bind_for_update()
.update_array("ndc_pos", WebGl2RenderingContext::STREAM_DRAW, VecData(&self.vertices))
.update_element_array(WebGl2RenderingContext::STREAM_DRAW, VecData(&self.indices));
}
fn draw(&mut self, window_size: &Vector2<f32>) -> Result<(), JsValue> {
self.gl.enable(WebGl2RenderingContext::BLEND);
self.gl.blend_func(WebGl2RenderingContext::ONE, WebGl2RenderingContext::ONE_MINUS_SRC_ALPHA); // premultiplied alpha
let shader = self.shader.bind(&self.gl);
self.vao.bind(&shader);
for meta in self.meta.iter() {
shader
.attach_uniform("u_color", &meta.color) // Strengh of the kernel
.attach_uniform("u_screen_size", window_size);
self.gl.draw_elements_with_i32(
WebGl2RenderingContext::TRIANGLES,
meta.num_idx as i32,
WebGl2RenderingContext::UNSIGNED_SHORT,
(meta.off_idx as i32) * (std::mem::size_of::<u16>() as i32)
);
}
self.gl.disable(WebGl2RenderingContext::BLEND);
Ok(())
}
}
}

View File

@@ -14,12 +14,16 @@ use crate::LonLatT;
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[
// * (lon1 - lon2).abs() < PI
pub fn project(lat: f64, mut lon1: f64, mut lon2: f64, camera: &CameraViewPort, projection: &ProjectionType) -> Vec<XYNDC> {
// * Remark
//
// - Parallel latitude between [-0.5*pi; 0.5*pi]
// - First longitude between [0; 2\pi[
// - Second lon length between [0; 2\pi[
// - (lon1 - lon2).abs() < PI
//
// * Returns
// A list of lines vertices
pub fn project(lat: f64, mut lon1: f64, mut lon2: f64, camera: &CameraViewPort, projection: &ProjectionType) -> Vec<[f32; 2]> {
let mut vertices = vec![];
let lon_len = crate::math::sph_geom::distance_from_two_lon(lon1, lon2);
@@ -84,7 +88,7 @@ fn sub_valid_domain(lat: f64, mut valid_lon: f64, mut invalid_lon: f64, projecti
}
fn subdivide_multi(
vertices: &mut Vec<XYNDC>,
vertices: &mut Vec<[f32; 2]>,
lat: f64,
lon_s: f64,
@@ -103,13 +107,12 @@ fn subdivide_multi(
}
}
fn subdivide(
vertices: &mut Vec<XYNDC>,
vertices: &mut Vec<[f32; 2]>,
lat: f64,
lon1: f64,
lon2: f64,
lon2: f64,
camera: &CameraViewPort,
projection: &ProjectionType,
@@ -127,64 +130,70 @@ fn subdivide(
(Some(p1), Some(pm), Some(p2)) => {
let ab = pm - p1;
let bc = p2 - pm;
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);
let vectors_nearly_colinear = theta.abs() < MAX_ANGLE_BEFORE_SUBDIVISION;
if vectors_nearly_colinear {
// Check if ab and bc are colinear
if crate::math::vector::det(&ab, &bc).abs() < 1e-2 {
vertices.push(p1);
vertices.push(p2);
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.x as f32, p1.y as f32]);
vertices.push([p2.x as f32, p2.y as f32]);
} else {
// not colinear
vertices.push(p1);
vertices.push(pm);
// not colinear but enough to stop
vertices.push([p1.x as f32, p1.y as f32]);
vertices.push([pm.x as f32, pm.y as f32]);
vertices.push(pm);
vertices.push(p2);
}
} else if ab_l.min(bc_l) / ab_l.max(bc_l) < 0.1 {
if ab_l < bc_l {
vertices.push(p1);
vertices.push(pm);
} else {
vertices.push(pm);
vertices.push(p2);
vertices.push([pm.x as f32, pm.y as f32]);
vertices.push([p2.x as f32, p2.y as f32]);
}
} else {
// Subdivide a->b and b->c
if !subdivide(
vertices,
lat,
lon1,
lon0,
camera,
projection,
iter + 1
) {
vertices.push(p1);
vertices.push(pm);
}
if !subdivide(
vertices,
lat,
lon0,
lon2,
camera,
projection,
iter + 1
) {
vertices.push(pm);
vertices.push(p2);
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.x as f32, p1.y as f32]);
vertices.push([pm.x as f32, pm.y as f32]);
} else {
vertices.push([pm.x as f32, pm.y as f32]);
vertices.push([p2.x as f32, p2.y as f32]);
}
} else {
// Subdivide a->b and b->c
if !subdivide(
vertices,
lat,
lon1,
lon0,
camera,
projection,
iter + 1
) {
vertices.push([p1.x as f32, p1.y as f32]);
vertices.push([pm.x as f32, pm.y as f32]);
}
if !subdivide(
vertices,
lat,
lon0,
lon2,
camera,
projection,
iter + 1
) {
vertices.push([pm.x as f32, pm.y as f32]);
vertices.push([p2.x as f32, p2.y as f32]);
}
}
}
true
},
_ => false

View File

@@ -6,8 +6,13 @@ use al_core::{WebGlContext, VertexArrayObject, VecData};
use moclib::{moc::{RangeMOCIterator, RangeMOCIntoIterator}, elem::cell::Cell};
use std::{borrow::Cow, collections::HashMap};
use web_sys::WebGl2RenderingContext;
use crate::renderable::line::RasterizedLineRenderer;
use crate::math::angle::ToAngle;
use crate::math::lonlat::LonLatT;
use cgmath::InnerSpace;
use al_api::coo_system::CooSystem;
use al_api::{coo_system::CooSystem, color::ColorRGBA};
use std::ops::Range;
type MOCIdx = String;
use crate::Abort;
@@ -176,33 +181,37 @@ pub fn rasterize_hpx_cell(cell: &HEALPixCell, n_segment_by_side: usize, camera:
}
struct HierarchicalHpxCoverage {
full_moc: HEALPixCoverage,
partially_degraded_moc: HEALPixCoverage,
full_res_depth: u8,
hierarchy: Vec<HEALPixCoverage>,
}
impl HierarchicalHpxCoverage {
fn new(full_moc: HEALPixCoverage) -> Self {
let partially_degraded_moc = HEALPixCoverage(full_moc.degraded(full_moc.depth_max() >> 1));
fn new(moc: HEALPixCoverage) -> Self {
let hierarchy = (0..=moc.depth()).map(|d| {
HEALPixCoverage(moc.degraded(d))
})
.collect();
let full_res_depth = moc.depth();
Self {
full_moc,
partially_degraded_moc
hierarchy,
full_res_depth,
}
}
fn get(&self, depth: u8) -> &HEALPixCoverage {
if depth <= self.partially_degraded_moc.depth_max() {
&self.partially_degraded_moc
} else {
&self.full_moc
}
// retrieve the full moc
let depth = self.full_res_depth.min(depth);
&self.hierarchy[depth as usize]
}
fn get_full_moc(&self) -> &HEALPixCoverage {
&self.full_moc
&self.hierarchy[self.full_res_depth as usize]
}
}
use crate::ProjectionType;
use super::line;
impl MOC {
pub fn new(gl: &WebGlContext) -> Self {
let mut vao = VertexArrayObject::new(gl);
@@ -292,7 +301,7 @@ impl MOC {
} else {
let moc = if params.is_adaptative_display() {
let partially_degraded_moc = coverage.get(depth);
fov_moc.intersection(partially_degraded_moc).degraded(depth)
fov_moc.intersection(partially_degraded_moc)
} else {
let full_moc = coverage.get_full_moc();
fov_moc.intersection(full_moc)
@@ -303,7 +312,6 @@ impl MOC {
(layer.clone(), moc)
}).collect();
}
pub fn insert(&mut self, moc: HEALPixCoverage, params: al_api::moc::MOC, camera: &CameraViewPort, projection: &ProjectionType) {
@@ -314,7 +322,7 @@ impl MOC {
self.layers.push(key);
self.recompute_draw_mocs(camera);
self.update_buffers(camera, projection);
//self.update(camera, projection, line_renderer);
// Compute or retrieve the mocs to render
}
@@ -324,7 +332,8 @@ impl MOC {
self.mocs.remove(key);
let moc = self.params.remove(key);
if let Some(index) = self.layers.iter().position(|x| x == key) {
moc
/*if let Some(index) = self.layers.iter().position(|x| x == key) {
self.layers.remove(index);
self.num_indices.remove(index);
self.first_idx.remove(index);
@@ -333,15 +342,15 @@ impl MOC {
moc
} else {
None
}
}*/
}
pub fn set_params(&mut self, params: al_api::moc::MOC, camera: &CameraViewPort, projection: &ProjectionType) -> Option<al_api::moc::MOC> {
pub fn set_params(&mut self, params: al_api::moc::MOC, camera: &CameraViewPort, projection: &ProjectionType, line_renderer: &mut RasterizedLineRenderer) -> Option<al_api::moc::MOC> {
let key = params.get_uuid().clone();
let old_params = self.params.insert(key, params);
self.recompute_draw_mocs(camera);
self.update_buffers(camera, projection);
self.update(camera, projection, line_renderer);
old_params
}
@@ -351,11 +360,18 @@ impl MOC {
self.mocs.get(key).map(|coverage| coverage.get_full_moc())
}
fn update_buffers(&mut self, camera: &CameraViewPort, projection: &ProjectionType) {
self.indices.clear();
fn update(&mut self, camera: &CameraViewPort, projection: &ProjectionType, line_renderer: &mut RasterizedLineRenderer) {
// Compute or retrieve the mocs to render
self.view.refresh(camera.get_tile_depth(), CooSystem::ICRS, camera);
if self.view.has_view_changed() {
self.recompute_draw_mocs(camera);
}
/*self.indices.clear();
self.position.clear();
self.num_indices.clear();
self.first_idx.clear();
self.first_idx.clear();*/
let mut idx_off = 0;
@@ -363,6 +379,50 @@ impl MOC {
let moc = self.adaptative_mocs.get(layer).unwrap_abort();
let params = self.params.get(layer).unwrap_abort();
if let Some(moc) = moc {
let mut indices: Vec<Range<usize>> = vec![];
let vertices: Vec<_> = moc.border_elementary_edges()
.filter_map(|((ra1, dec1), (ra2, dec2))| {
//let vert = crate::renderable::line::great_circle_arc::project(ra1, dec1, ra2, dec2, camera, projection);
let u = crate::math::lonlat::proj(&LonLatT::new(ra1.to_angle(), dec1.to_angle()), projection, camera);
let v = crate::math::lonlat::proj(&LonLatT::new(ra2.to_angle(), dec2.to_angle()), projection, camera);
if let (Some(u), Some(v)) = (u, v) {
let uv = v - u;
let uv_mag2 = uv.dot(uv);
// Do not draw to long lines on the NDC space
if uv_mag2 >= 0.04 {
None
} else {
let off = if indices.is_empty() {
0
} else {
(*(indices.last().unwrap())).end
};
indices.push(off..(off + 2));
Some(vec![
[u.x as f32, u.y as f32],
[v.x as f32, v.y as f32]
])
}
} else {
None
}
})
.flatten()
.collect();
if !vertices.is_empty() {
let paths = indices.iter().map(|r| {
&vertices[r.start..r.end]
});
line_renderer.add_paths(paths, 0.005, params.get_color(), &line::Style::None);
}
} else {
self.first_idx.push(self.indices.len());
self.num_indices.push(0);
}
/*
if let Some(moc) = moc {
let depth_max = moc.depth();
let mut indices_moc = vec![];
@@ -370,7 +430,7 @@ impl MOC {
let positions_moc = (&(moc.0)).into_range_moc_iter()
.cells()
.filter_map(|Cell { depth, idx, .. }| {
let delta_depth = depth_max - depth;
let delta_depth = ((depth_max - depth) as i32 - 2).max(0) as u8;
let n_segment_by_side = (1 << delta_depth) as usize;
let cell = HEALPixCell(depth, idx);
@@ -475,9 +535,11 @@ impl MOC {
self.first_idx.push(self.indices.len());
self.num_indices.push(0);
}
*/
}
self.vao.bind_for_update()
/*self.vao.bind_for_update()
.update_array(
"ndc_pos",
WebGl2RenderingContext::DYNAMIC_DRAW,
@@ -486,22 +548,7 @@ impl MOC {
.update_element_array(
WebGl2RenderingContext::DYNAMIC_DRAW,
VecData::<u32>(&self.indices),
);
}
pub fn update(&mut self, camera: &CameraViewPort, projection: &ProjectionType) {
if self.is_empty() {
return;
}
// Compute or retrieve the mocs to render
self.view.refresh(camera.get_tile_depth(), CooSystem::ICRS, camera);
if self.view.has_view_changed() {
self.recompute_draw_mocs(camera);
}
self.update_buffers(camera, projection);
);*/
}
pub fn is_empty(&self) -> bool {
@@ -509,15 +556,19 @@ impl MOC {
}
pub fn draw(
&self,
&mut self,
shaders: &mut ShaderManager,
camera: &CameraViewPort,
projection: &ProjectionType,
line_renderer: &mut RasterizedLineRenderer
) {
if self.is_empty() {
return;
}
self.gl.blend_func_separate(
self.update(camera, projection, line_renderer);
/*self.gl.blend_func_separate(
WebGl2RenderingContext::SRC_ALPHA,
WebGl2RenderingContext::ONE_MINUS_SRC_ALPHA,
WebGl2RenderingContext::ONE,
@@ -557,6 +608,6 @@ impl MOC {
//}
}
self.gl.disable(WebGl2RenderingContext::BLEND);
self.gl.disable(WebGl2RenderingContext::BLEND);*/
}
}

View File

@@ -1,20 +1,18 @@
pub mod catalog;
pub mod final_pass;
pub mod grid;
pub mod labels;
pub mod moc;
pub mod image;
pub mod hips;
pub mod utils;
pub mod line;
pub mod text;
use crate::renderable::image::Image;
use al_core::image::format::ChannelType;
pub use hips::HiPS;
pub use labels::TextRenderManager;
pub use catalog::Manager;
pub use grid::ProjetedGrid;
use al_api::hips::ImageMetadata;
use al_api::color::ColorRGB;
@@ -43,6 +41,11 @@ use wasm_bindgen::JsValue;
use std::borrow::Cow;
use std::collections::HashMap;
pub trait Renderer {
fn begin(&mut self);
fn end(&mut self);
}
pub(crate) type Url = String;
type LayerId = String;
pub struct Layers {

View File

@@ -0,0 +1,113 @@
use al_core::shader::Shader;
use al_core::texture::Texture2D;
use al_core::webgl_ctx::WebGlContext;
use al_core::VertexArrayObject;
use web_sys::CanvasRenderingContext2d;
use std::collections::HashMap;
use super::Renderer;
pub struct TextRenderManager {
gl: WebGlContext,
// The text canvas
canvas: HtmlCanvasElement,
ctx: CanvasRenderingContext2d,
color: JsValue,
font_size: u32,
}
use al_core::VecData;
use cgmath::{Rad, Vector2};
use wasm_bindgen::JsValue;
use crate::camera::CameraViewPort;
use al_api::color::{ColorRGBA, ColorRGB};
use web_sys::{WebGl2RenderingContext, HtmlCanvasElement};
use al_api::resources::Resources;
use crate::Abort;
use wasm_bindgen::JsCast;
impl TextRenderManager {
/// Init the buffers, VAO and shader
pub fn new(gl: &WebGlContext, camera: &CameraViewPort) -> Result<Self, JsValue> {
let document = web_sys::window().unwrap_abort().document().unwrap_abort();
let canvas = document
// Inside it, retrieve the canvas
.get_elements_by_class_name("aladin-gridCanvas")
.get_with_index(0)
.unwrap_abort()
.dyn_into::<web_sys::HtmlCanvasElement>()?;
let ctx = canvas
.get_context("2d")
.unwrap_abort()
.unwrap_abort()
.dyn_into::<web_sys::CanvasRenderingContext2d>().unwrap_abort();
let color = JsValue::from_str("#00ff00");
let gl = gl.clone();
let font_size = 30;
Ok(Self {
font_size,
color,
gl,
canvas,
ctx,
})
}
pub fn set_color(&mut self, color: &ColorRGB) {
let hex = al_api::color::Color::rgbToHex((color.r * 255.0) as u8, (color.g * 255.0) as u8, (color.b * 255.0) as u8);
self.color = JsValue::from_str(&hex);
}
pub fn set_font_size(&mut self, size: u32) {
self.font_size = size;
}
pub fn add_label<A: Into<Rad<f32>>>(
&mut self,
text: &str,
screen_pos: &Vector2<f32>,
angle: A,
) -> Result<(), JsValue>{
self.ctx.save();
self.ctx.translate(screen_pos.x as f64, screen_pos.y as f64)?;
let rot: Rad<f32> = angle.into();
self.ctx.rotate(rot.0 as f64)?;
self.ctx.set_text_align("center");
self.ctx.fill_text(text, 0.0, 0.0)?;
self.ctx.restore();
Ok(())
}
pub fn draw(&mut self, camera: &CameraViewPort, color: &ColorRGBA, scale: f32) -> Result<(), JsValue> {
Ok(())
}
pub fn clear_text_canvas(&mut self) {
self.ctx.clear_rect(0_f64, 0_f64, self.canvas.width() as f64, self.canvas.height() as f64);
}
}
impl Renderer for TextRenderManager {
fn begin(&mut self) {
self.ctx = self.canvas
.get_context("2d")
.unwrap_abort()
.unwrap_abort()
.dyn_into::<web_sys::CanvasRenderingContext2d>().unwrap_abort();
self.clear_text_canvas();
// reset the font and color
self.ctx.set_font(&format!("{}px verdana, sans-serif", self.font_size));
self.ctx.set_fill_style(&self.color);
}
fn end(&mut self) {}
}

View File

@@ -83,7 +83,7 @@ impl<'a> Iterator for BuildPatchIndicesIter<'a> {
}
}
struct Triangle<'a> {
pub struct Triangle<'a> {
v1: &'a [f32; 2],
v2: &'a [f32; 2],
v3: &'a [f32; 2],

View File

@@ -151,9 +151,6 @@ pub struct HiPSConfig {
// Max depth of the current HiPS tiles
max_depth_texture: u8,
max_depth_tile: u8,
num_textures_by_side_slice: i32,
num_textures_by_slice: i32,
num_slices: i32,
num_textures: usize,
pub is_allsky: bool,
@@ -182,6 +179,10 @@ use crate::HiPSProperties;
use al_api::coo_system::CooSystem;
use wasm_bindgen::JsValue;
const NUM_TEXTURES_BY_SIDE_SLICE: i32 = 8;
const NUM_TEXTURES_BY_SLICE: i32 = NUM_TEXTURES_BY_SIDE_SLICE * NUM_TEXTURES_BY_SIDE_SLICE;
const NUM_SLICES: i32 = 1;
impl HiPSConfig {
/// Define a HiPS configuration
///
@@ -196,10 +197,7 @@ impl HiPSConfig {
let root_url = properties.get_url();
// Define the size of the 2d texture array depending on the
// characterics of the client
let num_textures_by_side_slice = 8;
let num_textures_by_slice = num_textures_by_side_slice * num_textures_by_side_slice;
let num_slices = 2;
let num_textures = (num_textures_by_slice * num_slices) as usize;
let num_textures = (NUM_TEXTURES_BY_SLICE * NUM_SLICES) as usize;
let max_depth_tile = properties.get_max_order();
let tile_size = properties.get_tile_size();
@@ -317,9 +315,6 @@ impl HiPSConfig {
max_depth_texture,
max_depth_tile,
min_depth_tile,
num_textures_by_side_slice,
num_textures_by_slice,
num_slices,
num_textures,
is_allsky,
@@ -502,17 +497,17 @@ impl HiPSConfig {
#[inline]
pub fn num_textures_by_side_slice(&self) -> i32 {
self.num_textures_by_side_slice
NUM_TEXTURES_BY_SIDE_SLICE
}
#[inline]
pub fn num_textures_by_slice(&self) -> i32 {
self.num_textures_by_slice
NUM_TEXTURES_BY_SLICE
}
#[inline]
pub fn num_slices(&self) -> i32 {
self.num_slices
NUM_SLICES
}
#[inline]

View File

@@ -15,6 +15,13 @@
top: 0;
}
.aladin-gridCanvas {
position: absolute;
z-index: 1;
left: 0;
top: 0;
}
.aladin-catalogCanvas {
position: absolute;
z-index: 2;

View File

@@ -2,7 +2,9 @@
precision highp float;
in vec4 v_rgba;
out vec4 color;
void main() {
// Multiply vertex color with texture color (in linear space).
// Linear color is written and blended in Framebuffer and converted to sRGB later

View File

@@ -2,7 +2,6 @@
precision highp float;
layout (location = 0) in vec2 ndc_pos;
uniform vec2 u_screen_size;
uniform vec4 u_color;
out vec4 v_rgba;

View File

@@ -1,20 +0,0 @@
#version 300 es
precision highp float;
in vec2 v_tc;
out vec4 color;
uniform sampler2D u_sampler_font;
uniform float u_opacity;
uniform vec3 u_color;
void main() {
// The texture is set up with `SRGB8_ALPHA8`, so no need to decode here!
float alpha = texture(u_sampler_font, v_tc).r;
alpha = smoothstep(0.1, 0.9, alpha);
// Multiply vertex color with texture color (in linear space).
// Linear color is written and blended in Framebuffer and converted to sRGB later
color = vec4(u_color, u_opacity * alpha);
//color.a = color.a * alpha;
}

View File

@@ -1,29 +0,0 @@
#version 300 es
layout (location = 0) in vec2 pos;
layout (location = 1) in vec2 tx;
layout (location = 2) in vec2 u_screen_pos;
layout (location = 3) in float rot;
out vec3 v_rgb;
out vec2 v_tc;
uniform vec2 u_screen_size;
uniform float u_scale;
uniform float u_dpi;
void main() {
// The dpi accounts for the screen_position (given in px)
// and also for the scale
float st = sin(rot);
float ct = cos(rot);
mat2 u_rot = mat2(ct, st, -st, ct);
vec2 p = u_rot * u_scale * u_dpi * pos;
gl_Position = vec4(
2.0 * (p.x + u_screen_pos.x * u_dpi) / u_screen_size.x - 1.0,
1.0 - 2.0 * (p.y + u_screen_pos.y * u_dpi) / u_screen_size.y,
0.0,
1.0
);
v_tc = tx;
}

View File

@@ -198,7 +198,7 @@ export let Aladin = (function () {
opacity = options.gridOptions.opacity;
} else {
color = {r:0.0, g:1.0, b:0.0};
opacity = 1.0;
opacity = 0.5;
}
this.view.setGridConfig({

View File

@@ -306,10 +306,13 @@ export let View = (function () {
View.prototype.createCanvases = function () {
var a = $(this.aladinDiv);
a.find('.aladin-imageCanvas').remove();
a.find('.aladin-gridCanvas').remove();
a.find('.aladin-catalogCanvas').remove();
// canvas to draw the images
this.imageCanvas = $("<canvas class='aladin-imageCanvas'></canvas>").appendTo(this.aladinDiv)[0];
this.gridCanvas = $("<canvas class='aladin-gridCanvas'></canvas>").appendTo(this.aladinDiv)[0];
// canvas to draw the catalogs
this.catalogCanvas = $("<canvas class='aladin-catalogCanvas'></canvas>").appendTo(this.aladinDiv)[0];
};
@@ -342,10 +345,13 @@ export let View = (function () {
this.wasm.resize(this.width, this.height);
this.catalogCtx = this.catalogCanvas.getContext("2d");
this.catalogCtx.canvas.width = this.width;
this.catalogCtx.canvas.height = this.height;
/*this.gridCtx = this.gridCanvas.getContext("2d");
this.gridCtx.canvas.width = this.width;
this.gridCtx.canvas.height = this.height;*/
pixelateCanvasContext(this.imageCtx, this.aladin.options.pixelateCanvas);
// change logo
@@ -1050,7 +1056,7 @@ export let View = (function () {
};
};
View.FPS_INTERVAL = 1000 / 100;
View.FPS_INTERVAL = 1000 / 140;
/**
* redraw the whole view

View File

@@ -2,22 +2,17 @@
import { loadShadersWebGL2 } from "./ShadersWebGL2";
// Import resources images
import kernel from '../img/kernel.png';
import letters from '../img/letters.png';
import lettersMetadata from '../img/letters.json';
export let WebGLCtx = (function() {
// constructor
function WebGLCtx(ctx, div) {
const shaders = loadShadersWebGL2();
const lettersMeta = JSON.stringify(lettersMetadata);
this.webclient = new ctx.WebClient(
div,
shaders,
{
'kernel': kernel,
'letters': letters,
'letters_metadata': lettersMeta,
}
);
};

View File

@@ -65,11 +65,19 @@
// Coordinates grid plot
let labelCoordinatesGridCb = $('<div class="aladin-label">Coo grid options</div>');
let cooGridOptions = $('<div class="layer-options"><table><tbody><tr><td>Color</td><td><input type="color" value="#00ff00"></td></tr><tr><td>Opacity</td><td><input class="aladin-input opacity" value="1.0" type="range" min="0" max="1" step="0.05"></td></tr><tr><td>Label size</td><td><input class="aladin-input label-size" type="range" value="1" min="0" max="1" step="0.01"></td></tr></table></div>');
let cooGridOptions = $('<div class="layer-options"><table><tbody><tr><td>Color</td><td><input type="color" value="#00ff00"></td></tr><tr><td>Opacity</td><td><input class="aladin-input opacity" value="0.5" type="range" min="0.0" max="1.0" step="0.05"></td></tr><tr><td>Thickness</td><td><input class="aladin-input thickness" value="3.0" type="range" min="0.5" max="10.0" step="0.01"></td></tr><tr><td>Label size</td><td><input class="aladin-input label-size" type="range" value="30" min="10" max="60" step="0.01"></td></tr></table></div>');
layerBox.append(labelCoordinatesGridCb).append(cooGridOptions);
let gridColorInput = cooGridOptions.find('input[type="color"]');
let gridOpacityInput = cooGridOptions.find('.opacity');
let gridThicknessInput = cooGridOptions.find('.thickness');
gridThicknessInput.on('input', () => {
const thickness = +gridThicknessInput.val();
self.view.setGridConfig({
thickness: thickness
});
});
let updateGridcolor = function () {
let rgb = Color.hexToRgb(gridColorInput.val());
let opacity = gridOpacityInput.val();