diff --git a/app-proto/src/display.rs b/app-proto/src/display.rs index 7256c60..51b207d 100644 --- a/app-proto/src/display.rs +++ b/app-proto/src/display.rs @@ -92,7 +92,7 @@ pub trait DisplayItem { // the smallest positive depth, represented as a multiple of `dir`, where // the line generated by `dir` hits the element. returns `None` if the line // misses the element - fn cast(&self, dir: Vector3, assembly_to_world: &DMatrix) -> Option; + fn cast(&self, dir: Vector3, assembly_to_world: &DMatrix, pixel_size: f64) -> Option; } impl DisplayItem for Sphere { @@ -106,7 +106,7 @@ impl DisplayItem for Sphere { // this method should be kept synchronized with `sphere_cast` in // `spheres.frag`, which does essentially the same thing on the GPU side - fn cast(&self, dir: Vector3, assembly_to_world: &DMatrix) -> Option { + fn cast(&self, dir: Vector3, assembly_to_world: &DMatrix, _pixel_size: f64) -> Option { // if `a/b` is less than this threshold, we approximate // `a*u^2 + b*u + c` by the linear function `b*u + c` const DEG_THRESHOLD: f64 = 1e-9; @@ -155,8 +155,30 @@ impl DisplayItem for Point { } /* SCAFFOLDING */ - fn cast(&self, _dir: Vector3, _assembly_to_world: &DMatrix) -> Option { - None + fn cast(&self, dir: Vector3, assembly_to_world: &DMatrix, pixel_size: f64) -> Option { + 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 + } + } else { + None + } } } @@ -282,7 +304,7 @@ fn bind_new_buffer_to_attribute( } // the direction in camera space that a mouse event is pointing along -fn event_dir(event: &MouseEvent) -> Vector3 { +fn event_dir(event: &MouseEvent) -> (Vector3, f64) { let target: web_sys::Element = event.target().unwrap().unchecked_into(); let rect = target.get_bounding_client_rect(); let width = rect.width(); @@ -293,10 +315,13 @@ fn event_dir(event: &MouseEvent) -> Vector3 { // `point.vert` const FOCAL_SLOPE: f64 = 0.3; - Vector3::new( - 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, - -1.0 + ( + Vector3::new( + 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, + -1.0 + ), + FOCAL_SLOPE * 2.0 / shortdim ) } @@ -822,11 +847,11 @@ pub fn Display() -> View { }, on:click=move |event: MouseEvent| { // 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())); let mut clicked: Option<(ElementKey, f64)> = None; 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((_, best_depth)) => { if depth < best_depth {