2025-07-22 22:01:37 +00:00
|
|
|
use itertools::izip;
|
2025-09-22 04:20:49 -07:00
|
|
|
use std::{f64::consts::{FRAC_1_SQRT_2, PI, SQRT_2}, rc::Rc};
|
2025-07-22 22:01:37 +00:00
|
|
|
use nalgebra::Vector3;
|
|
|
|
use sycamore::prelude::*;
|
|
|
|
use web_sys::{console, wasm_bindgen::JsValue};
|
|
|
|
|
|
|
|
use crate::{
|
|
|
|
AppState,
|
|
|
|
assembly::{
|
|
|
|
Assembly,
|
|
|
|
Element,
|
|
|
|
ElementColor,
|
|
|
|
InversiveDistanceRegulator,
|
|
|
|
Point,
|
2025-09-22 04:20:49 -07:00
|
|
|
PointCoordinateRegulator,
|
|
|
|
Regulator,
|
2025-08-04 23:34:33 +00:00
|
|
|
Sphere,
|
2025-07-22 22:01:37 +00:00
|
|
|
},
|
2025-08-04 23:34:33 +00:00
|
|
|
engine,
|
2025-09-21 21:47:54 -07:00
|
|
|
engine::{DescentHistory, point},
|
2025-08-04 23:34:33 +00:00
|
|
|
specified::SpecifiedValue,
|
2025-07-22 22:01:37 +00:00
|
|
|
};
|
|
|
|
|
2025-09-21 22:30:38 -07:00
|
|
|
// Convenience: macro to allow elision of const array lengths
|
|
|
|
// adapted from https://stackoverflow.com/a/59905715
|
|
|
|
macro_rules! const_array {
|
|
|
|
($name: ident: $ty: ty = $value: expr) => {
|
|
|
|
const $name: [$ty; $value.len()] = $value;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2025-09-21 21:47:54 -07:00
|
|
|
// Convenience: use sqrt() as a function to get the square root of
|
|
|
|
// anything that can be losslessly converted to f64, since we happen
|
|
|
|
// to always want our sqrts to be f64.
|
|
|
|
// RUST KVETCHES: (I) Why is this so convoluted?
|
|
|
|
// In particular, what would be so bad about allowing
|
|
|
|
// const fn sqrt<T: Into<f64>>(x: T) -> f64 { (x as f64).sqrt() }
|
|
|
|
// ???
|
|
|
|
// (II) Oh dear, sqrt is not computable in a const context, so we have to
|
|
|
|
// roll our own. I hope I get it right! I definitely don't know how to ensure
|
|
|
|
// that the final double is correctly rounded :-(
|
|
|
|
|
|
|
|
const SIXTH: f64 = 1./6.;
|
|
|
|
const FOURTH: f64 = 1./4.;
|
|
|
|
const fn invsqrt(xin: f64) -> f64 {
|
|
|
|
if !xin.is_finite() { return f64::NAN; }
|
|
|
|
let mut log4 = 0;
|
|
|
|
let mut x = xin;
|
|
|
|
while x < 1. { x *= 4.; log4 -= 1; }
|
|
|
|
while x > 4. { x *= FOURTH; log4 += 1; }
|
|
|
|
let mut next = 1.1 - SIXTH * x;
|
|
|
|
let mut diff = 1.;
|
|
|
|
while diff > 4. * f64::EPSILON { // termination condition??
|
|
|
|
let last = next;
|
|
|
|
next = last * (1.5 - 0.5 * x * last * last);
|
|
|
|
diff = next - last;
|
|
|
|
if diff < 0. { diff *= -1.; }
|
|
|
|
}
|
|
|
|
while log4 > 0 { next *= 0.5; log4 -= 1; }
|
|
|
|
while log4 < 0 { next *= 2.; log4 += 1; }
|
|
|
|
return next;
|
|
|
|
}
|
|
|
|
|
|
|
|
#[const_trait]
|
|
|
|
trait ConstSqrt {fn sqr(self) -> f64;}
|
|
|
|
impl const ConstSqrt for f64 {fn sqr(self) -> f64 {self * invsqrt(self)}}
|
|
|
|
impl const ConstSqrt for i32 {fn sqr(self) -> f64 {(self as f64).sqr()}}
|
|
|
|
const fn sqrt<T: const ConstSqrt>(x: T) -> f64 {x.sqr()}
|
|
|
|
|
|
|
|
// RUST KVETCHES: (I) It is annoying that we must redundantly specify
|
|
|
|
// the type of the well-typed RHS to assign it to a const.
|
|
|
|
// See https://github.com/rust-lang/rfcs/pull/3546
|
|
|
|
// Can we fix this in husht? Seems like we will need to be able to do full
|
|
|
|
// rust type inference in the transpiler; how will we accomplish that?
|
|
|
|
// (2) It is very annoying that there doesn't seem to be any way to specify
|
|
|
|
// that we want an expression like `5.0 / 2` to be evaluated by converting
|
|
|
|
// the 2 to a float, because of the "orphan rule". Can we fix this in husht?
|
|
|
|
// Again, it seems we would need full rust type inference.
|
|
|
|
|
|
|
|
// FIXME: replace with std::f64::consts::PHI when that gets stabilized
|
|
|
|
const PHI: f64 = (1. + sqrt(5)) / 2.;
|
|
|
|
|
2025-09-22 04:20:49 -07:00
|
|
|
const fn gray(level: f32) -> ElementColor { [level, level, level] }
|
|
|
|
const GRAY: ElementColor = gray(0.75);
|
|
|
|
const RED: ElementColor = [1., 0., 0.25];
|
|
|
|
const GREEN: ElementColor = [0.25, 1., 0.];
|
|
|
|
const BLUE: ElementColor = [0., 0.25, 1.];
|
|
|
|
|
2025-07-22 22:01:37 +00:00
|
|
|
// --- 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
|
|
|
|
|
2025-08-07 23:24:07 +00:00
|
|
|
fn load_general(assembly: &Assembly) {
|
2025-07-22 22:01:37 +00:00
|
|
|
let _ = assembly.try_insert_element(
|
|
|
|
Sphere::new(
|
2025-09-22 04:20:49 -07:00
|
|
|
"gemini_a", "Castor", [1.00, 0.25, 0.00],
|
2025-08-04 23:34:33 +00:00
|
|
|
engine::sphere(0.5, 0.5, 0.0, 1.0),
|
2025-07-22 22:01:37 +00:00
|
|
|
)
|
|
|
|
);
|
|
|
|
let _ = assembly.try_insert_element(
|
|
|
|
Sphere::new(
|
2025-09-22 04:20:49 -07:00
|
|
|
"gemini_b", "Pollux", BLUE,
|
2025-08-04 23:34:33 +00:00
|
|
|
engine::sphere(-0.5, -0.5, 0.0, 1.0),
|
2025-07-22 22:01:37 +00:00
|
|
|
)
|
|
|
|
);
|
|
|
|
let _ = assembly.try_insert_element(
|
|
|
|
Sphere::new(
|
2025-09-22 04:20:49 -07:00
|
|
|
"ursa_major", "Ursa major", [0.25, 0.00, 1.00],
|
2025-08-04 23:34:33 +00:00
|
|
|
engine::sphere(-0.5, 0.5, 0.0, 0.75),
|
2025-07-22 22:01:37 +00:00
|
|
|
)
|
|
|
|
);
|
|
|
|
let _ = assembly.try_insert_element(
|
|
|
|
Sphere::new(
|
2025-09-22 04:20:49 -07:00
|
|
|
"ursa_minor", "Ursa minor", GREEN,
|
2025-08-04 23:34:33 +00:00
|
|
|
engine::sphere(0.5, -0.5, 0.0, 0.5),
|
2025-07-22 22:01:37 +00:00
|
|
|
)
|
|
|
|
);
|
|
|
|
let _ = assembly.try_insert_element(
|
|
|
|
Sphere::new(
|
2025-09-22 04:20:49 -07:00
|
|
|
"moon_deimos", "Deimos", [0.75, 0.75, 0.00],
|
2025-08-04 23:34:33 +00:00
|
|
|
engine::sphere(0.0, 0.15, 1.0, 0.25),
|
2025-07-22 22:01:37 +00:00
|
|
|
)
|
|
|
|
);
|
|
|
|
let _ = assembly.try_insert_element(
|
|
|
|
Sphere::new(
|
2025-09-22 04:20:49 -07:00
|
|
|
"moon_phobos", "Phobos", [0.00, 0.75, 0.50],
|
2025-08-04 23:34:33 +00:00
|
|
|
engine::sphere(0.0, -0.15, -1.0, 0.25),
|
2025-07-22 22:01:37 +00:00
|
|
|
)
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
2025-08-07 23:24:07 +00:00
|
|
|
fn load_low_curvature(assembly: &Assembly) {
|
2025-07-22 22:01:37 +00:00
|
|
|
// create the spheres
|
2025-09-21 21:47:54 -07:00
|
|
|
const A: f64 = sqrt(0.75);
|
2025-07-22 22:01:37 +00:00
|
|
|
let _ = assembly.try_insert_element(
|
|
|
|
Sphere::new(
|
2025-09-22 04:20:49 -07:00
|
|
|
"central", "Central", GRAY,
|
2025-08-04 23:34:33 +00:00
|
|
|
engine::sphere(0.0, 0.0, 0.0, 1.0),
|
2025-07-22 22:01:37 +00:00
|
|
|
)
|
|
|
|
);
|
|
|
|
let _ = assembly.try_insert_element(
|
|
|
|
Sphere::new(
|
2025-09-22 04:20:49 -07:00
|
|
|
"assemb_plane", "Assembly plane", GRAY,
|
2025-08-04 23:34:33 +00:00
|
|
|
engine::sphere_with_offset(0.0, 0.0, 1.0, 0.0, 0.0),
|
2025-07-22 22:01:37 +00:00
|
|
|
)
|
|
|
|
);
|
|
|
|
let _ = assembly.try_insert_element(
|
|
|
|
Sphere::new(
|
2025-09-22 04:20:49 -07:00
|
|
|
"side1", "Side 1", RED,
|
2025-08-04 23:34:33 +00:00
|
|
|
engine::sphere_with_offset(1.0, 0.0, 0.0, 1.0, 0.0),
|
2025-07-22 22:01:37 +00:00
|
|
|
)
|
|
|
|
);
|
|
|
|
let _ = assembly.try_insert_element(
|
|
|
|
Sphere::new(
|
2025-09-22 04:20:49 -07:00
|
|
|
"side2", "Side 2", GREEN,
|
2025-09-21 21:47:54 -07:00
|
|
|
engine::sphere_with_offset(-0.5, A, 0.0, 1.0, 0.0),
|
2025-07-22 22:01:37 +00:00
|
|
|
)
|
|
|
|
);
|
|
|
|
let _ = assembly.try_insert_element(
|
|
|
|
Sphere::new(
|
2025-09-22 04:20:49 -07:00
|
|
|
"side3", "Side 3", BLUE,
|
2025-09-21 21:47:54 -07:00
|
|
|
engine::sphere_with_offset(-0.5, -A, 0.0, 1.0, 0.0),
|
2025-07-22 22:01:37 +00:00
|
|
|
)
|
|
|
|
);
|
|
|
|
let _ = assembly.try_insert_element(
|
|
|
|
Sphere::new(
|
2025-09-22 04:20:49 -07:00
|
|
|
"corner1", "Corner 1", GRAY,
|
2025-08-04 23:34:33 +00:00
|
|
|
engine::sphere(-4.0/3.0, 0.0, 0.0, 1.0/3.0),
|
2025-07-22 22:01:37 +00:00
|
|
|
)
|
|
|
|
);
|
|
|
|
let _ = assembly.try_insert_element(
|
|
|
|
Sphere::new(
|
2025-09-22 04:20:49 -07:00
|
|
|
"corner2", "Corner 2", GRAY,
|
2025-09-21 21:47:54 -07:00
|
|
|
engine::sphere(2.0/3.0, -4.0/3.0 * A, 0.0, 1.0/3.0),
|
2025-07-22 22:01:37 +00:00
|
|
|
)
|
|
|
|
);
|
|
|
|
let _ = assembly.try_insert_element(
|
|
|
|
Sphere::new(
|
2025-09-22 04:20:49 -07:00
|
|
|
"corner3", "Corner 3", GRAY,
|
2025-09-21 21:47:54 -07:00
|
|
|
engine::sphere(2.0/3.0, 4.0/3.0 * A, 0.0, 1.0/3.0),
|
2025-07-22 22:01:37 +00:00
|
|
|
)
|
|
|
|
);
|
|
|
|
|
|
|
|
// impose the desired tangencies and make the sides planar
|
|
|
|
let index_range = 1..=3;
|
|
|
|
let [central, assemb_plane] = ["central", "assemb_plane"].map(
|
2025-09-22 04:20:49 -07:00
|
|
|
|id| assembly.find_element(id)
|
2025-07-22 22:01:37 +00:00
|
|
|
);
|
|
|
|
let sides = index_range.clone().map(
|
2025-09-22 04:20:49 -07:00
|
|
|
|k| assembly.find_element(&format!("side{k}"))
|
2025-07-22 22:01:37 +00:00
|
|
|
);
|
|
|
|
let corners = index_range.map(
|
2025-09-22 04:20:49 -07:00
|
|
|
|k| assembly.find_element(&format!("corner{k}"))
|
2025-07-22 22:01:37 +00:00
|
|
|
);
|
|
|
|
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));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2025-08-07 23:24:07 +00:00
|
|
|
fn load_pointed(assembly: &Assembly) {
|
2025-07-22 22:01:37 +00:00
|
|
|
let _ = assembly.try_insert_element(
|
|
|
|
Point::new(
|
2025-09-21 21:47:54 -07:00
|
|
|
"point_front",
|
|
|
|
"Front point",
|
2025-07-22 22:01:37 +00:00
|
|
|
[0.75_f32, 0.75_f32, 0.75_f32],
|
2025-08-04 23:34:33 +00:00
|
|
|
engine::point(0.0, 0.0, FRAC_1_SQRT_2),
|
2025-07-22 22:01:37 +00:00
|
|
|
)
|
|
|
|
);
|
|
|
|
let _ = assembly.try_insert_element(
|
|
|
|
Point::new(
|
2025-09-21 21:47:54 -07:00
|
|
|
"point_back",
|
|
|
|
"Back point",
|
2025-07-22 22:01:37 +00:00
|
|
|
[0.75_f32, 0.75_f32, 0.75_f32],
|
2025-08-04 23:34:33 +00:00
|
|
|
engine::point(0.0, 0.0, -FRAC_1_SQRT_2),
|
2025-07-22 22:01:37 +00:00
|
|
|
)
|
|
|
|
);
|
|
|
|
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(
|
2025-09-22 04:20:49 -07:00
|
|
|
&format!("sphere{index_x}{index_y}"),
|
|
|
|
&format!("Sphere {index_x}{index_y}"),
|
2025-07-22 22:01:37 +00:00
|
|
|
[0.5*(1.0 + x) as f32, 0.5*(1.0 + y) as f32, 0.5*(1.0 - x*y) as f32],
|
2025-08-04 23:34:33 +00:00
|
|
|
engine::sphere(x, y, 0.0, 1.0),
|
2025-07-22 22:01:37 +00:00
|
|
|
)
|
|
|
|
);
|
|
|
|
|
|
|
|
let _ = assembly.try_insert_element(
|
|
|
|
Point::new(
|
2025-09-21 21:47:54 -07:00
|
|
|
&format!("point{index_x}{index_y}"),
|
|
|
|
&format!("Point {index_x}{index_y}"),
|
2025-07-22 22:01:37 +00:00
|
|
|
[0.5*(1.0 + x) as f32, 0.5*(1.0 + y) as f32, 0.5*(1.0 - x*y) as f32],
|
2025-08-04 23:34:33 +00:00
|
|
|
engine::point(x, y, 0.0),
|
2025-07-22 22:01:37 +00:00
|
|
|
)
|
|
|
|
);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// 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
|
2025-08-07 23:24:07 +00:00
|
|
|
fn load_tridiminished_icosahedron(assembly: &Assembly) {
|
2025-07-22 22:01:37 +00:00
|
|
|
// create the vertices
|
2025-09-21 22:30:38 -07:00
|
|
|
const COLOR_A: ElementColor = [1.00, 0.25, 0.25];
|
|
|
|
const COLOR_B: ElementColor = [0.75, 0.75, 0.75];
|
|
|
|
const COLOR_C: ElementColor = [0.25, 0.50, 1.00];
|
2025-07-22 22:01:37 +00:00
|
|
|
let vertices = [
|
2025-09-21 21:47:54 -07:00
|
|
|
Point::new("a1", "A₁", COLOR_A, point( 0.25, 0.75, 0.75)),
|
|
|
|
Point::new("a2", "A₂", COLOR_A, point( 0.75, 0.25, 0.75)),
|
|
|
|
Point::new("a3", "A₃", COLOR_A, point( 0.75, 0.75, 0.25)),
|
|
|
|
Point::new("b1", "B₁", COLOR_B, point( 0.75, -0.25, -0.25)),
|
|
|
|
Point::new("b2", "B₂", COLOR_B, point(-0.25, 0.75, -0.25)),
|
|
|
|
Point::new("b3", "B₃", COLOR_B, point(-0.25, -0.25, 0.75)),
|
|
|
|
Point::new("c1", "C₁", COLOR_C, point( 0.0, -1.0, -1.0)),
|
|
|
|
Point::new("c2", "C₂", COLOR_C, point(-1.0, 0.0, -1.0)),
|
|
|
|
Point::new("c3", "C₃", COLOR_C, point(-1.0, -1.0, 0.0)),
|
2025-07-22 22:01:37 +00:00
|
|
|
];
|
|
|
|
for vertex in vertices {
|
|
|
|
let _ = assembly.try_insert_element(vertex);
|
|
|
|
}
|
|
|
|
|
|
|
|
// create the faces
|
2025-09-21 21:47:54 -07:00
|
|
|
const SQRT_1_6: f64 = invsqrt(6.);
|
|
|
|
const SQRT_2_3: f64 = 2. * SQRT_1_6;
|
2025-07-22 22:01:37 +00:00
|
|
|
let faces = [
|
|
|
|
Sphere::new(
|
2025-09-22 04:20:49 -07:00
|
|
|
"face1", "Face 1", GRAY,
|
|
|
|
engine::sphere_with_offset(
|
|
|
|
SQRT_2_3, -SQRT_1_6, -SQRT_1_6, -SQRT_1_6, 0.0),
|
2025-07-22 22:01:37 +00:00
|
|
|
),
|
|
|
|
Sphere::new(
|
2025-09-22 04:20:49 -07:00
|
|
|
"face2", "Face 2", GRAY,
|
|
|
|
engine::sphere_with_offset(
|
|
|
|
-SQRT_1_6, SQRT_2_3, -SQRT_1_6, -SQRT_1_6, 0.0),
|
2025-07-22 22:01:37 +00:00
|
|
|
),
|
|
|
|
Sphere::new(
|
2025-09-22 04:20:49 -07:00
|
|
|
"face3", "Face 3", GRAY,
|
|
|
|
engine::sphere_with_offset(
|
|
|
|
-SQRT_1_6, -SQRT_1_6, SQRT_2_3, -SQRT_1_6, 0.0),
|
2025-08-04 23:34:33 +00:00
|
|
|
),
|
2025-07-22 22:01:37 +00:00
|
|
|
];
|
|
|
|
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
|
2025-08-07 23:24:07 +00:00
|
|
|
fn load_dodecahedral_packing(assembly: &Assembly) {
|
2025-07-22 22:01:37 +00:00
|
|
|
// add the substrate
|
|
|
|
let _ = assembly.try_insert_element(
|
|
|
|
Sphere::new(
|
2025-09-22 04:20:49 -07:00
|
|
|
"substrate", "Substrate", GRAY,
|
2025-08-04 23:34:33 +00:00
|
|
|
engine::sphere(0.0, 0.0, 0.0, 1.0),
|
2025-07-22 22:01:37 +00:00
|
|
|
)
|
|
|
|
);
|
|
|
|
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];
|
2025-09-21 21:47:54 -07:00
|
|
|
const PHI_INV: f64 = 1.0 / PHI;
|
|
|
|
const COORD_SCALE: f64 = sqrt(PHI + 2.0);
|
2025-09-21 22:30:38 -07:00
|
|
|
const_array!(FACE_SCALES: f64 = [PHI_INV, (13.0 / 12.0) / COORD_SCALE]);
|
|
|
|
const_array!(FACE_RADII: f64 = [PHI_INV, 5.0 / 12.0]);
|
2025-07-22 22:01:37 +00:00
|
|
|
let mut faces = Vec::<Rc<dyn Element>>::new();
|
|
|
|
let subscripts = ["₀", "₁"];
|
|
|
|
for j in 0..2 {
|
|
|
|
for k in 0..2 {
|
2025-09-21 21:47:54 -07:00
|
|
|
let small_coord = FACE_SCALES[k] * if j > 0 {1.} else {-1.};
|
|
|
|
let big_coord = FACE_SCALES[k] * PHI * if k > 0 {1.} else {-1.};
|
2025-07-22 22:01:37 +00:00
|
|
|
|
|
|
|
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(
|
2025-09-22 04:20:49 -07:00
|
|
|
&id_a,
|
|
|
|
&format!("A{label_sub}"),
|
2025-07-22 22:01:37 +00:00
|
|
|
COLOR_A,
|
2025-09-21 21:47:54 -07:00
|
|
|
engine::sphere(0.0, small_coord, big_coord, FACE_RADII[k]),
|
2025-07-22 22:01:37 +00:00
|
|
|
)
|
|
|
|
);
|
|
|
|
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(
|
2025-09-22 04:20:49 -07:00
|
|
|
&id_b,
|
|
|
|
&format!("B{label_sub}"),
|
2025-07-22 22:01:37 +00:00
|
|
|
COLOR_B,
|
2025-09-21 21:47:54 -07:00
|
|
|
engine::sphere(small_coord, big_coord, 0.0, FACE_RADII[k]),
|
2025-07-22 22:01:37 +00:00
|
|
|
)
|
|
|
|
);
|
|
|
|
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(
|
2025-09-22 04:20:49 -07:00
|
|
|
&id_c,
|
|
|
|
&format!("C{label_sub}"),
|
2025-07-22 22:01:37 +00:00
|
|
|
COLOR_C,
|
2025-09-21 21:47:54 -07:00
|
|
|
engine::sphere(big_coord, 0.0, small_coord, FACE_RADII[k]),
|
2025-07-22 22:01:37 +00:00
|
|
|
)
|
|
|
|
);
|
|
|
|
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
|
2025-08-07 23:24:07 +00:00
|
|
|
fn load_balanced(assembly: &Assembly) {
|
2025-07-22 22:01:37 +00:00
|
|
|
// create the spheres
|
|
|
|
const R_OUTER: f64 = 10.0;
|
|
|
|
const R_INNER: f64 = 4.0;
|
|
|
|
let spheres = [
|
|
|
|
Sphere::new(
|
2025-09-22 04:20:49 -07:00
|
|
|
"outer","Outer", GRAY, engine::sphere(0.0, 0.0, 0.0, R_OUTER)),
|
|
|
|
Sphere::new("a", "A", RED, engine::sphere(0.0, 4.0, 0.0, R_INNER)),
|
|
|
|
Sphere::new("b", "B", BLUE, engine::sphere(0.0, -4.0, 0.0, R_INNER)),
|
2025-07-22 22:01:37 +00:00
|
|
|
];
|
|
|
|
for sphere in spheres {
|
|
|
|
let _ = assembly.try_insert_element(sphere);
|
|
|
|
}
|
|
|
|
|
|
|
|
// get references to the spheres
|
|
|
|
let [outer, a, b] = ["outer", "a", "b"].map(
|
2025-09-22 04:20:49 -07:00
|
|
|
|id| assembly.find_element(id)
|
2025-07-22 22:01:37 +00:00
|
|
|
);
|
|
|
|
|
|
|
|
// fix the diameters of the outer, sun, and moon spheres
|
|
|
|
for (sphere, radius) in [
|
|
|
|
(outer.clone(), R_OUTER),
|
|
|
|
(a.clone(), R_INNER),
|
2025-08-04 23:34:33 +00:00
|
|
|
(b.clone(), R_INNER),
|
2025-07-22 22:01:37 +00:00
|
|
|
] {
|
|
|
|
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
|
2025-08-07 23:24:07 +00:00
|
|
|
fn load_off_center(assembly: &Assembly) {
|
2025-07-22 22:01:37 +00:00
|
|
|
// create a point almost at the origin and a sphere centered on the origin
|
|
|
|
let _ = assembly.try_insert_element(
|
2025-09-22 04:20:49 -07:00
|
|
|
Point::new("point", "Point", GRAY, point(1e-9, 0.0, 0.0)),
|
2025-07-22 22:01:37 +00:00
|
|
|
);
|
|
|
|
let _ = assembly.try_insert_element(
|
|
|
|
Sphere::new(
|
2025-09-22 04:20:49 -07:00
|
|
|
"sphere", "Sphere", GRAY, engine::sphere(0.0, 0.0, 0.0, 1.0)),
|
2025-07-22 22:01:37 +00:00
|
|
|
);
|
2025-09-22 04:20:49 -07:00
|
|
|
|
2025-07-22 22:01:37 +00:00
|
|
|
|
|
|
|
// get references to the elements
|
|
|
|
let point_and_sphere = ["point", "sphere"].map(
|
2025-09-22 04:20:49 -07:00
|
|
|
|id| assembly.find_element(id)
|
2025-07-22 22:01:37 +00:00
|
|
|
);
|
|
|
|
|
|
|
|
// put the point on the sphere
|
|
|
|
let incidence = InversiveDistanceRegulator::new(point_and_sphere);
|
2025-09-22 04:20:49 -07:00
|
|
|
incidence.set_to(0.);
|
2025-07-22 22:01:37 +00:00
|
|
|
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
|
2025-08-07 23:24:07 +00:00
|
|
|
fn load_radius_ratio(assembly: &Assembly) {
|
2025-07-22 22:01:37 +00:00
|
|
|
let index_range = 1..=4;
|
|
|
|
|
|
|
|
// create the spheres
|
|
|
|
let spheres = [
|
|
|
|
Sphere::new(
|
2025-09-22 04:20:49 -07:00
|
|
|
"sphere_faces", "Insphere", GRAY,
|
2025-08-04 23:34:33 +00:00
|
|
|
engine::sphere(0.0, 0.0, 0.0, 0.5),
|
2025-07-22 22:01:37 +00:00
|
|
|
),
|
|
|
|
Sphere::new(
|
2025-09-22 04:20:49 -07:00
|
|
|
"sphere_vertices", "Circumsphere", GRAY,
|
2025-08-04 23:34:33 +00:00
|
|
|
engine::sphere(0.0, 0.0, 0.0, 0.25),
|
|
|
|
),
|
2025-07-22 22:01:37 +00:00
|
|
|
];
|
|
|
|
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],
|
2025-08-04 23:34:33 +00:00
|
|
|
[0.75_f32, 0.50_f32, 1.00_f32],
|
2025-07-22 22:01:37 +00:00
|
|
|
].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),
|
2025-08-04 23:34:33 +00:00
|
|
|
engine::point(0.6, 0.8, -0.6),
|
2025-07-22 22:01:37 +00:00
|
|
|
].into_iter()
|
|
|
|
).map(
|
|
|
|
|(k, color, representation)| {
|
|
|
|
Point::new(
|
2025-09-21 21:47:54 -07:00
|
|
|
&format!("v{k}"),
|
|
|
|
&format!("Vertex {k}"),
|
2025-07-22 22:01:37 +00:00
|
|
|
color,
|
2025-08-04 23:34:33 +00:00
|
|
|
representation,
|
2025-07-22 22:01:37 +00:00
|
|
|
)
|
|
|
|
}
|
|
|
|
);
|
|
|
|
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],
|
2025-08-04 23:34:33 +00:00
|
|
|
[0.25_f32, 0.00_f32, 1.00_f32],
|
2025-07-22 22:01:37 +00:00
|
|
|
].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),
|
2025-08-04 23:34:33 +00:00
|
|
|
engine::sphere_with_offset(-base_dir[0], -base_dir[1], base_dir[2], offset, 0.0),
|
2025-07-22 22:01:37 +00:00
|
|
|
].into_iter()
|
|
|
|
).map(
|
|
|
|
|(k, color, representation)| {
|
|
|
|
Sphere::new(
|
2025-09-22 04:20:49 -07:00
|
|
|
&format!("f{k}"),
|
|
|
|
&format!("Face {k}"),
|
2025-07-22 22:01:37 +00:00
|
|
|
color,
|
2025-08-04 23:34:33 +00:00
|
|
|
representation,
|
2025-07-22 22:01:37 +00:00
|
|
|
)
|
|
|
|
}
|
|
|
|
);
|
|
|
|
for face in faces {
|
|
|
|
face.ghost().set(true);
|
|
|
|
let _ = assembly.try_insert_element(face);
|
|
|
|
}
|
|
|
|
|
|
|
|
// impose the constraints
|
|
|
|
for j in index_range.clone() {
|
2025-09-22 04:20:49 -07:00
|
|
|
let [face_j, vertex_j] = [format!("f{j}"),format!("v{j}")]
|
|
|
|
.map(|id| assembly.find_element(&id));
|
|
|
|
|
2025-07-22 22:01:37 +00:00
|
|
|
|
|
|
|
// make the faces planar
|
|
|
|
let curvature_regulator = face_j.regulators().with_untracked(
|
|
|
|
|regs| regs.first().unwrap().clone()
|
|
|
|
);
|
2025-09-22 04:20:49 -07:00
|
|
|
curvature_regulator.set_to(0.);
|
2025-07-22 22:01:37 +00:00
|
|
|
|
|
|
|
for k in index_range.clone().filter(|&index| index != j) {
|
2025-09-22 04:20:49 -07:00
|
|
|
let vertex_k = assembly.find_element(&format!("v{k}"));
|
2025-07-22 22:01:37 +00:00
|
|
|
|
|
|
|
// 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()]);
|
2025-09-22 04:20:49 -07:00
|
|
|
incidence_regulator.set_to(0.);
|
2025-07-22 22:01:37 +00:00
|
|
|
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
|
2025-08-07 23:24:07 +00:00
|
|
|
fn load_irisawa_hexlet(assembly: &Assembly) {
|
2025-07-22 22:01:37 +00:00
|
|
|
let index_range = 1..=6;
|
|
|
|
let colors = [
|
2025-09-22 04:20:49 -07:00
|
|
|
[1.00, 0.00, 0.25],
|
|
|
|
[1.00, 0.25, 0.00],
|
|
|
|
[0.75, 0.75, 0.00],
|
|
|
|
[0.25, 1.00, 0.00],
|
|
|
|
[0.00, 0.25, 1.00],
|
|
|
|
[0.25, 0.00, 1.00],
|
2025-07-22 22:01:37 +00:00
|
|
|
].into_iter();
|
|
|
|
|
|
|
|
// create the spheres
|
|
|
|
let spheres = [
|
|
|
|
Sphere::new(
|
2025-09-22 04:20:49 -07:00
|
|
|
"outer", "Outer", gray(0.5),
|
2025-08-04 23:34:33 +00:00
|
|
|
engine::sphere(0.0, 0.0, 0.0, 1.5),
|
2025-07-22 22:01:37 +00:00
|
|
|
),
|
|
|
|
Sphere::new(
|
2025-09-22 04:20:49 -07:00
|
|
|
"sun", "Sun", GRAY,
|
2025-08-04 23:34:33 +00:00
|
|
|
engine::sphere(0.0, -0.75, 0.0, 0.75),
|
2025-07-22 22:01:37 +00:00
|
|
|
),
|
|
|
|
Sphere::new(
|
2025-09-22 04:20:49 -07:00
|
|
|
"moon", "Moon", gray(0.25),
|
2025-08-04 23:34:33 +00:00
|
|
|
engine::sphere(0.0, 0.75, 0.0, 0.75),
|
2025-07-22 22:01:37 +00:00
|
|
|
),
|
|
|
|
].into_iter().chain(
|
|
|
|
index_range.clone().zip(colors).map(
|
|
|
|
|(k, color)| {
|
|
|
|
let ang = (k as f64) * PI/3.0;
|
|
|
|
Sphere::new(
|
2025-09-22 04:20:49 -07:00
|
|
|
&format!("chain{k}"),
|
|
|
|
&format!("Chain {k}"),
|
2025-07-22 22:01:37 +00:00
|
|
|
color,
|
2025-08-04 23:34:33 +00:00
|
|
|
engine::sphere(1.0 * ang.sin(), 0.0, 1.0 * ang.cos(), 0.5),
|
2025-07-22 22:01:37 +00:00
|
|
|
)
|
|
|
|
}
|
|
|
|
)
|
|
|
|
);
|
|
|
|
for sphere in spheres {
|
|
|
|
let _ = assembly.try_insert_element(sphere);
|
|
|
|
}
|
|
|
|
|
|
|
|
// put the outer sphere in ghost mode and fix its curvature
|
2025-09-22 04:20:49 -07:00
|
|
|
let outer = assembly.find_element("outer");
|
2025-07-22 22:01:37 +00:00
|
|
|
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
|
2025-09-22 04:20:49 -07:00
|
|
|
let [sun, moon] = ["sun", "moon"].map(|id| assembly.find_element(id));
|
2025-07-22 22:01:37 +00:00
|
|
|
let chain = index_range.map(
|
2025-09-22 04:20:49 -07:00
|
|
|
|k| assembly.find_element(&format!("chain{k}"))
|
2025-07-22 22:01:37 +00:00
|
|
|
);
|
|
|
|
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"),
|
2025-08-04 23:34:33 +00:00
|
|
|
(chain_sphere_next.clone(), "-1"),
|
2025-07-22 22:01:37 +00:00
|
|
|
] {
|
|
|
|
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));
|
|
|
|
}
|
|
|
|
|
2025-09-21 21:47:54 -07:00
|
|
|
const HPHI: f64 = PHI / 2.; // "half phi"
|
|
|
|
const RTHPHI: f64 = sqrt(HPHI); // "root half phi"
|
|
|
|
const RTPHIPH: f64 = sqrt(PHI + 0.5); // "root phi plus (half)"
|
|
|
|
const P: bool = true; // Pinned
|
|
|
|
const F: bool = false; // Free
|
|
|
|
|
|
|
|
// Initial data for the vertices commmon to 554aug2 and 554domed.
|
|
|
|
// Used the Vectornaut near_miss branch final positions for 554aug2,
|
|
|
|
// to the 3 decimal places currently shown.
|
2025-09-22 04:20:49 -07:00
|
|
|
const_array!(ACRON554_COMMON: (&str, [f64; 3], bool, usize, &str) = [
|
2025-09-21 21:47:54 -07:00
|
|
|
// id, coordinates, Pin/Free, level, earlier neighbor IDs.
|
2025-09-22 04:20:49 -07:00
|
|
|
("A_NE", [ 0.5, 0.5, 0.], P, 0, ""),
|
|
|
|
("A_NW", [-0.5, 0.5, 0.], P, 0, ""),
|
|
|
|
("A_SE", [ 0.5, -0.5, 0.], P, 0, ""),
|
|
|
|
("A_SW", [-0.5, -0.5, 0.], P, 0, ""),
|
|
|
|
("Z_E", [ 0.229, -0.002, 0.821], F, 1, "A_NE,A_SE"),
|
|
|
|
("Z_S", [ 0.002, -0.229, 0.821], F, 1, "A_SE,A_SW"),
|
|
|
|
("B_NE", [ HPHI, HPHI, RTHPHI], P, 2, "Z_E"),
|
|
|
|
("B_NW", [-HPHI, HPHI, RTHPHI], P, 2, ""),
|
|
|
|
("B_SW", [-HPHI, -HPHI, RTHPHI], P, 2, "Z_S"),
|
|
|
|
("B_SE", [ 0.812, -0.812, 0.89], F, 2, "Z_E,Z_S"),
|
|
|
|
("Y_NE", [ 0.11, 0.103, 1.019], F, 3, "B_NE"),
|
|
|
|
("Y_NW", [-0.103, 0.103, 1.02], F, 3, "B_NW"),
|
|
|
|
("Y_SE", [ 0.11, -0.11, 1.017], F, 3, "B_SE"),
|
|
|
|
("Y_SW", [-0.103, -0.11, 1.019], F, 3, "B_SW"),
|
|
|
|
("C_N", [ 0., 1., RTPHIPH], P, 4, "Y_NE,Y_NW"),
|
|
|
|
("C_W", [-1., 0., RTPHIPH], P, 4, "Y_NW,Y_SW"),
|
|
|
|
("C_E", [ 1.006, -0.006, 1.45], F, 4, "Y_NE,Y_SE"),
|
|
|
|
("C_S", [ 0.006, -1.006, 1.45], F, 4, "Y_SE,Y_SW"),
|
|
|
|
("D_NE", [ 0.2, 0.181, 2.011], F, 5, "Y_NE,C_N,C_E"),
|
|
|
|
("D_NW", [-0.181, 0.181, 2.014], F, 5, "Y_NW,C_N,C_W"),
|
|
|
|
("D_SE", [ 0.2, -0.2, 2.009], F, 5, "Y_SE,C_E,C_S"),
|
|
|
|
("D_SW", [-0.181, -0.2, 2.011], F, 5, "Y_SW,C_W,C_S"),
|
|
|
|
("E_N", [ 0.012, 1.055, 2.46], F, 6, "C_N,D_NE,D_NW"),
|
|
|
|
("E_W", [-1.055, -0.012, 2.46], F, 6, "C_W,D_NW,D_SW"),
|
|
|
|
("E_E", [ 1.079, -0.012, 2.447], F, 6, "C_E,D_NE,D_SE"),
|
|
|
|
("E_S", [ 0.012, -1.079, 2.447], F, 6, "C_S,D_SE,D_SW"),
|
|
|
|
("F_NE", [ 0.296, 0.265, 3.003], F, 7, "D_NE,E_N,E_E"),
|
|
|
|
("F_NW", [-0.265, 0.265, 3.007], F, 7, "D_NW,E_N,E_W"),
|
|
|
|
("F_SE", [ 0.296, -0.296, 3.0], F, 7, "D_SE,E_E,E_S"),
|
|
|
|
("F_SW", [-0.265, -0.296, 3.003], F, 7, "D_SW,E_W,E_S"),
|
|
|
|
// The following must be in order around the octagon (in some orientation)
|
|
|
|
("G_1", [ 0.517, 1.19, 3.312], F, 8, "E_N,F_NE"),
|
|
|
|
("G_2", [ 1.224, 0.483, 3.304], F, 8, "E_E,F_NE"),
|
|
|
|
("G_4", [ 1.224, -0.517, 3.298], F, 8, "E_E,F_SE"),
|
|
|
|
("G_5", [ 0.517, -1.224, 3.298], F, 8, "E_S,F_SE"),
|
|
|
|
("G_7", [-0.483, -1.224, 3.304], F, 8, "E_S,F_SW"),
|
|
|
|
("G_8", [-1.19, -0.517, 3.312], F, 8, "E_W,F_SW"),
|
|
|
|
("G_10", [-1.19, 0.483, 3.318], F, 8, "E_W,F_NW"),
|
|
|
|
("G_11", [-0.483, 1.19, 3.318], F, 8, "E_N,F_NW"),
|
2025-09-21 22:30:38 -07:00
|
|
|
]);
|
2025-09-21 21:47:54 -07:00
|
|
|
|
2025-09-21 22:30:38 -07:00
|
|
|
const_array!(LEVEL_COLORS: ElementColor = [
|
2025-09-21 21:47:54 -07:00
|
|
|
[0.75, 0., 0.75],
|
|
|
|
[1., 0.4, 0.6],
|
|
|
|
[1., 0., 0.25],
|
|
|
|
[1., 0.75, 0.25],
|
|
|
|
[0.75, 0.5, 0.],
|
|
|
|
[0.9, 0.9, 0.],
|
|
|
|
[0.25, 0.75, 0.],
|
|
|
|
[0., 0.5, 0.75],
|
|
|
|
[0.25, 0., 1.],
|
2025-09-21 22:30:38 -07:00
|
|
|
]);
|
2025-09-21 21:47:54 -07:00
|
|
|
|
|
|
|
fn load_554aug2(assembly: &Assembly) {
|
2025-09-22 04:20:49 -07:00
|
|
|
// first a plane for the octagon
|
|
|
|
let oct_id = "f_g";
|
|
|
|
let engsph = engine::sphere_with_offset(
|
|
|
|
0., 0., 1., ACRON554_COMMON[37].1[2], 0.);
|
|
|
|
let octaface = Sphere::new(oct_id, "Octagon", [0.7, 0.7, 0.7], engsph);
|
|
|
|
octaface.ghost().set(true);
|
|
|
|
assembly.try_insert_element(octaface);
|
|
|
|
let face_rc = assembly.find_element(oct_id);
|
|
|
|
let face_curvature = face_rc.regulators().with_untracked(
|
|
|
|
|regs| regs.first().unwrap().clone()
|
|
|
|
);
|
|
|
|
face_curvature.set_to(0.);
|
|
|
|
// Octagon vertices and side/diagonal lengths
|
|
|
|
let mut oct_verts = Vec::<Rc<dyn Element>>::new();
|
|
|
|
const OCT_LONG: usize = 4;
|
|
|
|
const OCT_N_DIAG: usize = 5;
|
|
|
|
const OCT_N: usize = 8;
|
|
|
|
const OCT_DIST: [f64; OCT_N_DIAG] = [0., // dummy at start
|
|
|
|
-0.5,
|
|
|
|
-0.5*(2. + SQRT_2),
|
|
|
|
-0.5*(3. + 2.*SQRT_2),
|
|
|
|
-0.5*(4. + 2.*SQRT_2)
|
|
|
|
];
|
|
|
|
// Now process the acron data
|
|
|
|
for (id, v, pinned, l, _neighbors) in ACRON554_COMMON {
|
|
|
|
let pt = Point::new(id, id, LEVEL_COLORS[l], point(v[0], v[1], v[2]));
|
2025-09-21 21:47:54 -07:00
|
|
|
assembly.try_insert_element(pt);
|
2025-09-22 04:20:49 -07:00
|
|
|
if pinned { // regulate each coordinate to its given value
|
|
|
|
let mut freeze = Vec::<Rc<dyn Regulator>>::new();
|
|
|
|
// QUESTION: Would there be a way to insert an Rc<Element> into
|
|
|
|
// an assembly to avoid the need to re-lookup pt in the assembly
|
|
|
|
// after just inserting it?
|
|
|
|
let pt_rc = assembly.elements_by_id.with_untracked(
|
|
|
|
|elts| elts[id].clone()
|
|
|
|
);
|
|
|
|
// filter the three coordinate regulators into freeze
|
|
|
|
pt_rc.regulators().with_untracked( |regs| {
|
|
|
|
for reg in regs {
|
|
|
|
if let Some(_pcr) = reg
|
|
|
|
.as_any().downcast_ref::<PointCoordinateRegulator>() {
|
|
|
|
freeze.push(reg.clone())
|
|
|
|
}
|
|
|
|
}
|
|
|
|
});
|
|
|
|
// now set them to their originally specified values.
|
|
|
|
let mut coord: usize = 0;
|
|
|
|
for reg in freeze {
|
|
|
|
reg.set_to(v[coord]);
|
|
|
|
coord += 1;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
// If part of the octagon, make incident to the plane:
|
|
|
|
if l == 8 {
|
|
|
|
let pt_rc = assembly.find_element(id);
|
|
|
|
let oct_index = oct_verts.len();
|
|
|
|
oct_verts.push(pt_rc.clone());
|
|
|
|
let incidence = InversiveDistanceRegulator::new(
|
|
|
|
[face_rc.clone(), pt_rc.clone()]);
|
|
|
|
incidence.set_to(0.0);
|
|
|
|
assembly.insert_regulator(Rc::new(incidence));
|
|
|
|
// And regulate the length to the other vertices of the octagon
|
|
|
|
for offset in 1..OCT_N_DIAG {
|
|
|
|
if offset <= oct_index {
|
|
|
|
let dist = InversiveDistanceRegulator::new(
|
|
|
|
[oct_verts[oct_index - offset].clone(), pt_rc.clone()]);
|
|
|
|
dist.set_to(OCT_DIST[offset]);
|
|
|
|
assembly.insert_regulator(Rc::new(dist));
|
|
|
|
}
|
|
|
|
if offset < OCT_LONG && oct_index + offset >= OCT_N {
|
|
|
|
let forward = oct_index + offset - OCT_N;
|
|
|
|
let dist = InversiveDistanceRegulator::new(
|
|
|
|
[oct_verts[forward].clone(), pt_rc.clone()]);
|
|
|
|
dist.set_to(OCT_DIST[offset]);
|
|
|
|
assembly.insert_regulator(Rc::new(dist));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2025-09-21 21:47:54 -07:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2025-07-22 22:01:37 +00:00
|
|
|
// --- 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;
|
|
|
|
|
|
|
|
// 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() {
|
2025-08-07 23:24:07 +00:00
|
|
|
"general" => load_general(assembly),
|
|
|
|
"low-curvature" => load_low_curvature(assembly),
|
|
|
|
"pointed" => load_pointed(assembly),
|
|
|
|
"tridiminished-icosahedron" => load_tridiminished_icosahedron(assembly),
|
|
|
|
"dodecahedral-packing" => load_dodecahedral_packing(assembly),
|
|
|
|
"balanced" => load_balanced(assembly),
|
|
|
|
"off-center" => load_off_center(assembly),
|
|
|
|
"radius-ratio" => load_radius_ratio(assembly),
|
|
|
|
"irisawa-hexlet" => load_irisawa_hexlet(assembly),
|
2025-09-21 21:47:54 -07:00
|
|
|
"aug554" => load_554aug2(assembly),
|
2025-08-04 23:34:33 +00:00
|
|
|
_ => (),
|
2025-07-22 22:01:37 +00:00
|
|
|
};
|
|
|
|
});
|
|
|
|
});
|
2025-09-21 21:47:54 -07:00
|
|
|
|
|
|
|
// FIXME: Non-DRY -- should not need to reiterate thie list of assembly
|
|
|
|
// labels
|
2025-07-22 22:01:37 +00:00
|
|
|
// build the chooser
|
|
|
|
view! {
|
2025-08-04 23:34:33 +00:00
|
|
|
select(bind:value = assembly_name) {
|
|
|
|
option(value = "general") { "General" }
|
2025-08-07 23:24:07 +00:00
|
|
|
option(value = "low-curvature") { "Low-curvature" }
|
2025-08-04 23:34:33 +00:00
|
|
|
option(value = "pointed") { "Pointed" }
|
2025-08-07 23:24:07 +00:00
|
|
|
option(value = "tridiminished-icosahedron") { "Tridiminished icosahedron" }
|
|
|
|
option(value = "dodecahedral-packing") { "Dodecahedral packing" }
|
2025-08-04 23:34:33 +00:00
|
|
|
option(value = "balanced") { "Balanced" }
|
|
|
|
option(value = "off-center") { "Off-center" }
|
|
|
|
option(value = "radius-ratio") { "Radius ratio" }
|
|
|
|
option(value = "irisawa-hexlet") { "Irisawa hexlet" }
|
2025-09-21 21:47:54 -07:00
|
|
|
option(value = "aug554") { "McNeill acron 554" }
|
2025-08-04 23:34:33 +00:00
|
|
|
option(value = "empty") { "Empty" }
|
2025-07-22 22:01:37 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|