diff --git a/examples/al-event-listeners.html b/examples/al-event-listeners.html index cc21781d..51d8fe5b 100644 --- a/examples/al-event-listeners.html +++ b/examples/al-event-listeners.html @@ -52,6 +52,14 @@ $('#infoDiv').html(msg); }); + aladin.on('resizeChanged', function() { + console.log("resize") + }); + + aladin.on('projectionChanged', function(proj) { + console.log(proj) + }); + cat.sources[0].actionClicked(); }); diff --git a/examples/al-init-custom-options.html b/examples/al-init-custom-options.html index bbf6154f..ab7d0f68 100644 --- a/examples/al-init-custom-options.html +++ b/examples/al-init-custom-options.html @@ -24,6 +24,7 @@ showFrame: true, showZoomControl:true, showSettingsControl:true, + showCooGrid: true, } ); diff --git a/package.json b/package.json index 5a4e649b..ecab5837 100644 --- a/package.json +++ b/package.json @@ -2,7 +2,7 @@ "homepage": "https://aladin.u-strasbg.fr/", "name": "aladin-lite", "type": "module", - "version": "3.4.3-beta", + "version": "3.4.4-beta", "description": "An astronomical HiPS visualizer in the browser", "author": "Thomas Boch and Matthieu Baumann", "license": "GPL-3", diff --git a/src/core/src/camera/viewport.rs b/src/core/src/camera/viewport.rs index ffe87b6d..d5a6bfd5 100644 --- a/src/core/src/camera/viewport.rs +++ b/src/core/src/camera/viewport.rs @@ -470,12 +470,17 @@ impl CameraViewPort { self.update_rot_matrices(proj); } - /// lonlat must be given in icrs frame + /// center lonlat must be given in icrs frame pub fn set_center(&mut self, lonlat: &LonLatT, proj: &ProjectionType) { let icrs_pos: Vector4<_> = lonlat.vector(); let view_pos = CooSystem::ICRS.to(self.get_coo_system()) * icrs_pos; - let rot = Rotation::from_sky_position(&view_pos); + let rot_to_center = Rotation::from_sky_position(&view_pos); + + let phi = self.get_center_pos_angle(); + let third_euler_rot = Rotation::from_axis_angle(&view_pos.truncate(), phi); + + let rot = third_euler_rot * rot_to_center; // Apply the rotation to the camera to go // to the next lonlat diff --git a/src/core/src/renderable/grid/mod.rs b/src/core/src/renderable/grid/mod.rs index aca1a2a5..7aef4437 100644 --- a/src/core/src/renderable/grid/mod.rs +++ b/src/core/src/renderable/grid/mod.rs @@ -297,7 +297,9 @@ impl ProjetedGrid { crate::shader::get_shader(&self.gl, shaders, "line_inst_ndc.vert", "line_base.frag")? .bind(&self.gl) .attach_uniform("u_color", &self.color) - .attach_uniform("u_width", &self.thickness) + .attach_uniform("u_width", &(camera.get_width())) + .attach_uniform("u_height", &(camera.get_height())) + .attach_uniform("u_thickness", &self.thickness) .bind_vertex_array_object_ref(&self.vao) .draw_elements_instanced_with_i32( WebGl2RenderingContext::TRIANGLES, diff --git a/src/core/src/renderable/image/grid.rs b/src/core/src/renderable/image/grid.rs index e7eb782f..11d23667 100644 --- a/src/core/src/renderable/image/grid.rs +++ b/src/core/src/renderable/image/grid.rs @@ -11,7 +11,8 @@ use wcs::WCS; pub fn get_grid_params( xy_min: &(f64, f64), xy_max: &(f64, f64), - max_tex_size: u64, + max_tex_size_x: u64, + max_tex_size_y: u64, num_tri_per_tex_patch: u64, ) -> ( impl Iterator + Clone, @@ -31,8 +32,8 @@ pub fn get_grid_params( let step = (step_x.max(step_y)).max(1); // at least one pixel! ( - get_coord_uv_it(xmin, xmax, step, max_tex_size), - get_coord_uv_it(ymin, ymax, step, max_tex_size), + get_coord_uv_it(xmin, xmax, step, max_tex_size_x), + get_coord_uv_it(ymin, ymax, step, max_tex_size_y), ) } @@ -169,13 +170,20 @@ fn build_range_indices(it: impl Iterator + Clone) -> Vec (Vec, Vec, Vec, Vec) { - let (x_it, y_it) = get_grid_params(xy_min, xy_max, max_tex_size, num_tri_per_tex_patch); + let (x_it, y_it) = get_grid_params( + xy_min, + xy_max, + max_tex_size_x, + max_tex_size_y, + num_tri_per_tex_patch, + ); let idx_x_ranges = build_range_indices(x_it.clone()); let idx_y_ranges = build_range_indices(y_it.clone()); diff --git a/src/core/src/renderable/image/mod.rs b/src/core/src/renderable/image/mod.rs index fad4923f..15fc4bb6 100644 --- a/src/core/src/renderable/image/mod.rs +++ b/src/core/src/renderable/image/mod.rs @@ -62,13 +62,15 @@ pub struct Image { /// Texture indices that must be drawn idx_tex: Vec, /// The maximum webgl supported texture size - max_tex_size: usize, + max_tex_size_x: usize, + max_tex_size_y: usize, reg: Region, // The coo system in which the polygonal region has been defined coo_sys: CooSystem, } - +use al_core::pixel::Pixel; +use al_core::texture::TEX_PARAMS; use fitsrs::hdu::header::extension; use fitsrs::hdu::AsyncHDU; use futures::io::BufReader; @@ -76,7 +78,7 @@ use futures::AsyncReadExt; impl Image { pub async fn from_reader_and_wcs( gl: &WebGlContext, - reader: R, + mut reader: R, wcs: WCS, mut scale: Option, mut offset: Option, @@ -94,20 +96,86 @@ impl Image { WebGl2RenderingContext::get_parameter(gl, WebGl2RenderingContext::MAX_TEXTURE_SIZE)? .as_f64() .unwrap_or(4096.0) as usize; - let (textures, mut cuts) = - subdivide_texture::build::(gl, width, height, reader, max_tex_size, blank) - .await?; + + let mut max_tex_size_x = max_tex_size; + let mut max_tex_size_y = max_tex_size; // apply bscale to the cuts if F::NUM_CHANNELS == 1 { offset = offset.or(Some(0.0)); scale = scale.or(Some(1.0)); blank = blank.or(Some(std::f32::NAN)); - cuts = cuts.map(|cuts| { - let start = cuts.start * scale.unwrap() + offset.unwrap(); - let end = cuts.end * scale.unwrap() + offset.unwrap(); - start..end - }); + } + + let (textures, mut cuts) = if width <= max_tex_size as u64 && height <= max_tex_size as u64 + { + max_tex_size_x = width as usize; + max_tex_size_y = height as usize; + // can fit in one texture + + let num_pixels_to_read = (width as usize) * (height as usize); + let num_bytes_to_read = + num_pixels_to_read * std::mem::size_of::<::Item>() * F::NUM_CHANNELS; + let mut buf = vec![0; num_bytes_to_read]; + + let _ = reader.read_exact(&mut buf[..]).await.map_err(|e| { + JsValue::from_str("invalid data with respect to the NAXIS given in the WCS") + })?; + + unsafe { + let slice = std::slice::from_raw_parts( + buf.as_mut_ptr() as *const ::Item, + num_bytes_to_read / std::mem::size_of::<::Item>(), + ); + + let cuts = if F::NUM_CHANNELS == 1 { + let mut samples = slice + .iter() + .filter_map(|item| { + let t: f32 = + <::Item as al_core::convert::Cast>::cast(*item); + if t.is_nan() || t == blank.unwrap() { + None + } else { + Some(t) + } + }) + .collect::>(); + + let cuts = cuts::first_and_last_percent(&mut samples, 1, 99); + Some(cuts) + } else { + None + }; + + ( + vec![Texture2D::create_from_raw_pixels::( + gl, + width as i32, + height as i32, + TEX_PARAMS, + Some(slice), + )?], + cuts, + ) + } + } else { + subdivide_texture::crop_image::( + gl, + width, + height, + reader, + max_tex_size as u64, + blank, + ) + .await? + }; + + if let Some(cuts) = cuts.as_mut() { + let start = cuts.start * scale.unwrap() + offset.unwrap(); + let end = cuts.end * scale.unwrap() + offset.unwrap(); + + *cuts = start..end; } let num_indices = vec![]; @@ -213,7 +281,8 @@ impl Image { channel: F::CHANNEL_TYPE, textures, cuts, - max_tex_size, + max_tex_size_x, + max_tex_size_y, // Indices of textures that must be drawn idx_tex, // The polygonal region in the sky @@ -484,7 +553,8 @@ impl Image { let (pos, uv, indices, num_indices) = grid::vertices( &(x_mesh_range.start, y_mesh_range.start), &(x_mesh_range.end.ceil(), y_mesh_range.end.ceil()), - self.max_tex_size as u64, + self.max_tex_size_x as u64, + self.max_tex_size_y as u64, num_vertices, camera, &self.wcs, diff --git a/src/core/src/renderable/image/subdivide_texture.rs b/src/core/src/renderable/image/subdivide_texture.rs index f1dd8fa1..61e7bf5d 100644 --- a/src/core/src/renderable/image/subdivide_texture.rs +++ b/src/core/src/renderable/image/subdivide_texture.rs @@ -10,26 +10,30 @@ use al_core::Texture2D; use al_core::WebGlContext; use std::ops::Range; -pub async fn build<'a, F, R>( +pub async fn crop_image<'a, F, R>( gl: &WebGlContext, width: u64, height: u64, mut reader: R, - max_tex_size: usize, + max_tex_size: u64, blank: Option, ) -> Result<(Vec, Option>), JsValue> where F: ImageFormat, R: AsyncReadExt + Unpin, { - let mut buf = - vec![0; max_tex_size * std::mem::size_of::<::Item>() * F::NUM_CHANNELS]; - let max_tex_size = max_tex_size as u64; + let mut tex_chunks = vec![]; // Subdivision let num_textures = ((width / max_tex_size) + 1) * ((height / max_tex_size) + 1); - let mut tex_chunks = vec![]; + let mut buf = vec![ + 0; + (max_tex_size as usize) + * std::mem::size_of::<::Item>() + * F::NUM_CHANNELS + ]; + for _ in 0..num_textures { tex_chunks.push(Texture2D::create_from_raw_pixels::( gl, diff --git a/src/core/src/renderable/moc/mod.rs b/src/core/src/renderable/moc/mod.rs index 67a78206..bf8fc0ad 100644 --- a/src/core/src/renderable/moc/mod.rs +++ b/src/core/src/renderable/moc/mod.rs @@ -451,7 +451,9 @@ impl MOCIntern { .attach_uniforms_from(camera) .attach_uniform("u_2world", &icrs2world) .attach_uniform("u_color", &color) - .attach_uniform("u_width", &thickness) + .attach_uniform("u_width", &(camera.get_width())) + .attach_uniform("u_height", &(camera.get_height())) + .attach_uniform("u_thickness", &thickness) .attach_uniform("u_proj", proj) .bind_vertex_array_object_ref(&self.vao) .draw_elements_instanced_with_i32( @@ -487,7 +489,9 @@ impl MOCIntern { .attach_uniforms_from(camera) .attach_uniform("u_2world", &icrs2world) .attach_uniform("u_color", &color) - .attach_uniform("u_width", &thickness) + .attach_uniform("u_width", &(camera.get_width())) + .attach_uniform("u_height", &(camera.get_height())) + .attach_uniform("u_thickness", &thickness) .attach_uniform("u_proj", proj) .bind_vertex_array_object_ref(&self.vao) .draw_elements_instanced_with_i32( diff --git a/src/css/aladin.css b/src/css/aladin.css index 84eefc1b..fca00590 100644 --- a/src/css/aladin.css +++ b/src/css/aladin.css @@ -328,10 +328,10 @@ canvas { } .aladin-btn.aladin-dark-theme.toggled { - border-color: greenyellow; + border-color: dodgerblue; } .aladin-btn.aladin-dark-theme:hover, .aladin-input-select.aladin-dark-theme:hover { - border-color: red; + border-color: greenyellow; } .aladin-btn.disabled { @@ -1176,13 +1176,13 @@ canvas { line-height: 1.7rem; } -.aladin-fov .aladin-zoom-in { +.aladin-fov .aladin-zoom-out { margin-right: 0; border-right: none; border-radius: 5px 0px 0px 5px; } -.aladin-fov .aladin-zoom-out { +.aladin-fov .aladin-zoom-in { border-radius: 0px 5px 5px 0px; } diff --git a/src/glsl/webgl2/line/base.vert b/src/glsl/webgl2/line/base.vert index fef41d55..a5ce9c2e 100644 --- a/src/glsl/webgl2/line/base.vert +++ b/src/glsl/webgl2/line/base.vert @@ -1,5 +1,5 @@ #version 300 es -precision lowp float; +precision highp float; layout (location = 0) in vec2 ndc_pos; out float l; diff --git a/src/glsl/webgl2/line/inst_lonlat.vert b/src/glsl/webgl2/line/inst_lonlat.vert index 9687b972..309b9140 100644 --- a/src/glsl/webgl2/line/inst_lonlat.vert +++ b/src/glsl/webgl2/line/inst_lonlat.vert @@ -8,6 +8,8 @@ uniform mat4 u_2world; uniform vec2 ndc_to_clip; uniform float czf; uniform float u_width; +uniform float u_height; +uniform float u_thickness; out float l; @@ -34,6 +36,7 @@ void main() { vec2 x_b = p_b_ndc - p_a_ndc; vec2 y_b = normalize(vec2(-x_b.y, x_b.x)); - vec2 p_ndc = p_a_ndc + x_b * vertex.x + y_b * u_width * 0.001 * vertex.y; + float ndc2pix = 2.0 / u_width; + vec2 p_ndc = p_a_ndc + x_b * vertex.x + u_thickness * y_b * vertex.y * vec2(1.0, u_width/u_height) * ndc2pix; gl_Position = vec4(p_ndc, 0.f, 1.f); } \ No newline at end of file diff --git a/src/glsl/webgl2/line/inst_ndc.vert b/src/glsl/webgl2/line/inst_ndc.vert index f12f6f54..2a982ab3 100644 --- a/src/glsl/webgl2/line/inst_ndc.vert +++ b/src/glsl/webgl2/line/inst_ndc.vert @@ -1,5 +1,5 @@ #version 300 es -precision lowp float; +precision highp float; layout (location = 0) in vec2 p_a; layout (location = 1) in vec2 p_b; layout (location = 2) in vec2 vertex; @@ -7,11 +7,15 @@ layout (location = 2) in vec2 vertex; out float l; uniform float u_width; +uniform float u_height; +uniform float u_thickness; void main() { vec2 x_b = p_b - p_a; vec2 y_b = normalize(vec2(-x_b.y, x_b.x)); - vec2 p = p_a + x_b * vertex.x + y_b * u_width * 0.001 * vertex.y; + float ndc2pix = 2.0 / u_width; + + vec2 p = p_a + x_b * vertex.x + u_thickness * y_b * vertex.y * vec2(1.0, u_width/u_height) * ndc2pix; gl_Position = vec4(p, 0.f, 1.f); } \ No newline at end of file diff --git a/src/js/A.js b/src/js/A.js index f2b75dbd..6b6dbe6d 100644 --- a/src/js/A.js +++ b/src/js/A.js @@ -201,7 +201,7 @@ A.polygon = function (raDecArray, options) { } options = options || {}; - //options.closed = true; + options.closed = true; return new Polyline(raDecArray, options); }; diff --git a/src/js/Aladin.js b/src/js/Aladin.js index 52f8ff5f..8dc4812c 100644 --- a/src/js/Aladin.js +++ b/src/js/Aladin.js @@ -88,6 +88,8 @@ import { Polyline } from "./shapes/Polyline"; * @property {string} [target="0 +0"] - Target coordinates for the initial view. * @property {CooFrame} [cooFrame="J2000"] - Coordinate frame. * @property {number} [fov=60] - Field of view in degrees. + * @property {number} [northPoleOrientation=0] - North pole orientation in degrees. By default it is set to 0 deg i.e. the north pole will be found vertically north to the view. + * Positive orientation goes towards east i.e. in counter clockwise order as the east lies in the left direction of the view. * @property {string} [backgroundColor="rgb(60, 60, 60)"] - Background color in RGB format. * * @property {boolean} [showZoomControl=true] - Whether to show the zoom control toolbar. @@ -246,7 +248,9 @@ import { Polyline } from "./shapes/Polyline"; 'mouseMove', 'fullScreenToggled', - 'cooFrameChanged' + 'cooFrameChanged', + 'resizeChanged', + 'projectionChanged', */ export let Aladin = (function () { @@ -564,6 +568,10 @@ export let Aladin = (function () { if (options.inertia !== undefined) { this.wasm.setInertia(options.inertia); } + + if (options.northPoleOrientation) { + this.setViewCenter2NorthPoleAngle(options.northPoleOrientation); + } }; Aladin.prototype._setupUI = function (options) { @@ -704,6 +712,7 @@ export let Aladin = (function () { target: "0 +0", cooFrame: "J2000", fov: 60, + northPoleOrientation: 0, inertia: true, backgroundColor: "rgb(60, 60, 60)", // Zoom toolbar @@ -739,7 +748,7 @@ export let Aladin = (function () { gridOptions: { enabled: false, showLabels: true, - thickness: 3, + thickness: 2, labelSize: 15, }, projection: "SIN", @@ -815,7 +824,7 @@ export let Aladin = (function () { var fullScreenToggledFn = self.callbacksByEventName["fullScreenToggled"]; typeof fullScreenToggledFn === "function" && - fullScreenToggledFn(isInFullscreen); + fullScreenToggledFn(self.isInFullscreen); }; Aladin.prototype.getOptionsFromQueryString = function () { @@ -1966,6 +1975,8 @@ export let Aladin = (function () { "fullScreenToggled", "cooFrameChanged", + "resizeChanged", + "projectionChanged" ]; /** diff --git a/src/js/ImageFITS.js b/src/js/ImageFITS.js index 297f77a1..38d29868 100644 --- a/src/js/ImageFITS.js +++ b/src/js/ImageFITS.js @@ -247,7 +247,7 @@ export let Image = (function () { promise = new Promise((resolve, reject) => { - img.src = Aladin.JSONP_PROXY + '?url=' + this.url; + img.src = this.url; img.crossOrigin = "Anonymous"; img.onload = () => { var canvas = document.createElement("canvas"); @@ -264,11 +264,19 @@ export let Image = (function () { const blob = new Blob([imageData.data]); const stream = blob.stream(1024); - return resolve(stream) + resolve(stream) } + let proxyUsed = false; img.onerror = () => { - return reject('Error parsing img ' + self.url) + // use proxy + if (proxyUsed) { + reject('Error parsing img ' + self.url) + return; + } + + proxyUsed = true; + img.src = Aladin.JSONP_PROXY + '?url=' + self.url; } }) .then((readableStream) => { diff --git a/src/js/Source.js b/src/js/Source.js index 585d614b..54e92049 100644 --- a/src/js/Source.js +++ b/src/js/Source.js @@ -131,7 +131,7 @@ export let Source = (function() { if (!obj) { obj = this; } - view.selectObjects([obj]); + view.selectObjects([[obj]]); } else if (this.catalog.onClick == 'showPopup') { view.aladin.popup.setTitle('

