use crate::kernel::signed_volume::tetra_volume6;
use crate::mesh_orient::OrientVerdict;
#[path = "geom_closure.rs"]
mod closure;
pub use closure::GeometryClosure;
#[path = "geom_bounds.rs"]
mod bounds;
pub const DEFAULT_GEOM_HASH_TOLERANCE: f64 = 1.0e-3;
#[inline]
pub(crate) fn mix64(mut x: u64) -> u64 {
x = (x ^ (x >> 30)).wrapping_mul(0xbf58_476d_1ce4_e5b9);
x = (x ^ (x >> 27)).wrapping_mul(0x94d0_49bb_1331_11eb);
x ^ (x >> 31)
}
#[inline]
fn fold_i64(acc: u64, v: i64) -> u64 {
mix64(acc ^ (v as u64).wrapping_mul(0x9E37_79B9_7F4A_7C15))
}
#[inline]
fn quantize(world: f64, inv_tol: f64) -> i64 {
(world * inv_tol).round() as i64
}
#[derive(Clone, Debug)]
pub struct GeometryHasher {
inv_tol: f64,
rtc: [f64; 3],
triangle_accum: u64,
triangle_count: u64,
min: [f64; 3],
max: [f64; 3],
volume6: f64,
closure: GeometryClosure,
}
impl GeometryHasher {
pub fn new(tolerance: f64, rtc_offset: [f64; 3]) -> Self {
debug_assert!(tolerance > 0.0, "geometry hash tolerance must be positive");
Self {
inv_tol: 1.0 / tolerance,
rtc: rtc_offset,
triangle_accum: 0,
triangle_count: 0,
min: [f64::INFINITY; 3],
max: [f64::NEG_INFINITY; 3],
volume6: 0.0,
closure: GeometryClosure::EMPTY,
}
}
#[inline]
fn world(&self, positions: &[f32], vi: usize, origin: &[f64; 3]) -> [f64; 3] {
let base = vi * 3;
[
positions[base] as f64 + origin[0] + self.rtc[0],
positions[base + 1] as f64 + origin[1] + self.rtc[1],
positions[base + 2] as f64 + origin[2] + self.rtc[2],
]
}
#[inline]
fn quantize_corner(&self, world: &[f64; 3]) -> [i64; 3] {
[
quantize(world[0], self.inv_tol),
quantize(world[1], self.inv_tol),
quantize(world[2], self.inv_tol),
]
}
pub fn add_mesh(&mut self, positions: &[f32], indices: &[u32]) {
self.add_mesh_with_origin(positions, indices, [0.0; 3]);
}
pub fn add_mesh_with_origin(&mut self, positions: &[f32], indices: &[u32], origin: [f64; 3]) {
self.add_oriented_mesh(positions, indices, origin, OrientVerdict::INDETERMINATE);
}
pub fn add_oriented_mesh(
&mut self,
positions: &[f32],
indices: &[u32],
origin: [f64; 3],
verdict: OrientVerdict,
) {
let mut seg_volume6 = 0.0f64;
let mut vol_ref: Option<[f64; 3]> = None;
let vertex_limit = positions.len() / 3;
let triangle_end = indices.len() - (indices.len() % 3);
let mut i = 0;
while i < triangle_end {
let i0 = indices[i] as usize;
let i1 = indices[i + 1] as usize;
let i2 = indices[i + 2] as usize;
i += 3;
if i0 >= vertex_limit || i1 >= vertex_limit || i2 >= vertex_limit {
continue;
}
let world = [
self.world(positions, i0, &origin),
self.world(positions, i1, &origin),
self.world(positions, i2, &origin),
];
for corner in &world {
self.extend_bounds(corner);
}
let o = *vol_ref.get_or_insert(world[0]);
seg_volume6 += tetra_volume6(&world[0], &world[1], &world[2], &o);
let mut tri = [
self.quantize_corner(&world[0]),
self.quantize_corner(&world[1]),
self.quantize_corner(&world[2]),
];
tri.sort_unstable();
let e1 = [
tri[1][0] as i128 - tri[0][0] as i128,
tri[1][1] as i128 - tri[0][1] as i128,
tri[1][2] as i128 - tri[0][2] as i128,
];
let e2 = [
tri[2][0] as i128 - tri[0][0] as i128,
tri[2][1] as i128 - tri[0][1] as i128,
tri[2][2] as i128 - tri[0][2] as i128,
];
let cross_x = e1[1] * e2[2] - e1[2] * e2[1];
let cross_y = e1[2] * e2[0] - e1[0] * e2[2];
let cross_z = e1[0] * e2[1] - e1[1] * e2[0];
if cross_x == 0 && cross_y == 0 && cross_z == 0 {
continue;
}
let mut h = 0x5bd1_e995_u64; for corner in tri {
for c in corner {
h = fold_i64(h, c);
}
}
self.triangle_accum = self.triangle_accum.wrapping_add(mix64(h));
self.triangle_count = self.triangle_count.wrapping_add(1);
}
if vol_ref.is_none() {
return;
}
self.closure.fold_segment(&verdict);
self.volume6 += seg_volume6;
}
pub fn is_empty(&self) -> bool {
self.triangle_count == 0
}
pub fn finish(&self) -> u64 {
let mut h = self.triangle_accum;
h = fold_i64(h, self.triangle_count as i64);
mix64(h)
}
}
pub fn hash_mesh_world(
positions: &[f32],
indices: &[u32],
rtc_offset: [f64; 3],
tolerance: f64,
) -> u64 {
let mut hasher = GeometryHasher::new(tolerance, rtc_offset);
hasher.add_mesh(positions, indices);
hasher.finish()
}
#[cfg(test)]
#[path = "geom_hash_tests.rs"]
mod tests;