use std::{ collections::{BTreeMap, BTreeSet}, io::Write, }; // Same axes as in `fidget::mesh`, but available at build time. const X: usize = 1; const Y: usize = 2; const Z: usize = 4; fn next(axis: usize) -> usize { assert_eq!(axis.count_ones(), 1); assert!(axis < 8); let out = (axis | axis.rotate_right(3)).rotate_left(1) & (X | Y | Z); assert_eq!(out.count_ones(), 1); assert!(out < 8); out } fn main() { // The build script stands alone; ignore other changes (e.g. edits to // benchmarks in the benches subfolder). println!("cargo:rerun-if-changed=build.rs"); // Check CPU feature support and error out if we don't have the appropriate // features. This isn't a fool-proof – someone could build on a machine with // AVX2 support, then try running those binaries elsewhere – but is a good // first line of defense. if std::env::var("CARGO_FEATURE_JIT").is_ok() { #[cfg(target_arch = "x86_64")] if !std::arch::is_x86_feature_detected!("avx2") { eprintln!( "`x86_64` build with `jit` enabled requires AVX2 instructions" ); std::process::exit(1); } #[cfg(target_arch = "aarch64")] if !std::arch::is_aarch64_feature_detected!("neon") { eprintln!( "`aarch64` build with `jit` enabled requires NEON instructions" ); std::process::exit(1); } } if std::env::var("CARGO_FEATURE_MESH").is_ok() { build_mdc_table().unwrap(); } } /// Builds a table for Manifold Dual Contouring connectivity. /// /// This is roughly equivalent to Figure 5 in Nielson's Dual Marching Cubes /// (2004), but worked out automatically by clustering cell corners. fn build_mdc_table() -> Result<(), std::io::Error> { // vert_table will contain 256 entries. Each entry contains some number of // vertices, which each contain some number of edges (as `(u8, u8)` tuples, // from inside corner to outside corner) let mut vert_table: Vec>> = vec![]; let mut edge_table = vec![]; for i in 0..256 { let mut filled_regions = BTreeMap::new(); let mut empty_regions = BTreeMap::new(); for j in 0..8 { if (i & (1 << j)) == 0 { empty_regions.insert(j, 1 << j); } else { filled_regions.insert(j, 1 << j); } } // Collapse connected cells in both filled and empty regions for r in [&mut filled_regions, &mut empty_regions] { loop { let mut changed = false; let mut next = r.clone(); for f in r.keys() { for axis in [X, Y, Z] { let g = &(f ^ axis); if r.contains_key(g) { let v = next[f] | next[g]; changed |= (next[f] != v) | (next[g] != v); *next.get_mut(f).unwrap() = v; *next.get_mut(g).unwrap() = v; } } } *r = next; if !changed { break; } } } // At this point, {filled,empty}_regions are maps from a vertex // number (0-7) to a mask of the region containing that vertex. // // We can discard the vertex numbers and just store the region masks // before processing them further let filled_regions: BTreeSet = filled_regions.into_values().collect(); let empty_regions: BTreeSet = empty_regions.into_values().collect(); // Now, we can flatten into a map from vertex (0-7) to an abstract // region number (0-), since that's what actually matters when // grouping transitions. let mut regions = [u8::MAX; 8]; for (i, r) in filled_regions .into_iter() .chain(empty_regions.into_iter()) .enumerate() { for (j, region) in regions.iter_mut().enumerate() { if r & (1 << j) != 0 { assert_eq!(*region, u8::MAX); *region = i as u8; } } } // We're finally ready to build the edge transition table! // // vert_map is a map from start region to a vertex, defined as a list // of edges that built that vertex. let mut verts: BTreeMap<_, Vec<_>> = BTreeMap::new(); for rev in [false, true] { // t-u-v forms a right-handed coordinate system for t in [X, Y, Z] { let u = next(t); let v = next(u); for b in 0..2 { for a in 0..2 { let start = (a * u) | (b * v); let end = start | t; let (start, end) = if rev { (end, start) } else { (start, end) }; // Only process this edge if `start` is inside the model // (non-zero) and `end` is outside (0). if ((i & (1 << start)) != 0) && ((i & (1 << end)) == 0) { let start_region = regions[start]; let end_region = regions[end]; assert!(start_region != end_region); verts .entry(start_region) .or_default() .push((start, end)); } } } } } let mut vert_table_entry = vec![]; // There are two maps associated with this cell: // - A list of vertices, each of which has a list of transition edges // - A map from transition edge to vertex in the previous list let mut edge_map = [(u8::MAX, u8::MAX); 12]; let mut intersection_count = 0; let vert_count = verts.len(); #[allow(clippy::identity_op)] for (vert, (_, edges)) in verts.iter().enumerate() { let mut vert_entry = vec![]; for &(start, end) in edges { assert!((i & (1 << start)) != 0); assert!((i & (1 << end)) == 0); vert_entry.push((start as u8, end as u8)); // Build a right-handed coordinate system of T-U-V let t = start ^ end; let u = next(t); let v = next(u); assert_eq!(start & u, end & u); assert_eq!(start & v, end & v); let edge = (t.trailing_zeros() as usize * 4) + (((start & u) != 0) as usize) * 1 + (((start & v) != 0) as usize) * 2; edge_map[edge] = ( vert.try_into().unwrap(), (vert_count + intersection_count).try_into().unwrap(), ); intersection_count += 1; } vert_table_entry.push(vert_entry); } edge_table.push(edge_map); vert_table.push(vert_table_entry); } let out_dir = std::env::var_os("OUT_DIR").unwrap(); let dest_path = std::path::Path::new(&out_dir).join("mdc_tables.rs"); let mut file = std::fs::File::create(dest_path).expect("could not make output file"); writeln!( &mut file, " /// Lookup table to find edges for a particular cell configuration /// /// Given a cell index `i` (as an 8-bit value), looks up a list of vertices /// which are required for that cell. Each vertex is implicitly numbered based /// on its position in the list, and itself stores a list of edges (as tuples /// of `(start, end)` cell corners). pub const CELL_TO_VERT_TO_EDGES: [&[&[DirectedEdge]]; 256] = [" )?; for v in vert_table { writeln!(&mut file, " &[")?; for e in v { writeln!(&mut file, " &[")?; for (start, end) in e { writeln!( &mut file, " DirectedEdge::new(Corner::new({start}), \ Corner::new({end}))," )?; } writeln!(&mut file, " ],")?; } writeln!(&mut file, " ],")?; } writeln!(&mut file, "];")?; writeln!( &mut file, " /// Lookup table to find which vertex is associated with a particular edge /// /// Given a cell index `i` (as an 8-bit value) and an edge index `e` (as a /// packed undirected value in the range 0-12), returns an [`Intersection`] /// that encodes the vertex offsets for that edge. pub const CELL_TO_EDGE_TO_VERT: [[Intersection; 12]; 256] = [" )?; for e in edge_table { writeln!(&mut file, " [")?; for (vert, edge) in e { writeln!( &mut file, " Intersection {{ vert: Offset({vert}), \ edge: Offset({edge}) }}," )?; } writeln!(&mut file, " ],")?; } writeln!(&mut file, "];")?; Ok(()) }