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.
This commit is contained in:
Aaron Fenyes 2025-05-18 20:03:52 -07:00
parent ba1d87812f
commit fc230e4993
4 changed files with 40 additions and 7 deletions

View file

@ -90,6 +90,10 @@ summary > div, .regulator {
padding-right: 8px; padding-right: 8px;
} }
.element > input {
margin-left: 8px;
}
.element-switch { .element-switch {
width: 18px; width: 18px;
padding-left: 2px; padding-left: 2px;

View file

@ -101,6 +101,7 @@ pub trait Element: Serial + ProblemPoser + DisplayItem {
fn id(&self) -> &String; fn id(&self) -> &String;
fn label(&self) -> &String; fn label(&self) -> &String;
fn representation(&self) -> Signal<DVector<f64>>; fn representation(&self) -> Signal<DVector<f64>>;
fn ghost(&self) -> Signal<bool>;
// the regulators the element is subject to. the assembly that owns the // the regulators the element is subject to. the assembly that owns the
// element is responsible for keeping this set up to date // element is responsible for keeping this set up to date
@ -154,6 +155,7 @@ pub struct Sphere {
pub label: String, pub label: String,
pub color: ElementColor, pub color: ElementColor,
pub representation: Signal<DVector<f64>>, pub representation: Signal<DVector<f64>>,
pub ghost: Signal<bool>,
pub regulators: Signal<BTreeSet<Rc<dyn Regulator>>>, pub regulators: Signal<BTreeSet<Rc<dyn Regulator>>>,
serial: u64, serial: u64,
column_index: Cell<Option<usize>> column_index: Cell<Option<usize>>
@ -173,6 +175,7 @@ impl Sphere {
label: label, label: label,
color: color, color: color,
representation: create_signal(representation), representation: create_signal(representation),
ghost: create_signal(false),
regulators: create_signal(BTreeSet::new()), regulators: create_signal(BTreeSet::new()),
serial: Self::next_serial(), serial: Self::next_serial(),
column_index: None.into() column_index: None.into()
@ -210,6 +213,10 @@ impl Element for Sphere {
self.representation self.representation
} }
fn ghost(&self) -> Signal<bool> {
self.ghost
}
fn regulators(&self) -> Signal<BTreeSet<Rc<dyn Regulator>>> { fn regulators(&self) -> Signal<BTreeSet<Rc<dyn Regulator>>> {
self.regulators self.regulators
} }
@ -244,6 +251,7 @@ pub struct Point {
pub label: String, pub label: String,
pub color: ElementColor, pub color: ElementColor,
pub representation: Signal<DVector<f64>>, pub representation: Signal<DVector<f64>>,
pub ghost: Signal<bool>,
pub regulators: Signal<BTreeSet<Rc<dyn Regulator>>>, pub regulators: Signal<BTreeSet<Rc<dyn Regulator>>>,
serial: u64, serial: u64,
column_index: Cell<Option<usize>> column_index: Cell<Option<usize>>
@ -263,6 +271,7 @@ impl Point {
label, label,
color, color,
representation: create_signal(representation), representation: create_signal(representation),
ghost: create_signal(false),
regulators: create_signal(BTreeSet::new()), regulators: create_signal(BTreeSet::new()),
serial: Self::next_serial(), serial: Self::next_serial(),
column_index: None.into() column_index: None.into()
@ -296,6 +305,10 @@ impl Element for Point {
self.representation self.representation
} }
fn ghost(&self) -> Signal<bool> {
self.ghost
}
fn regulators(&self) -> Signal<BTreeSet<Rc<dyn Regulator>>> { fn regulators(&self) -> Signal<BTreeSet<Rc<dyn Regulator>>> {
self.regulators self.regulators
} }

View file

@ -25,6 +25,7 @@ use crate::{
struct SceneSpheres { struct SceneSpheres {
representations: Vec<DVector<f64>>, representations: Vec<DVector<f64>>,
colors: Vec<ElementColor>, colors: Vec<ElementColor>,
opacities: Vec<f32>,
highlights: Vec<f32> highlights: Vec<f32>
} }
@ -33,6 +34,7 @@ impl SceneSpheres {
SceneSpheres { SceneSpheres {
representations: Vec::new(), representations: Vec::new(),
colors: Vec::new(), colors: Vec::new(),
opacities: Vec::new(),
highlights: 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") self.representations.len().try_into().expect("Number of spheres must fit in a 32-bit integer")
} }
fn push(&mut self, representation: DVector<f64>, color: ElementColor, highlight: f32) { fn push(&mut self, representation: DVector<f64>, color: ElementColor, opacity: f32, highlight: f32) {
self.representations.push(representation); self.representations.push(representation);
self.colors.push(color); self.colors.push(color);
self.opacities.push(opacity);
self.highlights.push(highlight); self.highlights.push(highlight);
} }
} }
@ -98,11 +101,16 @@ pub trait DisplayItem {
impl DisplayItem for Sphere { impl DisplayItem for Sphere {
fn show(&self, scene: &mut Scene, selected: bool) { 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 representation = self.representation.get_clone_untracked();
let color = if selected { self.color.map(|channel| 0.2 + 0.8*channel) } else { self.color }; 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 }; 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 // this method should be kept synchronized with `sphere_cast` in
@ -365,6 +373,7 @@ pub fn Display() -> View {
state.assembly.elements.with(|elts| { state.assembly.elements.with(|elts| {
for elt in elts { for elt in elts {
elt.representation().track(); elt.representation().track();
elt.ghost().track();
} }
}); });
state.selection.track(); state.selection.track();
@ -395,7 +404,6 @@ pub fn Display() -> View {
const SHRINKING_SPEED: f64 = 0.15; // in length units per second const SHRINKING_SPEED: f64 = 0.15; // in length units per second
// display parameters // display parameters
const OPACITY: f32 = 0.5; /* SCAFFOLDING */
const LAYER_THRESHOLD: i32 = 0; /* DEBUG */ const LAYER_THRESHOLD: i32 = 0; /* DEBUG */
const DEBUG_MODE: i32 = 0; /* DEBUG */ const DEBUG_MODE: i32 = 0; /* DEBUG */
@ -648,7 +656,7 @@ pub fn Display() -> View {
let sphere_color = &mut [0.0; 4]; let sphere_color = &mut [0.0; 4];
sphere_color[..3].copy_from_slice(&scene.spheres.colors[n]); 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( ctx.uniform3fv_with_f32_array(
sphere_sp_locs[n].as_ref(), sphere_sp_locs[n].as_ref(),
@ -854,7 +862,11 @@ pub fn Display() -> View {
let (dir, pixel_size) = 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<(Rc<dyn Element>, f64)> = None; let mut clicked: Option<(Rc<dyn Element>, 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)) { 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)) => {

View file

@ -202,7 +202,11 @@ fn ElementOutlineItem(element: Rc<dyn Element>) -> View {
) { ) {
div(class="element-label") { (label) } div(class="element-label") { (label) }
div(class="element-representation") { (rep_components) } 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") { ul(class="regulators") {