diff --git a/app-proto/main.css b/app-proto/main.css index a00d309..ef8aaf9 100644 --- a/app-proto/main.css +++ b/app-proto/main.css @@ -227,6 +227,16 @@ details[open]:has(li) .element-switch::after { border-radius: 8px; } +#distortion-bar { + display: flex; + margin-top: 8px; + gap: 8px; +} + +#distortion-gauge { + flex-grow: 1; +} + /* display */ #display { diff --git a/app-proto/src/assembly.rs b/app-proto/src/assembly.rs index 669c0d0..9cd4533 100644 --- a/app-proto/src/assembly.rs +++ b/app-proto/src/assembly.rs @@ -3,6 +3,7 @@ use std::{ cell::Cell, cmp::Ordering, collections::{BTreeMap, BTreeSet}, + f64::consts::SQRT_2, fmt, fmt::{Debug, Formatter}, hash::{Hash, Hasher}, @@ -122,6 +123,11 @@ pub trait Element: Serial + ProblemPoser + DisplayItem { // be used carefully to preserve invariant (1), described in the comment on // the `tangent` field of the `Assembly` structure fn set_column_index(&self, index: usize); + + /* KLUDGE */ + fn has_distortion(&self) -> bool { + false + } } impl Debug for dyn Element { @@ -334,6 +340,10 @@ impl Element for Point { fn set_column_index(&self, index: usize) { self.column_index.set(Some(index)); } + + fn has_distortion(&self) -> bool { + true + } } impl Serial for Point { @@ -357,6 +367,12 @@ pub trait Regulator: Serial + ProblemPoser + OutlineItem { fn subjects(&self) -> Vec>; fn measurement(&self) -> ReadSignal; fn set_point(&self) -> Signal; + fn soft(&self) -> Option> { + None + } + fn distortion(&self) -> Option> { /* KLUDGE */ + None + } } impl Hash for dyn Regulator { @@ -389,6 +405,8 @@ pub struct InversiveDistanceRegulator { pub subjects: [Rc; 2], pub measurement: ReadSignal, pub set_point: Signal, + pub soft: Signal, + distortion: Option>, /* KLUDGE */ serial: u64, } @@ -404,9 +422,24 @@ impl InversiveDistanceRegulator { }); let set_point = create_signal(SpecifiedValue::from_empty_spec()); + let distortion = if subjects.iter().all(|subj| subj.has_distortion()) { + Some(create_memo(move || { + let set_point_opt = set_point.with(|set_pt| set_pt.value); + let measurement_val = measurement.get(); + match set_point_opt { + None => 0.0, + Some(set_point_val) => SQRT_2 * ( + (-measurement_val).sqrt() - (-set_point_val).sqrt() + ), + } + })) + } else { + None + }; + let soft = create_signal(false); let serial = Self::next_serial(); - Self { subjects, measurement, set_point, serial } + Self { subjects, measurement, set_point, soft, distortion, serial } } } @@ -422,6 +455,14 @@ impl Regulator for InversiveDistanceRegulator { fn set_point(&self) -> Signal { self.set_point } + + fn soft(&self) -> Option> { + Some(self.soft) + } + + fn distortion(&self) -> Option> { + self.distortion + } } impl Serial for InversiveDistanceRegulator { @@ -432,6 +473,7 @@ impl Serial for InversiveDistanceRegulator { impl ProblemPoser for InversiveDistanceRegulator { fn pose(&self, problem: &mut ConstraintProblem) { + let soft = self.soft.get_untracked(); self.set_point.with_untracked(|set_pt| { if let Some(val) = set_pt.value { let [row, col] = self.subjects.each_ref().map( @@ -439,7 +481,11 @@ impl ProblemPoser for InversiveDistanceRegulator { "Subjects should be indexed before inversive distance regulator writes problem data" ) ); - problem.gram.push_sym(row, col, val); + if soft { + problem.soft.push_sym(row, col, val); + } else { + problem.gram.push_sym(row, col, val); + } } }); } @@ -705,7 +751,7 @@ impl Assembly { // look for a configuration with the given Gram matrix let Realization { result, history } = realize_gram( - &problem, 1.0e-12, 0.5, 0.9, 1.1, 200, 110 + &problem, 1.0e-20, 0.5, 0.9, 1.1, 400, 110 ); /* DEBUG */ diff --git a/app-proto/src/components/diagnostics.rs b/app-proto/src/components/diagnostics.rs index 51d58f1..52e7ffa 100644 --- a/app-proto/src/components/diagnostics.rs +++ b/app-proto/src/components/diagnostics.rs @@ -111,6 +111,80 @@ fn StepInput() -> View { } } +#[component] +fn DistortionGauge() -> View { + let state = use_context::(); + let total_distortion = create_memo(move || { + state.assembly.regulators.with(|regs| { + let mut total = 0.0; + for reg in regs { + if let Some(distortion) = reg.distortion() { + total += distortion.get().abs(); + } + } + total + }) + }); + + view! { + div(id = "distortion-gauge") { + "Distortion: " (total_distortion.with(|distort| distort.to_string())) + } + } +} + +#[component] +fn DistortionPrintButton() -> View { + view! { + button( + on:click = |_| { + let state = use_context::(); + let mut hard_distortion_table = String::new(); + let mut soft_distortion_table = String::new(); + let mut highest_distortion = f64::NEG_INFINITY; + let mut lowest_distortion = f64::INFINITY; + let mut largest_hard_distortion = f64::NEG_INFINITY; + state.assembly.regulators.with_untracked(|regs| { + for reg in regs { + if let Some(distortion) = reg.distortion() { + let distortion_val = distortion.get(); + let subjects = reg.subjects(); + let distortion_line = format!( + "{}, {}: {distortion_val}\n", + subjects[0].id(), + subjects[1].id(), + ); + match reg.soft() { + Some(soft) if soft.get() => { + soft_distortion_table += &distortion_line; + highest_distortion = highest_distortion.max(distortion_val); + lowest_distortion = lowest_distortion.min(distortion_val); + }, + _ => { + hard_distortion_table += &distortion_line; + largest_hard_distortion = largest_hard_distortion.max(distortion_val.abs()); + } + }; + } + } + }); + console_log!("\ + === Distortions of flexible edges (for labels) ===\n\n\ + --- Range ---\n\n\ + Highest: {highest_distortion}\n\ + Lowest: {lowest_distortion}\n\n\ + --- Table ---\n\n{soft_distortion_table}\n\ + === Distortions of rigid edges (for validation) ===\n\n\ + These values should be small relative to the ones for the flexible edges\n\n\ + --- Range ---\n\n\ + Largest absolute: {largest_hard_distortion}\n\n\ + --- Table ---\n\n{hard_distortion_table}\ + "); + }, + ) { "Print" } + } +} + fn into_log10_time_point((step, value): (usize, f64)) -> Vec> { vec![ Some(step as f64), @@ -315,6 +389,10 @@ pub fn Diagnostics() -> View { } DiagnosticsPanel(name = "loss") { LossHistory {} } DiagnosticsPanel(name = "spectrum") { SpectrumHistory {} } + div(id = "distortion-bar") { + DistortionGauge {} + DistortionPrintButton {} + } } } } \ No newline at end of file diff --git a/app-proto/src/components/display.rs b/app-proto/src/components/display.rs index 98be85e..c4803dc 100644 --- a/app-proto/src/components/display.rs +++ b/app-proto/src/components/display.rs @@ -597,16 +597,16 @@ pub fn Display() -> View { |status| status.is_ok() ); let step_val = state.assembly.step.with_untracked(|step| step.value); - let on_init_step = step_val.is_some_and(|n| n == 0.0); let on_last_step = step_val.is_some_and( |n| state.assembly.descent_history.with_untracked( |history| n as usize + 1 == history.config.len().max(1) ) ); - let on_manipulable_step = - !realization_successful && on_init_step - || realization_successful && on_last_step; - if on_manipulable_step && state.selection.with(|sel| sel.len() == 1) { + if + state.selection.with(|sel| sel.len() == 1) + && realization_successful + && on_last_step + { let sel = state.selection.with( |sel| sel.into_iter().next().unwrap().clone() ); diff --git a/app-proto/src/components/test_assembly_chooser.rs b/app-proto/src/components/test_assembly_chooser.rs index 0d387d3..accbd1f 100644 --- a/app-proto/src/components/test_assembly_chooser.rs +++ b/app-proto/src/components/test_assembly_chooser.rs @@ -882,6 +882,2268 @@ fn load_irisawa_hexlet(assembly: &Assembly) { assembly.insert_regulator(Rc::new(outer_moon_tangency)); } +fn regular_diagonals<'a, const N: usize>(vertex_ids: [&'a str; N]) -> Vec<(bool, f64, Vec<[&'a str; 2]>)> { + let ang = PI / (N as f64); + let ang_sin = ang.sin(); + (2..N-1).map(|sep| ( + false, + (sep as f64 * ang).sin() / ang_sin, + (0..N-sep).map(|k| [vertex_ids[k], vertex_ids[k + sep]]).collect() + )).collect() +} + +fn load_554_base(assembly: &Assembly) { + // create the vertices + const COLOR_A: ElementColor = [0.75_f32, 0.00_f32, 0.75_f32]; + const COLOR_B: ElementColor = [1.00_f32, 0.00_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]; + 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( + "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( + "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), + ), + ]; + for vertex in vertices { + let _ = assembly.try_insert_element(vertex); + } + + // fix the distances between adjacent vertices + let f_a = ["a_SE", "a_SW", "a_NW", "a_NE"]; + let f_abc_n = ["a_NW", "b_NW", "c_N", "b_NE", "a_NE"]; + let f_abc_w = ["a_SW", "b_SW", "c_W", "b_NW", "a_NW"]; + let f_abc_s = ["a_SE", "b_SE", "c_S", "b_SW", "a_SW"]; + let f_abc_e = ["a_NE", "b_NE", "c_E", "b_SE", "a_SE"]; + let f_bcd_ne = ["b_NE", "c_N", "d_NE", "c_E"]; + let f_bcd_nw = ["b_NW", "c_W", "d_NW", "c_N"]; + let f_bcd_sw = ["b_SW", "c_S", "d_SW", "c_W"]; + let f_bcd_se = ["b_SE", "c_E", "d_SE", "c_S"]; + let f_g = [ + "g_NNE", "g_NNW", "g_WNW", "g_WSW", + "g_SSW", "g_SSE", "g_ESE", "g_ENE", + ]; + let struts: Vec<_> = [ + (false, 1.0, vec![ + ["a_NE", "a_NW"], + ["a_NW", "a_SW"], + ["a_SW", "a_SE"], + ["a_SE", "a_NE"], + ["a_NE", "b_NE"], + ["a_NW", "b_NW"], + ["a_SW", "b_SW"], + ["a_SE", "b_SE"], + ["b_NE", "c_N"], + ["b_NW", "c_N"], + ["b_NW", "c_W"], + ["b_SW", "c_W"], + ["b_SW", "c_S"], + ["b_SE", "c_S"], + ["b_SE", "c_E"], + ["b_NE", "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"], + ["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"], + ]), + (true, 1.0, vec![ + ["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"], + ]), + ].into_iter() + .chain(regular_diagonals(f_a)) + .chain(regular_diagonals(f_bcd_ne)) + .chain(regular_diagonals(f_bcd_nw)) + .chain(regular_diagonals(f_bcd_sw)) + .chain(regular_diagonals(f_bcd_se)) + .chain(regular_diagonals(f_abc_n)) + .chain(regular_diagonals(f_abc_w)) + .chain(regular_diagonals(f_abc_s)) + .chain(regular_diagonals(f_abc_e)) + .chain(regular_diagonals(f_g)) + .collect(); + 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)); + } + } + + // create the faces + const COLOR_FACE: ElementColor = [0.75_f32, 0.75_f32, 0.75_f32]; + let faces = [ + Sphere::new( + "f_a".to_string(), + "Face A".to_string(), + COLOR_FACE, + engine::sphere_with_offset(0.0, 0.0, -1.0, 0.0, 0.0), + ), + Sphere::new( + "f_abc_N".to_string(), + "Face ABC-N".to_string(), + COLOR_FACE, + engine::sphere_with_offset(0.0, 1.0, 0.0, 0.5, 0.0), + ), + Sphere::new( + "f_abc_W".to_string(), + "Face ABC-W".to_string(), + COLOR_FACE, + engine::sphere_with_offset(-1.0, 0.0, 0.0, 0.5, 0.0), + ), + Sphere::new( + "f_abc_S".to_string(), + "Face ABC-S".to_string(), + COLOR_FACE, + engine::sphere_with_offset(0.0, -1.0, 0.0, 0.5, 0.0), + ), + Sphere::new( + "f_abc_E".to_string(), + "Face ABC-E".to_string(), + COLOR_FACE, + engine::sphere_with_offset(1.0, 0.0, 0.0, 0.5, 0.0), + ), + Sphere::new( + "f_bcd_NE".to_string(), + "Face BCD-NE".to_string(), + COLOR_FACE, + engine::sphere_with_offset(FRAC_1_SQRT_2, FRAC_1_SQRT_2, 0.0, FRAC_1_SQRT_2, 0.0), + ), + Sphere::new( + "f_bcd_NW".to_string(), + "Face BCD-NW".to_string(), + COLOR_FACE, + engine::sphere_with_offset(-FRAC_1_SQRT_2, FRAC_1_SQRT_2, 0.0, FRAC_1_SQRT_2, 0.0), + ), + Sphere::new( + "f_bcd_SW".to_string(), + "Face BCD-SW".to_string(), + COLOR_FACE, + engine::sphere_with_offset(-FRAC_1_SQRT_2, -FRAC_1_SQRT_2, 0.0, FRAC_1_SQRT_2, 0.0), + ), + Sphere::new( + "f_bcd_SE".to_string(), + "Face BCD-SE".to_string(), + COLOR_FACE, + engine::sphere_with_offset(FRAC_1_SQRT_2, -FRAC_1_SQRT_2, 0.0, FRAC_1_SQRT_2, 0.0), + ), + Sphere::new( + "f_g".to_string(), + "Face G".to_string(), + COLOR_FACE, + engine::sphere_with_offset(0.0, 0.0, 1.0, 3.0, 0.0), + ), + ]; + for face in faces { + face.ghost().set(true); + let _ = assembly.try_insert_element(face); + } + + // make the faces planar and make them pass through their vertices + let face_incidences = [ + ("f_a", Vec::from(f_a)), + ("f_abc_N", Vec::from(f_abc_n)), + ("f_abc_W", Vec::from(f_abc_w)), + ("f_abc_S", Vec::from(f_abc_s)), + ("f_abc_E", Vec::from(f_abc_e)), + ("f_bcd_NE", Vec::from(f_bcd_ne)), + ("f_bcd_NW", Vec::from(f_bcd_nw)), + ("f_bcd_SW", Vec::from(f_bcd_sw)), + ("f_bcd_SE", Vec::from(f_bcd_se)), + ("f_g", Vec::from(f_g)), + ]; + for (face_id, vertex_ids) in face_incidences { + // make the face planar + let face = assembly.elements_by_id.with_untracked( + |elts_by_id| elts_by_id[face_id].clone() + ); + let curvature_regulator = face.regulators().with_untracked( + |regs| regs.first().unwrap().clone() + ); + curvature_regulator.set_point().set(SpecifiedValue::from(Some(0.0))); + + // make the face pass through its vertices + for v_id in vertex_ids { + let vertex = assembly.elements_by_id.with_untracked( + |elts_by_id| elts_by_id[v_id].clone() + ); + let incidence = InversiveDistanceRegulator::new([face.clone(), vertex]); + incidence.set_point.set(SpecifiedValue::from(Some(0.0))); + assembly.insert_regulator(Rc::new(incidence)); + } + } +} + +fn load_554_aug1(assembly: &Assembly) { + // create the vertices + const COLOR_A: ElementColor = [0.75_f32, 0.00_f32, 0.75_f32]; + const COLOR_B: ElementColor = [1.00_f32, 0.00_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 GRAY: ElementColor = [0.75_f32, 0.75_f32, 0.75_f32]; + 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( + "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( + "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( + "y_NE".to_string(), + "Y-NE".to_string(), + GRAY, + engine::point(0.7, 0.7, 1.6), + ), + Point::new( + "y_NW".to_string(), + "Y-NW".to_string(), + GRAY, + engine::point(-0.7, 0.7, 1.6), + ), + Point::new( + "y_SW".to_string(), + "Y-SW".to_string(), + GRAY, + engine::point(-0.7, -0.7, 1.6), + ), + Point::new( + "y_SE".to_string(), + "Y-SE".to_string(), + GRAY, + engine::point(0.7, -0.7, 1.6), + ), + 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), + ), + ]; + for vertex in vertices { + let _ = assembly.try_insert_element(vertex); + } + + // fix the distances between adjacent vertices + let f_a = ["a_SE", "a_SW", "a_NW", "a_NE"]; + let f_abc_n = ["a_NW", "b_NW", "c_N", "b_NE", "a_NE"]; + let f_abc_w = ["a_SW", "b_SW", "c_W", "b_NW", "a_NW"]; + let f_abc_s = ["a_SE", "b_SE", "c_S", "b_SW", "a_SW"]; + let f_abc_e = ["a_NE", "b_NE", "c_E", "b_SE", "a_SE"]; + let f_g = [ + "g_NNE", "g_NNW", "g_WNW", "g_WSW", + "g_SSW", "g_SSE", "g_ESE", "g_ENE", + ]; + let struts: Vec<_> = [ + (false, 1.0, vec![ + ["a_NE", "a_NW"], + ["a_NW", "a_SW"], + ["a_SW", "a_SE"], + ["a_SE", "a_NE"], + ["a_NE", "b_NE"], + ["a_NW", "b_NW"], + ["a_SW", "b_SW"], + ["a_SE", "b_SE"], + ["b_NE", "c_N"], + ["b_NW", "c_N"], + ["b_NW", "c_W"], + ["b_SW", "c_W"], + ["b_SW", "c_S"], + ["b_SE", "c_S"], + ["b_SE", "c_E"], + ["b_NE", "c_E"], + ["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"], + ]), + (true, 1.0, vec![ + ["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"], + ]), + ].into_iter() + .chain(regular_diagonals(f_a)) + .chain(regular_diagonals(f_abc_n)) + .chain(regular_diagonals(f_abc_w)) + .chain(regular_diagonals(f_abc_s)) + .chain(regular_diagonals(f_abc_e)) + .chain(regular_diagonals(f_g)) + .collect(); + 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)); + } + } + + // create the faces + const COLOR_FACE: ElementColor = [0.75_f32, 0.75_f32, 0.75_f32]; + let faces = [ + Sphere::new( + "f_a".to_string(), + "Face A".to_string(), + COLOR_FACE, + engine::sphere_with_offset(0.0, 0.0, -1.0, 0.0, 0.0), + ), + Sphere::new( + "f_abc_N".to_string(), + "Face ABC-N".to_string(), + COLOR_FACE, + engine::sphere_with_offset(0.0, 1.0, 0.0, 0.5, 0.0), + ), + Sphere::new( + "f_abc_W".to_string(), + "Face ABC-W".to_string(), + COLOR_FACE, + engine::sphere_with_offset(-1.0, 0.0, 0.0, 0.5, 0.0), + ), + Sphere::new( + "f_abc_S".to_string(), + "Face ABC-S".to_string(), + COLOR_FACE, + engine::sphere_with_offset(0.0, -1.0, 0.0, 0.5, 0.0), + ), + Sphere::new( + "f_abc_E".to_string(), + "Face ABC-E".to_string(), + COLOR_FACE, + engine::sphere_with_offset(1.0, 0.0, 0.0, 0.5, 0.0), + ), + Sphere::new( + "f_g".to_string(), + "Face G".to_string(), + COLOR_FACE, + engine::sphere_with_offset(0.0, 0.0, 1.0, 3.0, 0.0), + ), + ]; + for face in faces { + face.ghost().set(true); + let _ = assembly.try_insert_element(face); + } + + // make the faces planar and make them pass through their vertices + let face_incidences = [ + ("f_a", Vec::from(f_a)), + ("f_abc_N", Vec::from(f_abc_n)), + ("f_abc_W", Vec::from(f_abc_w)), + ("f_abc_S", Vec::from(f_abc_s)), + ("f_abc_E", Vec::from(f_abc_e)), + ("f_g", Vec::from(f_g)), + ]; + for (face_id, vertex_ids) in face_incidences { + // make the face planar + let face = assembly.elements_by_id.with_untracked( + |elts_by_id| elts_by_id[face_id].clone() + ); + let curvature_regulator = face.regulators().with_untracked( + |regs| regs.first().unwrap().clone() + ); + curvature_regulator.set_point().set(SpecifiedValue::from(Some(0.0))); + + // make the face pass through its vertices + for v_id in vertex_ids { + let vertex = assembly.elements_by_id.with_untracked( + |elts_by_id| elts_by_id[v_id].clone() + ); + let incidence = InversiveDistanceRegulator::new([face.clone(), vertex]); + incidence.set_point.set(SpecifiedValue::from(Some(0.0))); + assembly.insert_regulator(Rc::new(incidence)); + } + } +} + +fn load_554_aug1_inner(assembly: &Assembly) { + // create the vertices + const COLOR_A: ElementColor = [0.75_f32, 0.00_f32, 0.75_f32]; + const COLOR_B: ElementColor = [1.00_f32, 0.00_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 GRAY: ElementColor = [0.75_f32, 0.75_f32, 0.75_f32]; + 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( + "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(), + GRAY, + engine::point(0.1, 0.1, 1.0), + ), + Point::new( + "y_NW".to_string(), + "Y-NW".to_string(), + GRAY, + engine::point(-0.1, 0.1, 1.0), + ), + Point::new( + "y_SW".to_string(), + "Y-SW".to_string(), + GRAY, + engine::point(-0.1, -0.1, 1.0), + ), + Point::new( + "y_SE".to_string(), + "Y-SE".to_string(), + GRAY, + 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), + ), + ]; + for vertex in vertices { + let _ = assembly.try_insert_element(vertex); + } + + // fix the distances between adjacent vertices + let f_a = ["a_SE", "a_SW", "a_NW", "a_NE"]; + let f_abc_n = ["a_NW", "b_NW", "c_N", "b_NE", "a_NE"]; + let f_abc_w = ["a_SW", "b_SW", "c_W", "b_NW", "a_NW"]; + let f_abc_s = ["a_SE", "b_SE", "c_S", "b_SW", "a_SW"]; + let f_abc_e = ["a_NE", "b_NE", "c_E", "b_SE", "a_SE"]; + let f_g = [ + "g_NNE", "g_NNW", "g_WNW", "g_WSW", + "g_SSW", "g_SSE", "g_ESE", "g_ENE", + ]; + let struts: Vec<_> = [ + (false, 1.0, vec![ + ["a_NE", "a_NW"], + ["a_NW", "a_SW"], + ["a_SW", "a_SE"], + ["a_SE", "a_NE"], + ["a_NE", "b_NE"], + ["a_NW", "b_NW"], + ["a_SW", "b_SW"], + ["a_SE", "b_SE"], + ["b_NE", "c_N"], + ["b_NW", "c_N"], + ["b_NW", "c_W"], + ["b_SW", "c_W"], + ["b_SW", "c_S"], + ["b_SE", "c_S"], + ["b_SE", "c_E"], + ["b_NE", "c_E"], + ["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"], + ]), + (true, 1.0, vec![ + ["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"], + ]), + ].into_iter() + .chain(regular_diagonals(f_a)) + .chain(regular_diagonals(f_abc_n)) + .chain(regular_diagonals(f_abc_w)) + .chain(regular_diagonals(f_abc_s)) + .chain(regular_diagonals(f_abc_e)) + .chain(regular_diagonals(f_g)) + .collect(); + 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)); + } + } + + // create the faces + const COLOR_FACE: ElementColor = [0.75_f32, 0.75_f32, 0.75_f32]; + let faces = [ + Sphere::new( + "f_a".to_string(), + "Face A".to_string(), + COLOR_FACE, + engine::sphere_with_offset(0.0, 0.0, -1.0, 0.0, 0.0), + ), + Sphere::new( + "f_abc_N".to_string(), + "Face ABC-N".to_string(), + COLOR_FACE, + engine::sphere_with_offset(0.0, 1.0, 0.0, 0.5, 0.0), + ), + Sphere::new( + "f_abc_W".to_string(), + "Face ABC-W".to_string(), + COLOR_FACE, + engine::sphere_with_offset(-1.0, 0.0, 0.0, 0.5, 0.0), + ), + Sphere::new( + "f_abc_S".to_string(), + "Face ABC-S".to_string(), + COLOR_FACE, + engine::sphere_with_offset(0.0, -1.0, 0.0, 0.5, 0.0), + ), + Sphere::new( + "f_abc_E".to_string(), + "Face ABC-E".to_string(), + COLOR_FACE, + engine::sphere_with_offset(1.0, 0.0, 0.0, 0.5, 0.0), + ), + Sphere::new( + "f_g".to_string(), + "Face G".to_string(), + COLOR_FACE, + engine::sphere_with_offset(0.0, 0.0, 1.0, 3.0, 0.0), + ), + ]; + for face in faces { + face.ghost().set(true); + let _ = assembly.try_insert_element(face); + } + + // make the faces planar and make them pass through their vertices + let face_incidences = [ + ("f_a", Vec::from(f_a)), + ("f_abc_N", Vec::from(f_abc_n)), + ("f_abc_W", Vec::from(f_abc_w)), + ("f_abc_S", Vec::from(f_abc_s)), + ("f_abc_E", Vec::from(f_abc_e)), + ("f_g", Vec::from(f_g)), + ]; + for (face_id, vertex_ids) in face_incidences { + // make the face planar + let face = assembly.elements_by_id.with_untracked( + |elts_by_id| elts_by_id[face_id].clone() + ); + let curvature_regulator = face.regulators().with_untracked( + |regs| regs.first().unwrap().clone() + ); + curvature_regulator.set_point().set(SpecifiedValue::from(Some(0.0))); + + // make the face pass through its vertices + for v_id in vertex_ids { + let vertex = assembly.elements_by_id.with_untracked( + |elts_by_id| elts_by_id[v_id].clone() + ); + let incidence = InversiveDistanceRegulator::new([face.clone(), vertex]); + incidence.set_point.set(SpecifiedValue::from(Some(0.0))); + assembly.insert_regulator(Rc::new(incidence)); + } + } +} + +fn load_554_aug2(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]; + 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), + ), + ]; + for vertex in vertices { + let _ = assembly.try_insert_element(vertex); + } + + // fix the distances between adjacent vertices + let f_a = ["a_SE", "a_SW", "a_NW", "a_NE"]; + let f_abc_n = ["a_NW", "b_NW", "c_N", "b_NE", "a_NE"]; + let f_abc_w = ["a_SW", "b_SW", "c_W", "b_NW", "a_NW"]; + let f_g = [ + "g_NNE", "g_NNW", "g_WNW", "g_WSW", + "g_SSW", "g_SSE", "g_ESE", "g_ENE", + ]; + let struts: Vec<_> = [ + (false, 1.0, vec![ + ["a_NE", "a_NW"], + ["a_NW", "a_SW"], + ["a_SW", "a_SE"], + ["a_SE", "a_NE"], + ["a_NE", "b_NE"], + ["a_NW", "b_NW"], + ["a_SW", "b_SW"], + ["b_NE", "c_N"], + ["b_NW", "c_N"], + ["b_NW", "c_W"], + ["b_SW", "c_W"], + ["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"], + ]), + (true, 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"], + ]), + ].into_iter() + .chain(regular_diagonals(f_a)) + .chain(regular_diagonals(f_abc_n)) + .chain(regular_diagonals(f_abc_w)) + .chain(regular_diagonals(f_g)) + .collect(); + 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)); + } + } + + // create the faces + const COLOR_FACE: ElementColor = [0.75_f32, 0.75_f32, 0.75_f32]; + let faces = [ + Sphere::new( + "f_a".to_string(), + "Face A".to_string(), + COLOR_FACE, + engine::sphere_with_offset(0.0, 0.0, -1.0, 0.0, 0.0), + ), + Sphere::new( + "f_abc_N".to_string(), + "Face ABC-N".to_string(), + COLOR_FACE, + engine::sphere_with_offset(0.0, 1.0, 0.0, 0.5, 0.0), + ), + Sphere::new( + "f_abc_W".to_string(), + "Face ABC-W".to_string(), + COLOR_FACE, + engine::sphere_with_offset(-1.0, 0.0, 0.0, 0.5, 0.0), + ), + Sphere::new( + "f_g".to_string(), + "Face G".to_string(), + COLOR_FACE, + engine::sphere_with_offset(0.0, 0.0, 1.0, 3.0, 0.0), + ), + ]; + for face in faces { + face.ghost().set(true); + let _ = assembly.try_insert_element(face); + } + + // make the faces planar and make them pass through their vertices + let face_incidences = [ + ("f_a", Vec::from(f_a)), + ("f_abc_N", Vec::from(f_abc_n)), + ("f_abc_W", Vec::from(f_abc_w)), + ("f_g", Vec::from(f_g)), + ]; + for (face_id, vertex_ids) in face_incidences { + // make the face planar + let face = assembly.elements_by_id.with_untracked( + |elts_by_id| elts_by_id[face_id].clone() + ); + let curvature_regulator = face.regulators().with_untracked( + |regs| regs.first().unwrap().clone() + ); + curvature_regulator.set_point().set(SpecifiedValue::from(Some(0.0))); + + // make the face pass through its vertices + for v_id in vertex_ids { + let vertex = assembly.elements_by_id.with_untracked( + |elts_by_id| elts_by_id[v_id].clone() + ); + let incidence = InversiveDistanceRegulator::new([face.clone(), vertex]); + incidence.set_point.set(SpecifiedValue::from(Some(0.0))); + assembly.insert_regulator(Rc::new(incidence)); + } + } +} + +fn load_554_domed(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, 1.1, 3.6), + ), + Point::new( + "h_W".to_string(), + "H-W".to_string(), + COLOR_H, + engine::point(-1.1, 0.0, 3.6), + ), + Point::new( + "h_S".to_string(), + "H-S".to_string(), + COLOR_H, + engine::point(0.0, -1.1, 3.6), + ), + Point::new( + "h_E".to_string(), + "H-E".to_string(), + COLOR_H, + engine::point(1.1, 0.0, 3.6), + ), + 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, 4.3), + ), + ]; + for vertex in vertices { + let _ = assembly.try_insert_element(vertex); + } + + // fix the distances between adjacent vertices + let f_a = ["a_SE", "a_SW", "a_NW", "a_NE"]; + let f_abc_n = ["a_NW", "b_NW", "c_N", "b_NE", "a_NE"]; + let f_abc_w = ["a_SW", "b_SW", "c_W", "b_NW", "a_NW"]; + let struts: Vec<_> = [ + (false, 1.0, vec![ + ["a_NE", "a_NW"], + ["a_NW", "a_SW"], + ["a_SW", "a_SE"], + ["a_SE", "a_NE"], + ["a_NE", "b_NE"], + ["a_NW", "b_NW"], + ["a_SW", "b_SW"], + ["b_NE", "c_N"], + ["b_NW", "c_N"], + ["b_NW", "c_W"], + ["b_SW", "c_W"], + ]), + (true, 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"], + ]), + ].into_iter() + .chain(regular_diagonals(f_a)) + .chain(regular_diagonals(f_abc_n)) + .chain(regular_diagonals(f_abc_w)) + .collect(); + 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)); + } + } + + // create the faces + const COLOR_FACE: ElementColor = [0.75_f32, 0.75_f32, 0.75_f32]; + let faces = [ + Sphere::new( + "f_a".to_string(), + "Face A".to_string(), + COLOR_FACE, + engine::sphere_with_offset(0.0, 0.0, -1.0, 0.0, 0.0), + ), + Sphere::new( + "f_abc_N".to_string(), + "Face ABC-N".to_string(), + COLOR_FACE, + engine::sphere_with_offset(0.0, 1.0, 0.0, 0.5, 0.0), + ), + Sphere::new( + "f_abc_W".to_string(), + "Face ABC-W".to_string(), + COLOR_FACE, + engine::sphere_with_offset(-1.0, 0.0, 0.0, 0.5, 0.0), + ), + ]; + for face in faces { + face.ghost().set(true); + let _ = assembly.try_insert_element(face); + } + + // make the faces planar and make them pass through their vertices + let face_incidences = [ + ("f_a", Vec::from(f_a)), + ("f_abc_N", Vec::from(f_abc_n)), + ("f_abc_W", Vec::from(f_abc_w)), + ]; + for (face_id, vertex_ids) in face_incidences { + // make the face planar + let face = assembly.elements_by_id.with_untracked( + |elts_by_id| elts_by_id[face_id].clone() + ); + let curvature_regulator = face.regulators().with_untracked( + |regs| regs.first().unwrap().clone() + ); + curvature_regulator.set_point().set(SpecifiedValue::from(Some(0.0))); + + // make the face pass through its vertices + for v_id in vertex_ids { + let vertex = assembly.elements_by_id.with_untracked( + |elts_by_id| elts_by_id[v_id].clone() + ); + let incidence = InversiveDistanceRegulator::new([face.clone(), vertex]); + incidence.set_point.set(SpecifiedValue::from(Some(0.0))); + assembly.insert_regulator(Rc::new(incidence)); + } + } +} + // --- chooser --- /* DEBUG */ @@ -918,6 +3180,11 @@ pub fn TestAssemblyChooser() -> View { "off-center" => load_off_center(assembly), "radius-ratio" => load_radius_ratio(assembly), "irisawa-hexlet" => load_irisawa_hexlet(assembly), + "554-base" => load_554_base(assembly), + "554-aug1" => load_554_aug1(assembly), + "554-aug1-inner" => load_554_aug1_inner(assembly), + "554-aug2" => load_554_aug2(assembly), + "554-domed" => load_554_domed(assembly), _ => (), }; }); @@ -935,6 +3202,11 @@ pub fn TestAssemblyChooser() -> View { option(value = "off-center") { "Off-center" } option(value = "radius-ratio") { "Radius ratio" } option(value = "irisawa-hexlet") { "Irisawa hexlet" } + option(value = "554-base") { "5-5-4 base" } + option(value = "554-aug1") { "5-5-4 once augmented" } + 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 = "empty") { "Empty" } } } diff --git a/app-proto/src/engine.rs b/app-proto/src/engine.rs index ef150a0..4679aed 100644 --- a/app-proto/src/engine.rs +++ b/app-proto/src/engine.rs @@ -1,6 +1,7 @@ use lazy_static::lazy_static; use nalgebra::{Const, DMatrix, DVector, DVectorView, Dyn, SymmetricEigen}; use std::fmt::{Display, Error, Formatter}; +use sycamore::prelude::console_log; /* DEBUG */ // --- elements --- @@ -240,6 +241,7 @@ impl DescentHistory { pub struct ConstraintProblem { pub gram: PartialMatrix, + pub soft: PartialMatrix, pub frozen: PartialMatrix, pub guess: DMatrix, } @@ -249,6 +251,7 @@ impl ConstraintProblem { const ELEMENT_DIM: usize = 5; Self { gram: PartialMatrix::new(), + soft: PartialMatrix::new(), frozen: PartialMatrix::new(), guess: DMatrix::::zeros(ELEMENT_DIM, element_count), } @@ -258,6 +261,7 @@ impl ConstraintProblem { pub fn from_guess(guess_columns: &[DVector]) -> Self { Self { gram: PartialMatrix::new(), + soft: PartialMatrix::new(), frozen: PartialMatrix::new(), guess: DMatrix::from_columns(guess_columns), } @@ -280,14 +284,18 @@ lazy_static! { struct SearchState { config: DMatrix, err_proj: DMatrix, + loss_hard: f64, loss: f64, } impl SearchState { - fn from_config(gram: &PartialMatrix, config: DMatrix) -> Self { - let err_proj = gram.sub_proj(&(config.tr_mul(&*Q) * &config)); + fn from_config(gram: &PartialMatrix, soft: &PartialMatrix, softness: f64, config: DMatrix) -> Self { + let config_gram = &(config.tr_mul(&*Q) * &config); + let err_proj_hard = gram.sub_proj(config_gram); + let err_proj = &err_proj_hard + softness * soft.sub_proj(config_gram); + let loss_hard = err_proj_hard.norm_squared(); let loss = err_proj.norm_squared(); - Self { config, err_proj, loss } + Self { config, err_proj, loss_hard, loss } } } @@ -331,6 +339,8 @@ pub fn local_unif_to_std(v: DVectorView) -> DMatrix { // use backtracking line search to find a better configuration fn seek_better_config( gram: &PartialMatrix, + soft: &PartialMatrix, + softness: f64, state: &SearchState, base_step: &DMatrix, base_target_improvement: f64, @@ -341,7 +351,7 @@ fn seek_better_config( let mut rate = 1.0; for backoff_steps in 0..max_backoff_steps { let trial_config = &state.config + rate * base_step; - let trial_state = SearchState::from_config(gram, trial_config); + let trial_state = SearchState::from_config(gram, soft, softness, trial_config); let improvement = state.loss - trial_state.loss; if improvement >= min_efficiency * rate * base_target_improvement { return Some((trial_state, backoff_steps)); @@ -376,7 +386,7 @@ pub fn realize_gram( max_backoff_steps: i32, ) -> Realization { // destructure the problem data - let ConstraintProblem { gram, guess, frozen } = problem; + let ConstraintProblem { gram, soft, guess, frozen } = problem; // start the descent history let mut history = DescentHistory::new(); @@ -403,13 +413,18 @@ pub fn realize_gram( let scale_adjustment = (gram.0.len() as f64).sqrt(); let tol = scale_adjustment * scaled_tol; + // set up constants and variables related to minimizing the soft loss + const GRAD_TOL: f64 = 1e-12; + let mut grad_size = f64::INFINITY; + let mut softness = 1.0; + // convert the frozen indices to stacked format let frozen_stacked: Vec = frozen.into_iter().map( |MatrixEntry { index: (row, col), .. }| col*element_dim + row ).collect(); // use a regularized Newton's method with backtracking - let mut state = SearchState::from_config(gram, frozen.freeze(guess)); + let mut state = SearchState::from_config(gram, soft, softness, frozen.freeze(guess)); let mut hess = DMatrix::zeros(element_dim, assembly_dim); for _ in 0..max_descent_steps { // find the negative gradient of the loss function @@ -426,7 +441,7 @@ pub fn realize_gram( let neg_d_err = basis_mat.tr_mul(&*Q) * &state.config + state.config.tr_mul(&*Q) * &basis_mat; - let neg_d_err_proj = gram.proj(&neg_d_err); + let neg_d_err_proj = gram.proj(&neg_d_err) + softness * soft.proj(&neg_d_err); let deriv_grad = 4.0 * &*Q * ( -&basis_mat * &state.err_proj + &state.config * &neg_d_err_proj @@ -455,10 +470,13 @@ pub fn realize_gram( hess[(k, k)] = 1.0; } - // stop if the loss is tolerably low + // stop if the hard loss is tolerably low and the total loss is close to + // stationary. we use `neg_grad_stacked` to measure the size of the + // gradient because it's been projected onto the frozen subspace history.config.push(state.config.clone()); - history.scaled_loss.push(state.loss / scale_adjustment); - if state.loss < tol { break; } + history.scaled_loss.push(state.loss_hard / scale_adjustment); + grad_size = neg_grad_stacked.norm_squared(); + if state.loss_hard < tol && grad_size < softness * GRAD_TOL { break; } // compute the Newton step /* TO DO */ @@ -482,7 +500,7 @@ pub fn realize_gram( // use backtracking line search to find a better configuration if let Some((better_state, backoff_steps)) = seek_better_config( - gram, &state, &base_step, neg_grad.dot(&base_step), + gram, soft, softness, &state, &base_step, neg_grad.dot(&base_step), min_efficiency, backoff, max_backoff_steps, ) { state = better_state; @@ -493,8 +511,18 @@ pub fn realize_gram( history, }; } + + // if we're near a minimum of the total loss, but the hard loss still + // isn't tolerably low, make the soft constraints softer + const SOFTNESS_BACKOFF_THRESHOLD: f64 = 1e-6; + const SOFTNESS_BACKOFF: f64 = 0.95; + if state.loss_hard >= tol && grad_size < softness * SOFTNESS_BACKOFF_THRESHOLD { + softness *= SOFTNESS_BACKOFF; + state = SearchState::from_config(gram, soft, softness, state.config); + console_log!("Softness decreased to {softness}"); + } } - let result = if state.loss < tol { + let result = if state.loss_hard < tol && grad_size < softness * GRAD_TOL { // express the uniform basis in the standard basis const UNIFORM_DIM: usize = 4; let total_dim_unif = UNIFORM_DIM * assembly_dim;