diff --git a/app-proto/index.html b/app-proto/index.html index 941a153..92238f4 100644 --- a/app-proto/index.html +++ b/app-proto/index.html @@ -4,7 +4,7 @@ dyna3 - + diff --git a/app-proto/main.css b/app-proto/main.css index ba8a20b..204c8c8 100644 --- a/app-proto/main.css +++ b/app-proto/main.css @@ -2,7 +2,7 @@ body { margin: 0px; color: #fcfcfc; background-color: #222; - font-family: 'Lato'; + font-family: 'Fira Sans', sans-serif; } /* sidebar */ @@ -36,7 +36,7 @@ body { /* KLUDGE */ #add-remove > button.emoji { - font-family: 'Noto Emoji'; + font-family: 'Noto Emoji', sans-serif; } /* outline */ @@ -102,6 +102,7 @@ details[open]:has(li) .elt-switch::after { .elt-rep > div { padding: 2px 0px 0px 0px; font-size: 10pt; + font-variant-numeric: tabular-nums; text-align: right; width: 56px; } diff --git a/app-proto/src/add_remove.rs b/app-proto/src/add_remove.rs index ead66dd..d135449 100644 --- a/app-proto/src/add_remove.rs +++ b/app-proto/src/add_remove.rs @@ -1,4 +1,3 @@ -use std::collections::BTreeSet; /* DEBUG */ use sycamore::prelude::*; use web_sys::{console, wasm_bindgen::JsValue}; @@ -7,64 +6,52 @@ use crate::{engine, AppState, assembly::{Assembly, Constraint, Element}}; /* DEBUG */ fn load_gen_assemb(assembly: &Assembly) { let _ = assembly.try_insert_element( - Element { - id: String::from("gemini_a"), - label: String::from("Castor"), - color: [1.00_f32, 0.25_f32, 0.00_f32], - rep: engine::sphere(0.5, 0.5, 0.0, 1.0), - constraints: BTreeSet::default(), - index: 0 - } + Element::new( + String::from("gemini_a"), + String::from("Castor"), + [1.00_f32, 0.25_f32, 0.00_f32], + engine::sphere(0.5, 0.5, 0.0, 1.0) + ) ); let _ = assembly.try_insert_element( - Element { - id: String::from("gemini_b"), - label: String::from("Pollux"), - color: [0.00_f32, 0.25_f32, 1.00_f32], - rep: engine::sphere(-0.5, -0.5, 0.0, 1.0), - constraints: BTreeSet::default(), - index: 0 - } + Element::new( + String::from("gemini_b"), + String::from("Pollux"), + [0.00_f32, 0.25_f32, 1.00_f32], + engine::sphere(-0.5, -0.5, 0.0, 1.0) + ) ); let _ = assembly.try_insert_element( - Element { - id: String::from("ursa_major"), - label: String::from("Ursa major"), - color: [0.25_f32, 0.00_f32, 1.00_f32], - rep: engine::sphere(-0.5, 0.5, 0.0, 0.75), - constraints: BTreeSet::default(), - index: 0 - } + Element::new( + String::from("ursa_major"), + String::from("Ursa major"), + [0.25_f32, 0.00_f32, 1.00_f32], + engine::sphere(-0.5, 0.5, 0.0, 0.75) + ) ); let _ = assembly.try_insert_element( - Element { - id: String::from("ursa_minor"), - label: String::from("Ursa minor"), - color: [0.25_f32, 1.00_f32, 0.00_f32], - rep: engine::sphere(0.5, -0.5, 0.0, 0.5), - constraints: BTreeSet::default(), - index: 0 - } + Element::new( + String::from("ursa_minor"), + String::from("Ursa minor"), + [0.25_f32, 1.00_f32, 0.00_f32], + engine::sphere(0.5, -0.5, 0.0, 0.5) + ) ); let _ = assembly.try_insert_element( - Element { - id: String::from("moon_deimos"), - label: String::from("Deimos"), - color: [0.75_f32, 0.75_f32, 0.00_f32], - rep: engine::sphere(0.0, 0.15, 1.0, 0.25), - constraints: BTreeSet::default(), - index: 0 - } + Element::new( + String::from("moon_deimos"), + String::from("Deimos"), + [0.75_f32, 0.75_f32, 0.00_f32], + engine::sphere(0.0, 0.15, 1.0, 0.25) + ) ); let _ = assembly.try_insert_element( - Element { - id: String::from("moon_phobos"), - label: String::from("Phobos"), - color: [0.00_f32, 0.75_f32, 0.50_f32], - rep: engine::sphere(0.0, -0.15, -1.0, 0.25), - constraints: BTreeSet::default(), - index: 0 - } + Element::new( + String::from("moon_phobos"), + String::from("Phobos"), + [0.00_f32, 0.75_f32, 0.50_f32], + engine::sphere(0.0, -0.15, -1.0, 0.25) + ) ); } @@ -72,84 +59,68 @@ fn load_gen_assemb(assembly: &Assembly) { fn load_low_curv_assemb(assembly: &Assembly) { let a = 0.75_f64.sqrt(); let _ = assembly.try_insert_element( - Element { - id: "central".to_string(), - label: "Central".to_string(), - color: [0.75_f32, 0.75_f32, 0.75_f32], - rep: engine::sphere(0.0, 0.0, 0.0, 1.0), - constraints: BTreeSet::default(), - index: 0 - } + Element::new( + "central".to_string(), + "Central".to_string(), + [0.75_f32, 0.75_f32, 0.75_f32], + engine::sphere(0.0, 0.0, 0.0, 1.0) + ) ); let _ = assembly.try_insert_element( - Element { - id: "assemb_plane".to_string(), - label: "Assembly plane".to_string(), - color: [0.75_f32, 0.75_f32, 0.75_f32], - rep: engine::sphere_with_offset(0.0, 0.0, 1.0, 0.0, 0.0), - constraints: BTreeSet::default(), - index: 0 - } + Element::new( + "assemb_plane".to_string(), + "Assembly plane".to_string(), + [0.75_f32, 0.75_f32, 0.75_f32], + engine::sphere_with_offset(0.0, 0.0, 1.0, 0.0, 0.0) + ) ); let _ = assembly.try_insert_element( - Element { - id: "side1".to_string(), - label: "Side 1".to_string(), - color: [1.00_f32, 0.00_f32, 0.25_f32], - rep: engine::sphere_with_offset(1.0, 0.0, 0.0, 1.0, 0.0), - constraints: BTreeSet::default(), - index: 0 - } + Element::new( + "side1".to_string(), + "Side 1".to_string(), + [1.00_f32, 0.00_f32, 0.25_f32], + engine::sphere_with_offset(1.0, 0.0, 0.0, 1.0, 0.0) + ) ); let _ = assembly.try_insert_element( - Element { - id: "side2".to_string(), - label: "Side 2".to_string(), - color: [0.25_f32, 1.00_f32, 0.00_f32], - rep: engine::sphere_with_offset(-0.5, a, 0.0, 1.0, 0.0), - constraints: BTreeSet::default(), - index: 0 - } + Element::new( + "side2".to_string(), + "Side 2".to_string(), + [0.25_f32, 1.00_f32, 0.00_f32], + engine::sphere_with_offset(-0.5, a, 0.0, 1.0, 0.0) + ) ); let _ = assembly.try_insert_element( - Element { - id: "side3".to_string(), - label: "Side 3".to_string(), - color: [0.00_f32, 0.25_f32, 1.00_f32], - rep: engine::sphere_with_offset(-0.5, -a, 0.0, 1.0, 0.0), - constraints: BTreeSet::default(), - index: 0 - } + Element::new( + "side3".to_string(), + "Side 3".to_string(), + [0.00_f32, 0.25_f32, 1.00_f32], + engine::sphere_with_offset(-0.5, -a, 0.0, 1.0, 0.0) + ) ); let _ = assembly.try_insert_element( - Element { - id: "corner1".to_string(), - label: "Corner 1".to_string(), - color: [0.75_f32, 0.75_f32, 0.75_f32], - rep: engine::sphere(-4.0/3.0, 0.0, 0.0, 1.0/3.0), - constraints: BTreeSet::default(), - index: 0 - } + Element::new( + "corner1".to_string(), + "Corner 1".to_string(), + [0.75_f32, 0.75_f32, 0.75_f32], + engine::sphere(-4.0/3.0, 0.0, 0.0, 1.0/3.0) + ) ); let _ = assembly.try_insert_element( - Element { - id: "corner2".to_string(), - label: "Corner 2".to_string(), - color: [0.75_f32, 0.75_f32, 0.75_f32], - rep: engine::sphere(2.0/3.0, -4.0/3.0 * a, 0.0, 1.0/3.0), - constraints: BTreeSet::default(), - index: 0 - } + Element::new( + "corner2".to_string(), + "Corner 2".to_string(), + [0.75_f32, 0.75_f32, 0.75_f32], + engine::sphere(2.0/3.0, -4.0/3.0 * a, 0.0, 1.0/3.0) + ) ); let _ = assembly.try_insert_element( - Element { - id: String::from("corner3"), - label: String::from("Corner 3"), - color: [0.75_f32, 0.75_f32, 0.75_f32], - rep: engine::sphere(2.0/3.0, 4.0/3.0 * a, 0.0, 1.0/3.0), - constraints: BTreeSet::default(), - index: 0 - } + Element::new( + String::from("corner3"), + String::from("Corner 3"), + [0.75_f32, 0.75_f32, 0.75_f32], + engine::sphere(2.0/3.0, 4.0/3.0 * a, 0.0, 1.0/3.0) + ) ); } @@ -216,15 +187,15 @@ pub fn AddRemove() -> View { } ); let rep = create_signal(0.0); + let rep_valid = create_signal(false); let active = create_signal(true); state.assembly.insert_constraint(Constraint { args: args, rep: rep, rep_text: create_signal(String::new()), - rep_valid: create_signal(false), + rep_valid: rep_valid, active: active, }); - state.assembly.realize(); state.selection.update(|sel| sel.clear()); /* DEBUG */ @@ -242,15 +213,14 @@ pub fn AddRemove() -> View { } }); - // update the realization when the constraint activated, or - // edited while active + // update the realization when the constraint becomes active + // and valid, or is edited while active and valid create_effect(move || { + console::log_1(&JsValue::from( + format!("Constraint ({}, {}) updated", args.0, args.1) + )); rep.track(); - console::log_2( - &JsValue::from("Lorentz product updated to"), - &JsValue::from(rep.get_untracked()) - ); - if active.get() { + if active.get() && rep_valid.get() { state.assembly.realize(); } }); diff --git a/app-proto/src/assembly.rs b/app-proto/src/assembly.rs index 0970932..7b7c015 100644 --- a/app-proto/src/assembly.rs +++ b/app-proto/src/assembly.rs @@ -12,13 +12,32 @@ pub struct Element { pub id: String, pub label: String, pub color: [f32; 3], - pub rep: DVector, - pub constraints: BTreeSet, + pub rep: Signal>, + pub constraints: Signal>, // internal properties, not reflected in any view pub index: usize } +impl Element { + pub fn new( + id: String, + label: String, + color: [f32; 3], + rep: DVector + ) -> Element { + Element { + id: id, + label: label, + color: color, + rep: create_signal(rep), + constraints: create_signal(BTreeSet::default()), + index: 0 + } + } +} + + #[derive(Clone)] pub struct Constraint { pub args: (usize, usize), @@ -82,24 +101,23 @@ impl Assembly { // create and insert a new element self.insert_element_unchecked( - Element { - id: id, - label: format!("Sphere {}", id_num), - color: [0.75_f32, 0.75_f32, 0.75_f32], - rep: DVector::::from_column_slice(&[0.0, 0.0, 0.0, 0.5, -0.5]), - constraints: BTreeSet::default(), - index: 0 - } + Element::new( + id, + format!("Sphere {}", id_num), + [0.75_f32, 0.75_f32, 0.75_f32], + DVector::::from_column_slice(&[0.0, 0.0, 0.0, 0.5, -0.5]) + ) ); } pub fn insert_constraint(&self, constraint: Constraint) { let args = constraint.args; let key = self.constraints.update(|csts| csts.insert(constraint)); - self.elements.update(|elts| { - elts[args.0].constraints.insert(key); - elts[args.1].constraints.insert(key); - }); + let arg_constraints = self.elements.with( + |elts| (elts[args.0].constraints, elts[args.1].constraints) + ); + arg_constraints.0.update(|csts| csts.insert(key)); + arg_constraints.1.update(|csts| csts.insert(key)); } // --- realization --- @@ -133,7 +151,7 @@ impl Assembly { for (_, elt) in elts { let index = elt.index; gram_to_be.push_sym(index, index, 1.0); - guess_to_be.set_column(index, &elt.rep); + guess_to_be.set_column(index, &elt.rep.get_clone_untracked()); } (gram_to_be, guess_to_be) @@ -175,11 +193,11 @@ impl Assembly { if success { // read out the solution - self.elements.update(|elts| { - for (_, elt) in elts.iter_mut() { - elt.rep.set_column(0, &config.column(elt.index)); - } - }); + for (_, elt) in self.elements.get_clone_untracked() { + elt.rep.update( + |rep| rep.set_column(0, &config.column(elt.index)) + ); + } } } } \ No newline at end of file diff --git a/app-proto/src/display.rs b/app-proto/src/display.rs index c32b470..ce1655d 100644 --- a/app-proto/src/display.rs +++ b/app-proto/src/display.rs @@ -103,7 +103,11 @@ pub fn Display() -> View { // change listener let scene_changed = create_signal(true); create_effect(move || { - state.assembly.elements.track(); + state.assembly.elements.with(|elts| { + for (_, elt) in elts { + elt.rep.track(); + } + }); state.selection.track(); scene_changed.set(true); }); @@ -295,23 +299,40 @@ pub fn Display() -> View { let assembly_to_world = &location * &orientation; // get the assembly - let elements = state.assembly.elements.get_clone(); - let element_iter = (&elements).into_iter(); - let reps_world: Vec<_> = element_iter.clone().map(|(_, elt)| &assembly_to_world * &elt.rep).collect(); - let colors: Vec<_> = element_iter.clone().map(|(key, elt)| - if state.selection.with(|sel| sel.contains(&key)) { - elt.color.map(|ch| 0.2 + 0.8*ch) - } else { - elt.color - } - ).collect(); - let highlights: Vec<_> = element_iter.map(|(key, _)| - if state.selection.with(|sel| sel.contains(&key)) { - 1.0_f32 - } else { - HIGHLIGHT - } - ).collect(); + let ( + elt_cnt, + reps_world, + colors, + highlights + ) = state.assembly.elements.with(|elts| { + ( + // number of elements + elts.len() as i32, + + // representation vectors in world coordinates + elts.iter().map( + |(_, elt)| elt.rep.with(|rep| &assembly_to_world * rep) + ).collect::>(), + + // colors + elts.iter().map(|(key, elt)| { + if state.selection.with(|sel| sel.contains(&key)) { + elt.color.map(|ch| 0.2 + 0.8*ch) + } else { + elt.color + } + }).collect::>(), + + // highlight levels + elts.iter().map(|(key, _)| { + if state.selection.with(|sel| sel.contains(&key)) { + 1.0_f32 + } else { + HIGHLIGHT + } + }).collect::>() + ) + }); // set the resolution let width = canvas.width() as f32; @@ -320,7 +341,7 @@ pub fn Display() -> View { ctx.uniform1f(shortdim_loc.as_ref(), width.min(height)); // pass the assembly - ctx.uniform1i(sphere_cnt_loc.as_ref(), elements.len() as i32); + ctx.uniform1i(sphere_cnt_loc.as_ref(), elt_cnt); for n in 0..reps_world.len() { let v = &reps_world[n]; ctx.uniform3f( diff --git a/app-proto/src/outline.rs b/app-proto/src/outline.rs index 4a2b36a..11cc061 100644 --- a/app-proto/src/outline.rs +++ b/app-proto/src/outline.rs @@ -1,5 +1,5 @@ use itertools::Itertools; -use sycamore::{prelude::*, web::tags::div}; +use sycamore::prelude::*; use web_sys::{ Event, HtmlInputElement, @@ -43,13 +43,9 @@ fn ConstraintOutlineItem(constraint_key: usize, element_key: usize) -> View { constraint.args.0 }; let other_arg_label = assembly.elements.with(|elts| elts[other_arg].label.clone()); - let class = create_memo(move || { - if constraint.rep_valid.get() { - "cst" - } else { - "cst invalid" - } - }); + let class = constraint.rep_valid.map( + |&rep_valid| if rep_valid { "cst" } else { "cst invalid" } + ); view! { li(class=class.get()) { input(r#type="checkbox", bind:checked=constraint.active) @@ -64,19 +60,19 @@ fn ConstraintOutlineItem(constraint_key: usize, element_key: usize) -> View { #[component(inline_props)] fn ElementOutlineItem(key: usize, element: assembly::Element) -> View { let state = use_context::(); - let class = create_memo(move || { - if state.selection.with(|sel| sel.contains(&key)) { - "selected" - } else { - "" - } - }); + let class = state.selection.map( + move |sel| if sel.contains(&key) { "selected" } else { "" } + ); let label = element.label.clone(); - let rep_components = element.rep.iter().map(|u| { - let u_coord = format!("{:.3}", u).replace("-", "\u{2212}"); - View::from(div().children(u_coord)) - }).collect::>(); - let constrained = element.constraints.len() > 0; + let rep_components = element.rep.map( + |rep| rep.iter().map( + |u| format!("{:.3}", u).replace("-", "\u{2212}") + ).collect() + ); + let constrained = element.constraints.map(|csts| csts.len() > 0); + let constraint_list = element.constraints.map( + |csts| csts.clone().into_iter().collect() + ); let details_node = create_node_ref(); view! { li { @@ -101,7 +97,7 @@ fn ElementOutlineItem(key: usize, element: assembly::Element) -> View { } event.prevent_default(); }, - "ArrowRight" if constrained => { + "ArrowRight" if constrained.get() => { let _ = details_node .get() .unchecked_into::() @@ -144,13 +140,20 @@ fn ElementOutlineItem(key: usize, element: assembly::Element) -> View { } ) { div(class="elt-label") { (label) } - div(class="elt-rep") { (rep_components) } + div(class="elt-rep") { + Indexed( + list=rep_components, + view=|coord_str| view! { + div { (coord_str) } + } + ) + } div(class="status") } } ul(class="constraints") { Keyed( - list=element.constraints.into_iter().collect::>(), + list=constraint_list, view=move |cst_key| view! { ConstraintOutlineItem( constraint_key=cst_key, @@ -173,15 +176,16 @@ fn ElementOutlineItem(key: usize, element: assembly::Element) -> View { // #[component] pub fn Outline() -> View { - // sort the elements alphabetically by ID - let elements_sorted = create_memo(|| { - let state = use_context::(); - state.assembly.elements - .get_clone() + let state = use_context::(); + + // list the elements alphabetically by ID + let element_list = state.assembly.elements.map( + |elts| elts + .clone() .into_iter() .sorted_by_key(|(_, elt)| elt.id.clone()) .collect() - }); + ); view! { ul( @@ -192,16 +196,11 @@ pub fn Outline() -> View { } ) { Keyed( - list=elements_sorted, + list=element_list, view=|(key, elt)| view! { ElementOutlineItem(key=key, element=elt) }, - key=|(key, elt)| ( - key.clone(), - elt.id.clone(), - elt.label.clone(), - elt.constraints.clone() - ) + key=|(key, _)| key.clone() ) } }