mirror of
https://github.com/cds-astro/aladin-lite.git
synced 2025-12-12 07:40:26 -08:00
wip perimeter moc draw
This commit is contained in:
23
examples/al-eso-catalogs.html
Normal file
23
examples/al-eso-catalogs.html
Normal 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>
|
||||
@@ -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);
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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)]
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -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(())
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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
153
src/core/src/grid/label.rs
Normal 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,
|
||||
})
|
||||
}
|
||||
}
|
||||
237
src/core/src/grid/meridian.rs
Normal file
237
src/core/src/grid/meridian.rs
Normal 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
327
src/core/src/grid/mod.rs
Normal 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
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
159
src/core/src/grid/parallel.rs
Normal file
159
src/core/src/grid/parallel.rs
Normal 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()
|
||||
}
|
||||
}
|
||||
@@ -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,
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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() {
|
||||
|
||||
@@ -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]
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
mod manager;
|
||||
pub use manager::{Catalog, Manager};
|
||||
mod source;
|
||||
pub use source::Source;
|
||||
mod index;
|
||||
|
||||
@@ -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*/)
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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(())
|
||||
}
|
||||
}
|
||||
@@ -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(())
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
|
||||
@@ -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);*/
|
||||
}
|
||||
}
|
||||
@@ -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 {
|
||||
|
||||
113
src/core/src/renderable/text.rs
Normal file
113
src/core/src/renderable/text.rs
Normal 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) {}
|
||||
}
|
||||
@@ -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],
|
||||
|
||||
@@ -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]
|
||||
|
||||
@@ -15,6 +15,13 @@
|
||||
top: 0;
|
||||
}
|
||||
|
||||
.aladin-gridCanvas {
|
||||
position: absolute;
|
||||
z-index: 1;
|
||||
left: 0;
|
||||
top: 0;
|
||||
}
|
||||
|
||||
.aladin-catalogCanvas {
|
||||
position: absolute;
|
||||
z-index: 2;
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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({
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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,
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
@@ -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();
|
||||
|
||||
Reference in New Issue
Block a user