feat[WIP]: begin a test assembly for the doubly augmented 554 acron

This commit is contained in:
Glen Whitney 2025-09-21 21:47:54 -07:00
parent ecbbe2068c
commit 9d9e0da2c3
5 changed files with 176 additions and 96 deletions

View file

@ -0,0 +1,2 @@
[toolchain]
channel = "nightly"

View file

@ -274,14 +274,14 @@ impl Point {
const NORM_COMPONENT: usize = 4;
pub fn new(
id: String,
label: String,
id: &str,
label: &str,
color: ElementColor,
representation: DVector<f64>,
) -> Self {
Self {
id,
label,
id: id.to_string(),
label: label.to_string(),
color,
representation: create_signal(representation),
ghost: create_signal(false),
@ -299,8 +299,8 @@ impl Element for Point {
fn default(id: String, id_num: u64) -> Self {
Self::new(
id,
format!("Point {id_num}"),
&id,
&format!("Point {id_num}"),
[0.75_f32, 0.75_f32, 0.75_f32],
point(0.0, 0.0, 0.0),
)

View file

@ -15,10 +15,61 @@ use crate::{
Sphere,
},
engine,
engine::DescentHistory,
engine::{DescentHistory, point},
specified::SpecifiedValue,
};
// 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.;
// --- loaders ---
/* DEBUG */
@ -79,7 +130,7 @@ fn load_general(assembly: &Assembly) {
fn load_low_curvature(assembly: &Assembly) {
// create the spheres
let a = 0.75_f64.sqrt();
const A: f64 = sqrt(0.75);
let _ = assembly.try_insert_element(
Sphere::new(
"central".to_string(),
@ -109,7 +160,7 @@ fn load_low_curvature(assembly: &Assembly) {
"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),
engine::sphere_with_offset(-0.5, A, 0.0, 1.0, 0.0),
)
);
let _ = assembly.try_insert_element(
@ -117,7 +168,7 @@ fn load_low_curvature(assembly: &Assembly) {
"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),
engine::sphere_with_offset(-0.5, -A, 0.0, 1.0, 0.0),
)
);
let _ = assembly.try_insert_element(
@ -133,7 +184,7 @@ fn load_low_curvature(assembly: &Assembly) {
"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),
engine::sphere(2.0/3.0, -4.0/3.0 * A, 0.0, 1.0/3.0),
)
);
let _ = assembly.try_insert_element(
@ -141,7 +192,7 @@ fn load_low_curvature(assembly: &Assembly) {
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),
engine::sphere(2.0/3.0, 4.0/3.0 * A, 0.0, 1.0/3.0),
)
);
@ -199,16 +250,16 @@ fn load_low_curvature(assembly: &Assembly) {
fn load_pointed(assembly: &Assembly) {
let _ = assembly.try_insert_element(
Point::new(
format!("point_front"),
format!("Front point"),
"point_front",
"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"),
"point_back",
"Back point",
[0.75_f32, 0.75_f32, 0.75_f32],
engine::point(0.0, 0.0, -FRAC_1_SQRT_2),
)
@ -229,8 +280,8 @@ fn load_pointed(assembly: &Assembly) {
let _ = assembly.try_insert_element(
Point::new(
format!("point{index_x}{index_y}"),
format!("Point {index_x}{index_y}"),
&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),
)
@ -252,60 +303,15 @@ fn load_tridiminished_icosahedron(assembly: &Assembly) {
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),
),
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)),
];
for vertex in vertices {
let _ = assembly.try_insert_element(vertex);
@ -313,26 +319,26 @@ fn load_tridiminished_icosahedron(assembly: &Assembly) {
// 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;
const SQRT_1_6: f64 = invsqrt(6.);
const SQRT_2_3: f64 = 2. * SQRT_1_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),
engine::sphere_with_offset(SQRT_2_3, -SQRT_1_6, -SQRT_1_6, -SQRT_1_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),
engine::sphere_with_offset(-SQRT_1_6, SQRT_2_3, -SQRT_1_6, -SQRT_1_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),
engine::sphere_with_offset(-SQRT_1_6, -SQRT_1_6, SQRT_2_3, -SQRT_1_6, 0.0),
),
];
for face in faces {
@ -434,17 +440,16 @@ fn load_dodecahedral_packing(assembly: &Assembly) {
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];
const PHI_INV: f64 = 1.0 / PHI;
const COORD_SCALE: f64 = sqrt(PHI + 2.0);
const FACE_SCALES: [f64; 2] = [PHI_INV, (13.0 / 12.0) / COORD_SCALE];
const FACE_RADII: [f64; 2] = [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 small_coord = FACE_SCALES[k] * if j > 0 {1.} else {-1.};
let big_coord = FACE_SCALES[k] * PHI * if k > 0 {1.} else {-1.};
let id_num = format!("{j}{k}");
let label_sub = format!("{}{}", subscripts[j], subscripts[k]);
@ -456,7 +461,7 @@ fn load_dodecahedral_packing(assembly: &Assembly) {
id_a.clone(),
format!("A{label_sub}"),
COLOR_A,
engine::sphere(0.0, small_coord, big_coord, face_radii[k]),
engine::sphere(0.0, small_coord, big_coord, FACE_RADII[k]),
)
);
faces.push(
@ -472,7 +477,7 @@ fn load_dodecahedral_packing(assembly: &Assembly) {
id_b.clone(),
format!("B{label_sub}"),
COLOR_B,
engine::sphere(small_coord, big_coord, 0.0, face_radii[k]),
engine::sphere(small_coord, big_coord, 0.0, FACE_RADII[k]),
)
);
faces.push(
@ -488,7 +493,7 @@ fn load_dodecahedral_packing(assembly: &Assembly) {
id_c.clone(),
format!("C{label_sub}"),
COLOR_C,
engine::sphere(big_coord, 0.0, small_coord, face_radii[k]),
engine::sphere(big_coord, 0.0, small_coord, FACE_RADII[k]),
)
);
faces.push(
@ -614,12 +619,7 @@ fn load_balanced(assembly: &Assembly) {
fn load_off_center(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),
),
Point::new("point", "Point", [0.75, 0.75, 0.75], point(1e-9, 0.0, 0.0)),
);
let _ = assembly.try_insert_element(
Sphere::new(
@ -689,8 +689,8 @@ fn load_radius_ratio(assembly: &Assembly) {
).map(
|(k, color, representation)| {
Point::new(
format!("v{k}"),
format!("Vertex {k}"),
&format!("v{k}"),
&format!("Vertex {k}"),
color,
representation,
)
@ -882,6 +882,76 @@ fn load_irisawa_hexlet(assembly: &Assembly) {
assembly.insert_regulator(Rc::new(outer_moon_tangency));
}
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.
const ACRON554_COMMON: [(&str, (f64, f64, f64), bool, usize, &str); 38] = [
// id, coordinates, Pin/Free, level, earlier neighbor IDs.
("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"),
("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_1"),
("G_4", ( 1.224, -0.517, 3.298), F, 8, "E_E,F_SE,G_2"),
("G_5", ( 0.517, -1.224, 3.298), F, 8, "E_S,F_SE,G_4"),
("G_7", (-0.483, -1.224, 3.304), F, 8, "E_S,F_SW,G_5"),
("G_8", (-1.19, -0.517, 3.312), F, 8, "E_W,F_SW,G_7"),
("G_10", (-1.19, 0.483, 3.318), F, 8, "E_W,F_NW,G_8"),
("G_11", (-0.483, 1.19, 3.318), F, 8, "E_N,F_NW,G_1,G_10"),
];
const LEVEL_COLORS: [[f32; 3]; 9] = [
[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.],
];
fn load_554aug2(assembly: &Assembly) {
for (id, v, _pinned, level, _neighbors) in ACRON554_COMMON {
let pt = Point::new(id, id, LEVEL_COLORS[level], point(v.0, v.1, v.2));
assembly.try_insert_element(pt);
}
}
// --- chooser ---
/* DEBUG */
@ -918,11 +988,14 @@ pub fn TestAssemblyChooser() -> View {
"off-center" => load_off_center(assembly),
"radius-ratio" => load_radius_ratio(assembly),
"irisawa-hexlet" => load_irisawa_hexlet(assembly),
"aug554" => load_554aug2(assembly),
_ => (),
};
});
});
// FIXME: Non-DRY -- should not need to reiterate thie list of assembly
// labels
// build the chooser
view! {
select(bind:value = assembly_name) {
@ -935,6 +1008,7 @@ pub fn TestAssemblyChooser() -> View {
option(value = "off-center") { "Off-center" }
option(value = "radius-ratio") { "Radius ratio" }
option(value = "irisawa-hexlet") { "Irisawa hexlet" }
option(value = "aug554") { "McNeill acron 554" }
option(value = "empty") { "Empty" }
}
}

View file

@ -1 +1,3 @@
#![feature(const_trait_impl)]
pub mod engine;

View file

@ -1,3 +1,5 @@
#![feature(const_trait_impl)]
mod assembly;
mod components;
mod engine;