diff --git a/common/src/cursor.rs b/common/src/cursor.rs index 1f31ac51..4e36c74a 100644 --- a/common/src/cursor.rs +++ b/common/src/cursor.rs @@ -1,8 +1,7 @@ use crate::dodeca::{Side, Vertex, SIDE_COUNT}; use crate::graph::{Graph, NodeId}; use crate::node::ChunkId; - -use lazy_static::lazy_static; +use std::sync::OnceLock; /// Navigates the cubic dual of a graph #[derive(Debug, Copy, Clone, Eq, PartialEq)] @@ -28,9 +27,9 @@ impl Cursor { // in both the dodecahedron sharing the face unique to the new vertex and that sharing the // face that the new vertex isn't incident to. let (a, b, c) = (self.a, self.b, self.c); - let a_prime = NEIGHBORS[a as usize][b as usize][c as usize].unwrap(); - let b_prime = NEIGHBORS[b as usize][a as usize][c as usize].unwrap(); - let c_prime = NEIGHBORS[c as usize][b as usize][a as usize].unwrap(); + let a_prime = neighbors_static()[a as usize][b as usize][c as usize].unwrap(); + let b_prime = neighbors_static()[b as usize][a as usize][c as usize].unwrap(); + let c_prime = neighbors_static()[c as usize][b as usize][a as usize].unwrap(); use Dir::*; let (sides, neighbor) = match dir { Left => ((a, b, c_prime), c), @@ -103,9 +102,10 @@ impl std::ops::Neg for Dir { } } -lazy_static! { - /// Maps every (A, B, C) sharing a vertex to A', the side that shares edges with B and C but not A - static ref NEIGHBORS: [[[Option; SIDE_COUNT]; SIDE_COUNT]; SIDE_COUNT] = { +/// Maps every (A, B, C) sharing a vertex to A', the side that shares edges with B and C but not A +fn neighbors_static() -> &'static [[[Option; SIDE_COUNT]; SIDE_COUNT]; SIDE_COUNT] { + static LOCK: OnceLock<[[[Option; SIDE_COUNT]; SIDE_COUNT]; SIDE_COUNT]> = OnceLock::new(); + LOCK.get_or_init(|| { let mut result = [[[None; SIDE_COUNT]; SIDE_COUNT]; SIDE_COUNT]; for a in Side::iter() { for b in Side::iter() { @@ -114,19 +114,20 @@ lazy_static! { if s == a || s == b || s == c { continue; } - let (opposite, shared) = match (s.adjacent_to(a), s.adjacent_to(b), s.adjacent_to(c)) { - (false, true, true) => (a, (b, c)), - (true, false, true) => (b, (a, c)), - (true, true, false) => (c, (a, b)), - _ => continue, - }; + let (opposite, shared) = + match (s.adjacent_to(a), s.adjacent_to(b), s.adjacent_to(c)) { + (false, true, true) => (a, (b, c)), + (true, false, true) => (b, (a, c)), + (true, true, false) => (c, (a, b)), + _ => continue, + }; result[opposite as usize][shared.0 as usize][shared.1 as usize] = Some(s); } } } } result - }; + }) } #[cfg(test)] @@ -139,8 +140,8 @@ mod tests { for v in Vertex::iter() { let [a, b, c] = v.canonical_sides(); assert_eq!( - NEIGHBORS[a as usize][b as usize][c as usize], - NEIGHBORS[a as usize][c as usize][b as usize] + neighbors_static()[a as usize][b as usize][c as usize], + neighbors_static()[a as usize][c as usize][b as usize] ); } } diff --git a/common/src/dodeca.rs b/common/src/dodeca.rs index 0fc67201..b73291aa 100644 --- a/common/src/dodeca.rs +++ b/common/src/dodeca.rs @@ -1,7 +1,7 @@ //! Tools for processing the geometry of a right dodecahedron -use lazy_static::lazy_static; use serde::{Deserialize, Serialize}; +use std::sync::OnceLock; use crate::math; @@ -40,31 +40,31 @@ impl Side { /// `false` when `self == other`. #[inline] pub fn adjacent_to(self, other: Side) -> bool { - ADJACENT[self as usize][other as usize] + adjacent_static()[self as usize][other as usize] } /// Outward normal vector of this side #[inline] pub fn normal(self) -> &'static na::Vector4 { - &SIDE_NORMALS_F32[self as usize] + &side_normals_f32_static()[self as usize] } /// Outward normal vector of this side #[inline] pub fn normal_f64(self) -> &'static na::Vector4 { - &SIDE_NORMALS_F64[self as usize] + &side_normals_f64_static()[self as usize] } /// Reflection across this side #[inline] pub fn reflection(self) -> &'static na::Matrix4 { - &REFLECTIONS_F32[self as usize] + &reflections_f32_static()[self as usize] } /// Reflection across this side #[inline] pub fn reflection_f64(self) -> &'static na::Matrix4 { - &REFLECTIONS_F64[self as usize] + &reflections_f64_static()[self as usize] } /// Whether `p` is opposite the dodecahedron across the plane containing `self` @@ -111,19 +111,19 @@ impl Vertex { /// Vertex shared by three sides, if any #[inline] pub fn from_sides(a: Side, b: Side, c: Side) -> Option { - SIDES_TO_VERTEX[a as usize][b as usize][c as usize] + sides_to_vertex_static()[a as usize][b as usize][c as usize] } /// Sides incident to this vertex, in canonical order #[inline] pub fn canonical_sides(self) -> [Side; 3] { - VERTEX_SIDES[self as usize] + vertex_sides_static()[self as usize] } /// Vertices adjacent to this vertex, opposite the sides in canonical order #[inline] pub fn adjacent_vertices(self) -> [Vertex; 3] { - ADJACENT_VERTICES[self as usize] + adjacent_vertices_static()[self as usize] } /// For each vertex of the cube dual to this dodecahedral vertex, provides an iterator of at @@ -171,55 +171,55 @@ impl Vertex { /// Transform from cube-centric coordinates to dodeca-centric coordinates pub fn dual_to_node(self) -> &'static na::Matrix4 { - &DUAL_TO_NODE_F32[self as usize] + &dual_to_node_f32_static()[self as usize] } /// Transform from cube-centric coordinates to dodeca-centric coordinates pub fn dual_to_node_f64(self) -> &'static na::Matrix4 { - &DUAL_TO_NODE_F64[self as usize] + &dual_to_node_f64_static()[self as usize] } /// Transform from dodeca-centric coordinates to cube-centric coordinates pub fn node_to_dual(self) -> &'static na::Matrix4 { - &NODE_TO_DUAL_F32[self as usize] + &node_to_dual_f32_static()[self as usize] } /// Transform from dodeca-centric coordinates to cube-centric coordinates pub fn node_to_dual_f64(self) -> &'static na::Matrix4 { - &NODE_TO_DUAL_F64[self as usize] + &node_to_dual_f64_static()[self as usize] } /// Scale factor used in conversion from cube-centric coordinates to euclidean chunk coordinates. /// Scaling the x, y, and z components of a vector in cube-centric coordinates by this value /// and dividing them by the w coordinate will yield euclidean chunk coordinates. pub fn dual_to_chunk_factor() -> f32 { - *DUAL_TO_CHUNK_FACTOR_F32 + *dual_to_chunk_factor_f32_static() } /// Scale factor used in conversion from cube-centric coordinates to euclidean chunk coordinates. /// Scaling the x, y, and z components of a vector in cube-centric coordinates by this value /// and dividing them by the w coordinate will yield euclidean chunk coordinates. pub fn dual_to_chunk_factor_f64() -> f64 { - *DUAL_TO_CHUNK_FACTOR_F64 + *dual_to_chunk_factor_f64_static() } /// Scale factor used in conversion from euclidean chunk coordinates to cube-centric coordinates. /// Scaling the x, y, and z components of a vector in homogeneous euclidean chunk coordinates by this value /// and lorentz-normalizing the result will yield cube-centric coordinates. pub fn chunk_to_dual_factor() -> f32 { - *CHUNK_TO_DUAL_FACTOR_F32 + *chunk_to_dual_factor_f32_static() } /// Scale factor used in conversion from euclidean chunk coordinates to cube-centric coordinates. /// Scaling the x, y, and z components of a vector in homogeneous euclidean chunk coordinates by this value /// and lorentz-normalizing the result will yield cube-centric coordinates. pub fn chunk_to_dual_factor_f64() -> f64 { - *CHUNK_TO_DUAL_FACTOR_F64 + *chunk_to_dual_factor_f64_static() } /// Convenience method for `self.chunk_to_node().determinant() < 0`. pub fn parity(self) -> bool { - CHUNK_TO_NODE_PARITY[self as usize] + chunk_to_node_parity_static()[self as usize] } } @@ -228,23 +228,28 @@ pub const SIDE_COUNT: usize = 12; pub const BOUNDING_SPHERE_RADIUS_F64: f64 = 1.2264568712514068; pub const BOUNDING_SPHERE_RADIUS: f32 = BOUNDING_SPHERE_RADIUS_F64 as f32; -lazy_static! { - /// Whether two sides share an edge - static ref ADJACENT: [[bool; SIDE_COUNT]; SIDE_COUNT] = { +/// Whether two sides share an edge +fn adjacent_static() -> &'static [[bool; SIDE_COUNT]; SIDE_COUNT] { + static LOCK: OnceLock<[[bool; SIDE_COUNT]; SIDE_COUNT]> = OnceLock::new(); + LOCK.get_or_init(|| { let mut result = [[false; SIDE_COUNT]; SIDE_COUNT]; - for i in 0..SIDE_COUNT { - for j in 0..SIDE_COUNT { - let cosh_distance = (REFLECTIONS_F64[i] * REFLECTIONS_F64[j])[(3, 3)]; + for (i, triple) in result.iter_mut().enumerate().take(SIDE_COUNT) { + for (j, is_adjacent) in triple.iter_mut().enumerate().take(SIDE_COUNT) { + let cosh_distance = + (reflections_f64_static()[i] * reflections_f64_static()[j])[(3, 3)]; // Possile cosh_distances: 1, 4.23606 = 2+sqrt(5), 9.47213 = 5+2*sqrt(5), 12.70820 = 6+3*sqrt(5); // < 2.0 indicates identical faces; < 5.0 indicates adjacent faces; > 5.0 indicates non-adjacent faces - result[i][j] = (2.0..5.0).contains(&cosh_distance); + *is_adjacent = (2.0..5.0).contains(&cosh_distance); } } result - }; + }) +} - /// Vector corresponding to the outer normal of each side - static ref SIDE_NORMALS_F64: [na::Vector4; SIDE_COUNT] = { +/// Vector corresponding to the outer normal of each side +fn side_normals_f64_static() -> &'static [na::Vector4; SIDE_COUNT] { + static LOCK: OnceLock<[na::Vector4; SIDE_COUNT]> = OnceLock::new(); + LOCK.get_or_init(|| { let phi = libm::sqrt(1.25) + 0.5; // golden ratio let f = math::lorentz_normalize(&na::Vector4::new(1.0, phi, 0.0, libm::sqrt(phi))); @@ -255,97 +260,133 @@ lazy_static! { (-f.x, f.y, -f.z, f.w), (f.x, -f.y, -f.z, f.w), (-f.x, -f.y, f.z, f.w), - ] - { + ] { for (x, y, z, w) in [(x, y, z, w), (y, z, x, w), (z, x, y, w)] { result[i] = na::Vector4::new(x, y, z, w); i += 1; } } result - }; + }) +} - /// Transform that moves from a neighbor to a reference node, for each side - static ref REFLECTIONS_F64: [na::Matrix4; SIDE_COUNT] = { - SIDE_NORMALS_F64.map(|r| math::reflect(&r)) - }; +/// Transform that moves from a neighbor to a reference node, for each side +fn reflections_f64_static() -> &'static [na::Matrix4; SIDE_COUNT] { + static LOCK: OnceLock<[na::Matrix4; SIDE_COUNT]> = OnceLock::new(); + LOCK.get_or_init(|| side_normals_f64_static().map(|r| math::reflect(&r))) +} - /// Sides incident to a vertex, in canonical order - static ref VERTEX_SIDES: [[Side; 3]; VERTEX_COUNT] = { +/// Sides incident to a vertex, in canonical order +fn vertex_sides_static() -> &'static [[Side; 3]; VERTEX_COUNT] { + static LOCK: OnceLock<[[Side; 3]; VERTEX_COUNT]> = OnceLock::new(); + LOCK.get_or_init(|| { let mut result = [[Side::A; 3]; VERTEX_COUNT]; let mut vertex = 0; // Kind of a hack, but working this out by hand isn't any fun. for a in 0..SIDE_COUNT { - for b in (a+1)..SIDE_COUNT { - for c in (b+1)..SIDE_COUNT { - if !ADJACENT[a][b] || !ADJACENT[b][c] || !ADJACENT[c][a] { + for b in (a + 1)..SIDE_COUNT { + for c in (b + 1)..SIDE_COUNT { + if !adjacent_static()[a][b] + || !adjacent_static()[b][c] + || !adjacent_static()[c][a] + { continue; } - result[vertex] = [Side::from_index(a), Side::from_index(b), Side::from_index(c)]; + result[vertex] = [ + Side::from_index(a), + Side::from_index(b), + Side::from_index(c), + ]; vertex += 1; } } } assert_eq!(vertex, 20); result - }; + }) +} - // Which vertices are adjacent to other vertices and opposite the canonical sides - static ref ADJACENT_VERTICES: [[Vertex; 3]; VERTEX_COUNT] = { +// Which vertices are adjacent to other vertices and opposite the canonical sides +fn adjacent_vertices_static() -> &'static [[Vertex; 3]; VERTEX_COUNT] { + static LOCK: OnceLock<[[Vertex; 3]; VERTEX_COUNT]> = OnceLock::new(); + LOCK.get_or_init(|| { let mut result = [[Vertex::A; 3]; VERTEX_COUNT]; - for vertex in 0..VERTEX_COUNT { + for (i, triple) in result.iter_mut().enumerate().take(VERTEX_COUNT) { for result_index in 0..3 { - let mut test_sides = VERTEX_SIDES[vertex]; + let mut test_sides = vertex_sides_static()[i]; // Keep modifying the result_index'th element of test_sides until its three elements are all // adjacent to a single vertex. That vertex is the vertex we're looking for. for side in Side::iter() { - if side == VERTEX_SIDES[vertex][result_index] { + if side == vertex_sides_static()[i][result_index] { continue; } test_sides[result_index] = side; - if let Some(adjacent_vertex) = Vertex::from_sides(test_sides[0], test_sides[1], test_sides[2]) { - result[vertex][result_index] = adjacent_vertex; + if let Some(adjacent_vertex) = + Vertex::from_sides(test_sides[0], test_sides[1], test_sides[2]) + { + triple[result_index] = adjacent_vertex; } } } } result - }; + }) +} - /// Transform that converts from cube-centric coordinates to dodeca-centric coordinates - static ref DUAL_TO_NODE_F64: [na::Matrix4; VERTEX_COUNT] = { - let mip_origin_normal = math::mip(&math::origin(), &SIDE_NORMALS_F64[0]); // This value is the same for every side +/// Transform that converts from cube-centric coordinates to dodeca-centric coordinates +fn dual_to_node_f64_static() -> &'static [na::Matrix4; VERTEX_COUNT] { + static LOCK: OnceLock<[na::Matrix4; VERTEX_COUNT]> = OnceLock::new(); + LOCK.get_or_init(|| { + let mip_origin_normal = math::mip(&math::origin(), &side_normals_f64_static()[0]); // This value is the same for every side let mut result = [na::zero(); VERTEX_COUNT]; - for i in 0..VERTEX_COUNT { - let [a, b, c] = VERTEX_SIDES[i]; + for (i, item) in result.iter_mut().enumerate().take(VERTEX_COUNT) { + let [a, b, c] = vertex_sides_static()[i]; let vertex_position = math::lorentz_normalize( - &(math::origin() - (a.normal_f64() + b.normal_f64() + c.normal_f64()) * mip_origin_normal), + &(math::origin() + - (a.normal_f64() + b.normal_f64() + c.normal_f64()) * mip_origin_normal), ); - result[i] = na::Matrix4::from_columns(&[-a.normal_f64(), -b.normal_f64(), -c.normal_f64(), vertex_position]); + *item = na::Matrix4::from_columns(&[ + -a.normal_f64(), + -b.normal_f64(), + -c.normal_f64(), + vertex_position, + ]); } result - }; + }) +} + +/// Transform that converts from dodeca-centric coordinates to cube-centric coordinates +fn node_to_dual_f64_static() -> &'static [na::Matrix4; VERTEX_COUNT] { + static LOCK: OnceLock<[na::Matrix4; VERTEX_COUNT]> = OnceLock::new(); + LOCK.get_or_init(|| dual_to_node_f64_static().map(|m| math::mtranspose(&m))) +} - /// Transform that converts from dodeca-centric coordinates to cube-centric coordinates - static ref NODE_TO_DUAL_F64: [na::Matrix4; VERTEX_COUNT] = { - DUAL_TO_NODE_F64.map(|m| math::mtranspose(&m)) - }; +fn dual_to_chunk_factor_f64_static() -> &'static f64 { + static LOCK: OnceLock = OnceLock::new(); + LOCK.get_or_init(|| (2.0 + 5.0f64.sqrt()).sqrt()) +} - static ref DUAL_TO_CHUNK_FACTOR_F64: f64 = (2.0 + 5.0f64.sqrt()).sqrt(); - static ref CHUNK_TO_DUAL_FACTOR_F64: f64 = 1.0 / *DUAL_TO_CHUNK_FACTOR_F64; +fn chunk_to_dual_factor_f64_static() -> &'static f64 { + static LOCK: OnceLock = OnceLock::new(); + LOCK.get_or_init(|| 1.0 / *dual_to_chunk_factor_f64_static()) +} - /// Vertex shared by 3 sides - static ref SIDES_TO_VERTEX: [[[Option; SIDE_COUNT]; SIDE_COUNT]; SIDE_COUNT] = { +/// Vertex shared by 3 sides +fn sides_to_vertex_static() -> &'static [[[Option; SIDE_COUNT]; SIDE_COUNT]; SIDE_COUNT] { + static LOCK: OnceLock<[[[Option; SIDE_COUNT]; SIDE_COUNT]; SIDE_COUNT]> = + OnceLock::new(); + LOCK.get_or_init(|| { let mut result = [[[None; SIDE_COUNT]; SIDE_COUNT]; SIDE_COUNT]; let mut vertex = Vertex::iter(); // Kind of a hack, but working this out by hand isn't any fun. for a in 0..SIDE_COUNT { - for b in (a+1)..SIDE_COUNT { - for c in (b+1)..SIDE_COUNT { - if !Side::from_index(a).adjacent_to(Side::from_index(b)) || - !Side::from_index(b).adjacent_to(Side::from_index(c)) || - !Side::from_index(c).adjacent_to(Side::from_index(a)) + for b in (a + 1)..SIDE_COUNT { + for c in (b + 1)..SIDE_COUNT { + if !Side::from_index(a).adjacent_to(Side::from_index(b)) + || !Side::from_index(b).adjacent_to(Side::from_index(c)) + || !Side::from_index(c).adjacent_to(Side::from_index(a)) { continue; } @@ -361,10 +402,13 @@ lazy_static! { } assert_eq!(vertex.next(), None); result - }; + }) +} - /// Whether the determinant of the cube-to-node transform is negative - static ref CHUNK_TO_NODE_PARITY: [bool; VERTEX_COUNT] = { +/// Whether the determinant of the cube-to-node transform is negative +fn chunk_to_node_parity_static() -> &'static [bool; VERTEX_COUNT] { + static LOCK: OnceLock<[bool; VERTEX_COUNT]> = OnceLock::new(); + LOCK.get_or_init(|| { let mut result = [false; VERTEX_COUNT]; for v in Vertex::iter() { @@ -372,14 +416,37 @@ lazy_static! { } result - }; - - static ref SIDE_NORMALS_F32: [na::Vector4; SIDE_COUNT] = SIDE_NORMALS_F64.map(|n| n.cast()); - static ref REFLECTIONS_F32: [na::Matrix4; SIDE_COUNT] = REFLECTIONS_F64.map(|n| n.cast()); - static ref DUAL_TO_NODE_F32: [na::Matrix4; VERTEX_COUNT] = DUAL_TO_NODE_F64.map(|n| n.cast()); - static ref NODE_TO_DUAL_F32: [na::Matrix4; VERTEX_COUNT] = NODE_TO_DUAL_F64.map(|n| n.cast()); - static ref DUAL_TO_CHUNK_FACTOR_F32: f32 = *DUAL_TO_CHUNK_FACTOR_F64 as f32; - static ref CHUNK_TO_DUAL_FACTOR_F32: f32 = *CHUNK_TO_DUAL_FACTOR_F64 as f32; + }) +} + +fn side_normals_f32_static() -> &'static [na::Vector4; SIDE_COUNT] { + static LOCK: OnceLock<[na::Vector4; SIDE_COUNT]> = OnceLock::new(); + LOCK.get_or_init(|| side_normals_f64_static().map(|n| n.cast())) +} + +fn reflections_f32_static() -> &'static [na::Matrix4; SIDE_COUNT] { + static LOCK: OnceLock<[na::Matrix4; SIDE_COUNT]> = OnceLock::new(); + LOCK.get_or_init(|| reflections_f64_static().map(|n| n.cast())) +} + +fn dual_to_node_f32_static() -> &'static [na::Matrix4; VERTEX_COUNT] { + static LOCK: OnceLock<[na::Matrix4; VERTEX_COUNT]> = OnceLock::new(); + LOCK.get_or_init(|| dual_to_node_f64_static().map(|n| n.cast())) +} + +fn node_to_dual_f32_static() -> &'static [na::Matrix4; VERTEX_COUNT] { + static LOCK: OnceLock<[na::Matrix4; VERTEX_COUNT]> = OnceLock::new(); + LOCK.get_or_init(|| node_to_dual_f64_static().map(|n| n.cast())) +} + +fn dual_to_chunk_factor_f32_static() -> &'static f32 { + static LOCK: OnceLock = OnceLock::new(); + LOCK.get_or_init(|| *dual_to_chunk_factor_f64_static() as f32) +} + +fn chunk_to_dual_factor_f32_static() -> &'static f32 { + static LOCK: OnceLock = OnceLock::new(); + LOCK.get_or_init(|| *chunk_to_dual_factor_f64_static() as f32) } #[cfg(test)] @@ -390,15 +457,15 @@ mod tests { #[test] fn vertex_sides() { use std::collections::HashSet; - let triples = VERTEX_SIDES.iter().collect::>(); + let triples = vertex_sides_static().iter().collect::>(); assert_eq!(triples.len(), VERTEX_COUNT); - for &triple in &*VERTEX_SIDES { + for &triple in vertex_sides_static() { let mut sorted = triple; sorted.sort_unstable(); assert_eq!(triple, sorted); - assert!(ADJACENT[triple[0] as usize][triple[1] as usize]); - assert!(ADJACENT[triple[1] as usize][triple[2] as usize]); - assert!(ADJACENT[triple[2] as usize][triple[0] as usize]); + assert!(adjacent_static()[triple[0] as usize][triple[1] as usize]); + assert!(adjacent_static()[triple[1] as usize][triple[2] as usize]); + assert!(adjacent_static()[triple[2] as usize][triple[0] as usize]); } }