Compare commits

...

2 commits

Author SHA1 Message Date
Aaron Fenyes
b7375e7101 Click the display to select points 2025-04-28 00:30:38 -07:00
Aaron Fenyes
bbd4ee08b6 Highlight selected points
In the process, make points round, since the highlighting works better
visually that way.
2025-04-27 22:23:41 -07:00
5 changed files with 81 additions and 21 deletions

View file

@ -138,7 +138,7 @@ fn load_pointed_assemb(assembly: &Assembly) {
Point::new( Point::new(
format!("point_front"), format!("point_front"),
format!("Front point"), format!("Front point"),
[0.875_f32, 0.875_f32, 0.875_f32], [0.75_f32, 0.75_f32, 0.75_f32],
engine::point(0.0, 0.0, FRAC_1_SQRT_2) engine::point(0.0, 0.0, FRAC_1_SQRT_2)
) )
); );
@ -146,7 +146,7 @@ fn load_pointed_assemb(assembly: &Assembly) {
Point::new( Point::new(
format!("point_back"), format!("point_back"),
format!("Back point"), format!("Back point"),
[0.875_f32, 0.875_f32, 0.875_f32], [0.75_f32, 0.75_f32, 0.75_f32],
engine::point(0.0, 0.0, -FRAC_1_SQRT_2) engine::point(0.0, 0.0, -FRAC_1_SQRT_2)
) )
); );
@ -168,7 +168,7 @@ fn load_pointed_assemb(assembly: &Assembly) {
Point::new( Point::new(
format!("point{index_x}{index_y}"), format!("point{index_x}{index_y}"),
format!("Point {index_x}{index_y}"), format!("Point {index_x}{index_y}"),
[0.4*(2.0 + x) as f32, 0.4*(2.0 + y) as f32, 0.4*(2.0 - x*y) as f32], [0.5*(1.0 + x) as f32, 0.5*(1.0 + y) as f32, 0.5*(1.0 - x*y) as f32],
engine::point(x, y, 0.0) engine::point(x, y, 0.0)
) )
); );

View file

@ -239,7 +239,7 @@ impl Element for Point {
Point::new( Point::new(
id, id,
format!("Point {id_num}"), format!("Point {id_num}"),
[0.875_f32, 0.875_f32, 0.875_f32], [0.75_f32, 0.75_f32, 0.75_f32],
point(0.0, 0.0, 0.0) point(0.0, 0.0, 0.0)
) )
} }

View file

