Add a half-curvature regulator

In the process, add the `OutlineItem` trait so that each regulator can
implement its own outline item view.
This commit is contained in:
Aaron Fenyes 2025-04-01 22:23:08 -07:00
parent d57ff59730
commit bba0ac3cd6
3 changed files with 189 additions and 62 deletions

View file

@ -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<DVector<f64>>,
// 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<BTreeSet<RegulatorKey>>,
// 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<ElementKey>;
fn measurement(&self) -> ReadSignal<f64>;
@ -177,6 +179,39 @@ impl Regulator for ProductRegulator {
}
}
pub struct HalfCurvatureRegulator {
pub subject: ElementKey,
pub measurement: ReadSignal<f64>,
pub set_point: Signal<SpecifiedValue>
}
impl Regulator for HalfCurvatureRegulator {
fn subjects(&self) -> Vec<ElementKey> {
vec![self.subject]
}
fn measurement(&self) -> ReadSignal<f64> {
self.measurement
}
fn set_point(&self) -> Signal<SpecifiedValue> {
self.set_point
}
fn write_to_problem(&self, problem: &mut ConstraintProblem, elts: &Slab<Element>) {
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<ElementKey> {
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::<f64>::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<dyn Regulator>) {
@ -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) {