From ba1d87812f0049c0e3e88fe5de88977da579394e Mon Sep 17 00:00:00 2001 From: Aaron Fenyes Date: Sun, 18 May 2025 13:35:01 -0700 Subject: [PATCH 1/4] Allow each sphere to have its own opacity To confirm that we haven't accidentally changed the rendering behavior, we still give every sphere the same default opacity. --- app-proto/src/display.rs | 11 +++++++---- app-proto/src/spheres.frag | 13 +++++++------ 2 files changed, 14 insertions(+), 10 deletions(-) diff --git a/app-proto/src/display.rs b/app-proto/src/display.rs index a2fe4b6..b663030 100644 --- a/app-proto/src/display.rs +++ b/app-proto/src/display.rs @@ -469,7 +469,6 @@ pub fn Display() -> View { ); let resolution_loc = ctx.get_uniform_location(&sphere_program, "resolution"); let shortdim_loc = ctx.get_uniform_location(&sphere_program, "shortdim"); - let opacity_loc = ctx.get_uniform_location(&sphere_program, "opacity"); let layer_threshold_loc = ctx.get_uniform_location(&sphere_program, "layer_threshold"); let debug_mode_loc = ctx.get_uniform_location(&sphere_program, "debug_mode"); @@ -646,6 +645,11 @@ pub fn Display() -> View { ctx.uniform1i(sphere_cnt_loc.as_ref(), sphere_cnt); for n in 0..sphere_reps_world.len() { let v = &sphere_reps_world[n]; + + let sphere_color = &mut [0.0; 4]; + sphere_color[..3].copy_from_slice(&scene.spheres.colors[n]); + sphere_color[3] = OPACITY; + ctx.uniform3fv_with_f32_array( sphere_sp_locs[n].as_ref(), v.rows(0, 3).as_slice() @@ -654,9 +658,9 @@ pub fn Display() -> View { sphere_lt_locs[n].as_ref(), v.rows(3, 2).as_slice() ); - ctx.uniform3fv_with_f32_array( + ctx.uniform4fv_with_f32_array( sphere_color_locs[n].as_ref(), - &scene.spheres.colors[n] + sphere_color ); ctx.uniform1f( sphere_highlight_locs[n].as_ref(), @@ -665,7 +669,6 @@ pub fn Display() -> View { } // pass the display parameters - ctx.uniform1f(opacity_loc.as_ref(), OPACITY); ctx.uniform1i(layer_threshold_loc.as_ref(), LAYER_THRESHOLD); ctx.uniform1i(debug_mode_loc.as_ref(), DEBUG_MODE); diff --git a/app-proto/src/spheres.frag b/app-proto/src/spheres.frag index d50cb1e..fa317a8 100644 --- a/app-proto/src/spheres.frag +++ b/app-proto/src/spheres.frag @@ -17,7 +17,7 @@ struct vecInv { const int SPHERE_MAX = 200; uniform int sphere_cnt; uniform vecInv sphere_list[SPHERE_MAX]; -uniform vec3 color_list[SPHERE_MAX]; +uniform vec4 color_list[SPHERE_MAX]; uniform float highlight_list[SPHERE_MAX]; // view @@ -25,7 +25,6 @@ uniform vec2 resolution; uniform float shortdim; // controls -uniform float opacity; uniform int layer_threshold; uniform bool debug_mode; @@ -69,7 +68,7 @@ struct Fragment { vec4 color; }; -Fragment sphere_shading(vecInv v, vec3 pt, vec3 base_color) { +Fragment sphere_shading(vecInv v, vec3 pt, vec4 base_color) { // the expression for normal needs to be checked. it's supposed to give the // negative gradient of the lorentz product between the impact point vector // and the sphere vector with respect to the coordinates of the impact @@ -79,7 +78,7 @@ Fragment sphere_shading(vecInv v, vec3 pt, vec3 base_color) { float incidence = dot(normal, light_dir); float illum = mix(0.4, 1.0, max(incidence, 0.0)); - return Fragment(pt, normal, vec4(illum * base_color, opacity)); + return Fragment(pt, normal, vec4(illum * base_color.rgb, base_color.a)); } float intersection_dist(Fragment a, Fragment b) { @@ -192,10 +191,11 @@ void main() { vec3 color = vec3(0.); int layer = layer_cnt - 1; TaggedDepth hit = top_hits[layer]; + vec4 sphere_color = color_list[hit.id]; Fragment frag_next = sphere_shading( sphere_list[hit.id], hit.depth * dir, - hit.dimming * color_list[hit.id] + vec4(hit.dimming * sphere_color.rgb, sphere_color.a) ); float highlight_next = highlight_list[hit.id]; --layer; @@ -206,10 +206,11 @@ void main() { // shade the next fragment hit = top_hits[layer]; + sphere_color = color_list[hit.id]; frag_next = sphere_shading( sphere_list[hit.id], hit.depth * dir, - hit.dimming * color_list[hit.id] + vec4(hit.dimming * sphere_color.rgb, sphere_color.a) ); highlight_next = highlight_list[hit.id]; -- 2.43.0 From fc230e499345d5f9c097a0605e46f18be1af6778 Mon Sep 17 00:00:00 2001 From: Aaron Fenyes Date: Sun, 18 May 2025 20:03:52 -0700 Subject: [PATCH 2/4] Introduce ghost mode Elements in ghost mode are intangible. Spheres in ghost mode are also more transparent. Points in ghost mode should probably be more transparent too, but that hasn't been implemented yet. --- app-proto/main.css | 4 ++++ app-proto/src/assembly.rs | 13 +++++++++++++ app-proto/src/display.rs | 24 ++++++++++++++++++------ app-proto/src/outline.rs | 6 +++++- 4 files changed, 40 insertions(+), 7 deletions(-) diff --git a/app-proto/main.css b/app-proto/main.css index f787535..d56784f 100644 --- a/app-proto/main.css +++ b/app-proto/main.css @@ -90,6 +90,10 @@ summary > div, .regulator { padding-right: 8px; } +.element > input { + margin-left: 8px; +} + .element-switch { width: 18px; padding-left: 2px; diff --git a/app-proto/src/assembly.rs b/app-proto/src/assembly.rs index bd185c8..e48b802 100644 --- a/app-proto/src/assembly.rs +++ b/app-proto/src/assembly.rs @@ -101,6 +101,7 @@ pub trait Element: Serial + ProblemPoser + DisplayItem { fn id(&self) -> &String; fn label(&self) -> &String; fn representation(&self) -> Signal>; + fn ghost(&self) -> Signal; // the regulators the element is subject to. the assembly that owns the // element is responsible for keeping this set up to date @@ -154,6 +155,7 @@ pub struct Sphere { pub label: String, pub color: ElementColor, pub representation: Signal>, + pub ghost: Signal, pub regulators: Signal>>, serial: u64, column_index: Cell> @@ -173,6 +175,7 @@ impl Sphere { label: label, color: color, representation: create_signal(representation), + ghost: create_signal(false), regulators: create_signal(BTreeSet::new()), serial: Self::next_serial(), column_index: None.into() @@ -210,6 +213,10 @@ impl Element for Sphere { self.representation } + fn ghost(&self) -> Signal { + self.ghost + } + fn regulators(&self) -> Signal>> { self.regulators } @@ -244,6 +251,7 @@ pub struct Point { pub label: String, pub color: ElementColor, pub representation: Signal>, + pub ghost: Signal, pub regulators: Signal>>, serial: u64, column_index: Cell> @@ -263,6 +271,7 @@ impl Point { label, color, representation: create_signal(representation), + ghost: create_signal(false), regulators: create_signal(BTreeSet::new()), serial: Self::next_serial(), column_index: None.into() @@ -296,6 +305,10 @@ impl Element for Point { self.representation } + fn ghost(&self) -> Signal { + self.ghost + } + fn regulators(&self) -> Signal>> { self.regulators } diff --git a/app-proto/src/display.rs b/app-proto/src/display.rs index b663030..68f4b1b 100644 --- a/app-proto/src/display.rs +++ b/app-proto/src/display.rs @@ -25,6 +25,7 @@ use crate::{ struct SceneSpheres { representations: Vec>, colors: Vec, + opacities: Vec, highlights: Vec } @@ -33,6 +34,7 @@ impl SceneSpheres { SceneSpheres { representations: Vec::new(), colors: Vec::new(), + opacities: Vec::new(), highlights: Vec::new() } } @@ -41,9 +43,10 @@ impl SceneSpheres { self.representations.len().try_into().expect("Number of spheres must fit in a 32-bit integer") } - fn push(&mut self, representation: DVector, color: ElementColor, highlight: f32) { + fn push(&mut self, representation: DVector, color: ElementColor, opacity: f32, highlight: f32) { self.representations.push(representation); self.colors.push(color); + self.opacities.push(opacity); self.highlights.push(highlight); } } @@ -98,11 +101,16 @@ pub trait DisplayItem { impl DisplayItem for Sphere { fn show(&self, scene: &mut Scene, selected: bool) { - const HIGHLIGHT: f32 = 0.2; /* SCAFFOLDING */ + /* SCAFFOLDING */ + const DEFAULT_OPACITY: f32 = 0.5; + const GHOST_OPACITY: f32 = 0.2; + const HIGHLIGHT: f32 = 0.2; + let representation = self.representation.get_clone_untracked(); let color = if selected { self.color.map(|channel| 0.2 + 0.8*channel) } else { self.color }; + let opacity = if self.ghost.get() { GHOST_OPACITY } else { DEFAULT_OPACITY }; let highlight = if selected { 1.0 } else { HIGHLIGHT }; - scene.spheres.push(representation, color, highlight); + scene.spheres.push(representation, color, opacity, highlight); } // this method should be kept synchronized with `sphere_cast` in @@ -365,6 +373,7 @@ pub fn Display() -> View { state.assembly.elements.with(|elts| { for elt in elts { elt.representation().track(); + elt.ghost().track(); } }); state.selection.track(); @@ -395,7 +404,6 @@ pub fn Display() -> View { const SHRINKING_SPEED: f64 = 0.15; // in length units per second // display parameters - const OPACITY: f32 = 0.5; /* SCAFFOLDING */ const LAYER_THRESHOLD: i32 = 0; /* DEBUG */ const DEBUG_MODE: i32 = 0; /* DEBUG */ @@ -648,7 +656,7 @@ pub fn Display() -> View { let sphere_color = &mut [0.0; 4]; sphere_color[..3].copy_from_slice(&scene.spheres.colors[n]); - sphere_color[3] = OPACITY; + sphere_color[3] = scene.spheres.opacities[n]; ctx.uniform3fv_with_f32_array( sphere_sp_locs[n].as_ref(), @@ -854,7 +862,11 @@ pub fn Display() -> View { let (dir, pixel_size) = event_dir(&event); console::log_1(&JsValue::from(dir.to_string())); let mut clicked: Option<(Rc, f64)> = None; - for elt in state.assembly.elements.get_clone_untracked() { + let tangible_elts = state.assembly.elements + .get_clone_untracked() + .into_iter() + .filter(|elt| !elt.ghost().get()); + for elt in tangible_elts { match assembly_to_world.with(|asm_to_world| elt.cast(dir, asm_to_world, pixel_size)) { Some(depth) => match clicked { Some((_, best_depth)) => { diff --git a/app-proto/src/outline.rs b/app-proto/src/outline.rs index caf11e8..59bbdcc 100644 --- a/app-proto/src/outline.rs +++ b/app-proto/src/outline.rs @@ -202,7 +202,11 @@ fn ElementOutlineItem(element: Rc) -> View { ) { div(class="element-label") { (label) } div(class="element-representation") { (rep_components) } - div(class="status") + input( + r#type="checkbox", + bind:checked=element.ghost(), + on:click=|event: MouseEvent| event.stop_propagation() + ) } } ul(class="regulators") { -- 2.43.0 From 592f327e622d28441e7e72a4752f0297c94deecc Mon Sep 17 00:00:00 2001 From: Aaron Fenyes Date: Mon, 19 May 2025 00:06:21 -0700 Subject: [PATCH 3/4] Make points more transparent in ghost mode --- app-proto/src/display.rs | 25 ++++++++++++++++++------- app-proto/src/point.frag | 7 ++++--- app-proto/src/point.vert | 4 ++-- 3 files changed, 24 insertions(+), 12 deletions(-) diff --git a/app-proto/src/display.rs b/app-proto/src/display.rs index 68f4b1b..21c7f86 100644 --- a/app-proto/src/display.rs +++ b/app-proto/src/display.rs @@ -22,6 +22,9 @@ use crate::{ // --- scene data --- +const COLOR_SIZE: usize = 3; +type ColorWithOpacity = [f32; COLOR_SIZE + 1]; + struct SceneSpheres { representations: Vec>, colors: Vec, @@ -53,7 +56,7 @@ impl SceneSpheres { struct ScenePoints { representations: Vec>, - colors: Vec, + colors_with_opacity: Vec, highlights: Vec, selections: Vec } @@ -62,15 +65,19 @@ impl ScenePoints { fn new() -> ScenePoints { ScenePoints { representations: Vec::new(), - colors: Vec::new(), + colors_with_opacity: Vec::new(), highlights: Vec::new(), selections: Vec::new() } } - fn push(&mut self, representation: DVector, color: ElementColor, highlight: f32, selected: bool) { + fn push(&mut self, representation: DVector, color: ElementColor, opacity: f32, highlight: f32, selected: bool) { + let mut color_with_opacity = [0.0; COLOR_SIZE + 1]; + color_with_opacity[..COLOR_SIZE].copy_from_slice(&color); + color_with_opacity[COLOR_SIZE] = opacity; + self.representations.push(representation); - self.colors.push(color); + self.colors_with_opacity.push(color_with_opacity); self.highlights.push(highlight); self.selections.push(if selected { 1.0 } else { 0.0 }); } @@ -156,11 +163,15 @@ impl DisplayItem for Sphere { impl DisplayItem for Point { fn show(&self, scene: &mut Scene, selected: bool) { - const HIGHLIGHT: f32 = 0.5; /* SCAFFOLDING */ + /* SCAFFOLDING */ + const GHOST_OPACITY: f32 = 0.4; + const HIGHLIGHT: f32 = 0.5; + let representation = self.representation.get_clone_untracked(); let color = if selected { self.color.map(|channel| 0.2 + 0.8*channel) } else { self.color }; + let opacity = if self.ghost.get() { GHOST_OPACITY } else { 1.0 }; let highlight = if selected { 1.0 } else { HIGHLIGHT }; - scene.points.push(representation, color, highlight, selected); + scene.points.push(representation, color, opacity, highlight, selected); } /* SCAFFOLDING */ @@ -714,7 +725,7 @@ pub fn Display() -> View { // bind them to the corresponding attributes in the vertex // 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_color_attr, COLOR_SIZE as i32, scene.points.colors.concat().as_slice()); + bind_new_buffer_to_attribute(&ctx, point_color_attr, (COLOR_SIZE + 1) as i32, scene.points.colors_with_opacity.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()); diff --git a/app-proto/src/point.frag b/app-proto/src/point.frag index 3a361a8..194a072 100644 --- a/app-proto/src/point.frag +++ b/app-proto/src/point.frag @@ -2,7 +2,7 @@ precision highp float; -in vec3 point_color; +in vec4 point_color; in float point_highlight; in float total_radius; @@ -13,6 +13,7 @@ void main() { 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)); + float disk = 1. - smoothstep(total_radius - 1., total_radius, r); + vec4 color = mix(point_color, vec4(1.), border * point_highlight); + outColor = vec4(vec3(1.), disk) * color; } \ No newline at end of file diff --git a/app-proto/src/point.vert b/app-proto/src/point.vert index 6945010..0b76bc1 100644 --- a/app-proto/src/point.vert +++ b/app-proto/src/point.vert @@ -1,11 +1,11 @@ #version 300 es in vec4 position; -in vec3 color; +in vec4 color; in float highlight; in float selected; -out vec3 point_color; +out vec4 point_color; out float point_highlight; out float total_radius; -- 2.43.0 From 2aa1adcd070e0d6bce288c079fb3cee7383e411e Mon Sep 17 00:00:00 2001 From: Aaron Fenyes Date: Mon, 19 May 2025 00:21:05 -0700 Subject: [PATCH 4/4] Combine color and opacity on push to scene --- app-proto/src/display.rs | 33 +++++++++++++++------------------ 1 file changed, 15 insertions(+), 18 deletions(-) diff --git a/app-proto/src/display.rs b/app-proto/src/display.rs index 21c7f86..69a3659 100644 --- a/app-proto/src/display.rs +++ b/app-proto/src/display.rs @@ -20,15 +20,23 @@ use crate::{ assembly::{Element, ElementColor, ElementMotion, Point, Sphere} }; -// --- scene data --- +// --- color --- const COLOR_SIZE: usize = 3; type ColorWithOpacity = [f32; COLOR_SIZE + 1]; +fn combine_channels(color: ElementColor, opacity: f32) -> ColorWithOpacity { + let mut color_with_opacity = [0.0; COLOR_SIZE + 1]; + color_with_opacity[..COLOR_SIZE].copy_from_slice(&color); + color_with_opacity[COLOR_SIZE] = opacity; + color_with_opacity +} + +// --- scene data --- + struct SceneSpheres { representations: Vec>, - colors: Vec, - opacities: Vec, + colors_with_opacity: Vec, highlights: Vec } @@ -36,8 +44,7 @@ impl SceneSpheres { fn new() -> SceneSpheres{ SceneSpheres { representations: Vec::new(), - colors: Vec::new(), - opacities: Vec::new(), + colors_with_opacity: Vec::new(), highlights: Vec::new() } } @@ -48,8 +55,7 @@ impl SceneSpheres { fn push(&mut self, representation: DVector, color: ElementColor, opacity: f32, highlight: f32) { self.representations.push(representation); - self.colors.push(color); - self.opacities.push(opacity); + self.colors_with_opacity.push(combine_channels(color, opacity)); self.highlights.push(highlight); } } @@ -72,12 +78,8 @@ impl ScenePoints { } fn push(&mut self, representation: DVector, color: ElementColor, opacity: f32, highlight: f32, selected: bool) { - let mut color_with_opacity = [0.0; COLOR_SIZE + 1]; - color_with_opacity[..COLOR_SIZE].copy_from_slice(&color); - color_with_opacity[COLOR_SIZE] = opacity; - self.representations.push(representation); - self.colors_with_opacity.push(color_with_opacity); + self.colors_with_opacity.push(combine_channels(color, opacity)); self.highlights.push(highlight); self.selections.push(if selected { 1.0 } else { 0.0 }); } @@ -664,11 +666,6 @@ pub fn Display() -> View { ctx.uniform1i(sphere_cnt_loc.as_ref(), sphere_cnt); for n in 0..sphere_reps_world.len() { let v = &sphere_reps_world[n]; - - let sphere_color = &mut [0.0; 4]; - sphere_color[..3].copy_from_slice(&scene.spheres.colors[n]); - sphere_color[3] = scene.spheres.opacities[n]; - ctx.uniform3fv_with_f32_array( sphere_sp_locs[n].as_ref(), v.rows(0, 3).as_slice() @@ -679,7 +676,7 @@ pub fn Display() -> View { ); ctx.uniform4fv_with_f32_array( sphere_color_locs[n].as_ref(), - sphere_color + &scene.spheres.colors_with_opacity[n] ); ctx.uniform1f( sphere_highlight_locs[n].as_ref(), -- 2.43.0