use ifc_lite_processing::MeshData;
struct Fnv(u64);
impl Fnv {
fn bytes(&mut self, bytes: &[u8]) {
for &byte in bytes {
self.0 ^= u64::from(byte);
self.0 = self.0.wrapping_mul(0x100_0000_01b3);
}
}
fn len(&mut self, len: usize) {
self.bytes(&(len as u64).to_le_bytes());
}
fn floats32(&mut self, values: &[f32]) {
self.len(values.len());
for value in values {
self.bytes(&value.to_bits().to_le_bytes());
}
}
fn floats64(&mut self, values: &[f64]) {
self.len(values.len());
for value in values {
self.bytes(&value.to_bits().to_le_bytes());
}
}
fn optional<T>(&mut self, value: Option<T>, write: impl FnOnce(&mut Self, T)) {
self.bytes(&[u8::from(value.is_some())]);
if let Some(value) = value {
write(self, value);
}
}
}
pub(super) fn mesh_fingerprint(meshes: &[MeshData]) -> String {
let mut hash = Fnv(0xcbf2_9ce4_8422_2325);
hash.bytes(b"ifc-lite-perf-mesh-v1");
hash.len(meshes.len());
for mesh in meshes {
hash.bytes(&mesh.express_id.to_le_bytes());
hash.optional(mesh.geometry_item_id, |h, id| h.bytes(&id.to_le_bytes()));
hash.optional(mesh.material_id, |h, id| h.bytes(&id.to_le_bytes()));
hash.bytes(&[mesh.geometry_class]);
hash.floats32(&mesh.positions);
hash.floats32(&mesh.normals);
hash.len(mesh.indices.len());
for index in &mesh.indices {
hash.bytes(&index.to_le_bytes());
}
hash.floats32(&mesh.color);
hash.floats64(&mesh.origin);
hash.optional(mesh.local_bounds.as_ref(), |h, v| h.floats32(v));
hash.optional(mesh.local_to_world.as_ref(), |h, v| h.floats64(v));
}
format!("{:016x}", hash.0)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn fnv1a_matches_known_hello_vector() {
let mut hash = Fnv(0xcbf2_9ce4_8422_2325);
hash.bytes(b"hello");
assert_eq!(hash.0, 0xa430_d846_80aa_bd0b);
}
#[test]
fn fingerprint_preserves_float_bits_and_field_boundaries() {
let mut mesh = MeshData::new(1, "IfcWall".into(), vec![0.0], vec![1.0], vec![], [1.0; 4]);
let original = mesh_fingerprint(std::slice::from_ref(&mesh));
mesh.positions[0] = -0.0;
assert_ne!(original, mesh_fingerprint(std::slice::from_ref(&mesh)));
mesh.positions = vec![0.0, 1.0];
mesh.normals.clear();
assert_ne!(original, mesh_fingerprint(std::slice::from_ref(&mesh)));
mesh.positions = vec![0.0];
mesh.normals = vec![1.0];
mesh.local_bounds = Some([0.0; 6]);
assert_ne!(original, mesh_fingerprint(std::slice::from_ref(&mesh)));
}
}