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;
}
.element > input {
margin-left: 8px;
}
.element-switch {
width: 18px;
padding-left: 2px;

View file

@ -101,6 +101,7 @@ pub trait Element: Serial + ProblemPoser + DisplayItem {
fn id(&self) -> &String;
fn label(&self) -> &String;
fn representation(&self) -> Signal<DVector<f64>>;
fn ghost(&self) -> Signal<bool>;
// 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<DVector<f64>>,
pub ghost: Signal<bool>,
pub regulators: Signal<BTreeSet<Rc<dyn Regulator>>>,
serial: u64,
column_index: Cell<Option<usize>>
@ -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<bool> {
self.ghost
}
fn regulators(&self) -> Signal<BTreeSet<Rc<dyn Regulator>>> {
self.regulators
}
@ -244,6 +251,7 @@ pub struct Point {
pub label: String,
pub color: ElementColor,
pub representation: Signal<DVector<f64>>,
pub ghost: Signal<bool>,
pub regulators: Signal<BTreeSet<Rc<dyn Regulator>>>,
serial: u64,
column_index: Cell<Option<usize>>
@ -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<bool> {
self.ghost
}
fn regulators(&self) -> Signal<BTreeSet<Rc<dyn Regulator>>> {
self.regulators
}

View file

@ -25,6 +25,7 @@ use crate::{
struct SceneSpheres {
representations: Vec<DVector<f64>>,
colors: Vec<ElementColor>,
opacities: Vec<f32>,
highlights: Vec<f32>
}
@ -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<f64>, color: ElementColor, highlight: f32) {
fn push(&mut self, representation: DVector<f64>, 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<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)) {
Some(depth) => match clicked {
Some((_, best_depth)) => {

View file

@ -202,7 +202,11 @@ fn ElementOutlineItem(element: Rc<dyn Element>) -> 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") {