@ -50,19 +50,25 @@ impl SceneSpheres {
struct ScenePoints { struct ScenePoints {
representations: Vec<DVector<f64>>, representations: Vec<DVector<f64>>,
colors: Vec<ElementColor>, colors: Vec<ElementColor>,
highlights: Vec<f32>,
selections: Vec<f32>
} }
impl ScenePoints { impl ScenePoints {
fn new() -> ScenePoints { fn new() -> ScenePoints {
ScenePoints { ScenePoints {
representations: Vec::new(), representations: Vec::new(),
colors: Vec::new() colors: Vec::new(),
highlights: Vec::new(),
selections: Vec::new()
} }
} }
fn push(&mut self, representation: DVector<f64>, color: ElementColor) { fn push(&mut self, representation: DVector<f64>, color: ElementColor, highlight: f32, selected: bool) {
self.representations.push(representation); self.representations.push(representation);
self.colors.push(color); self.colors.push(color);
self.highlights.push(highlight);
self.selections.push(if selected { 1.0 } else { 0.0 });
} }
} }
@ -86,7 +92,7 @@ pub trait DisplayItem {
// the smallest positive depth, represented as a multiple of `dir`, where // the smallest positive depth, represented as a multiple of `dir`, where
// the line generated by `dir` hits the element. returns `None` if the line // the line generated by `dir` hits the element. returns `None` if the line
// misses the element // misses the element
fn cast(&self, dir: Vector3<f64>, assembly_to_world: &DMatrix<f64>) -> Option<f64>; fn cast(&self, dir: Vector3<f64>, assembly_to_world: &DMatrix<f64>, pixel_size: f64) -> Option<f64>;
} }
impl DisplayItem for Sphere { impl DisplayItem for Sphere {
@ -100,7 +106,7 @@ impl DisplayItem for Sphere {
// this method should be kept synchronized with `sphere_cast` in // this method should be kept synchronized with `sphere_cast` in
// `spheres.frag`, which does essentially the same thing on the GPU side // `spheres.frag`, which does essentially the same thing on the GPU side
fn cast(&self, dir: Vector3<f64>, assembly_to_world: &DMatrix<f64>) -> Option<f64> { fn cast(&self, dir: Vector3<f64>, assembly_to_world: &DMatrix<f64>, _pixel_size: f64) -> Option<f64> {
// if `a/b` is less than this threshold, we approximate // if `a/b` is less than this threshold, we approximate
// `a*u^2 + b*u + c` by the linear function `b*u + c` // `a*u^2 + b*u + c` by the linear function `b*u + c`
const DEG_THRESHOLD: f64 = 1e-9; const DEG_THRESHOLD: f64 = 1e-9;
@ -140,15 +146,40 @@ impl DisplayItem for Sphere {
} }
impl DisplayItem for Point { impl DisplayItem for Point {
fn show(&self, scene: &mut Scene, _selected: bool) { fn show(&self, scene: &mut Scene, selected: bool) {
const HIGHLIGHT: f32 = 0.5; /* SCAFFOLDING */
let representation = self.representation.get_clone_untracked(); let representation = self.representation.get_clone_untracked();
scene.points.push(representation, self.color); let color = if selected { self.color.map(|channel| 0.2 + 0.8*channel) } else { self.color };
let highlight = if selected { 1.0 } else { HIGHLIGHT };
scene.points.push(representation, color, highlight, selected);
} }
/* SCAFFOLDING */ /* SCAFFOLDING */
fn cast(&self, _dir: Vector3<f64>, _assembly_to_world: &DMatrix<f64>) -> Option<f64> { fn cast(&self, dir: Vector3<f64>, assembly_to_world: &DMatrix<f64>, pixel_size: f64) -> Option<f64> {
let rep = self.representation.with_untracked(|rep| assembly_to_world * rep);
if rep[2] < 0.0 {
// this constant should be kept synchronized with `point.frag`
const POINT_RADIUS_PX: f64 = 4.0;
// find the radius of the point in screen projection units
let point_radius_proj = POINT_RADIUS_PX * pixel_size;
// find the squared distance between the screen projections of the
// ray and the point
let dir_proj = -dir.fixed_rows::<2>(0) / dir[2];
let rep_proj = -rep.fixed_rows::<2>(0) / rep[2];
let dist_sq = (dir_proj - rep_proj).norm_squared();
// if the ray hits the point, return its depth
if dist_sq < point_radius_proj * point_radius_proj {
Some(rep[2] / dir[2])
} else {
None None
} }
} else {
None
}
}
} }
// --- WebGL utilities --- // --- WebGL utilities ---
@ -273,7 +304,7 @@ fn bind_new_buffer_to_attribute(
} }
// the direction in camera space that a mouse event is pointing along // the direction in camera space that a mouse event is pointing along
fn event_dir(event: &MouseEvent) -> Vector3<f64> { fn event_dir(event: &MouseEvent) -> (Vector3<f64>, f64) {
let target: web_sys::Element = event.target().unwrap().unchecked_into(); let target: web_sys::Element = event.target().unwrap().unchecked_into();
let rect = target.get_bounding_client_rect(); let rect = target.get_bounding_client_rect();
let width = rect.width(); let width = rect.width();
@ -284,10 +315,13 @@ fn event_dir(event: &MouseEvent) -> Vector3<f64> {
// `point.vert` // `point.vert`
const FOCAL_SLOPE: f64 = 0.3; const FOCAL_SLOPE: f64 = 0.3;
(
Vector3::new( Vector3::new(
FOCAL_SLOPE * (2.0*(f64::from(event.client_x()) - rect.left()) - width) / shortdim, FOCAL_SLOPE * (2.0*(f64::from(event.client_x()) - rect.left()) - width) / shortdim,
FOCAL_SLOPE * (2.0*(rect.bottom() - f64::from(event.client_y())) - height) / shortdim, FOCAL_SLOPE * (2.0*(rect.bottom() - f64::from(event.client_y())) - height) / shortdim,
-1.0 -1.0
),
FOCAL_SLOPE * 2.0 / shortdim
) )
} }
@ -379,6 +413,10 @@ pub fn Display() -> View {
// disable depth testing // disable depth testing
ctx.disable(WebGl2RenderingContext::DEPTH_TEST); ctx.disable(WebGl2RenderingContext::DEPTH_TEST);
// set blend mode
ctx.enable(WebGl2RenderingContext::BLEND);
ctx.blend_func(WebGl2RenderingContext::SRC_ALPHA, WebGl2RenderingContext::ONE_MINUS_SRC_ALPHA);
// set up the sphere rendering program // set up the sphere rendering program
let sphere_program = set_up_program( let sphere_program = set_up_program(
&ctx, &ctx,
@ -451,6 +489,8 @@ pub fn Display() -> View {
// find the point program's vertex attributes // find the point program's vertex attributes
let point_position_attr = ctx.get_attrib_location(&point_program, "position") as u32; let point_position_attr = ctx.get_attrib_location(&point_program, "position") as u32;
let point_color_attr = ctx.get_attrib_location(&point_program, "color") as u32; let point_color_attr = ctx.get_attrib_location(&point_program, "color") as u32;
let point_highlight_attr = ctx.get_attrib_location(&point_program, "highlight") as u32;
let point_selection_attr = ctx.get_attrib_location(&point_program, "selected") as u32;
// set up a repainting routine // set up a repainting routine
let (_, start_animation_loop, _) = create_raf(move || { let (_, start_animation_loop, _) = create_raf(move || {
@ -647,6 +687,8 @@ pub fn Display() -> View {
// enable the point program's vertex attributes // enable the point program's vertex attributes
ctx.enable_vertex_attrib_array(point_position_attr); ctx.enable_vertex_attrib_array(point_position_attr);
ctx.enable_vertex_attrib_array(point_color_attr); ctx.enable_vertex_attrib_array(point_color_attr);
ctx.enable_vertex_attrib_array(point_highlight_attr);
ctx.enable_vertex_attrib_array(point_selection_attr);
// write the points in world coordinates // write the points in world coordinates
let asm_to_world_sp = asm_to_world.rows(0, SPACE_DIM); let asm_to_world_sp = asm_to_world.rows(0, SPACE_DIM);
@ -661,6 +703,8 @@ pub fn Display() -> View {
// shader // shader
bind_new_buffer_to_attribute(&ctx, point_position_attr, SPACE_DIM as i32, point_positions.as_slice()); bind_new_buffer_to_attribute(&ctx, point_position_attr, SPACE_DIM as i32, point_positions.as_slice());
bind_new_buffer_to_attribute(&ctx, point_color_attr, COLOR_SIZE as i32, scene.points.colors.concat().as_slice()); bind_new_buffer_to_attribute(&ctx, point_color_attr, COLOR_SIZE as i32, scene.points.colors.concat().as_slice());
bind_new_buffer_to_attribute(&ctx, point_highlight_attr, 1 as i32, scene.points.highlights.as_slice());
bind_new_buffer_to_attribute(&ctx, point_selection_attr, 1 as i32, scene.points.selections.as_slice());
// draw the scene // draw the scene
ctx.draw_arrays(WebGl2RenderingContext::POINTS, 0, point_positions.ncols() as i32); ctx.draw_arrays(WebGl2RenderingContext::POINTS, 0, point_positions.ncols() as i32);
@ -668,6 +712,8 @@ pub fn Display() -> View {
// disable the point program's vertex attributes // disable the point program's vertex attributes
ctx.disable_vertex_attrib_array(point_position_attr); ctx.disable_vertex_attrib_array(point_position_attr);
ctx.disable_vertex_attrib_array(point_color_attr); ctx.disable_vertex_attrib_array(point_color_attr);
ctx.disable_vertex_attrib_array(point_highlight_attr);
ctx.disable_vertex_attrib_array(point_selection_attr);
} }
// --- update the display state --- // --- update the display state ---
@ -801,11 +847,11 @@ pub fn Display() -> View {
}, },
on:click=move |event: MouseEvent| { on:click=move |event: MouseEvent| {
// find the nearest element along the pointer direction // find the nearest element along the pointer direction
let dir = event_dir(&event); let (dir, pixel_size) = event_dir(&event);
console::log_1(&JsValue::from(dir.to_string())); console::log_1(&JsValue::from(dir.to_string()));
let mut clicked: Option<(ElementKey, f64)> = None; let mut clicked: Option<(ElementKey, f64)> = None;
for (key, elt) in state.assembly.elements.get_clone_untracked() { for (key, elt) in state.assembly.elements.get_clone_untracked() {
match assembly_to_world.with(|asm_to_world| elt.cast(dir, asm_to_world)) { match assembly_to_world.with(|asm_to_world| elt.cast(dir, asm_to_world, pixel_size)) {
Some(depth) => match clicked { Some(depth) => match clicked {
Some((_, best_depth)) => { Some((_, best_depth)) => {
if depth < best_depth { if depth < best_depth {

View file

@ -3,9 +3,16 @@
precision highp float; precision highp float;
in vec3 point_color; in vec3 point_color;
in float point_highlight;
in float total_radius;
out vec4 outColor; out vec4 outColor;
void main() { void main() {
outColor = vec4(point_color, 1.); float r = total_radius * length(2.*gl_PointCoord - vec2(1.));
const float POINT_RADIUS = 4.;
float border = smoothstep(POINT_RADIUS - 1., POINT_RADIUS, r);
vec3 color = mix(point_color, vec3(1.), border * point_highlight);
outColor = vec4(color, 1. - smoothstep(total_radius - 1., total_radius, r));
} }

View file

@ -2,16 +2,23 @@
in vec4 position; in vec4 position;
in vec3 color; in vec3 color;
in float highlight;
in float selected;
out vec3 point_color; out vec3 point_color;
out float point_highlight;
out float total_radius;
// camera // camera
const float focal_slope = 0.3; const float focal_slope = 0.3;
void main() { void main() {
total_radius = 5. + 0.5*selected;
float depth = -focal_slope * position.z; float depth = -focal_slope * position.z;
gl_Position = vec4(position.xy / depth, 0., 1.); gl_Position = vec4(position.xy / depth, 0., 1.);
gl_PointSize = 5.; gl_PointSize = 2.*total_radius;
point_color = color; point_color = color;
point_highlight = highlight;
} }