#[cfg(test)]
mod tests;
use crate::IndexType;
use crate::hash_grid::{SignedPrimInt, get_range};
use crate::prelude::*;
use crate::threading::{ThreadingKernel, dedup_with_hash_type};
use crate::util::Aabb;
use num_traits::{Float, ToPrimitive};
use std::fmt::Debug;
use std::hash::Hash;
use std::ops::AddAssign;
use vob::Vob;
pub trait ScalarKernel: Float + Debug + AddAssign + Send + Sync + 'static {
type Bits: Hash + Eq + Copy;
fn to_bits(self) -> Self::Bits;
fn distance_squared(a: [Self; 3], b: [Self; 3]) -> Self;
const ZERO: Self;
const ONE: Self;
const TWO: Self;
const QUARTER_OF_MAX: Self;
const FOUR: Self;
const HALF: Self;
fn dedup_with_optimal_hash<
Index: IndexType,
Vout: Into<[Self; 3]> + From<[Self; 3]> + Clone + Sync,
Threading: ThreadingKernel,
Topology: TopologyPolicy,
>(
vertices: &[impl Into<[Self; 3]> + Clone + Sync],
indices: &[Index],
tolerance: Self,
prune_degenerate: PruneDegenerateEnum,
used_vertices: Option<Vob>,
allow_scaling: bool,
) -> Result<(Vec<Vout>, Vec<Index>), DeDupError>;
fn optimize_grid_tolerance_and_type(
longest_axis: Self,
tolerance: Self,
allow_scaling: bool,
) -> Result<(Self, SignedPrimInt), DeDupError>;
}
impl ScalarKernel for f32 {
const ZERO: Self = 0.0;
const ONE: Self = 1.0;
const TWO: Self = 2.0;
const QUARTER_OF_MAX: Self = f32::MAX / 4.0;
const FOUR: Self = 4.0;
const HALF: Self = 0.5;
type Bits = u32;
#[inline(always)]
fn to_bits(self) -> Self::Bits {
<f32>::to_bits(self)
}
#[cfg(not(feature = "simd"))]
#[inline(always)]
fn distance_squared(a: [f32; 3], b: [f32; 3]) -> f32 {
(a[0] - b[0]).powi(2) + (a[1] - b[1]).powi(2) + (a[2] - b[2]).powi(2)
}
#[cfg(feature = "simd")]
#[inline(always)]
fn distance_squared(a: [f32; 3], b: [f32; 3]) -> f32 {
use wide::f32x4;
let a_vec = f32x4::new([a[0], a[1], a[2], 0.0]);
let b_vec = f32x4::new([b[0], b[1], b[2], 0.0]);
let diff = a_vec - b_vec;
let squared = diff * diff;
let squared = squared.as_array_ref();
squared[0] + squared[1] + squared[2]
}
fn dedup_with_optimal_hash<
Index: IndexType,
Vout: Into<[f32; 3]> + From<[f32; 3]> + Clone + Sync,
Threading: ThreadingKernel,
Topology: TopologyPolicy,
>(
vertices: &[impl Into<[Self; 3]> + Clone + Sync],
indices: &[Index],
tolerance: Self,
prune_degenerate: PruneDegenerateEnum,
used_vertices: Option<Vob>,
allow_scaling: bool,
) -> Result<(Vec<Vout>, Vec<Index>), DeDupError> {
let aabb = Aabb::<Self>::new_from_checked_data(vertices, &used_vertices)?;
let (longest_axis, center) = aabb.longest_axis_and_center();
match get_range(aabb.clone(), center, longest_axis, tolerance, allow_scaling)? {
(updated_tolerance, SignedPrimInt::I16) => {
dedup_with_hash_type::<Self, Index, Vout, i16, Threading, Topology>(
vertices,
indices,
aabb,
center,
longest_axis,
updated_tolerance,
prune_degenerate,
used_vertices,
)
}
(updated_tolerance, SignedPrimInt::I32) => {
dedup_with_hash_type::<Self, Index, Vout, i32, Threading, Topology>(
vertices,
indices,
aabb,
center,
longest_axis,
updated_tolerance,
prune_degenerate,
used_vertices,
)
}
_ => unreachable!(),
}
}
fn optimize_grid_tolerance_and_type(
longest_axis: Self,
tolerance: Self,
allow_scaling: bool,
) -> Result<(Self, SignedPrimInt), DeDupError> {
let grid_size = tolerance * Self::TWO;
let longest_axis = longest_axis.abs();
const MANTISSA_LIMIT: f32 = ((1_u64 << f32::MANTISSA_DIGITS) - 8) as f32;
let max_safe_coord = MANTISSA_LIMIT * grid_size;
let updated_tolerance = if longest_axis >= max_safe_coord {
if allow_scaling {
longest_axis.abs() / (MANTISSA_LIMIT * Self::TWO)
} else {
#[allow(clippy::uninlined_format_args)]
return Err(DeDupError(format!(
"Aabb max centered coordinate {} too large for grid_size {}. Maximum safe coordinate: {}",
longest_axis, grid_size, max_safe_coord
)));
}
} else {
tolerance
};
let grid_size = updated_tolerance * Self::TWO;
debug_assert!(if allow_scaling {
let max_safe_coord = MANTISSA_LIMIT * grid_size;
longest_axis >= max_safe_coord
} else {
true
});
if let Some(max_abs_i128) = (longest_axis / grid_size).to_i128() {
if max_abs_i128 < i16::MAX as i128 {
Ok((updated_tolerance, SignedPrimInt::I16))
} else if max_abs_i128 < i32::MAX as i128 {
Ok((updated_tolerance, SignedPrimInt::I32))
} else {
unreachable!()
}
} else {
unreachable!()
}
}
}
impl ScalarKernel for f64 {
const ZERO: Self = 0.0;
const ONE: Self = 1.0;
const TWO: Self = 2.0;
const QUARTER_OF_MAX: Self = f64::MAX / 4.0;
const FOUR: Self = 4.0;
const HALF: Self = 0.5;
type Bits = u64;
#[inline(always)]
fn to_bits(self) -> Self::Bits {
<f64>::to_bits(self)
}
#[cfg(not(feature = "simd"))]
#[inline(always)]
fn distance_squared(a: [Self; 3], b: [Self; 3]) -> Self {
(a[0] - b[0]).powi(2) + (a[1] - b[1]).powi(2) + (a[2] - b[2]).powi(2)
}
#[cfg(feature = "simd")]
#[inline(always)]
fn distance_squared(a: [Self; 3], b: [Self; 3]) -> Self {
use wide::f64x4;
let a_vec = f64x4::new([a[0], a[1], a[2], 0.0]);
let b_vec = f64x4::new([b[0], b[1], b[2], 0.0]);
let diff = a_vec - b_vec;
let squared = diff * diff;
let squared = squared.as_array_ref();
squared[0] + squared[1] + squared[2]
}
fn optimize_grid_tolerance_and_type(
longest_axis: Self,
tolerance: Self,
allow_scaling: bool,
) -> Result<(f64, SignedPrimInt), DeDupError> {
let grid_size = tolerance * Self::TWO;
let longest_axis = longest_axis.abs();
const MANTISSA_LIMIT: f64 = ((1_u64 << f64::MANTISSA_DIGITS) - 8) as f64;
let max_safe_coord = MANTISSA_LIMIT * grid_size;
let updated_tolerance = if longest_axis >= max_safe_coord {
if allow_scaling {
longest_axis.abs() / (MANTISSA_LIMIT * Self::TWO)
} else {
#[allow(clippy::uninlined_format_args)]
return Err(DeDupError(format!(
"Aabb max centered coordinate {} too large for grid_size {}. Maximum safe coordinate: {}",
longest_axis, grid_size, max_safe_coord
)));
}
} else {
tolerance
};
let grid_size = updated_tolerance * Self::TWO;
debug_assert!(if allow_scaling {
let max_safe_coord = MANTISSA_LIMIT * grid_size;
longest_axis >= max_safe_coord
} else {
true
});
if let Some(max_abs_i128) = (longest_axis / grid_size).to_i128() {
if max_abs_i128 < i16::MAX as i128 {
Ok((updated_tolerance, SignedPrimInt::I16))
} else if max_abs_i128 < i32::MAX as i128 {
Ok((updated_tolerance, SignedPrimInt::I32))
} else {
Ok((updated_tolerance, SignedPrimInt::I64))
}
} else {
unreachable!()
}
}
fn dedup_with_optimal_hash<
Index: IndexType,
Vout: Into<[Self; 3]> + From<[Self; 3]> + Clone + Sync,
Threading: ThreadingKernel,
Topology: TopologyPolicy,
>(
vertices: &[impl Into<[Self; 3]> + Clone + Sync],
indices: &[Index],
tolerance: Self,
prune_degenerate: PruneDegenerateEnum,
used_vertices: Option<Vob>,
allow_scaling: bool,
) -> Result<(Vec<Vout>, Vec<Index>), DeDupError> {
let aabb = Aabb::<Self>::new_from_checked_data(vertices, &used_vertices)?;
let (longest_axis, center) = aabb.longest_axis_and_center();
match get_range(
aabb.clone(),
center,
longest_axis,
tolerance * 2.0,
allow_scaling,
)? {
(updated_tolerance, SignedPrimInt::I16) => {
dedup_with_hash_type::<Self, Index, Vout, i16, Threading, Topology>(
vertices,
indices,
aabb,
center,
longest_axis,
updated_tolerance,
prune_degenerate,
used_vertices,
)
}
(updated_tolerance, SignedPrimInt::I32) => {
dedup_with_hash_type::<Self, Index, Vout, i32, Threading, Topology>(
vertices,
indices,
aabb,
center,
longest_axis,
updated_tolerance,
prune_degenerate,
used_vertices,
)
}
(updated_tolerance, SignedPrimInt::I64) => {
dedup_with_hash_type::<Self, Index, Vout, i64, Threading, Topology>(
vertices,
indices,
aabb,
center,
longest_axis,
updated_tolerance,
prune_degenerate,
used_vertices,
)
}
}
}
}
impl<T: ScalarKernel> Scalar for T {}