Add more test assemblies #103
13 changed files with 1045 additions and 277 deletions
|
@ -1,259 +0,0 @@
|
|||
use std::{f64::consts::FRAC_1_SQRT_2, rc::Rc};
|
||||
use sycamore::prelude::*;
|
||||
use web_sys::{console, wasm_bindgen::JsValue};
|
||||
|
||||
use crate::{
|
||||
AppState,
|
||||
engine,
|
||||
engine::DescentHistory,
|
||||
assembly::{Assembly, InversiveDistanceRegulator, Point, Sphere}
|
||||
};
|
||||
|
||||
/* DEBUG */
|
||||
// load an example assembly for testing. this code will be removed once we've
|
||||
// built a more formal test assembly system
|
||||
fn load_gen_assemb(assembly: &Assembly) {
|
||||
let _ = assembly.try_insert_element(
|
||||
Sphere::new(
|
||||
String::from("gemini_a"),
|
||||
String::from("Castor"),
|
||||
[1.00_f32, 0.25_f32, 0.00_f32],
|
||||
engine::sphere(0.5, 0.5, 0.0, 1.0)
|
||||
)
|
||||
);
|
||||
let _ = assembly.try_insert_element(
|
||||
Sphere::new(
|
||||
String::from("gemini_b"),
|
||||
String::from("Pollux"),
|
||||
[0.00_f32, 0.25_f32, 1.00_f32],
|
||||
engine::sphere(-0.5, -0.5, 0.0, 1.0)
|
||||
)
|
||||
);
|
||||
let _ = assembly.try_insert_element(
|
||||
Sphere::new(
|
||||
String::from("ursa_major"),
|
||||
String::from("Ursa major"),
|
||||
[0.25_f32, 0.00_f32, 1.00_f32],
|
||||
engine::sphere(-0.5, 0.5, 0.0, 0.75)
|
||||
)
|
||||
);
|
||||
let _ = assembly.try_insert_element(
|
||||
Sphere::new(
|
||||
String::from("ursa_minor"),
|
||||
String::from("Ursa minor"),
|
||||
[0.25_f32, 1.00_f32, 0.00_f32],
|
||||
engine::sphere(0.5, -0.5, 0.0, 0.5)
|
||||
)
|
||||
);
|
||||
let _ = assembly.try_insert_element(
|
||||
Sphere::new(
|
||||
String::from("moon_deimos"),
|
||||
String::from("Deimos"),
|
||||
[0.75_f32, 0.75_f32, 0.00_f32],
|
||||
engine::sphere(0.0, 0.15, 1.0, 0.25)
|
||||
)
|
||||
);
|
||||
let _ = assembly.try_insert_element(
|
||||
Sphere::new(
|
||||
String::from("moon_phobos"),
|
||||
String::from("Phobos"),
|
||||
[0.00_f32, 0.75_f32, 0.50_f32],
|
||||
engine::sphere(0.0, -0.15, -1.0, 0.25)
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
/* DEBUG */
|
||||
// load an example assembly for testing. this code will be removed once we've
|
||||
// built a more formal test assembly system
|
||||
fn load_low_curv_assemb(assembly: &Assembly) {
|
||||
let a = 0.75_f64.sqrt();
|
||||
let _ = assembly.try_insert_element(
|
||||
Sphere::new(
|
||||
"central".to_string(),
|
||||
"Central".to_string(),
|
||||
[0.75_f32, 0.75_f32, 0.75_f32],
|
||||
engine::sphere(0.0, 0.0, 0.0, 1.0)
|
||||
)
|
||||
);
|
||||
let _ = assembly.try_insert_element(
|
||||
Sphere::new(
|
||||
"assemb_plane".to_string(),
|
||||
"Assembly plane".to_string(),
|
||||
[0.75_f32, 0.75_f32, 0.75_f32],
|
||||
engine::sphere_with_offset(0.0, 0.0, 1.0, 0.0, 0.0)
|
||||
)
|
||||
);
|
||||
let _ = assembly.try_insert_element(
|
||||
Sphere::new(
|
||||
"side1".to_string(),
|
||||
"Side 1".to_string(),
|
||||
[1.00_f32, 0.00_f32, 0.25_f32],
|
||||
engine::sphere_with_offset(1.0, 0.0, 0.0, 1.0, 0.0)
|
||||
)
|
||||
);
|
||||
let _ = assembly.try_insert_element(
|
||||
Sphere::new(
|
||||
"side2".to_string(),
|
||||
"Side 2".to_string(),
|
||||
[0.25_f32, 1.00_f32, 0.00_f32],
|
||||
engine::sphere_with_offset(-0.5, a, 0.0, 1.0, 0.0)
|
||||
)
|
||||
);
|
||||
let _ = assembly.try_insert_element(
|
||||
Sphere::new(
|
||||
"side3".to_string(),
|
||||
"Side 3".to_string(),
|
||||
[0.00_f32, 0.25_f32, 1.00_f32],
|
||||
engine::sphere_with_offset(-0.5, -a, 0.0, 1.0, 0.0)
|
||||
)
|
||||
);
|
||||
let _ = assembly.try_insert_element(
|
||||
Sphere::new(
|
||||
"corner1".to_string(),
|
||||
"Corner 1".to_string(),
|
||||
[0.75_f32, 0.75_f32, 0.75_f32],
|
||||
engine::sphere(-4.0/3.0, 0.0, 0.0, 1.0/3.0)
|
||||
)
|
||||
);
|
||||
let _ = assembly.try_insert_element(
|
||||
Sphere::new(
|
||||
"corner2".to_string(),
|
||||
"Corner 2".to_string(),
|
||||
[0.75_f32, 0.75_f32, 0.75_f32],
|
||||
engine::sphere(2.0/3.0, -4.0/3.0 * a, 0.0, 1.0/3.0)
|
||||
)
|
||||
);
|
||||
let _ = assembly.try_insert_element(
|
||||
Sphere::new(
|
||||
String::from("corner3"),
|
||||
String::from("Corner 3"),
|
||||
[0.75_f32, 0.75_f32, 0.75_f32],
|
||||
engine::sphere(2.0/3.0, 4.0/3.0 * a, 0.0, 1.0/3.0)
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
fn load_pointed_assemb(assembly: &Assembly) {
|
||||
let _ = assembly.try_insert_element(
|
||||
Point::new(
|
||||
format!("point_front"),
|
||||
format!("Front point"),
|
||||
[0.75_f32, 0.75_f32, 0.75_f32],
|
||||
engine::point(0.0, 0.0, FRAC_1_SQRT_2)
|
||||
)
|
||||
);
|
||||
let _ = assembly.try_insert_element(
|
||||
Point::new(
|
||||
format!("point_back"),
|
||||
format!("Back point"),
|
||||
[0.75_f32, 0.75_f32, 0.75_f32],
|
||||
engine::point(0.0, 0.0, -FRAC_1_SQRT_2)
|
||||
)
|
||||
);
|
||||
for index_x in 0..=1 {
|
||||
for index_y in 0..=1 {
|
||||
let x = index_x as f64 - 0.5;
|
||||
let y = index_y as f64 - 0.5;
|
||||
|
||||
let _ = assembly.try_insert_element(
|
||||
Sphere::new(
|
||||
format!("sphere{index_x}{index_y}"),
|
||||
format!("Sphere {index_x}{index_y}"),
|
||||
[0.5*(1.0 + x) as f32, 0.5*(1.0 + y) as f32, 0.5*(1.0 - x*y) as f32],
|
||||
engine::sphere(x, y, 0.0, 1.0)
|
||||
)
|
||||
);
|
||||
|
||||
let _ = assembly.try_insert_element(
|
||||
Point::new(
|
||||
format!("point{index_x}{index_y}"),
|
||||
format!("Point {index_x}{index_y}"),
|
||||
[0.5*(1.0 + x) as f32, 0.5*(1.0 + y) as f32, 0.5*(1.0 - x*y) as f32],
|
||||
engine::point(x, y, 0.0)
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[component]
|
||||
pub fn AddRemove() -> View {
|
||||
/* DEBUG */
|
||||
let assembly_name = create_signal("general".to_string());
|
||||
create_effect(move || {
|
||||
// get name of chosen assembly
|
||||
let name = assembly_name.get_clone();
|
||||
console::log_1(
|
||||
&JsValue::from(format!("Showing assembly \"{}\"", name.clone()))
|
||||
);
|
||||
|
||||
batch(|| {
|
||||
let state = use_context::<AppState>();
|
||||
let assembly = &state.assembly;
|
||||
|
||||
// clear state
|
||||
assembly.regulators.update(|regs| regs.clear());
|
||||
assembly.elements.update(|elts| elts.clear());
|
||||
assembly.elements_by_id.update(|elts_by_id| elts_by_id.clear());
|
||||
assembly.descent_history.set(DescentHistory::new());
|
||||
state.selection.update(|sel| sel.clear());
|
||||
|
||||
// load assembly
|
||||
match name.as_str() {
|
||||
"general" => load_gen_assemb(assembly),
|
||||
"low-curv" => load_low_curv_assemb(assembly),
|
||||
"pointed" => load_pointed_assemb(assembly),
|
||||
_ => ()
|
||||
};
|
||||
});
|
||||
});
|
||||
|
||||
view! {
|
||||
div(id="add-remove") {
|
||||
button(
|
||||
on:click=|_| {
|
||||
let state = use_context::<AppState>();
|
||||
state.assembly.insert_element_default::<Sphere>();
|
||||
}
|
||||
) { "Add sphere" }
|
||||
button(
|
||||
on:click=|_| {
|
||||
let state = use_context::<AppState>();
|
||||
state.assembly.insert_element_default::<Point>();
|
||||
}
|
||||
) { "Add point" }
|
||||
button(
|
||||
class="emoji", /* KLUDGE */ // for convenience, we're using an emoji as a temporary icon for this button
|
||||
disabled={
|
||||
let state = use_context::<AppState>();
|
||||
state.selection.with(|sel| sel.len() != 2)
|
||||
},
|
||||
on:click=|_| {
|
||||
let state = use_context::<AppState>();
|
||||
let subjects: [_; 2] = state.selection.with(
|
||||
// the button is only enabled when two elements are
|
||||
// selected, so we know the cast to a two-element array
|
||||
// will succeed
|
||||
|sel| sel
|
||||
.clone()
|
||||
.into_iter()
|
||||
.collect::<Vec<_>>()
|
||||
.try_into()
|
||||
.unwrap()
|
||||
);
|
||||
state.assembly.insert_regulator(
|
||||
Rc::new(InversiveDistanceRegulator::new(subjects))
|
||||
);
|
||||
state.selection.update(|sel| sel.clear());
|
||||
}
|
||||
) { "🔗" }
|
||||
select(bind:value=assembly_name) { /* DEBUG */ // example assembly chooser
|
||||
option(value="general") { "General" }
|
||||
option(value="low-curv") { "Low-curvature" }
|
||||
option(value="pointed") { "Pointed" }
|
||||
option(value="empty") { "Empty" }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -13,7 +13,7 @@ use sycamore::prelude::*;
|
|||
use web_sys::{console, wasm_bindgen::JsValue}; /* DEBUG */
|
||||
|
||||
use crate::{
|
||||
display::DisplayItem,
|
||||
components::{display::DisplayItem, outline::OutlineItem},
|
||||
engine::{
|
||||
Q,
|
||||
change_half_curvature,
|
||||
|
@ -29,7 +29,6 @@ use crate::{
|
|||
DescentHistory,
|
||||
Realization
|
||||
},
|
||||
outline::OutlineItem,
|
||||
specified::SpecifiedValue
|
||||
};
|
||||
|
||||
|
@ -552,6 +551,10 @@ pub struct Assembly {
|
|||
// indexing
|
||||
pub elements_by_id: Signal<BTreeMap<String, Rc<dyn Element>>>,
|
||||
|
||||
// realization control
|
||||
pub keep_realized: Signal<bool>,
|
||||
pub needs_realization: Signal<bool>,
|
||||
|
||||
// realization diagnostics
|
||||
pub realization_status: Signal<Result<(), String>>,
|
||||
pub descent_history: Signal<DescentHistory>
|
||||
|
@ -559,14 +562,30 @@ pub struct Assembly {
|
|||
|
||||
impl Assembly {
|
||||
pub fn new() -> Assembly {
|
||||
Assembly {
|
||||
// create an assembly
|
||||
let assembly = Assembly {
|
||||
elements: create_signal(BTreeSet::new()),
|
||||
regulators: create_signal(BTreeSet::new()),
|
||||
tangent: create_signal(ConfigSubspace::zero(0)),
|
||||
elements_by_id: create_signal(BTreeMap::default()),
|
||||
keep_realized: create_signal(true),
|
||||
needs_realization: create_signal(false),
|
||||
realization_status: create_signal(Ok(())),
|
||||
descent_history: create_signal(DescentHistory::new())
|
||||
}
|
||||
};
|
||||
|
||||
// realize the assembly whenever it becomes simultaneously true that
|
||||
// we're trying to keep it realized and it needs realization
|
||||
let assembly_for_effect = assembly.clone();
|
||||
create_effect(move || {
|
||||
let should_realize = assembly_for_effect.keep_realized.get()
|
||||
&& assembly_for_effect.needs_realization.get();
|
||||
if should_realize {
|
||||
assembly_for_effect.realize();
|
||||
}
|
||||
});
|
||||
|
||||
assembly
|
||||
}
|
||||
|
||||
// --- inserting elements and regulators ---
|
||||
|
@ -627,7 +646,7 @@ impl Assembly {
|
|||
regulators.update(|regs| regs.insert(regulator.clone()));
|
||||
}
|
||||
|
||||
// update the realization when the regulator becomes a constraint, or is
|
||||
// request a realization when the regulator becomes a constraint, or is
|
||||
// edited while acting as a constraint
|
||||
let self_for_effect = self.clone();
|
||||
create_effect(move || {
|
||||
|
@ -636,7 +655,7 @@ impl Assembly {
|
|||
console_log!("Updated regulator with subjects {:?}", regulator.subjects());
|
||||
|
||||
if regulator.try_activate() {
|
||||
self_for_effect.realize();
|
||||
self_for_effect.needs_realization.set(true);
|
||||
}
|
||||
});
|
||||
|
||||
|
@ -731,6 +750,9 @@ impl Assembly {
|
|||
|
||||
// save the tangent space
|
||||
self.tangent.set_silent(tangent);
|
||||
|
||||
// clear the realization request flag
|
||||
self.needs_realization.set(false);
|
||||
},
|
||||
Err(message) => {
|
||||
// report the realization status. the `Err(message)` we're
|
||||
|
@ -826,10 +848,10 @@ impl Assembly {
|
|||
});
|
||||
}
|
||||
|
||||
// bring the configuration back onto the solution variety. this also
|
||||
// gets the elements' column indices and the saved tangent space back in
|
||||
// sync
|
||||
self.realize();
|
||||
// request a realization to bring the configuration back onto the
|
||||
// solution variety. this also gets the elements' column indices and the
|
||||
// saved tangent space back in sync
|
||||
self.needs_realization.set(true);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
5
app-proto/src/components.rs
Normal file
5
app-proto/src/components.rs
Normal file
|
@ -0,0 +1,5 @@
|
|||
pub mod add_remove;
|
||||
pub mod diagnostics;
|
||||
pub mod display;
|
||||
pub mod outline;
|
||||
pub mod test_assembly_chooser;
|
54
app-proto/src/components/add_remove.rs
Normal file
54
app-proto/src/components/add_remove.rs
Normal file
|
@ -0,0 +1,54 @@
|
|||
use std::rc::Rc;
|
||||
use sycamore::prelude::*;
|
||||
|
||||
use super::test_assembly_chooser::TestAssemblyChooser;
|
||||
use crate::{
|
||||
AppState,
|
||||
assembly::{InversiveDistanceRegulator, Point, Sphere}
|
||||
};
|
||||
|
||||
#[component]
|
||||
pub fn AddRemove() -> View {
|
||||
view! {
|
||||
div(id="add-remove") {
|
||||
button(
|
||||
on:click=|_| {
|
||||
let state = use_context::<AppState>();
|
||||
state.assembly.insert_element_default::<Sphere>();
|
||||
}
|
||||
) { "Add sphere" }
|
||||
button(
|
||||
on:click=|_| {
|
||||
let state = use_context::<AppState>();
|
||||
state.assembly.insert_element_default::<Point>();
|
||||
}
|
||||
) { "Add point" }
|
||||
button(
|
||||
class="emoji", /* KLUDGE */ // for convenience, we're using an emoji as a temporary icon for this button
|
||||
disabled={
|
||||
let state = use_context::<AppState>();
|
||||
state.selection.with(|sel| sel.len() != 2)
|
||||
},
|
||||
on:click=|_| {
|
||||
let state = use_context::<AppState>();
|
||||
let subjects: [_; 2] = state.selection.with(
|
||||
// the button is only enabled when two elements are
|
||||
// selected, so we know the cast to a two-element array
|
||||
// will succeed
|
||||
|sel| sel
|
||||
.clone()
|
||||
.into_iter()
|
||||
.collect::<Vec<_>>()
|
||||
.try_into()
|
||||
.unwrap()
|
||||
);
|
||||
state.assembly.insert_regulator(
|
||||
Rc::new(InversiveDistanceRegulator::new(subjects))
|
||||
);
|
||||
state.selection.update(|sel| sel.clear());
|
||||
}
|
||||
) { "🔗" }
|
||||
TestAssemblyChooser {}
|
||||
}
|
||||
}
|
||||
}
|
947
app-proto/src/components/test_assembly_chooser.rs
Normal file
947
app-proto/src/components/test_assembly_chooser.rs
Normal file
|
@ -0,0 +1,947 @@
|
|||
use itertools::izip;
|
||||
use std::{f64::consts::{FRAC_1_SQRT_2, PI}, rc::Rc};
|
||||
use nalgebra::Vector3;
|
||||
use sycamore::prelude::*;
|
||||
use web_sys::{console, wasm_bindgen::JsValue};
|
||||
|
||||
use crate::{
|
||||
AppState,
|
||||
engine,
|
||||
engine::DescentHistory,
|
||||
assembly::{
|
||||
Assembly,
|
||||
Element,
|
||||
ElementColor,
|
||||
InversiveDistanceRegulator,
|
||||
Point,
|
||||
Sphere
|
||||
},
|
||||
specified::SpecifiedValue
|
||||
};
|
||||
|
||||
// --- loaders ---
|
||||
|
||||
/* DEBUG */
|
||||
// each of these functions loads an example assembly for testing. once we've
|
||||
// done more work on saving and loading assemblies, we should come back to this
|
||||
// code to see if it can be simplified
|
||||
|
||||
fn load_gen_assemb(assembly: &Assembly) {
|
||||
let _ = assembly.try_insert_element(
|
||||
Sphere::new(
|
||||
String::from("gemini_a"),
|
||||
String::from("Castor"),
|
||||
[1.00_f32, 0.25_f32, 0.00_f32],
|
||||
engine::sphere(0.5, 0.5, 0.0, 1.0)
|
||||
)
|
||||
);
|
||||
let _ = assembly.try_insert_element(
|
||||
Sphere::new(
|
||||
String::from("gemini_b"),
|
||||
String::from("Pollux"),
|
||||
[0.00_f32, 0.25_f32, 1.00_f32],
|
||||
engine::sphere(-0.5, -0.5, 0.0, 1.0)
|
||||
)
|
||||
);
|
||||
let _ = assembly.try_insert_element(
|
||||
Sphere::new(
|
||||
String::from("ursa_major"),
|
||||
String::from("Ursa major"),
|
||||
[0.25_f32, 0.00_f32, 1.00_f32],
|
||||
engine::sphere(-0.5, 0.5, 0.0, 0.75)
|
||||
)
|
||||
);
|
||||
let _ = assembly.try_insert_element(
|
||||
Sphere::new(
|
||||
String::from("ursa_minor"),
|
||||
String::from("Ursa minor"),
|
||||
[0.25_f32, 1.00_f32, 0.00_f32],
|
||||
engine::sphere(0.5, -0.5, 0.0, 0.5)
|
||||
)
|
||||
);
|
||||
let _ = assembly.try_insert_element(
|
||||
Sphere::new(
|
||||
String::from("moon_deimos"),
|
||||
String::from("Deimos"),
|
||||
[0.75_f32, 0.75_f32, 0.00_f32],
|
||||
engine::sphere(0.0, 0.15, 1.0, 0.25)
|
||||
)
|
||||
);
|
||||
let _ = assembly.try_insert_element(
|
||||
Sphere::new(
|
||||
String::from("moon_phobos"),
|
||||
String::from("Phobos"),
|
||||
[0.00_f32, 0.75_f32, 0.50_f32],
|
||||
engine::sphere(0.0, -0.15, -1.0, 0.25)
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
fn load_low_curv_assemb(assembly: &Assembly) {
|
||||
// create the spheres
|
||||
let a = 0.75_f64.sqrt();
|
||||
let _ = assembly.try_insert_element(
|
||||
Sphere::new(
|
||||
"central".to_string(),
|
||||
"Central".to_string(),
|
||||
[0.75_f32, 0.75_f32, 0.75_f32],
|
||||
engine::sphere(0.0, 0.0, 0.0, 1.0)
|
||||
)
|
||||
);
|
||||
let _ = assembly.try_insert_element(
|
||||
Sphere::new(
|
||||
"assemb_plane".to_string(),
|
||||
"Assembly plane".to_string(),
|
||||
[0.75_f32, 0.75_f32, 0.75_f32],
|
||||
engine::sphere_with_offset(0.0, 0.0, 1.0, 0.0, 0.0)
|
||||
)
|
||||
);
|
||||
let _ = assembly.try_insert_element(
|
||||
Sphere::new(
|
||||
"side1".to_string(),
|
||||
"Side 1".to_string(),
|
||||
[1.00_f32, 0.00_f32, 0.25_f32],
|
||||
engine::sphere_with_offset(1.0, 0.0, 0.0, 1.0, 0.0)
|
||||
)
|
||||
);
|
||||
let _ = assembly.try_insert_element(
|
||||
Sphere::new(
|
||||
"side2".to_string(),
|
||||
"Side 2".to_string(),
|
||||
[0.25_f32, 1.00_f32, 0.00_f32],
|
||||
engine::sphere_with_offset(-0.5, a, 0.0, 1.0, 0.0)
|
||||
)
|
||||
);
|
||||
let _ = assembly.try_insert_element(
|
||||
Sphere::new(
|
||||
"side3".to_string(),
|
||||
"Side 3".to_string(),
|
||||
[0.00_f32, 0.25_f32, 1.00_f32],
|
||||
engine::sphere_with_offset(-0.5, -a, 0.0, 1.0, 0.0)
|
||||
)
|
||||
);
|
||||
let _ = assembly.try_insert_element(
|
||||
Sphere::new(
|
||||
"corner1".to_string(),
|
||||
"Corner 1".to_string(),
|
||||
[0.75_f32, 0.75_f32, 0.75_f32],
|
||||
engine::sphere(-4.0/3.0, 0.0, 0.0, 1.0/3.0)
|
||||
)
|
||||
);
|
||||
let _ = assembly.try_insert_element(
|
||||
Sphere::new(
|
||||
"corner2".to_string(),
|
||||
"Corner 2".to_string(),
|
||||
[0.75_f32, 0.75_f32, 0.75_f32],
|
||||
engine::sphere(2.0/3.0, -4.0/3.0 * a, 0.0, 1.0/3.0)
|
||||
)
|
||||
);
|
||||
let _ = assembly.try_insert_element(
|
||||
Sphere::new(
|
||||
String::from("corner3"),
|
||||
String::from("Corner 3"),
|
||||
[0.75_f32, 0.75_f32, 0.75_f32],
|
||||
engine::sphere(2.0/3.0, 4.0/3.0 * a, 0.0, 1.0/3.0)
|
||||
)
|
||||
);
|
||||
|
||||
// impose the desired tangencies and make the sides planar
|
||||
let index_range = 1..=3;
|
||||
let [central, assemb_plane] = ["central", "assemb_plane"].map(
|
||||
|id| assembly.elements_by_id.with_untracked(
|
||||
|elts_by_id| elts_by_id[id].clone()
|
||||
)
|
||||
);
|
||||
let sides = index_range.clone().map(
|
||||
|k| assembly.elements_by_id.with_untracked(
|
||||
|elts_by_id| elts_by_id[&format!("side{k}")].clone()
|
||||
)
|
||||
);
|
||||
let corners = index_range.map(
|
||||
|k| assembly.elements_by_id.with_untracked(
|
||||
|elts_by_id| elts_by_id[&format!("corner{k}")].clone()
|
||||
)
|
||||
);
|
||||
for plane in [assemb_plane.clone()].into_iter().chain(sides.clone()) {
|
||||
// fix the curvature of each plane
|
||||
let curvature = plane.regulators().with_untracked(
|
||||
|regs| regs.first().unwrap().clone()
|
||||
);
|
||||
curvature.set_point().set(SpecifiedValue::try_from("0".to_string()).unwrap());
|
||||
}
|
||||
let all_perpendicular = [central.clone()].into_iter()
|
||||
.chain(sides.clone())
|
||||
.chain(corners.clone());
|
||||
for sphere in all_perpendicular {
|
||||
// make each side and packed sphere perpendicular to the assembly plane
|
||||
let right_angle = InversiveDistanceRegulator::new([sphere, assemb_plane.clone()]);
|
||||
right_angle.set_point.set(SpecifiedValue::try_from("0".to_string()).unwrap());
|
||||
assembly.insert_regulator(Rc::new(right_angle));
|
||||
}
|
||||
for sphere in sides.clone().chain(corners.clone()) {
|
||||
// make each side and corner sphere tangent to the central sphere
|
||||
let tangency = InversiveDistanceRegulator::new([sphere.clone(), central.clone()]);
|
||||
tangency.set_point.set(SpecifiedValue::try_from("-1".to_string()).unwrap());
|
||||
assembly.insert_regulator(Rc::new(tangency));
|
||||
}
|
||||
for (side_index, side) in sides.enumerate() {
|
||||
// make each side tangent to the two adjacent corner spheres
|
||||
for (corner_index, corner) in corners.clone().enumerate() {
|
||||
if side_index != corner_index {
|
||||
let tangency = InversiveDistanceRegulator::new([side.clone(), corner]);
|
||||
tangency.set_point.set(SpecifiedValue::try_from("-1".to_string()).unwrap());
|
||||
assembly.insert_regulator(Rc::new(tangency));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn load_pointed_assemb(assembly: &Assembly) {
|
||||
let _ = assembly.try_insert_element(
|
||||
Point::new(
|
||||
format!("point_front"),
|
||||
format!("Front point"),
|
||||
[0.75_f32, 0.75_f32, 0.75_f32],
|
||||
engine::point(0.0, 0.0, FRAC_1_SQRT_2)
|
||||
)
|
||||
);
|
||||
let _ = assembly.try_insert_element(
|
||||
Point::new(
|
||||
format!("point_back"),
|
||||
format!("Back point"),
|
||||
[0.75_f32, 0.75_f32, 0.75_f32],
|
||||
engine::point(0.0, 0.0, -FRAC_1_SQRT_2)
|
||||
)
|
||||
);
|
||||
for index_x in 0..=1 {
|
||||
for index_y in 0..=1 {
|
||||
let x = index_x as f64 - 0.5;
|
||||
let y = index_y as f64 - 0.5;
|
||||
|
||||
let _ = assembly.try_insert_element(
|
||||
Sphere::new(
|
||||
format!("sphere{index_x}{index_y}"),
|
||||
format!("Sphere {index_x}{index_y}"),
|
||||
[0.5*(1.0 + x) as f32, 0.5*(1.0 + y) as f32, 0.5*(1.0 - x*y) as f32],
|
||||
engine::sphere(x, y, 0.0, 1.0)
|
||||
)
|
||||
);
|
||||
|
||||
let _ = assembly.try_insert_element(
|
||||
Point::new(
|
||||
format!("point{index_x}{index_y}"),
|
||||
format!("Point {index_x}{index_y}"),
|
||||
[0.5*(1.0 + x) as f32, 0.5*(1.0 + y) as f32, 0.5*(1.0 - x*y) as f32],
|
||||
engine::point(x, y, 0.0)
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// to finish describing the tridiminished icosahedron, set the inversive
|
||||
// distance regulators as follows:
|
||||
// A-A -0.25
|
||||
// A-B "
|
||||
// B-C "
|
||||
// C-C "
|
||||
// A-C -0.25 * φ^2 = -0.6545084971874737
|
||||
fn load_tridim_icosahedron_assemb(assembly: &Assembly) {
|
||||
// create the vertices
|
||||
const COLOR_A: ElementColor = [1.00_f32, 0.25_f32, 0.25_f32];
|
||||
const COLOR_B: ElementColor = [0.75_f32, 0.75_f32, 0.75_f32];
|
||||
const COLOR_C: ElementColor = [0.25_f32, 0.50_f32, 1.00_f32];
|
||||
let vertices = [
|
||||
Point::new(
|
||||
"a1".to_string(),
|
||||
"A₁".to_string(),
|
||||
COLOR_A,
|
||||
engine::point(0.25, 0.75, 0.75)
|
||||
),
|
||||
Point::new(
|
||||
"a2".to_string(),
|
||||
"A₂".to_string(),
|
||||
COLOR_A,
|
||||
engine::point(0.75, 0.25, 0.75)
|
||||
),
|
||||
Point::new(
|
||||
"a3".to_string(),
|
||||
"A₃".to_string(),
|
||||
COLOR_A,
|
||||
engine::point(0.75, 0.75, 0.25)
|
||||
),
|
||||
Point::new(
|
||||
"b1".to_string(),
|
||||
"B₁".to_string(),
|
||||
COLOR_B,
|
||||
engine::point(0.75, -0.25, -0.25)
|
||||
),
|
||||
Point::new(
|
||||
"b2".to_string(),
|
||||
"B₂".to_string(),
|
||||
COLOR_B,
|
||||
engine::point(-0.25, 0.75, -0.25)
|
||||
),
|
||||
Point::new(
|
||||
"b3".to_string(),
|
||||
"B₃".to_string(),
|
||||
COLOR_B,
|
||||
engine::point(-0.25, -0.25, 0.75)
|
||||
),
|
||||
Point::new(
|
||||
"c1".to_string(),
|
||||
"C₁".to_string(),
|
||||
COLOR_C,
|
||||
engine::point(0.0, -1.0, -1.0)
|
||||
),
|
||||
Point::new(
|
||||
"c2".to_string(),
|
||||
"C₂".to_string(),
|
||||
COLOR_C,
|
||||
engine::point(-1.0, 0.0, -1.0)
|
||||
),
|
||||
Point::new(
|
||||
"c3".to_string(),
|
||||
"C₃".to_string(),
|
||||
COLOR_C,
|
||||
engine::point(-1.0, -1.0, 0.0)
|
||||
)
|
||||
];
|
||||
for vertex in vertices {
|
||||
let _ = assembly.try_insert_element(vertex);
|
||||
}
|
||||
|
||||
// create the faces
|
||||
const COLOR_FACE: ElementColor = [0.75_f32, 0.75_f32, 0.75_f32];
|
||||
let frac_1_sqrt_6 = 1.0 / 6.0_f64.sqrt();
|
||||
let frac_2_sqrt_6 = 2.0 * frac_1_sqrt_6;
|
||||
let faces = [
|
||||
Sphere::new(
|
||||
"face1".to_string(),
|
||||
"Face 1".to_string(),
|
||||
COLOR_FACE,
|
||||
engine::sphere_with_offset(frac_2_sqrt_6, -frac_1_sqrt_6, -frac_1_sqrt_6, -frac_1_sqrt_6, 0.0)
|
||||
),
|
||||
Sphere::new(
|
||||
"face2".to_string(),
|
||||
"Face 2".to_string(),
|
||||
COLOR_FACE,
|
||||
engine::sphere_with_offset(-frac_1_sqrt_6, frac_2_sqrt_6, -frac_1_sqrt_6, -frac_1_sqrt_6, 0.0)
|
||||
),
|
||||
Sphere::new(
|
||||
"face3".to_string(),
|
||||
"Face 3".to_string(),
|
||||
COLOR_FACE,
|
||||
engine::sphere_with_offset(-frac_1_sqrt_6, -frac_1_sqrt_6, frac_2_sqrt_6, -frac_1_sqrt_6, 0.0)
|
||||
)
|
||||
];
|
||||
for face in faces {
|
||||
face.ghost().set(true);
|
||||
let _ = assembly.try_insert_element(face);
|
||||
}
|
||||
|
||||
let index_range = 1..=3;
|
||||
for j in index_range.clone() {
|
||||
// make each face planar
|
||||
let face = assembly.elements_by_id.with_untracked(
|
||||
|elts_by_id| elts_by_id[&format!("face{j}")].clone()
|
||||
);
|
||||
let curvature_regulator = face.regulators().with_untracked(
|
||||
|regs| regs.first().unwrap().clone()
|
||||
);
|
||||
curvature_regulator.set_point().set(
|
||||
SpecifiedValue::try_from("0".to_string()).unwrap()
|
||||
);
|
||||
|
||||
// put each A vertex on the face it belongs to
|
||||
let vertex_a = assembly.elements_by_id.with_untracked(
|
||||
|elts_by_id| elts_by_id[&format!("a{j}")].clone()
|
||||
);
|
||||
let incidence_a = InversiveDistanceRegulator::new([face.clone(), vertex_a.clone()]);
|
||||
incidence_a.set_point.set(SpecifiedValue::try_from("0".to_string()).unwrap());
|
||||
assembly.insert_regulator(Rc::new(incidence_a));
|
||||
|
||||
// regulate the B-C vertex distances
|
||||
let vertices_bc = ["b", "c"].map(
|
||||
|series| assembly.elements_by_id.with_untracked(
|
||||
|elts_by_id| elts_by_id[&format!("{series}{j}")].clone()
|
||||
)
|
||||
);
|
||||
assembly.insert_regulator(
|
||||
Rc::new(InversiveDistanceRegulator::new(vertices_bc))
|
||||
);
|
||||
|
||||
// get the pair of indices adjacent to `j`
|
||||
let adjacent_indices = [j % 3 + 1, (j + 1) % 3 + 1];
|
||||
|
||||
for k in adjacent_indices.clone() {
|
||||
for series in ["b", "c"] {
|
||||
// put each B and C vertex on the faces it belongs to
|
||||
let vertex = assembly.elements_by_id.with_untracked(
|
||||
|elts_by_id| elts_by_id[&format!("{series}{k}")].clone()
|
||||
);
|
||||
let incidence = InversiveDistanceRegulator::new([face.clone(), vertex.clone()]);
|
||||
incidence.set_point.set(SpecifiedValue::try_from("0".to_string()).unwrap());
|
||||
assembly.insert_regulator(Rc::new(incidence));
|
||||
|
||||
// regulate the A-B and A-C vertex distances
|
||||
assembly.insert_regulator(
|
||||
Rc::new(InversiveDistanceRegulator::new([vertex_a.clone(), vertex]))
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// regulate the A-A and C-C vertex distances
|
||||
let adjacent_pairs = ["a", "c"].map(
|
||||
|series| adjacent_indices.map(
|
||||
|index| assembly.elements_by_id.with_untracked(
|
||||
|elts_by_id| elts_by_id[&format!("{series}{index}")].clone()
|
||||
)
|
||||
)
|
||||
);
|
||||
for pair in adjacent_pairs {
|
||||
assembly.insert_regulator(
|
||||
Rc::new(InversiveDistanceRegulator::new(pair))
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// to finish describing the dodecahedral circle packing, set the inversive
|
||||
// distance regulators to -1. some of the regulators have already been set
|
||||
fn load_dodeca_packing_assemb(assembly: &Assembly) {
|
||||
// add the substrate
|
||||
let _ = assembly.try_insert_element(
|
||||
Sphere::new(
|
||||
"substrate".to_string(),
|
||||
"Substrate".to_string(),
|
||||
[0.75_f32, 0.75_f32, 0.75_f32],
|
||||
engine::sphere(0.0, 0.0, 0.0, 1.0)
|
||||
)
|
||||
);
|
||||
let substrate = assembly.elements_by_id.with_untracked(
|
||||
|elts_by_id| elts_by_id["substrate"].clone()
|
||||
);
|
||||
|
||||
// fix the substrate's curvature
|
||||
substrate.regulators().with_untracked(
|
||||
|regs| regs.first().unwrap().clone()
|
||||
).set_point().set(
|
||||
SpecifiedValue::try_from("0.5".to_string()).unwrap()
|
||||
);
|
||||
|
||||
// add the circles to be packed
|
||||
const COLOR_A: ElementColor = [1.00_f32, 0.25_f32, 0.00_f32];
|
||||
const COLOR_B: ElementColor = [1.00_f32, 0.00_f32, 0.25_f32];
|
||||
const COLOR_C: ElementColor = [0.25_f32, 0.00_f32, 1.00_f32];
|
||||
let phi = 0.5 + 1.25_f64.sqrt(); /* TO DO */ // replace with std::f64::consts::PHI when that gets stabilized
|
||||
let phi_inv = 1.0 / phi;
|
||||
let coord_scale = (phi + 2.0).sqrt();
|
||||
let face_scales = [phi_inv, (13.0 / 12.0) / coord_scale];
|
||||
let face_radii = [phi_inv, 5.0 / 12.0];
|
||||
let mut faces = Vec::<Rc<dyn Element>>::new();
|
||||
let subscripts = ["₀", "₁"];
|
||||
for j in 0..2 {
|
||||
for k in 0..2 {
|
||||
let small_coord = face_scales[k] * (2.0*(j as f64) - 1.0);
|
||||
let big_coord = face_scales[k] * (2.0*(k as f64) - 1.0) * phi;
|
||||
|
||||
let id_num = format!("{j}{k}");
|
||||
let label_sub = format!("{}{}", subscripts[j], subscripts[k]);
|
||||
|
||||
// add the A face
|
||||
let id_a = format!("a{id_num}");
|
||||
let _ = assembly.try_insert_element(
|
||||
Sphere::new(
|
||||
id_a.clone(),
|
||||
format!("A{label_sub}"),
|
||||
COLOR_A,
|
||||
engine::sphere(0.0, small_coord, big_coord, face_radii[k])
|
||||
)
|
||||
);
|
||||
faces.push(
|
||||
assembly.elements_by_id.with_untracked(
|
||||
|elts_by_id| elts_by_id[&id_a].clone()
|
||||
)
|
||||
);
|
||||
|
||||
// add the B face
|
||||
let id_b = format!("b{id_num}");
|
||||
let _ = assembly.try_insert_element(
|
||||
Sphere::new(
|
||||
id_b.clone(),
|
||||
format!("B{label_sub}"),
|
||||
COLOR_B,
|
||||
engine::sphere(small_coord, big_coord, 0.0, face_radii[k])
|
||||
)
|
||||
);
|
||||
faces.push(
|
||||
assembly.elements_by_id.with_untracked(
|
||||
|elts_by_id| elts_by_id[&id_b].clone()
|
||||
)
|
||||
);
|
||||
|
||||
// add the C face
|
||||
let id_c = format!("c{id_num}");
|
||||
let _ = assembly.try_insert_element(
|
||||
Sphere::new(
|
||||
id_c.clone(),
|
||||
format!("C{label_sub}"),
|
||||
COLOR_C,
|
||||
engine::sphere(big_coord, 0.0, small_coord, face_radii[k])
|
||||
)
|
||||
);
|
||||
faces.push(
|
||||
assembly.elements_by_id.with_untracked(
|
||||
|elts_by_id| elts_by_id[&id_c].clone()
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// make each face sphere perpendicular to the substrate
|
||||
for face in faces {
|
||||
let right_angle = InversiveDistanceRegulator::new([face, substrate.clone()]);
|
||||
right_angle.set_point.set(SpecifiedValue::try_from("0".to_string()).unwrap());
|
||||
assembly.insert_regulator(Rc::new(right_angle));
|
||||
}
|
||||
|
||||
// set up the tangencies that define the packing
|
||||
for [long_edge_plane, short_edge_plane] in [["a", "b"], ["b", "c"], ["c", "a"]] {
|
||||
for k in 0..2 {
|
||||
let long_edge_ids = [
|
||||
format!("{long_edge_plane}{k}0"),
|
||||
format!("{long_edge_plane}{k}1")
|
||||
];
|
||||
let short_edge_ids = [
|
||||
format!("{short_edge_plane}0{k}"),
|
||||
format!("{short_edge_plane}1{k}")
|
||||
];
|
||||
let [long_edge, short_edge] = [long_edge_ids, short_edge_ids].map(
|
||||
|edge_ids| edge_ids.map(
|
||||
|id| assembly.elements_by_id.with_untracked(
|
||||
|elts_by_id| elts_by_id[&id].clone()
|
||||
)
|
||||
)
|
||||
);
|
||||
|
||||
// set up the short-edge tangency
|
||||
let short_tangency = InversiveDistanceRegulator::new(short_edge.clone());
|
||||
if k == 0 {
|
||||
short_tangency.set_point.set(SpecifiedValue::try_from("-1".to_string()).unwrap());
|
||||
}
|
||||
assembly.insert_regulator(Rc::new(short_tangency));
|
||||
|
||||
// set up the side tangencies
|
||||
for i in 0..2 {
|
||||
for j in 0..2 {
|
||||
let side_tangency = InversiveDistanceRegulator::new(
|
||||
[long_edge[i].clone(), short_edge[j].clone()]
|
||||
);
|
||||
if i == 0 && k == 0 {
|
||||
side_tangency.set_point.set(SpecifiedValue::try_from("-1".to_string()).unwrap());
|
||||
}
|
||||
assembly.insert_regulator(Rc::new(side_tangency));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// the initial configuration of this test assembly deliberately violates the
|
||||
// constraints, so loading the assembly will trigger a non-trivial realization
|
||||
fn load_balanced_assemb(assembly: &Assembly) {
|
||||
// create the spheres
|
||||
const R_OUTER: f64 = 10.0;
|
||||
const R_INNER: f64 = 4.0;
|
||||
let spheres = [
|
||||
Sphere::new(
|
||||
"outer".to_string(),
|
||||
"Outer".to_string(),
|
||||
[0.75_f32, 0.75_f32, 0.75_f32],
|
||||
engine::sphere(0.0, 0.0, 0.0, R_OUTER)
|
||||
),
|
||||
Sphere::new(
|
||||
"a".to_string(),
|
||||
"A".to_string(),
|
||||
[1.00_f32, 0.00_f32, 0.25_f32],
|
||||
engine::sphere(0.0, 4.0, 0.0, R_INNER)
|
||||
),
|
||||
Sphere::new(
|
||||
"b".to_string(),
|
||||
"B".to_string(),
|
||||
[0.00_f32, 0.25_f32, 1.00_f32],
|
||||
engine::sphere(0.0, -4.0, 0.0, R_INNER)
|
||||
),
|
||||
];
|
||||
for sphere in spheres {
|
||||
let _ = assembly.try_insert_element(sphere);
|
||||
}
|
||||
|
||||
// get references to the spheres
|
||||
let [outer, a, b] = ["outer", "a", "b"].map(
|
||||
|id| assembly.elements_by_id.with_untracked(
|
||||
|elts_by_id| elts_by_id[id].clone()
|
||||
)
|
||||
);
|
||||
|
||||
// fix the diameters of the outer, sun, and moon spheres
|
||||
for (sphere, radius) in [
|
||||
(outer.clone(), R_OUTER),
|
||||
(a.clone(), R_INNER),
|
||||
(b.clone(), R_INNER)
|
||||
] {
|
||||
let curvature_regulator = sphere.regulators().with_untracked(
|
||||
|regs| regs.first().unwrap().clone()
|
||||
);
|
||||
let curvature = 0.5 / radius;
|
||||
curvature_regulator.set_point().set(
|
||||
SpecifiedValue::try_from(curvature.to_string()).unwrap()
|
||||
);
|
||||
}
|
||||
|
||||
// set the inversive distances between the spheres. as described above, the
|
||||
// initial configuration deliberately violates these constraints
|
||||
for inner in [a, b] {
|
||||
let tangency = InversiveDistanceRegulator::new([outer.clone(), inner]);
|
||||
tangency.set_point.set(SpecifiedValue::try_from("1".to_string()).unwrap());
|
||||
assembly.insert_regulator(Rc::new(tangency));
|
||||
}
|
||||
}
|
||||
|
||||
// the initial configuration of this test assembly deliberately violates the
|
||||
// constraints, so loading the assembly will trigger a non-trivial realization
|
||||
fn load_off_center_assemb(assembly: &Assembly) {
|
||||
// create a point almost at the origin and a sphere centered on the origin
|
||||
let _ = assembly.try_insert_element(
|
||||
Point::new(
|
||||
"point".to_string(),
|
||||
"Point".to_string(),
|
||||
[0.75_f32, 0.75_f32, 0.75_f32],
|
||||
engine::point(1e-9, 0.0, 0.0)
|
||||
),
|
||||
);
|
||||
let _ = assembly.try_insert_element(
|
||||
Sphere::new(
|
||||
"sphere".to_string(),
|
||||
"Sphere".to_string(),
|
||||
[0.75_f32, 0.75_f32, 0.75_f32],
|
||||
engine::sphere(0.0, 0.0, 0.0, 1.0)
|
||||
),
|
||||
);
|
||||
|
||||
// get references to the elements
|
||||
let point_and_sphere = ["point", "sphere"].map(
|
||||
|id| assembly.elements_by_id.with_untracked(
|
||||
|elts_by_id| elts_by_id[id].clone()
|
||||
)
|
||||
);
|
||||
|
||||
// put the point on the sphere
|
||||
let incidence = InversiveDistanceRegulator::new(point_and_sphere);
|
||||
incidence.set_point.set(SpecifiedValue::try_from("0".to_string()).unwrap());
|
||||
assembly.insert_regulator(Rc::new(incidence));
|
||||
}
|
||||
|
||||
// setting the inversive distances between the vertices to -2 gives a regular
|
||||
// tetrahedron with side length 1, whose insphere and circumsphere have radii
|
||||
// sqrt(1/6) and sqrt(3/2), respectively. to measure those radii, set an
|
||||
// inversive distance of -1 between the insphere and each face, and then set an
|
||||
// inversive distance of 0 between the circumsphere and each vertex
|
||||
fn load_radius_ratio_assemb(assembly: &Assembly) {
|
||||
let index_range = 1..=4;
|
||||
|
||||
// create the spheres
|
||||
const GRAY: ElementColor = [0.75_f32, 0.75_f32, 0.75_f32];
|
||||
let spheres = [
|
||||
Sphere::new(
|
||||
"sphere_faces".to_string(),
|
||||
"Insphere".to_string(),
|
||||
GRAY,
|
||||
engine::sphere(0.0, 0.0, 0.0, 0.5)
|
||||
),
|
||||
Sphere::new(
|
||||
"sphere_vertices".to_string(),
|
||||
"Circumsphere".to_string(),
|
||||
GRAY,
|
||||
engine::sphere(0.0, 0.0, 0.0, 0.25)
|
||||
)
|
||||
];
|
||||
for sphere in spheres {
|
||||
let _ = assembly.try_insert_element(sphere);
|
||||
}
|
||||
|
||||
// create the vertices
|
||||
let vertices = izip!(
|
||||
index_range.clone(),
|
||||
[
|
||||
[1.00_f32, 0.50_f32, 0.75_f32],
|
||||
[1.00_f32, 0.75_f32, 0.50_f32],
|
||||
[1.00_f32, 1.00_f32, 0.50_f32],
|
||||
[0.75_f32, 0.50_f32, 1.00_f32]
|
||||
].into_iter(),
|
||||
[
|
||||
engine::point(-0.6, -0.8, -0.6),
|
||||
engine::point(-0.6, 0.8, 0.6),
|
||||
engine::point(0.6, -0.8, 0.6),
|
||||
engine::point(0.6, 0.8, -0.6)
|
||||
].into_iter()
|
||||
).map(
|
||||
|(k, color, representation)| {
|
||||
Point::new(
|
||||
format!("v{k}"),
|
||||
format!("Vertex {k}"),
|
||||
color,
|
||||
representation
|
||||
)
|
||||
}
|
||||
);
|
||||
for vertex in vertices {
|
||||
let _ = assembly.try_insert_element(vertex);
|
||||
}
|
||||
|
||||
// create the faces
|
||||
let base_dir = Vector3::new(1.0, 0.75, 1.0).normalize();
|
||||
let offset = base_dir.dot(&Vector3::new(-0.6, 0.8, 0.6));
|
||||
let faces = izip!(
|
||||
index_range.clone(),
|
||||
[
|
||||
[1.00_f32, 0.00_f32, 0.25_f32],
|
||||
[1.00_f32, 0.25_f32, 0.00_f32],
|
||||
[0.75_f32, 0.75_f32, 0.00_f32],
|
||||
[0.25_f32, 0.00_f32, 1.00_f32]
|
||||
].into_iter(),
|
||||
[
|
||||
engine::sphere_with_offset(base_dir[0], base_dir[1], base_dir[2], offset, 0.0),
|
||||
engine::sphere_with_offset(base_dir[0], -base_dir[1], -base_dir[2], offset, 0.0),
|
||||
engine::sphere_with_offset(-base_dir[0], base_dir[1], -base_dir[2], offset, 0.0),
|
||||
engine::sphere_with_offset(-base_dir[0], -base_dir[1], base_dir[2], offset, 0.0)
|
||||
].into_iter()
|
||||
).map(
|
||||
|(k, color, representation)| {
|
||||
Sphere::new(
|
||||
format!("f{k}"),
|
||||
format!("Face {k}"),
|
||||
color,
|
||||
representation
|
||||
)
|
||||
}
|
||||
);
|
||||
for face in faces {
|
||||
face.ghost().set(true);
|
||||
let _ = assembly.try_insert_element(face);
|
||||
}
|
||||
|
||||
// impose the constraints
|
||||
for j in index_range.clone() {
|
||||
let [face_j, vertex_j] = [
|
||||
format!("f{j}"),
|
||||
format!("v{j}")
|
||||
].map(
|
||||
|id| assembly.elements_by_id.with_untracked(
|
||||
|elts_by_id| elts_by_id[&id].clone()
|
||||
)
|
||||
);
|
||||
|
||||
// make the faces planar
|
||||
let curvature_regulator = face_j.regulators().with_untracked(
|
||||
|regs| regs.first().unwrap().clone()
|
||||
);
|
||||
curvature_regulator.set_point().set(
|
||||
SpecifiedValue::try_from("0".to_string()).unwrap()
|
||||
);
|
||||
|
||||
for k in index_range.clone().filter(|&index| index != j) {
|
||||
let vertex_k = assembly.elements_by_id.with_untracked(
|
||||
|elts_by_id| elts_by_id[&format!("v{k}")].clone()
|
||||
);
|
||||
|
||||
// fix the distances between the vertices
|
||||
if j < k {
|
||||
let distance_regulator = InversiveDistanceRegulator::new(
|
||||
[vertex_j.clone(), vertex_k.clone()]
|
||||
);
|
||||
assembly.insert_regulator(Rc::new(distance_regulator));
|
||||
}
|
||||
|
||||
// put the vertices on the faces
|
||||
let incidence_regulator = InversiveDistanceRegulator::new([face_j.clone(), vertex_k.clone()]);
|
||||
incidence_regulator.set_point.set(SpecifiedValue::try_from("0".to_string()).unwrap());
|
||||
assembly.insert_regulator(Rc::new(incidence_regulator));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// to finish setting up the problem, fix the following curvatures:
|
||||
// sun 1
|
||||
// moon 5/3 = 1.666666666666666...
|
||||
// chain1 2
|
||||
// a tiny `x` or `z` nudge of the outer sphere reliably prevents realization
|
||||
// failures before they happen, or resolves them after they happen. the result
|
||||
// depends sensitively on the translation direction, suggesting that realization
|
||||
// is failing because the engine is having trouble breaking a symmetry
|
||||
// /* TO DO */
|
||||
// the engine's performance on this problem is scale-dependent! with the current
|
||||
// initial conditions, realization fails for any order of imposing the remaining
|
||||
// curvature constraints. scaling everything up by a factor of ten, as done in
|
||||
// the original problem, makes realization succeed reliably. one potentially
|
||||
// relevant difference is that a lot of the numbers in the current initial
|
||||
// conditions are exactly representable as floats, unlike the analogous numbers
|
||||
// in the scaled-up problem. the inexact representations might break the
|
||||
// symmetry that's getting the engine stuck
|
||||
fn load_irisawa_hexlet_assemb(assembly: &Assembly) {
|
||||
let index_range = 1..=6;
|
||||
let colors = [
|
||||
[1.00_f32, 0.00_f32, 0.25_f32],
|
||||
[1.00_f32, 0.25_f32, 0.00_f32],
|
||||
[0.75_f32, 0.75_f32, 0.00_f32],
|
||||
[0.25_f32, 1.00_f32, 0.00_f32],
|
||||
[0.00_f32, 0.25_f32, 1.00_f32],
|
||||
[0.25_f32, 0.00_f32, 1.00_f32]
|
||||
].into_iter();
|
||||
|
||||
// create the spheres
|
||||
let spheres = [
|
||||
Sphere::new(
|
||||
"outer".to_string(),
|
||||
"Outer".to_string(),
|
||||
[0.5_f32, 0.5_f32, 0.5_f32],
|
||||
engine::sphere(0.0, 0.0, 0.0, 1.5)
|
||||
),
|
||||
Sphere::new(
|
||||
"sun".to_string(),
|
||||
"Sun".to_string(),
|
||||
[0.75_f32, 0.75_f32, 0.75_f32],
|
||||
engine::sphere(0.0, -0.75, 0.0, 0.75)
|
||||
),
|
||||
Sphere::new(
|
||||
"moon".to_string(),
|
||||
"Moon".to_string(),
|
||||
[0.25_f32, 0.25_f32, 0.25_f32],
|
||||
engine::sphere(0.0, 0.75, 0.0, 0.75)
|
||||
),
|
||||
].into_iter().chain(
|
||||
index_range.clone().zip(colors).map(
|
||||
|(k, color)| {
|
||||
let ang = (k as f64) * PI/3.0;
|
||||
Sphere::new(
|
||||
format!("chain{k}"),
|
||||
format!("Chain {k}"),
|
||||
color,
|
||||
engine::sphere(1.0 * ang.sin(), 0.0, 1.0 * ang.cos(), 0.5)
|
||||
)
|
||||
}
|
||||
)
|
||||
);
|
||||
for sphere in spheres {
|
||||
let _ = assembly.try_insert_element(sphere);
|
||||
}
|
||||
|
||||
// put the outer sphere in ghost mode and fix its curvature
|
||||
let outer = assembly.elements_by_id.with_untracked(
|
||||
|elts_by_id| elts_by_id["outer"].clone()
|
||||
);
|
||||
outer.ghost().set(true);
|
||||
let outer_curvature_regulator = outer.regulators().with_untracked(
|
||||
|regs| regs.first().unwrap().clone()
|
||||
);
|
||||
outer_curvature_regulator.set_point().set(
|
||||
SpecifiedValue::try_from((1.0 / 3.0).to_string()).unwrap()
|
||||
);
|
||||
|
||||
// impose the desired tangencies
|
||||
let [outer, sun, moon] = ["outer", "sun", "moon"].map(
|
||||
|id| assembly.elements_by_id.with_untracked(
|
||||
|elts_by_id| elts_by_id[id].clone()
|
||||
)
|
||||
);
|
||||
let chain = index_range.map(
|
||||
|k| assembly.elements_by_id.with_untracked(
|
||||
|elts_by_id| elts_by_id[&format!("chain{k}")].clone()
|
||||
)
|
||||
);
|
||||
for (chain_sphere, chain_sphere_next) in chain.clone().zip(chain.cycle().skip(1)) {
|
||||
for (other_sphere, inversive_distance) in [
|
||||
(outer.clone(), "1"),
|
||||
(sun.clone(), "-1"),
|
||||
(moon.clone(), "-1"),
|
||||
(chain_sphere_next.clone(), "-1")
|
||||
] {
|
||||
let tangency = InversiveDistanceRegulator::new([chain_sphere.clone(), other_sphere]);
|
||||
tangency.set_point.set(SpecifiedValue::try_from(inversive_distance.to_string()).unwrap());
|
||||
assembly.insert_regulator(Rc::new(tangency));
|
||||
}
|
||||
}
|
||||
|
||||
let outer_sun_tangency = InversiveDistanceRegulator::new([outer.clone(), sun]);
|
||||
outer_sun_tangency.set_point.set(SpecifiedValue::try_from("1".to_string()).unwrap());
|
||||
assembly.insert_regulator(Rc::new(outer_sun_tangency));
|
||||
|
||||
let outer_moon_tangency = InversiveDistanceRegulator::new([outer.clone(), moon]);
|
||||
outer_moon_tangency.set_point.set(SpecifiedValue::try_from("1".to_string()).unwrap());
|
||||
assembly.insert_regulator(Rc::new(outer_moon_tangency));
|
||||
}
|
||||
|
||||
// --- chooser ---
|
||||
|
||||
/* DEBUG */
|
||||
#[component]
|
||||
pub fn TestAssemblyChooser() -> View {
|
||||
// create an effect that loads the selected test assembly
|
||||
let assembly_name = create_signal("general".to_string());
|
||||
create_effect(move || {
|
||||
// get name of chosen assembly
|
||||
let name = assembly_name.get_clone();
|
||||
console::log_1(
|
||||
&JsValue::from(format!("Showing assembly \"{}\"", name.clone()))
|
||||
);
|
||||
|
||||
batch(|| {
|
||||
let state = use_context::<AppState>();
|
||||
let assembly = &state.assembly;
|
||||
|
||||
// pause realization
|
||||
assembly.keep_realized.set(false);
|
||||
|
||||
// clear state
|
||||
assembly.regulators.update(|regs| regs.clear());
|
||||
assembly.elements.update(|elts| elts.clear());
|
||||
assembly.elements_by_id.update(|elts_by_id| elts_by_id.clear());
|
||||
assembly.descent_history.set(DescentHistory::new());
|
||||
state.selection.update(|sel| sel.clear());
|
||||
|
||||
// load assembly
|
||||
match name.as_str() {
|
||||
"general" => load_gen_assemb(assembly),
|
||||
"low-curv" => load_low_curv_assemb(assembly),
|
||||
"pointed" => load_pointed_assemb(assembly),
|
||||
"tridim-icosahedron" => load_tridim_icosahedron_assemb(assembly),
|
||||
"dodeca-packing" => load_dodeca_packing_assemb(assembly),
|
||||
"balanced" => load_balanced_assemb(assembly),
|
||||
"off-center" => load_off_center_assemb(assembly),
|
||||
"radius-ratio" => load_radius_ratio_assemb(assembly),
|
||||
"irisawa-hexlet" => load_irisawa_hexlet_assemb(assembly),
|
||||
_ => ()
|
||||
};
|
||||
|
||||
// resume realization
|
||||
assembly.keep_realized.set(true);
|
||||
});
|
||||
});
|
||||
|
||||
// build the chooser
|
||||
view! {
|
||||
select(bind:value=assembly_name) {
|
||||
option(value="general") { "General" }
|
||||
option(value="low-curv") { "Low-curvature" }
|
||||
option(value="pointed") { "Pointed" }
|
||||
option(value="tridim-icosahedron") { "Tridiminished icosahedron" }
|
||||
option(value="dodeca-packing") { "Dodecahedral packing" }
|
||||
option(value="balanced") { "Balanced" }
|
||||
option(value="off-center") { "Off-center" }
|
||||
option(value="radius-ratio") { "Radius ratio" }
|
||||
option(value="irisawa-hexlet") { "Irisawa hexlet" }
|
||||
option(value="empty") { "Empty" }
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,9 +1,6 @@
|
|||
mod add_remove;
|
||||
mod assembly;
|
||||
mod diagnostics;
|
||||
mod display;
|
||||
mod components;
|
||||
mod engine;
|
||||
mod outline;
|
||||
mod specified;
|
||||
|
||||
#[cfg(test)]
|
||||
|
@ -12,11 +9,13 @@ mod tests;
|
|||
use std::{collections::BTreeSet, rc::Rc};
|
||||
use sycamore::prelude::*;
|
||||
|
||||
use add_remove::AddRemove;
|
||||
use assembly::{Assembly, Element};
|
||||
use diagnostics::Diagnostics;
|
||||
use display::Display;
|
||||
use outline::Outline;
|
||||
use components::{
|
||||
add_remove::AddRemove,
|
||||
diagnostics::Diagnostics,
|
||||
display::Display,
|
||||
outline::Outline
|
||||
};
|
||||
|
||||
#[derive(Clone)]
|
||||
struct AppState {
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue