use crate::geom_hash::mix64;
use ifc_lite_core::EntityDecoder;
use rustc_hash::FxHashMap;
const MAX_DEPTH: u32 = 256;
const CYCLE_SENTINEL: u128 = 0xC1C1_C1C1_C1C1_C1C1_C1C1_C1C1_C1C1_C1C1;
const FACETED_BREP_TAG: u64 = 0xFACE_7B16_5160_0001;
#[inline]
fn fold(state: u128, v: u64) -> u128 {
let lo = state as u64;
let hi = (state >> 64) as u64;
let lo2 = mix64(lo.wrapping_add(v).wrapping_mul(0x9E37_79B9_7F4A_7C15));
let hi2 = mix64(
hi.rotate_left(23) ^ v.wrapping_mul(0xC2B2_AE3D_27D4_EB4F).wrapping_add(0x1656_67B1),
);
((hi2 as u128) << 64) | (lo2 as u128)
}
#[inline]
fn fold_bytes(mut state: u128, bytes: &[u8]) -> u128 {
state = fold(state, bytes.len() as u64);
let mut chunks = bytes.chunks_exact(8);
for c in &mut chunks {
state = fold(state, u64::from_le_bytes(c.try_into().unwrap()));
}
let rem = chunks.remainder();
if !rem.is_empty() {
let mut buf = [0u8; 8];
buf[..rem.len()].copy_from_slice(rem);
state = fold(state, u64::from_le_bytes(buf));
}
state
}
#[inline]
fn type_token_is(bytes: &[u8], name: &[u8]) -> bool {
let len = bytes.len();
let mut i = 0;
while i < len && matches!(bytes[i], b' ' | b'\r' | b'\n' | b'\t') {
i += 1;
}
if i < len && bytes[i] == b'#' {
i += 1;
while i < len && bytes[i].is_ascii_digit() {
i += 1;
}
while i < len && bytes[i] != b'=' && bytes[i] != b'(' {
i += 1;
}
if i < len && bytes[i] == b'=' {
i += 1;
}
while i < len && matches!(bytes[i], b' ' | b'\r' | b'\n' | b'\t') {
i += 1;
}
}
let ty = &bytes[i..];
ty.len() >= name.len()
&& ty[..name.len()].eq_ignore_ascii_case(name)
&& ty.get(name.len()).is_none_or(|&c| matches!(c, b'(' | b' ' | b'\r' | b'\n' | b'\t'))
}
#[inline]
fn parse_first_ref(bytes: &[u8]) -> Option<u32> {
let len = bytes.len();
let mut i = 0;
while i < len && bytes[i] != b'(' {
i += 1;
}
while i < len && bytes[i] != b'#' {
i += 1;
}
if i >= len {
return None;
}
i += 1; let start = i;
let mut id = 0u32;
while i < len && bytes[i].is_ascii_digit() {
id = id.wrapping_mul(10).wrapping_add((bytes[i] - b'0') as u32);
i += 1;
}
if i > start {
Some(id)
} else {
None
}
}
#[inline]
fn is_faceted_brep(decoder: &mut EntityDecoder, id: u32) -> bool {
decoder
.get_raw_bytes(id)
.is_some_and(|b| type_token_is(b, b"IFCFACETEDBREP"))
}
fn try_faceted_brep_signature(decoder: &mut EntityDecoder, brep_id: u32) -> Option<u128> {
let shell_id = {
let bytes = decoder.get_raw_bytes(brep_id)?;
parse_first_ref(bytes)?
};
let face_ids = decoder.get_entity_ref_list_fast(shell_id)?;
let mut acc = fold(0, FACETED_BREP_TAG);
acc = fold(acc, face_ids.len() as u64);
for face_id in face_ids {
let bound_ids = decoder.get_entity_ref_list_fast(face_id)?;
acc = fold(acc, bound_ids.len() as u64);
for bound_id in bound_ids {
let (loop_id, orientation, is_outer) = decoder.get_face_bound_fast(bound_id)?;
acc = fold(acc, orientation as u64);
acc = fold(acc, is_outer as u64);
let coords = decoder.get_polyloop_coords_cached(loop_id)?;
acc = fold(acc, coords.len() as u64);
for (x, y, z) in coords {
acc = fold(acc, x.to_bits());
acc = fold(acc, y.to_bits());
acc = fold(acc, z.to_bits());
}
}
}
Some(acc)
}
pub fn item_signature(decoder: &mut EntityDecoder, root_id: u32, memo: &mut FxHashMap<u32, u128>) -> u128 {
sig_entity(decoder, root_id, memo, 0)
}
pub fn key_with_params(structural: u128, quality_index: u8, unit_scale: f64, rtc: (f64, f64, f64)) -> u128 {
let mut s = fold(structural, quality_index as u64);
s = fold(s, unit_scale.to_bits());
s = fold(s, rtc.0.to_bits());
s = fold(s, rtc.1.to_bits());
fold(s, rtc.2.to_bits())
}
fn sig_entity(decoder: &mut EntityDecoder, id: u32, memo: &mut FxHashMap<u32, u128>, depth: u32) -> u128 {
if let Some(&s) = memo.get(&id) {
return s;
}
if depth > MAX_DEPTH {
return fold(0xDEAD_BEEF_DEAD_BEEF, id as u64);
}
if is_faceted_brep(decoder, id) {
if let Some(s) = try_faceted_brep_signature(decoder, id) {
memo.insert(id, s);
return s;
}
}
memo.insert(id, CYCLE_SENTINEL); let raw: Vec<u8> = match decoder.get_raw_bytes(id) {
Some(b) => b.to_vec(),
None => {
let s = fold(0, 0x00BA_D0BA_D0BA_D000);
memo.insert(id, s);
return s;
}
};
let acc = sig_walk_bytes(decoder, &raw, memo, depth);
memo.insert(id, acc);
acc
}
fn sig_walk_bytes(
decoder: &mut EntityDecoder,
bytes: &[u8],
memo: &mut FxHashMap<u32, u128>,
depth: u32,
) -> u128 {
let len = bytes.len();
let mut i = 0;
{
let mut k = 0;
while k < len && bytes[k] != b'(' && bytes[k] != b'=' {
k += 1;
}
if k < len && bytes[k] == b'=' {
i = k + 1;
}
}
let mut acc = fold(0, 0x5EED_5EED);
let mut lit_start = i;
let mut in_str = false;
while i < len {
let c = bytes[i];
if in_str {
if c == b'\'' {
if i + 1 < len && bytes[i + 1] == b'\'' {
i += 2;
continue;
}
in_str = false;
}
i += 1;
continue;
}
if c == b'\'' {
in_str = true;
i += 1;
continue;
}
if c == b'#' && i + 1 < len && bytes[i + 1].is_ascii_digit() {
acc = fold_bytes(acc, &bytes[lit_start..i]);
let mut j = i + 1;
let mut rid = 0u32;
while j < len && bytes[j].is_ascii_digit() {
rid = rid.wrapping_mul(10).wrapping_add((bytes[j] - b'0') as u32);
j += 1;
}
let child = sig_entity(decoder, rid, memo, depth + 1);
acc = fold(fold(fold(acc, 1), child as u64), (child >> 64) as u64);
i = j;
lit_start = i;
continue;
}
i += 1;
}
fold_bytes(acc, &bytes[lit_start..len])
}