From 476b13c87f3d631ad59e560c5055629a4fc853a7 Mon Sep 17 00:00:00 2001 From: Glen Whitney Date: Sat, 20 Sep 2025 00:51:26 -0700 Subject: [PATCH 1/2] feat: Point coordinate regulators MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Implements regulators for the Euclidean coordinates of Point entities, automatically creating all three of them for each added point entity. When such a regulator is set, it freezes the corresponding representation coordinate to the set point. In addition, if all three coordinates of a given Point are set, the coradius coordinate (which holds the norm of the point) is frozen as well. Note that a PointCoordinateRegulator must be created with a Point as the subject. This commit modifies HalfCurvatureRegulator analogously, so that it can only be created with a Sphere. A couple of prospective issues that should be filed in association with this commit: * The new coordinate regulators create redundant display information with the raw representation coordinates of a point that are already shown in the outline view. * The optimization status of these regulators together with HalfCurvature regulators (i.e., the ones implemented by freezing coordinates) is different from InversiveDistance regulators when an Assembly is unrealizable: the frozen-coordinate constraints will be "hard" in that they will be forced to precisely equal their set point, whereas the distance regulators are "soft" in that they can be relaxed from their set points in an effort to minimize the loss function of the configuration as compared to the values of the constraints. Perhaps at some point we should/will have a mechanism to specify the softness/hardness of constraints, but in the meantime, there should not be two different categories of constraints. Suppose we decide that by default that all constraints are soft. Then the optimizer should be able to search changing, for example, the radius of a curvature-constrained sphere, so as to minimize the loss function (for a loss that would therefore presumably have a term akin to the square of the difference between the specified and actual half-curvature of the sphere). For example, suppose you specify that the half-curvature of a sphere is 1 (so it has radius 1/2) but that its distance to a point is -1. These constraints cannot be satisfied, so the optimization fails, presumably with the point at the sphere center, and the sphere with radius 1/2. So all of the loss is concentrated in the difference between the actual point-sphere distance being -1/2, not -1. It would be more appropriate (in the all-soft constraint regime) to end up at something like a sphere of half-curvature 1/√2 with the point at the center, so that the loss is split between both the half-curvature and the distance to the sphere being off by 1 - 1/√2. (At a guess, that would minimize the sum of the squares of the two differences.) --- app-proto/Cargo.lock | 21 ++++++++ app-proto/Cargo.toml | 1 + app-proto/src/assembly.rs | 79 ++++++++++++++++++++++++++++- app-proto/src/components/outline.rs | 15 ++++++ app-proto/src/engine.rs | 6 +-- 5 files changed, 117 insertions(+), 5 deletions(-) diff --git a/app-proto/Cargo.lock b/app-proto/Cargo.lock index 4f75c45..731dd84 100644 --- a/app-proto/Cargo.lock +++ b/app-proto/Cargo.lock @@ -255,6 +255,7 @@ dependencies = [ "charming", "console_error_panic_hook", "dyna3", + "enum-iterator", "itertools", "js-sys", "lazy_static", @@ -271,6 +272,26 @@ version = "1.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "60b1af1c220855b6ceac025d3f6ecdd2b7c4894bfe9cd9bda4fbb4bc7c0d4cf0" +[[package]] +name = "enum-iterator" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a4549325971814bda7a44061bf3fe7e487d447cba01e4220a4b454d630d7a016" +dependencies = [ + "enum-iterator-derive", +] + +[[package]] +name = "enum-iterator-derive" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "685adfa4d6f3d765a26bc5dbc936577de9abf756c1feeb3089b01dd395034842" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "equivalent" version = "1.0.1" diff --git a/app-proto/Cargo.toml b/app-proto/Cargo.toml index 1230b47..d5221a1 100644 --- a/app-proto/Cargo.toml +++ b/app-proto/Cargo.toml @@ -10,6 +10,7 @@ default = ["console_error_panic_hook"] dev = [] [dependencies] +enum-iterator = "2.3.0" itertools = "0.13.0" js-sys = "0.3.70" lazy_static = "1.5.0" diff --git a/app-proto/src/assembly.rs b/app-proto/src/assembly.rs index bacc63b..e84554d 100644 --- a/app-proto/src/assembly.rs +++ b/app-proto/src/assembly.rs @@ -1,3 +1,4 @@ +use enum_iterator::{all, Sequence}; use nalgebra::{DMatrix, DVector, DVectorView}; use std::{ cell::Cell, @@ -27,6 +28,7 @@ use crate::{ ConfigSubspace, ConstraintProblem, DescentHistory, + MatrixEntry, Realization, }, specified::SpecifiedValue, @@ -275,6 +277,7 @@ pub struct Point { impl Point { const WEIGHT_COMPONENT: usize = 3; + const NORM_COMPONENT: usize = 4; pub fn new( id: String, @@ -308,6 +311,15 @@ impl Element for Point { point(0.0, 0.0, 0.0), ) } + + fn default_regulators(self: Rc) -> Vec> { + all::() + .map(|axis| { + Rc::new(PointCoordinateRegulator::new(self.clone(), axis)) + as Rc:: + }) + .collect() + } fn id(&self) -> &String { &self.id @@ -492,14 +504,14 @@ impl ProblemPoser for InversiveDistanceRegulator { } pub struct HalfCurvatureRegulator { - pub subject: Rc, + pub subject: Rc, pub measurement: ReadSignal, pub set_point: Signal, serial: u64, } impl HalfCurvatureRegulator { - pub fn new(subject: Rc) -> Self { + pub fn new(subject: Rc) -> Self { let measurement = subject.representation().map( |rep| rep[Sphere::CURVATURE_COMPONENT] ); @@ -544,6 +556,69 @@ impl ProblemPoser for HalfCurvatureRegulator { } } +#[derive(Clone, Copy, Sequence)] +pub enum Axis {X = 0, Y = 1, Z = 2} + +impl Axis { + pub const N_AXIS: usize = (Axis::Z as usize) + 1; + pub const NAME: [&str; Axis::N_AXIS] = ["X", "Y", "Z"]; +} + +pub struct PointCoordinateRegulator { + pub subject: Rc, + pub axis: Axis, + pub measurement: ReadSignal, + pub set_point: Signal, + serial: u64 +} + +impl PointCoordinateRegulator { + pub fn new(subject: Rc, axis: Axis) -> Self { + let measurement = subject.representation().map( + move |rep| rep[axis as usize] + ); + let set_point = create_signal(SpecifiedValue::from_empty_spec()); + Self { subject, axis, measurement, set_point, serial: Self::next_serial() } + } +} + +impl Serial for PointCoordinateRegulator { + fn serial(&self) -> u64 { self.serial } +} + +impl Regulator for PointCoordinateRegulator { + fn subjects(&self) -> Vec> { vec![self.subject.clone()] } + fn measurement(&self) -> ReadSignal { self.measurement } + fn set_point(&self) -> Signal { self.set_point } +} + +impl ProblemPoser for PointCoordinateRegulator { + fn pose(&self, problem: &mut ConstraintProblem) { + self.set_point.with_untracked(|set_pt| { + if let Some(val) = set_pt.value { + let col = self.subject.column_index().expect( + "Subject must be indexed before point-coordinate regulator poses."); + problem.frozen.push(self.axis as usize, col, val); + // Check if all three coordinates have been frozen, and if so, + // freeze the coradius as well + let mut coords = [0.0; Axis::N_AXIS]; + let mut nset: usize = 0; + for &MatrixEntry {index, value} in &(problem.frozen) { + if index.1 == col && index.0 < Axis::N_AXIS { + nset += 1; + coords[index.0] = value + } + } + if nset == Axis::N_AXIS { + let [x, y, z] = coords; + problem.frozen.push( + Point::NORM_COMPONENT, col, point(x,y,z)[Point::NORM_COMPONENT]); + } + } + }); + } +} + // the velocity is expressed in uniform coordinates pub struct ElementMotion<'a> { pub element: Rc, diff --git a/app-proto/src/components/outline.rs b/app-proto/src/components/outline.rs index 5355042..79781fa 100644 --- a/app-proto/src/components/outline.rs +++ b/app-proto/src/components/outline.rs @@ -6,9 +6,11 @@ use web_sys::{KeyboardEvent, MouseEvent, wasm_bindgen::JsCast}; use crate::{ AppState, assembly::{ + Axis, Element, HalfCurvatureRegulator, InversiveDistanceRegulator, + PointCoordinateRegulator, Regulator, }, specified::SpecifiedValue @@ -119,6 +121,19 @@ impl OutlineItem for HalfCurvatureRegulator { } } +impl OutlineItem for PointCoordinateRegulator { + fn outline_item(self: Rc, _element: &Rc) -> View { + view! { + li(class = "regulator") { + div(class = "regulator-label") { (Axis::NAME[self.axis as usize]) } + div(class = "regulator-type") { "Coordinate" } + RegulatorInput(regulator = self) + div(class = "status") + } + } + } +} + // a list item that shows an element in an outline view of an assembly #[component(inline_props)] fn ElementOutlineItem(element: Rc) -> View { diff --git a/app-proto/src/engine.rs b/app-proto/src/engine.rs index 4679aed..4148ae2 100644 --- a/app-proto/src/engine.rs +++ b/app-proto/src/engine.rs @@ -47,14 +47,14 @@ pub fn project_sphere_to_normalized(rep: &mut DVector) { // normalize a point's representation vector by scaling pub fn project_point_to_normalized(rep: &mut DVector) { - rep.scale_mut(0.5 / rep[3]); + rep.scale_mut(0.5 / rep[3]); //FIXME: This 3 should be Point::WEIGHT_COMPONENT } // --- partial matrices --- pub struct MatrixEntry { - index: (usize, usize), - value: f64, + pub index: (usize, usize), + pub value: f64, } pub struct PartialMatrix(Vec); From 096a153f043b48c83dbb6f89e3b09a94220ae8b4 Mon Sep 17 00:00:00 2001 From: Aaron Fenyes Date: Mon, 22 Sep 2025 16:56:13 -0700 Subject: [PATCH 2/2] Try using pinned vertices as the hard constraints --- app-proto/src/assembly.rs | 30 +- .../src/components/test_assembly_chooser.rs | 492 ++++++++++++++++++ 2 files changed, 518 insertions(+), 4 deletions(-) diff --git a/app-proto/src/assembly.rs b/app-proto/src/assembly.rs index e84554d..0bde53d 100644 --- a/app-proto/src/assembly.rs +++ b/app-proto/src/assembly.rs @@ -1,6 +1,7 @@ use enum_iterator::{all, Sequence}; use nalgebra::{DMatrix, DVector, DVectorView}; use std::{ + any::Any, cell::Cell, cmp::Ordering, collections::{BTreeMap, BTreeSet}, @@ -375,7 +376,7 @@ impl ProblemPoser for Point { } } -pub trait Regulator: Serial + ProblemPoser + OutlineItem { +pub trait Regulator: Any + Serial + ProblemPoser + OutlineItem { fn subjects(&self) -> Vec>; fn measurement(&self) -> ReadSignal; fn set_point(&self) -> Signal; @@ -385,6 +386,7 @@ pub trait Regulator: Serial + ProblemPoser + OutlineItem { fn distortion(&self) -> Option> { /* KLUDGE */ None } + fn as_any(&self) -> &dyn Any; } impl Hash for dyn Regulator { @@ -475,6 +477,10 @@ impl Regulator for InversiveDistanceRegulator { fn distortion(&self) -> Option> { self.distortion } + + fn as_any(&self) -> &dyn Any { + self + } } impl Serial for InversiveDistanceRegulator { @@ -535,6 +541,10 @@ impl Regulator for HalfCurvatureRegulator { fn set_point(&self) -> Signal { self.set_point } + + fn as_any(&self) -> &dyn Any { + self + } } impl Serial for HalfCurvatureRegulator { @@ -587,9 +597,21 @@ impl Serial for PointCoordinateRegulator { } impl Regulator for PointCoordinateRegulator { - fn subjects(&self) -> Vec> { vec![self.subject.clone()] } - fn measurement(&self) -> ReadSignal { self.measurement } - fn set_point(&self) -> Signal { self.set_point } + fn subjects(&self) -> Vec> { + vec![self.subject.clone()] + } + + fn measurement(&self) -> ReadSignal { + self.measurement + } + + fn set_point(&self) -> Signal { + self.set_point + } + + fn as_any(&self) -> &dyn Any { + self + } } impl ProblemPoser for PointCoordinateRegulator { diff --git a/app-proto/src/components/test_assembly_chooser.rs b/app-proto/src/components/test_assembly_chooser.rs index accbd1f..1e6e124 100644 --- a/app-proto/src/components/test_assembly_chooser.rs +++ b/app-proto/src/components/test_assembly_chooser.rs @@ -12,6 +12,8 @@ use crate::{ ElementColor, InversiveDistanceRegulator, Point, + PointCoordinateRegulator, + Regulator, Sphere, }, engine, @@ -3144,6 +3146,494 @@ fn load_554_domed(assembly: &Assembly) { } } +fn load_554_domed_pinned(assembly: &Assembly) { + // create the vertices + const COLOR_A: ElementColor = [0.75_f32, 0.00_f32, 0.75_f32]; + const COLOR_Z: ElementColor = [1.00_f32, 0.40_f32, 0.60_f32]; + const COLOR_B: ElementColor = [1.00_f32, 0.00_f32, 0.25_f32]; + const COLOR_Y: ElementColor = [1.00_f32, 0.75_f32, 0.25_f32]; + const COLOR_C: ElementColor = [0.75_f32, 0.50_f32, 0.00_f32]; + const COLOR_D: ElementColor = [0.90_f32, 0.90_f32, 0.00_f32]; + const COLOR_E: ElementColor = [0.25_f32, 0.75_f32, 0.00_f32]; + const COLOR_F: ElementColor = [0.00_f32, 0.50_f32, 0.75_f32]; + const COLOR_G: ElementColor = [0.25_f32, 0.00_f32, 1.00_f32]; + const COLOR_H: ElementColor = COLOR_A; + const COLOR_I: ElementColor = COLOR_B; + const COLOR_J: ElementColor = COLOR_C; + let vertices = [ + Point::new( + "a_NE".to_string(), + "A-NE".to_string(), + COLOR_A, + engine::point(0.5, 0.5, 0.0), + ), + Point::new( + "a_NW".to_string(), + "A-NW".to_string(), + COLOR_A, + engine::point(-0.5, 0.5, 0.0), + ), + Point::new( + "a_SW".to_string(), + "A-SW".to_string(), + COLOR_A, + engine::point(-0.5, -0.5, 0.0), + ), + Point::new( + "a_SE".to_string(), + "A-SE".to_string(), + COLOR_A, + engine::point(0.5, -0.5, 0.0), + ), + Point::new( + "z_S".to_string(), + "Z-S".to_string(), + COLOR_Z, + engine::point(0.0, -0.4, 0.6), + ), + Point::new( + "z_E".to_string(), + "Z-E".to_string(), + COLOR_Z, + engine::point(0.4, 0.0, 0.6), + ), + Point::new( + "b_NE".to_string(), + "B-NE".to_string(), + COLOR_B, + engine::point(0.7, 0.7, 0.9), + ), + Point::new( + "b_NW".to_string(), + "B-NW".to_string(), + COLOR_B, + engine::point(-0.7, 0.7, 0.9), + ), + Point::new( + "b_SW".to_string(), + "B-SW".to_string(), + COLOR_B, + engine::point(-0.7, -0.7, 0.9), + ), + Point::new( + "b_SE".to_string(), + "B-SE".to_string(), + COLOR_B, + engine::point(0.7, -0.7, 0.9), + ), + Point::new( + "y_NE".to_string(), + "Y-NE".to_string(), + COLOR_Y, + engine::point(0.1, 0.1, 1.0), + ), + Point::new( + "y_NW".to_string(), + "Y-NW".to_string(), + COLOR_Y, + engine::point(-0.1, 0.1, 1.0), + ), + Point::new( + "y_SW".to_string(), + "Y-SW".to_string(), + COLOR_Y, + engine::point(-0.1, -0.1, 1.0), + ), + Point::new( + "y_SE".to_string(), + "Y-SE".to_string(), + COLOR_Y, + engine::point(0.1, -0.1, 1.0), + ), + Point::new( + "c_N".to_string(), + "C-N".to_string(), + COLOR_C, + engine::point(0.0, 0.8, 1.4), + ), + Point::new( + "c_W".to_string(), + "C-W".to_string(), + COLOR_C, + engine::point(-0.8, 0.0, 1.4), + ), + Point::new( + "c_S".to_string(), + "C-S".to_string(), + COLOR_C, + engine::point(0.0, -0.8, 1.4), + ), + Point::new( + "c_E".to_string(), + "C-E".to_string(), + COLOR_C, + engine::point(0.8, 0.0, 1.4), + ), + Point::new( + "d_NE".to_string(), + "D-NE".to_string(), + COLOR_D, + engine::point(0.1, 0.1, 1.8), + ), + Point::new( + "d_NW".to_string(), + "D-NW".to_string(), + COLOR_D, + engine::point(-0.1, 0.1, 1.8), + ), + Point::new( + "d_SW".to_string(), + "D-SW".to_string(), + COLOR_D, + engine::point(-0.1, -0.1, 1.8), + ), + Point::new( + "d_SE".to_string(), + "D-SE".to_string(), + COLOR_D, + engine::point(0.1, -0.1, 1.8), + ), + Point::new( + "e_N".to_string(), + "E-N".to_string(), + COLOR_E, + engine::point(0.0, 0.7, 2.3), + ), + Point::new( + "e_W".to_string(), + "E-W".to_string(), + COLOR_E, + engine::point(-0.7, 0.0, 2.3), + ), + Point::new( + "e_S".to_string(), + "E-S".to_string(), + COLOR_E, + engine::point(0.0, -0.7, 2.3), + ), + Point::new( + "e_E".to_string(), + "E-E".to_string(), + COLOR_E, + engine::point(0.7, 0.0, 2.3), + ), + Point::new( + "f_NE".to_string(), + "F-NE".to_string(), + COLOR_F, + engine::point(0.2, 0.2, 2.7), + ), + Point::new( + "f_NW".to_string(), + "F-NW".to_string(), + COLOR_F, + engine::point(-0.2, 0.2, 2.7), + ), + Point::new( + "f_SW".to_string(), + "F-SW".to_string(), + COLOR_F, + engine::point(-0.2, -0.2, 2.7), + ), + Point::new( + "f_SE".to_string(), + "F-SE".to_string(), + COLOR_F, + engine::point(0.2, -0.2, 2.7), + ), + Point::new( + "g_NNE".to_string(), + "G-NNE".to_string(), + COLOR_G, + engine::point(0.5, 1.2, 3.0), + ), + Point::new( + "g_NNW".to_string(), + "G-NNW".to_string(), + COLOR_G, + engine::point(-0.5, 1.2, 3.0), + ), + Point::new( + "g_WNW".to_string(), + "G-WNW".to_string(), + COLOR_G, + engine::point(-1.2, 0.5, 3.0), + ), + Point::new( + "g_WSW".to_string(), + "G-WSW".to_string(), + COLOR_G, + engine::point(-1.2, -0.5, 3.0), + ), + Point::new( + "g_SSW".to_string(), + "G-SSW".to_string(), + COLOR_G, + engine::point(-0.5, -1.2, 3.0), + ), + Point::new( + "g_SSE".to_string(), + "G-SSE".to_string(), + COLOR_G, + engine::point(0.5, -1.2, 3.0), + ), + Point::new( + "g_ESE".to_string(), + "G-ESE".to_string(), + COLOR_G, + engine::point(1.2, -0.5, 3.0), + ), + Point::new( + "g_ENE".to_string(), + "G-ENE".to_string(), + COLOR_G, + engine::point(1.2, 0.5, 3.0), + ), + Point::new( + "h_N".to_string(), + "H-N".to_string(), + COLOR_H, + engine::point(0.0, 0.6, 2.9), + ), + Point::new( + "h_W".to_string(), + "H-W".to_string(), + COLOR_H, + engine::point(-0.6, 0.0, 2.9), + ), + Point::new( + "h_S".to_string(), + "H-S".to_string(), + COLOR_H, + engine::point(0.0, -0.6, 2.9), + ), + Point::new( + "h_E".to_string(), + "H-E".to_string(), + COLOR_H, + engine::point(0.6, 0.0, 2.9), + ), + Point::new( + "i_NE".to_string(), + "I-NE".to_string(), + COLOR_I, + engine::point(0.5, 0.5, 3.5), + ), + Point::new( + "i_NW".to_string(), + "I-NW".to_string(), + COLOR_I, + engine::point(-0.5, 0.5, 3.5), + ), + Point::new( + "i_SW".to_string(), + "I-SW".to_string(), + COLOR_I, + engine::point(-0.5, -0.5, 3.5), + ), + Point::new( + "i_SE".to_string(), + "I-SE".to_string(), + COLOR_I, + engine::point(0.5, -0.5, 3.5), + ), + Point::new( + "j".to_string(), + "J".to_string(), + COLOR_J, + engine::point(0.0, 0.0, 3.0), + ), + ]; + for vertex in vertices { + let _ = assembly.try_insert_element(vertex); + } + + // fix the distances between adjacent vertices + let struts: Vec<_> = vec![ + (false, 1.0, vec![ + ["a_SE", "b_SE"], + ["b_SW", "c_S"], + ["b_SE", "c_S"], + ["b_SE", "c_E"], + ["b_NE", "c_E"], + ["z_S", "a_SW"], + ["z_S", "a_SE"], + ["z_E", "a_SE"], + ["z_E", "a_NE"], + ["z_S", "b_SW"], + ["z_S", "b_SE"], + ["z_E", "b_SE"], + ["z_E", "b_NE"], + ["z_S", "c_S"], + ["z_E", "c_E"], + ["c_N", "d_NE"], + ["c_N", "d_NW"], + ["c_W", "d_NW"], + ["c_W", "d_SW"], + ["c_S", "d_SW"], + ["c_S", "d_SE"], + ["c_E", "d_SE"], + ["c_E", "d_NE"], + ["y_NE", "b_NE"], + ["y_NW", "b_NW"], + ["y_SW", "b_SW"], + ["y_SE", "b_SE"], + ["y_NE", "c_N"], + ["y_NW", "c_N"], + ["y_NW", "c_W"], + ["y_SW", "c_W"], + ["y_SW", "c_S"], + ["y_SE", "c_S"], + ["y_SE", "c_E"], + ["y_NE", "c_E"], + ["y_NE", "d_NE"], + ["y_NW", "d_NW"], + ["y_SW", "d_SW"], + ["y_SE", "d_SE"], + ["d_NE", "e_N"], + ["d_NW", "e_N"], + ["d_NW", "e_W"], + ["d_SW", "e_W"], + ["d_SW", "e_S"], + ["d_SE", "e_S"], + ["d_SE", "e_E"], + ["d_NE", "e_E"], + ["c_N", "e_N"], + ["c_W", "e_W"], + ["c_S", "e_S"], + ["c_E", "e_E"], + ["e_N", "f_NE"], + ["e_N", "f_NW"], + ["e_W", "f_NW"], + ["e_W", "f_SW"], + ["e_S", "f_SW"], + ["e_S", "f_SE"], + ["e_E", "f_SE"], + ["e_E", "f_NE"], + ["d_NE", "f_NE"], + ["d_NW", "f_NW"], + ["d_SW", "f_SW"], + ["d_SE", "f_SE"], + ["f_NE", "g_ENE"], + ["f_NE", "g_NNE"], + ["f_NW", "g_NNW"], + ["f_NW", "g_WNW"], + ["f_SW", "g_WSW"], + ["f_SW", "g_SSW"], + ["f_SE", "g_SSE"], + ["f_SE", "g_ESE"], + ["e_N", "g_NNE"], + ["e_N", "g_NNW"], + ["e_W", "g_WNW"], + ["e_W", "g_WSW"], + ["e_S", "g_SSW"], + ["e_S", "g_SSE"], + ["e_E", "g_ESE"], + ["e_E", "g_ENE"], + ["g_NNE", "g_NNW"], + ["g_NNW", "g_WNW"], + ["g_WNW", "g_WSW"], + ["g_WSW", "g_SSW"], + ["g_SSW", "g_SSE"], + ["g_SSE", "g_ESE"], + ["g_ESE", "g_ENE"], + ["g_ENE", "g_NNE"], + ["g_NNE", "h_N"], + ["g_NNW", "h_N"], + ["g_WNW", "h_W"], + ["g_WSW", "h_W"], + ["g_SSW", "h_S"], + ["g_SSE", "h_S"], + ["g_ESE", "h_E"], + ["g_ENE", "h_E"], + ["h_N", "i_NE"], + ["h_N", "i_NW"], + ["h_W", "i_NW"], + ["h_W", "i_SW"], + ["h_S", "i_SW"], + ["h_S", "i_SE"], + ["h_E", "i_SE"], + ["h_E", "i_NE"], + ["g_NNE", "i_NE"], + ["g_NNW", "i_NW"], + ["g_WNW", "i_NW"], + ["g_WSW", "i_SW"], + ["g_SSW", "i_SW"], + ["g_SSE", "i_SE"], + ["g_ESE", "i_SE"], + ["g_ENE", "i_NE"], + ["i_NE", "i_NW"], + ["i_NW", "i_SW"], + ["i_SW", "i_SE"], + ["i_SE", "i_NE"], + ["i_NE", "j"], + ["i_NW", "j"], + ["i_SW", "j"], + ["i_SE", "j"], + ]), + ]; + for (soft, length, vertex_pairs) in struts { + let inv_dist = Some(-0.5 * length * length); + for pair in vertex_pairs { + let adjacent_vertices = pair.map( + |id| assembly.elements_by_id.with_untracked( + |elts_by_id| elts_by_id[id].clone() + ) + ); + let distance = InversiveDistanceRegulator::new(adjacent_vertices); + distance.set_point.set(SpecifiedValue::from(inv_dist)); + distance.soft.set(soft); + assembly.insert_regulator(Rc::new(distance)); + } + } + + // pin the vertices of the rigid faces + let phi = 0.5 + 1.25_f64.sqrt(); /* TO DO */ // replace with std::f64::consts::PHI when that gets stabilized + let phi_2 = 0.5 * phi; + let height_b = phi_2.sqrt(); + let height_c = (phi + 0.5).sqrt(); + let pinned_vertices = [ + ("a_NE", [0.5, 0.5, 0.0]), + ("a_NW", [-0.5, 0.5, 0.0]), + ("a_SW", [-0.5, -0.5, 0.0]), + ("a_SE", [0.5, -0.5, 0.0]), + ("b_NE", [phi_2, phi_2, height_b]), + ("b_NW", [-phi_2, phi_2, height_b]), + ("b_SW", [-phi_2, -phi_2, height_b]), + ("c_N", [0.0, 1.0, height_c]), + ("c_W", [-1.0, 0.0, height_c]), + ]; + for (id, coords) in pinned_vertices { + // get the point's coordinate regulators + let point = assembly.elements_by_id.with_untracked( + |elts_by_id| elts_by_id[id].clone() + ); + let coord_regs: Vec> = point.regulators().with_untracked( + |regs| regs.into_iter().filter_map( + /* KLUDGE */ + // there must be a better way to do the type-checking here + move |reg| reg + .as_any() + .downcast_ref::() + .map(|_| reg.clone()) + ).collect() + ); + + // set the coordinates + for reg in coord_regs { + let reg_downcast_ref = reg + .as_any() + .downcast_ref::(); + if let Some(coord_reg) = reg_downcast_ref { + let coord_val = SpecifiedValue::from( + Some(coords[coord_reg.axis as usize]) + ); + reg.set_point().set(coord_val); + } + } + } +} + // --- chooser --- /* DEBUG */ @@ -3185,6 +3675,7 @@ pub fn TestAssemblyChooser() -> View { "554-aug1-inner" => load_554_aug1_inner(assembly), "554-aug2" => load_554_aug2(assembly), "554-domed" => load_554_domed(assembly), + "554-domed-pinned" => load_554_domed_pinned(assembly), _ => (), }; }); @@ -3207,6 +3698,7 @@ pub fn TestAssemblyChooser() -> View { option(value = "554-aug1-inner") { "5-5-4 once augmented (inner)" } option(value = "554-aug2") { "5-5-4 twice augmented" } option(value = "554-domed") { "5-5-4 domed" } + option(value = "554-domed-pinned") { "5-5-4 domed (pinned)" } option(value = "empty") { "Empty" } } }