Compare commits

...

9 Commits

Author SHA1 Message Date
Matthieu Baumann
68c724c478 fine tune the default linewidth values 2025-08-29 16:03:52 +02:00
Matthieu Baumann
f3e1ecf94c run clippy and fmt 2025-08-29 15:32:02 +02:00
Matthieu Baumann
b7c5e2a56e update default thickness to match with previous lineWidth user were interested of 2025-08-29 15:28:32 +02:00
bmatthieu3
d4298f3389 anti aliasing on lines (grid + mocs) drawn by the gpu. Change moc linewidth from 3 -> 2 and twick the coogrid ang step/opacity/linewidth 2025-08-29 14:06:55 +02:00
Matthieu Baumann
3454083449 fix Circle::intersectBbox 2025-08-29 11:57:15 +02:00
Erik Mellegard
311fa84919 Fix Circle intersectBBox 2025-08-29 11:57:15 +02:00
Matthieu Baumann
3d445e4f6f fix 26 channel color offset 2025-08-28 17:52:45 +02:00
Matthieu Baumann
950d0c693e Fix wrong coordinates frame 2025-08-28 16:17:30 +02:00
Matthieu Baumann
895aa169b4 fix clipping polyline containing vertices that fails to be projected (i.e. for SIN, TAN proj) 2025-07-04 14:00:55 +02:00
22 changed files with 188 additions and 106 deletions

View File