'); diff --git a/src/js/View.js b/src/js/View.js index 4491bff5..5f33b833 100644 --- a/src/js/View.js +++ b/src/js/View.js @@ -153,6 +153,14 @@ export let View = (function () { View.CALLBACKS_THROTTLE_TIME_MS, ); + this.throttledDivResized = Utils.throttle( + () => { + const resizeFn = self.aladin.callbacksByEventName['resizeChanged']; + (typeof resizeFn === 'function') && resizeFn(self.width, self.height); + }, + View.CALLBACKS_THROTTLE_TIME_MS, + ); + this.mustClearCatalog = true; this.mode = View.PAN; @@ -416,6 +424,8 @@ export let View = (function () { this.computeNorder(); this.aladinDiv.style.removeProperty('line-height'); + + this.throttledDivResized(); }; var pixelateCanvasContext = function (ctx, pixelateFlag) { @@ -1907,11 +1917,14 @@ export let View = (function () { } this.projection = ProjectionEnum[projName]; - + // Change the projection here this.wasm.setProjection(projName); this.updateZoomState(); + const projFn = this.aladin.callbacksByEventName['projectionChanged']; + (typeof projFn === 'function') && projFn(projName); + this.requestRedraw(); }; @@ -1992,7 +2005,6 @@ export let View = (function () { this.viewCenter.lon = ra; this.viewCenter.lat = dec; - //this.updateLocation({lon: this.viewCenter.lon, lat: this.viewCenter.lat}); // Put a javascript code here to do some animation this.wasm.setCenter(this.viewCenter.lon, this.viewCenter.lat); diff --git a/src/js/gui/FoV.js b/src/js/gui/FoV.js index 13d93a2d..c29ea48a 100644 --- a/src/js/gui/FoV.js +++ b/src/js/gui/FoV.js @@ -73,8 +73,8 @@ export class FoV extends DOMElement { zoomIn.el.classList.add('aladin-zoom-in'); zoomOut.el.classList.add('aladin-zoom-out'); - layout.push(zoomIn) layout.push(zoomOut) + layout.push(zoomIn) } if (options.showFov) { diff --git a/src/js/vo/samp.js b/src/js/vo/samp.js index cdfb5ca5..5cdb4161 100644 --- a/src/js/vo/samp.js +++ b/src/js/vo/samp.js @@ -144,7 +144,8 @@ export class SAMPConnector { let catalog = selectCatalog(id, url) if (catalog) { - aladin.selectObjects([catalog.sources[row]]); + const source = catalog.sources[row]; + aladin.selectObjects([[source]]); } };