diff --git a/app-proto/src/add_remove.rs b/app-proto/src/add_remove.rs index 8bf6bb1..f44f5c0 100644 --- a/app-proto/src/add_remove.rs +++ b/app-proto/src/add_remove.rs @@ -166,18 +166,7 @@ pub fn AddRemove() -> View { button( on:click=|_| { let state = use_context::(); - state.assembly.insert_new_element(); - - /* DEBUG */ - // print updated list of elements by identifier - console::log_1(&JsValue::from("elements by identifier:")); - for (id, key) in state.assembly.elements_by_id.get_clone().iter() { - console::log_3( - &JsValue::from(" "), - &JsValue::from(id), - &JsValue::from(*key) - ); - } + state.assembly.insert_new_sphere(); } ) { "+" } button( diff --git a/app-proto/src/assembly.rs b/app-proto/src/assembly.rs index dc0bf91..f41397b 100644 --- a/app-proto/src/assembly.rs +++ b/app-proto/src/assembly.rs @@ -10,9 +10,11 @@ use crate::{ Q, local_unif_to_std, realize_gram, + sphere, ConfigSubspace, ConstraintProblem }, + outline::OutlineItem, specified::SpecifiedValue }; @@ -36,8 +38,8 @@ pub struct Element { pub color: ElementColor, pub representation: Signal>, - // All regulators with this element as a subject. The assembly owning - // this element is responsible for keeping this set up to date. + // the regulators this element is subject to. the assembly that owns the + // element is responsible for keeping this set up to date pub regulators: Signal>, // a serial number, assigned by `Element::new`, that uniquely identifies @@ -132,7 +134,7 @@ impl Element { } } -pub trait Regulator { +pub trait Regulator: OutlineItem { // get information fn subjects(&self) -> Vec; fn measurement(&self) -> ReadSignal; @@ -177,6 +179,39 @@ impl Regulator for ProductRegulator { } } +pub struct HalfCurvatureRegulator { + pub subject: ElementKey, + pub measurement: ReadSignal, + pub set_point: Signal +} + +impl Regulator for HalfCurvatureRegulator { + fn subjects(&self) -> Vec { + vec![self.subject] + } + + fn measurement(&self) -> ReadSignal { + self.measurement + } + + fn set_point(&self) -> Signal { + self.set_point + } + + fn write_to_problem(&self, problem: &mut ConstraintProblem, elts: &Slab) { + self.set_point.with_untracked(|set_pt| { + if let Some(val) = set_pt.value { + if let Some(col) = elts[self.subject].column_index { + const CURVATURE_COMPONENT: usize = 3; + problem.frozen.push(CURVATURE_COMPONENT, col, val); + } else { + panic!("Tried to write problem data from a regulator with an unindexed subject"); + } + } + }); + } +} + // the velocity is expressed in uniform coordinates pub struct ElementMotion<'a> { pub key: ElementKey, @@ -223,23 +258,25 @@ impl Assembly { // insert an element into the assembly without checking whether we already // have an element with the same identifier. any element that does have the // same identifier will get kicked out of the `elements_by_id` index - fn insert_element_unchecked(&self, elt: Element) { + fn insert_element_unchecked(&self, elt: Element) -> ElementKey { let id = elt.id.clone(); let key = self.elements.update(|elts| elts.insert(elt)); self.elements_by_id.update(|elts_by_id| elts_by_id.insert(id, key)); + key } - pub fn try_insert_element(&self, elt: Element) -> bool { + pub fn try_insert_element(&self, elt: Element) -> Option { let can_insert = self.elements_by_id.with_untracked( |elts_by_id| !elts_by_id.contains_key(&elt.id) ); if can_insert { - self.insert_element_unchecked(elt); + Some(self.insert_element_unchecked(elt)) + } else { + None } - can_insert } - pub fn insert_new_element(&self) { + pub fn insert_new_sphere(self) { // find the next unused identifier in the default sequence let mut id_num = 1; let mut id = format!("sphere{}", id_num); @@ -250,15 +287,18 @@ impl Assembly { id = format!("sphere{}", id_num); } - // create and insert a new element - self.insert_element_unchecked( + // create and insert a sphere + let key = self.insert_element_unchecked( 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]) + sphere(0.0, 0.0, 0.0, 1.0) ) ); + + // create and insert a curvature regulator + self.insert_new_half_curvature_regulator(key); } fn insert_regulator(&self, regulator: Rc) { @@ -274,26 +314,6 @@ impl Assembly { for regulators in subject_regulators { regulators.update(|regs| regs.insert(key)); } - } - - pub fn insert_new_product_regulator(self, subjects: [ElementKey; 2]) { - // create and insert a new product regulator - let measurement = self.elements.map( - move |elts| { - let representations = subjects.map(|subj| elts[subj].representation); - representations[0].with(|rep_0| - representations[1].with(|rep_1| - rep_0.dot(&(&*Q * rep_1)) - ) - ) - } - ); - let set_point = create_signal(SpecifiedValue::from_empty_spec()); - self.insert_regulator(Rc::new(ProductRegulator { - subjects: subjects, - measurement: measurement, - set_point: set_point - })); /* DEBUG */ // print an updated list of regulators @@ -316,6 +336,26 @@ impl Assembly { ))); } }); + } + + pub fn insert_new_product_regulator(self, subjects: [ElementKey; 2]) { + // create and insert a new product regulator + let measurement = self.elements.map( + move |elts| { + let representations = subjects.map(|subj| elts[subj].representation); + representations[0].with(|rep_0| + representations[1].with(|rep_1| + rep_0.dot(&(&*Q * rep_1)) + ) + ) + } + ); + let set_point = create_signal(SpecifiedValue::from_empty_spec()); + self.insert_regulator(Rc::new(ProductRegulator { + subjects: subjects, + measurement: measurement, + set_point: set_point + })); // update the realization when the regulator becomes a constraint, or is // edited while acting as a constraint @@ -329,6 +369,64 @@ impl Assembly { }); } + pub fn insert_new_half_curvature_regulator(self, subject: ElementKey) { + // create and insert a new half-curvature regulator + let measurement = self.elements.map( + move |elts| elts[subject].representation.with(|rep| rep[3]) + ); + let set_point = create_signal(SpecifiedValue::from_empty_spec()); + self.insert_regulator(Rc::new(HalfCurvatureRegulator { + subject: subject, + measurement: measurement, + set_point: set_point + })); + + // update the realization when the regulator becomes a constraint, or is + // edited while acting as a constraint + create_effect(move || { + console::log_1(&JsValue::from( + format!("Updated regulator with subjects [{}]", subject) + )); + if let Some(half_curv) = set_point.with(|set_pt| set_pt.value) { + let representation = self.elements.with( + |elts| elts[subject].representation + ); + representation.update(|rep| { + // set the sphere's half-curvature to the desired value + rep[3] = half_curv; + + // restore normalization by contracting toward the curvature + // axis + const SIZE_THRESHOLD: f64 = 1e-9; + let half_q_lt = -2.0 * half_curv * rep[4]; + let half_q_lt_sq = half_q_lt * half_q_lt; + let mut spatial = rep.fixed_rows_mut::<3>(0); + let q_sp = spatial.norm_squared(); + if q_sp < SIZE_THRESHOLD && half_q_lt_sq < SIZE_THRESHOLD { + spatial.copy_from_slice( + &[0.0, 0.0, (1.0 - 2.0 * half_q_lt).sqrt()] + ); + } else { + let scaling = half_q_lt + (q_sp + half_q_lt_sq).sqrt(); + spatial.scale_mut(1.0 / scaling); + rep[4] /= scaling; + } + + /* DEBUG */ + // verify normalization + let rep_for_debug = rep.clone(); + console::log_1(&JsValue::from( + format!( + "Sphere self-product after curvature change: {}", + rep_for_debug.dot(&(&*Q * &rep_for_debug)) + ) + )); + }); + self.realize(); + } + }); + } + // --- realization --- pub fn realize(&self) { diff --git a/app-proto/src/outline.rs b/app-proto/src/outline.rs index 497677d..32ff2e7 100644 --- a/app-proto/src/outline.rs +++ b/app-proto/src/outline.rs @@ -10,7 +10,13 @@ use web_sys::{ use crate::{ AppState, assembly, - assembly::{ElementKey, Regulator, RegulatorKey}, + assembly::{ + ElementKey, + HalfCurvatureRegulator, + ProductRegulator, + Regulator, + RegulatorKey + }, specified::SpecifiedValue }; @@ -84,27 +90,53 @@ fn RegulatorInput(regulator: Rc) -> View { } } +pub trait OutlineItem { + fn outline_item(self: Rc, element_key: ElementKey) -> View; +} + +impl OutlineItem for ProductRegulator { + fn outline_item(self: Rc, element_key: ElementKey) -> View { + let state = use_context::(); + let other_subject = if self.subjects[0] == element_key { + self.subjects[1] + } else { + self.subjects[0] + }; + let other_subject_label = state.assembly.elements.with( + |elts| elts[other_subject].label.clone() + ); + view! { + li(class="regulator") { + div(class="regulator-label") { (other_subject_label) } + div(class="regulator-type") { "Inversive distance" } + RegulatorInput(regulator=self) + div(class="status") + } + } + } +} + +impl OutlineItem for HalfCurvatureRegulator { + fn outline_item(self: Rc, _element_key: ElementKey) -> View { + view! { + li(class="regulator") { + div(class="regulator-label") // for spacing + div(class="regulator-type") { "Half-curvature" } + RegulatorInput(regulator=self) + div(class="status") + } + } + } +} + // a list item that shows a regulator in an outline view of an element #[component(inline_props)] fn RegulatorOutlineItem(regulator_key: RegulatorKey, element_key: ElementKey) -> View { let state = use_context::(); - let assembly = &state.assembly; - let regulator = assembly.regulators.with(|regs| regs[regulator_key].clone()); - let subjects = regulator.subjects(); - let other_subject = if subjects[0] == element_key { - subjects[1] - } else { - subjects[0] - }; - let other_subject_label = assembly.elements.with(|elts| elts[other_subject].label.clone()); - view! { - li(class="regulator") { - div(class="regulator-label") { (other_subject_label) } - div(class="regulator-type") { "Inversive distance" } - RegulatorInput(regulator=regulator) - div(class="status") - } - } + let regulator = state.assembly.regulators.with( + |regs| regs[regulator_key].clone() + ); + regulator.outline_item(element_key) } // a list item that shows an element in an outline view of an assembly @@ -127,7 +159,15 @@ fn ElementOutlineItem(key: ElementKey, element: assembly::Element) -> View { }; let regulated = element.regulators.map(|regs| regs.len() > 0); let regulator_list = element.regulators.map( - |regs| regs.clone().into_iter().collect() + move |elt_reg_keys| elt_reg_keys + .clone() + .into_iter() + .sorted_by_key( + |®_key| state.assembly.regulators.with( + |regs| regs[reg_key].subjects().len() + ) + ) + .collect() ); let details_node = create_node_ref(); view! {