@@ -71,7 +71,7 @@ impl Texture3D {
self.gl.generate_mipmap(WebGlRenderingCtx::TEXTURE_3D);
}
pub fn bind(&self) -> Texture3DBound {
pub fn bind(&self) -> Texture3DBound<'_> {
self.gl
.bind_texture(WebGlRenderingCtx::TEXTURE_3D, self.texture.as_ref());

View File

@@ -70,7 +70,7 @@ impl Texture2DArray {
self.gl.generate_mipmap(WebGlRenderingCtx::TEXTURE_2D_ARRAY);
}
pub fn bind(&self) -> Texture2DArrayBound {
pub fn bind(&self) -> Texture2DArrayBound<'_> {
self.gl
.bind_texture(WebGlRenderingCtx::TEXTURE_2D_ARRAY, self.texture.as_ref());

View File

@@ -295,7 +295,7 @@ impl Texture2D {
self
}
pub fn bind(&self) -> Texture2DBound {
pub fn bind(&self) -> Texture2DBound<'_> {
self.gl
.bind_texture(WebGlRenderingCtx::TEXTURE_2D, self.texture.as_ref());

View File

@@ -133,7 +133,6 @@ impl App {
//let exec = Rc::new(RefCell::new(TaskExecutor::new()));
let projection = ProjectionType::Sin(mapproj::zenithal::sin::Sin);
gl.enable(WebGl2RenderingContext::BLEND);
// TODO: https://caniuse.com/?search=scissor is not supported for safari <= 14.1
// When it will be supported nearly everywhere, we will need to uncomment this line to
@@ -829,6 +828,7 @@ impl App {
// Render the scene
// Clear all the screen first (only the region set by the scissor)
gl.clear(WebGl2RenderingContext::COLOR_BUFFER_BIT);
// set the blending options
layers.draw(camera, shaders, colormaps, projection)?;
@@ -844,12 +844,12 @@ impl App {
);*/
moc.draw(camera, projection, shaders)?;
gl.blend_func_separate(
/*gl.blend_func_separate(
WebGl2RenderingContext::SRC_ALPHA,
WebGl2RenderingContext::ONE,
WebGl2RenderingContext::ONE,
WebGl2RenderingContext::ONE,
);
);*/
grid.draw(camera, projection, shaders)?;
// Ok(())
// },

View File

@@ -198,14 +198,15 @@ impl ProjetedGrid {
if self.enabled {
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;
//let max_dim_px = camera.get_width().max(camera.get_height()) as f64;
//let step_line_px = max_dim_px * 0.15;
let aspect = camera.get_aspect() as f64;
// update meridians
self.meridians = {
// Select the good step with a binary search
let step_lon_precised =
bbox.get_lon_size() * step_line_px / (camera.get_width() as f64);
let step_lon_precised = bbox.get_lon_size() * 0.15;
let step_lon = select_fixed_step(step_lon_precised);
let decimal_lon_prec = step_lon.to_degrees().log10().abs().ceil() as u8;
@@ -235,8 +236,7 @@ impl ProjetedGrid {
};
self.parallels = {
let step_lat_precised =
bbox.get_lat_size() * step_line_px / (camera.get_height() as f64);
let step_lat_precised = aspect * bbox.get_lat_size() * 0.15;
let step_lat = select_fixed_step(step_lat_precised);
let decimal_lat_prec = step_lat.to_degrees().log10().abs().ceil() as u8;
@@ -349,7 +349,7 @@ const GRID_STEPS: &[f64] = &[
0.08726647,
0.17453293,
0.34906585,
std::f64::consts::FRAC_PI_4,
std::f64::consts::FRAC_PI_6,
];
fn select_fixed_step(fov: f64) -> f64 {

View File

@@ -213,9 +213,13 @@ impl Layers {
let raytracer = &self.raytracer;
let raytracing = camera.is_raytracing(projection);
// The first layer or the background must be plot with no blending
self.gl.disable(WebGl2RenderingContext::BLEND);
// Check whether a hips to plot is allsky
// if neither are, we draw a font
// if there are, we do not draw nothing
let mut idx_start_layer = -1;
for (idx, layer) in self.layers.iter().enumerate() {
@@ -235,6 +239,8 @@ impl Layers {
}
}
let mut blending_enabled = false;
// Need to render transparency font
if idx_start_layer == -1 {
let vao = if raytracing {
@@ -258,6 +264,9 @@ impl Layers {
// The background (index -1) has been drawn, we can draw the first HiPS
idx_start_layer = 0;
self.gl.enable(WebGl2RenderingContext::BLEND);
blending_enabled = true;
}
let layers_to_render = &self.layers[(idx_start_layer as usize)..];
@@ -283,6 +292,11 @@ impl Layers {
}
}
}
if !blending_enabled {
self.gl.enable(WebGl2RenderingContext::BLEND);
blending_enabled = true;
}
}
Ok(())

View File

@@ -1,17 +1,25 @@
#version 300 es
precision lowp float;
precision highp float;
out vec4 color;
in float l;
in vec2 l;
uniform vec4 u_color;
uniform float u_thickness;
uniform float u_width;
uniform float u_height;
void main() {
// Multiply vertex color with texture color (in linear space).
// Linear color is written and blended in Framebuffer and converted to sRGB later
if (l > 0.05) {
if (l.x > 0.05) {
discard;
} else {
color = u_color;
// distance from line to compute the anti-aliasing
float dist = abs((u_thickness + 2.0) * l.y);
float half_thickness = (u_thickness + 2.0) * 0.5;
color.a = color.a * (1.0 - smoothstep(half_thickness - 1.0, half_thickness, dist));
}
}

View File

@@ -11,7 +11,7 @@ uniform float u_width;
uniform float u_height;
uniform float u_thickness;
out float l;
out vec2 l;
#include ../projection/projection.glsl;
@@ -27,7 +27,6 @@ void main() {
vec2 p_b_clip = proj(p_b_w);
vec2 da = p_a_clip - p_b_clip;
l = dot(da, da);
vec2 p_a_ndc = p_a_clip / (ndc_to_clip * czf);
vec2 p_b_ndc = p_b_clip / (ndc_to_clip * czf);
@@ -37,6 +36,12 @@ void main() {
vec2 y_b = normalize(vec2(-x_b.y, x_b.x));
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;
vec2 p_ndc_x = x_b * vertex.x;
vec2 p_ndc_y = (u_thickness + 2.0) * y_b * vertex.y * vec2(1.0, u_width/u_height) * ndc2pix;
vec2 p_ndc = p_a_ndc + p_ndc_x + p_ndc_y;
gl_Position = vec4(p_ndc, 0.f, 1.f);
l = vec2(dot(da, da), vertex.y);
}

View File

@@ -4,7 +4,7 @@ layout (location = 0) in vec2 p_a;
layout (location = 1) in vec2 p_b;
layout (location = 2) in vec2 vertex;
out float l;
out vec2 l;
uniform float u_width;
uniform float u_height;
@@ -16,6 +16,7 @@ void main() {
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;
vec2 p = p_a + x_b * vertex.x + (u_thickness + 2.0) * y_b * vertex.y * vec2(1.0, u_width/u_height) * ndc2pix;
gl_Position = vec4(p, 0.f, 1.f);
l = vec2(0.0, vertex.y);
}

View File

@@ -736,7 +736,7 @@ export let Aladin = (function () {
gridOptions: {
enabled: false,
showLabels: true,
thickness: 2,
thickness: 1,
labelSize: 15,
},
projection: "SIN",
@@ -3126,7 +3126,7 @@ aladin.displayFITS(
);
}
if (executeDefaultSuccessAction === true) {
self.wasm.setCenter(meta.ra, meta.dec);
self.gotoRaDec(meta.ra, meta.dec);
self.setFoV(meta.fov);
}

View File

@@ -45,7 +45,6 @@ export let DefaultActionsForContextMenu = (function () {
const a = aladinInstance;
const selectObjects = (selection) => {
console.log(selection)
a.view.selectObjects(selection);
};
return [

View File

@@ -251,7 +251,7 @@ export let Footprint= (function() {
return true;
}
}
return false;
return this.shapes.some((shape) => shape.intersectsBBox(x, y, w, h, view));
};
return Footprint;

View File

@@ -26,7 +26,7 @@ import { ALEvent } from "./events/ALEvent.js";
* @property {Boolean} [perimeter=false] - Draw the perimeter of the MOC only with `options.color`.
* @property {string} [edge=!fill && !perimeter] - Draw the edges of the HEALPix cells with `options.color`.
The HEALPix cell edges compositing the MOC will be drawn if `fill` and `perimeter` are false
* @property {number} [lineWidth=3] - The line width in pixels
* @property {number} [lineWidth=1] - The line width in pixels
* @property {number} [opacity=1.0] - The opacity of the colors
*/
@@ -93,7 +93,7 @@ export let MOC = (function() {
}
this.opacity = Math.max(0, Math.min(1, this.opacity)); // 0 <= this.opacity <= 1
this.lineWidth = options["lineWidth"] || 3;
this.lineWidth = options["lineWidth"] || 1;
//this.proxyCalled = false; // this is a flag to check whether we already tried to load the MOC through the proxy

View File

@@ -135,10 +135,12 @@ export class Selector {
}
// footprints
overlayItems = cat.getFootprints();
if (overlayItems) {
const {x, y, w, h} = selection.bbox();
for (var l = 0; l < overlayItems.length; l++) {
f = overlayItems[l];
if (f.intersectsBBox(x, y, w, h, view)) {
objListPerCatalog.push(f);
}

View File

@@ -213,7 +213,7 @@ export let View = (function () {
this.fov = this.options.fov || 180.0
// Target position settings
this.viewCenter = { lon, lat }; // position of center of view
this.viewCenter = { ra: lon, dec: lat }; // position of center of view always in ICRS
// Coo frame setting
const cooFrame = CooFrameEnum.fromString(this.options.cooFrame, CooFrameEnum.ICRS);
@@ -2022,9 +2022,17 @@ export let View = (function () {
};
View.prototype.updateCenter = function() {
const [ra, dec] = this.wasm.getCenter();
this.viewCenter.lon = ra;
this.viewCenter.lat = dec;
// Center position in the frame of the view
const [lon, lat] = this.wasm.getCenter();
// ICRS conversion
let [ra, dec] = this.wasm.viewToICRSCooSys(lon, lat);
if (ra < 0) {
ra = ra + 360.0
}
this.viewCenter = {ra, dec};
}
View.prototype.showHealpixGrid = function (show) {
@@ -2069,11 +2077,10 @@ export let View = (function () {
return;
}
this.viewCenter.lon = ra;
this.viewCenter.lat = dec;
this.viewCenter = {ra, dec};
// Put a javascript code here to do some animation
this.wasm.setCenter(this.viewCenter.lon, this.viewCenter.lat);
this.wasm.setCenter(this.viewCenter.ra, this.viewCenter.dec);
ALEvent.POSITION_CHANGED.dispatchedTo(this.aladin.aladinDiv, this.viewCenter);
@@ -2224,12 +2231,11 @@ export let View = (function () {
if (!footprint.source || !footprint.source.tooSmallFootprint) {
const originLineWidth = footprint.getLineWidth();
let spreadedLineWidth = (originLineWidth || 1) + 3;
footprint.setLineWidth(spreadedLineWidth);
if (footprint.isShowing && footprint.isInStroke(ctx, this, x * window.devicePixelRatio, y * window.devicePixelRatio)) {
closests.push(footprint);
}
footprint.setLineWidth(originLineWidth);
}
})

View File

@@ -47,6 +47,7 @@ export class ALEvent {
static UPDATE_CMAP_LIST = new ALEvent("AL:cmap.updated");
// Gives the center position in ICRS
static POSITION_CHANGED = new ALEvent("AL:position.changed");
static ZOOM_CHANGED = new ALEvent("AL:zoom.changed");

View File

@@ -304,7 +304,6 @@ export class OverlayStackBox extends Box {
let moc = A.MOCFromURL(url, {
name: file.name,
lineWidth: 3.0,
});
self.aladin.addMOC(moc);
},
@@ -347,7 +346,6 @@ export class OverlayStackBox extends Box {
{ ra, dec, radius },
{
name: "cone",
lineWidth: 3.0,
}
);
self.aladin.addMOC(moc);
@@ -418,7 +416,6 @@ export class OverlayStackBox extends Box {
},
{
name: "rect",
lineWidth: 3.0,
}
);
self.aladin.addMOC(moc);
@@ -463,7 +460,6 @@ export class OverlayStackBox extends Box {
{ ra, dec },
{
name: "poly",
lineWidth: 3.0,
}
);
self.aladin.addMOC(moc);

View File

@@ -41,6 +41,16 @@ import { ActionButton } from "./Widgets/ActionButton.js";
import { Input } from "./Widgets/Input.js";
import { Utils } from "../Utils.ts";
function radec2Lonlat(radec, frame) {
// convert to the view frame
let lonlat = radec;
if (frame === "GAL") {
lonlat = CooConversion.ICRSToGalactic(radec)
}
return lonlat
}
export class Location extends DOMElement {
// constructor
constructor(aladin) {
@@ -141,20 +151,15 @@ export class Location extends DOMElement {
ALEvent.CANVAS_EVENT.listenedBy(aladin.aladinDiv, function (e) {
let param = e.detail;
if (param.type === 'mouseout') {
let radec = aladin.getRaDec();
// convert to the view frame
let lonlat = radec;
if (aladin.getFrame() === "GAL") {
lonlat = CooConversion.ICRSToGalactic(radec)
}
let frame = aladin.getFrame();
if (param.type === 'mouseout') {
let [ra, dec] = aladin.getRaDec();
let [lon, lat] = lonlat;
//self.field.el.blur()
self.update({
lon, lat,
frame: aladin.view.cooFrame,
isViewCenter: true,
ra, dec,
frame,
center: true,
}, aladin);
}
@@ -170,39 +175,46 @@ export class Location extends DOMElement {
self.update({
mouseX: param.xy.x,
mouseY: param.xy.y,
frame: aladin.view.cooFrame,
isViewCenter: false,
frame,
center: false,
}, aladin);
}
});
ALEvent.POSITION_CHANGED.listenedBy(aladin.aladinDiv, function (e) {
// center position in ICRS
let {ra, dec} = e.detail;
let frame = aladin.getFrame();
self.update({
lon: e.detail.lon,
lat: e.detail.lat,
isViewCenter: true,
frame: aladin.view.cooFrame
ra,
dec,
center: true,
frame
}, aladin);
});
ALEvent.FRAME_CHANGED.listenedBy(aladin.aladinDiv, function (e) {
let [lon, lat] = aladin.getRaDec();
let [ra, dec] = aladin.getRaDec();
let frame = aladin.getFrame();
self.update({
lon, lat,
isViewCenter: true,
frame: e.detail.cooFrame
ra, dec,
center: true,
frame
}, aladin);
});
this.aladin = aladin;
let [lon, lat] = aladin.getRaDec();
let [ra, dec] = aladin.getRaDec();
let frame = aladin.getFrame();
this.update({
lon, lat,
isViewCenter: true,
frame: aladin.view.cooFrame
ra,
dec,
frame,
center: true
}, aladin)
};
@@ -210,6 +222,7 @@ export class Location extends DOMElement {
update(options, aladin) {
let self = this;
// lon and lat must be given in cooFrame
const updateFromLonLatFunc = (lon, lat, cooFrame) => {
var coo = new Coo(lon, lat, Location.prec);
if (cooFrame == CooFrameEnum.ICRS) {
@@ -224,21 +237,21 @@ export class Location extends DOMElement {
self.field.removeClass('aladin-not-valid');
self.field.removeClass('aladin-valid');
self.field.element().style.color = options.isViewCenter ? 'var(--aladin-color)' : 'white';
//self.field.el.blur()
self.field.element().style.color = options.center ? 'var(--aladin-color)' : 'white';
};
if (options.lon && options.lat) {
updateFromLonLatFunc(options.lon, options.lat, options.frame, true);
if (options.ra && options.dec) {
let [lon, lat] = radec2Lonlat([options.ra, options.dec], options.frame)
updateFromLonLatFunc(lon, lat, options.frame);
} else if (options.mouseX && options.mouseY) {
try {
let radec = aladin.pix2world(options.mouseX, options.mouseY); // This is given in the frame of the view
if (radec) {
if (radec[0] < 0) {
radec = [radec[0] + 360.0, radec[1]];
let lonlat = aladin.pix2world(options.mouseX, options.mouseY); // This is given in the frame of the view
if (lonlat) {
if (lonlat[0] < 0) {
lonlat = [lonlat[0] + 360.0, lonlat[1]];
}
updateFromLonLatFunc(radec[0], radec[1], options.frame, false);
updateFromLonLatFunc(lonlat[0], lonlat[1], options.frame);
}
} catch(e) {}
}

View File

@@ -317,15 +317,24 @@ export let Circle = (function() {
};
Circle.prototype.isInStroke = function(ctx, view, x, y) {
this.draw(ctx, view, true);
if (!this.draw(ctx, view, true)) {
return false;
}
return ctx.isPointInStroke(x, y);
};
// From StackOverflow: https://stackoverflow.com/questions/401847/circle-rectangle-collision-detection-intersection
Circle.prototype.intersectsBBox = function(x, y, w, h) {
Circle.prototype.intersectsBBox = function(x, y, w, h, view) {
var centerXyview = view.aladin.world2pix(this.centerRaDec[0], this.centerRaDec[1]);
if (!centerXyview) {
return false;
}
// compute the absolute distance between the middle of the bbox
// and the center of the circle
const circleDistance = {
x: Math.abs(this.center.x - x),
y: Math.abs(this.center.y - y)
x: Math.abs(centerXyview[0] - (x + w/2)),
y: Math.abs(centerXyview[1] - (y + h/2))
};
if (circleDistance.x > (w/2 + this.radius)) { return false; }

View File

@@ -337,7 +337,7 @@ export let Ellipse = (function() {
};
Ellipse.prototype.isInStroke = function(ctx, view, x, y) {
if (!this.draw(ctx, view, true, true)) {
if (!this.draw(ctx, view, true)) {
return false;
}
@@ -346,6 +346,7 @@ export let Ellipse = (function() {
Ellipse.prototype.intersectsBBox = function(x, y, w, h) {
// todo
return false;
};
return Ellipse;

View File

@@ -232,6 +232,7 @@ export let Polyline = (function() {
return false;
}
noSmallCheck = noSmallCheck===true || false;
noStroke = noStroke===true || false;
@@ -269,20 +270,29 @@ export let Polyline = (function() {
let ymin = Number.POSITIVE_INFINITY
let ymax = Number.NEGATIVE_INFINITY;
let behind = true;
for (var k=0; k<len; k++) {
var xyview = view.aladin.world2pix(this.raDecArray[k][0], this.raDecArray[k][1]);
if (!xyview) {
return false;
xyView.push(undefined);
} else {
behind = false;
let [x, y] = xyview
xyView.push({x, y});
xmin = Math.min(xmin, x);
ymin = Math.min(ymin, y);
xmax = Math.max(xmax, x);
ymax = Math.max(ymax, y);
}
xyView.push({x: xyview[0], y: xyview[1]});
xmin = Math.min(xmin, xyview[0]);
ymin = Math.min(ymin, xyview[1]);
xmax = Math.max(xmax, xyview[0]);
ymax = Math.max(ymax, xyview[1]);
}
if (behind)
return false;
// 2. do not draw the polygon if it lies outside the view
if (xmax < 0 || xmin > view.width || ymax < 0 || ymin > view.height) {
return false;
@@ -290,7 +300,7 @@ export let Polyline = (function() {
// do not draw neither if the polygone does not lie inside lineWidth
if (!noSmallCheck) {
this.isTooSmall = (xmax - xmin) < this.lineWidth || (ymax - ymin) < this.lineWidth;
this.isTooSmall = (xmax - xmin) < this.lineWidth && (ymax - ymin) < this.lineWidth;
if (this.isTooSmall) {
return false;
@@ -302,6 +312,10 @@ export let Polyline = (function() {
if (view.projection === ProjectionEnum.SIN) {
drawLine = (v0, v1) => {
if (v0 === undefined || v1 === undefined) {
return false;
}
const l = {x1: v0.x, y1: v0.y, x2: v1.x, y2: v1.y};
if (Polyline.isInsideView(l.x1, l.y1, l.x2, l.y2, view.width, view.height)) {
@@ -311,6 +325,9 @@ export let Polyline = (function() {
if (this.closed && this.fill) {
fillPoly = (v0, v1, index) => {
if (v0 === undefined || v1 === undefined)
return false;
const l = {x1: v0.x, y1: v0.y, x2: v1.x, y2: v1.y};
if (index === 0) {
@@ -391,7 +408,6 @@ export let Polyline = (function() {
v1 = v1 + 1;
}
//ctx.globalAlpha = 1;
ctx.save();
ctx.fillStyle = this.fillColor;
ctx.globalAlpha = this.opacity;
@@ -409,30 +425,41 @@ export let Polyline = (function() {
for (var j = 0; j < this.raDecArray.length; j++) {
var xy = view.aladin.world2pix(this.raDecArray[j][0], this.raDecArray[j][1]);
if (!xy) {
return false;
pointXY.push(undefined)
} else {
pointXY.push({
x: xy[0],
y: xy[1]
});
}
pointXY.push({
x: xy[0],
y: xy[1]
});
}
const lastPointIdx = pointXY.length - 1;
for (var l = 0; l < lastPointIdx; l++) {
const line = {x1: pointXY[l].x, y1: pointXY[l].y, x2: pointXY[l + 1].x, y2: pointXY[l + 1].y}; // new segment
_drawLine(line, ctx, true);
let v1 = pointXY[l];
let v2 = pointXY[l + 1];
if (ctx.isPointInStroke(x, y)) { // x,y is on line?
return true;
if (v1 && v2) {
const line = {x1: v1.x, y1: v1.y, x2: v2.x, y2: v2.y}; // new segment
_drawLine(line, ctx, true);
if (ctx.isPointInStroke(x, y)) { // x, y is on line?
return true;
}
}
}
if(this.closed) {
const line = {x1: pointXY[lastPointIdx].x, y1: pointXY[lastPointIdx].y, x2: pointXY[0].x, y2: pointXY[0].y}; // new segment
_drawLine(line, ctx, true);
let v1 = pointXY[lastPointIdx];
let v2 = pointXY[0];
if (ctx.isPointInStroke(x, y)) { // x,y is on line?
return true;
if (v1 && v2) {
const line = {x1: v1.x, y1: v1.y, x2: v2.x, y2: v2.y}; // new segment
_drawLine(line, ctx, true);
if (ctx.isPointInStroke(x, y)) { // x,y is on line?
return true;
}
}
}

View File

@@ -72,7 +72,7 @@ export class SAMPConnector {
var params = message["samp.params"];
const {url, name} = params;
let moc = A.MOCFromURL(url, {name, lineWidth: 3});
let moc = A.MOCFromURL(url, {name});
aladin.addMOC(moc);
};