mod dihedral_angle;
#[cfg(test)]
mod tests;
use crate::bvh::StaticBVH;
use crate::common::VertexIndex;
use crate::common::macros::{integrity_assert, integrity_assert_eq, integrity_println};
use crate::common::remesh_error::RemeshError;
use crate::corner_table::{CornerIndex, CornerTable};
use crate::isotropic_remesh::{FlipStrategy, RemeshParams};
use crate::prelude::{CollapseStrategy, SplitStrategy};
use crate::util::index_pool::{Allocation, SifoPool};
use std::fmt::Debug;
use std::marker::PhantomData;
use vector_traits::glam::DVec3;
use vector_traits::num_traits::AsPrimitive;
use vector_traits::prelude::{GenericVector3, SimdUpgradable};
use vob::Vob;
pub struct IsotropicRemesh<S, V, const ENABLE_UNSAFE: bool = false>
where
S: crate::common::sealed::ScalarType,
V: Copy + From<[S; 3]> + Into<[S; 3]> + Sync + 'static,
{
pub(super) vertices: Vec<S::Vec3>, pub(super) indices: Vec<VertexIndex>,
pub(super) max_index: u32,
pub(super) params: RemeshParams<S>,
pub(super) _pd: PhantomData<V>,
}
impl<S, V, const ENABLE_UNSAFE: bool> IsotropicRemesh<S, V, ENABLE_UNSAFE>
where
S: crate::common::sealed::ScalarType,
f64: AsPrimitive<S>,
V: Debug + Copy + From<[S; 3]> + Into<[S; 3]> + Sync + 'static,
{
pub(crate) fn new_algo(self) -> Result<IsotropicRemeshAlgo<S, V, ENABLE_UNSAFE>, RemeshError> {
let mut vertices = self.vertices;
let corner_table = if self.params.fix_non_manifold {
if let Some(vertex_limit) = self.params.print_stats {
IsotropicRemeshAlgo::<S, V, ENABLE_UNSAFE>::print_input_status(
vertex_limit,
&vertices,
&self.indices,
);
}
CornerTable::from_non_manifold_triangles(self.indices, self.max_index, &mut vertices)?
} else {
CornerTable::from_manifold_triangles(self.indices, self.max_index)?
};
let bvh = if self.params.smooth_weight.is_some() {
StaticBVH::new(corner_table.vertex_of_corners(), &vertices)
} else {
StaticBVH::default()
};
let algo = IsotropicRemeshAlgo::<S, V, ENABLE_UNSAFE>::new(
vertices,
corner_table,
bvh,
self.params,
);
Ok(algo)
}
pub fn run<I>(self, iterations: I) -> Result<(Vec<V>, Vec<u32>), RemeshError>
where
I: TryInto<u32>,
<I as TryInto<u32>>::Error: Debug,
{
let mut algo = self.new_algo()?;
algo.run_internal(iterations)?;
algo.compress_mesh()
}
#[inline(always)]
pub(crate) fn to_glam(v: V) -> S::Vec3 {
v.into().into()
}
}
pub struct IsotropicRemeshAlgo<S, V, const ENABLE_UNSAFE: bool = false>
where
S: crate::common::sealed::ScalarType,
V: Copy + From<[S; 3]> + Into<[S; 3]> + Sync + 'static,
{
pub(super) vertices: Vec<S::Vec3>, pub(super) vertices_buffer: Vec<S::Vec3>, pub(super) bvh: StaticBVH<S>,
pub(super) vertex_pool: SifoPool<VertexIndex, ENABLE_UNSAFE>,
pub(super) corner_table: CornerTable<ENABLE_UNSAFE>,
pub(super) params: RemeshParams<S>,
pub(super) dirty_vertices: DirtyVertices<ENABLE_UNSAFE>,
_pd: PhantomData<V>,
}
impl<S, V, const ENABLE_UNSAFE: bool> IsotropicRemeshAlgo<S, V, ENABLE_UNSAFE>
where
S: crate::common::sealed::ScalarType,
f64: AsPrimitive<S>,
V: Debug + Copy + From<[S; 3]> + Into<[S; 3]> + Sync + 'static,
{
pub(super) fn new(
vertices: Vec<S::Vec3>,
corner_table: CornerTable<ENABLE_UNSAFE>,
bvh: StaticBVH<S>,
params: RemeshParams<S>,
) -> Self {
let vertices_len = vertices.len();
let vertices_buffer = vec![S::Vec3::ZERO; vertices_len];
Self {
vertices,
vertices_buffer,
bvh,
corner_table,
vertex_pool: SifoPool::<VertexIndex, ENABLE_UNSAFE>::new(vertices_len as u32),
params,
dirty_vertices: DirtyVertices::with_capacity(vertices_len),
_pd: PhantomData,
}
}
pub(crate) fn run_internal<I>(&mut self, iterations: I) -> Result<(), RemeshError>
where
I: TryInto<u32>,
<I as TryInto<u32>>::Error: Debug,
{
self.params.iterations = self.check_iterations(iterations)?;
self.params.validate()?;
self.params.print_essentials();
if let Some(vertex_limit) = self.params.print_stats {
Self::print_input_status(
vertex_limit,
&self.vertices,
self.corner_table.vertex_of_corners(),
);
}
for _i in 0..self.params.iterations {
integrity_println!(
"####### running remesh iteration {:?}. Vertices:{} allocated vertices:{} triangles:{}",
_i,
self.vertex_pool.active_count(),
self.vertices.len(),
self.corner_table.active_triangles()
);
integrity_assert_eq!(self.vertex_pool.total_count() as usize, self.vertices.len());
if !self.remesh_iteration() {
integrity_println!("terminating with no changes after {} iterations ", _i + 1);
break;
}
}
Ok(())
}
fn remesh_iteration(&mut self) -> bool {
let mut did_something = false;
#[cfg(feature = "integrity_check")]
self.check_mesh_integrity("start of iteration").unwrap();
match self.params.split_strategy {
SplitStrategy::DihedralAngle => {
did_something |= self.split_edges_dihedral(self.params.split_threshold_sq);
#[cfg(feature = "integrity_check")]
self.check_mesh_integrity("after split_edges_dihedral()")
.unwrap();
}
SplitStrategy::Disabled => {}
_ => unimplemented!(),
}
match self.params.collapse_strategy {
CollapseStrategy::DihedralAngle => {
did_something |= self.collapse_edges_dihedral(self.params.collapse_threshold_sq);
#[cfg(feature = "integrity_check")]
self.check_mesh_integrity("after collapse_edges_dihedral()")
.unwrap();
}
CollapseStrategy::Qem => {
did_something |= self.collapse_edges_qem(self.params.collapse_threshold_sq);
#[cfg(feature = "integrity_check")]
self.check_mesh_integrity("after collapse_edges_qem()")
.unwrap();
}
CollapseStrategy::Disabled => {}
_ => unimplemented!(),
}
if let Some(defrag_ratio) = self.params.corner_table_defragmentation_ratio {
let deleted_triangles: f64 = self.corner_table.deleted_triangle_count().as_();
let total_triangle_count: f64 = self.corner_table.total_triangle_count().as_();
if (deleted_triangles / total_triangle_count).as_() > defrag_ratio {
self.corner_table.defragment_triangles();
#[cfg(feature = "integrity_check")]
self.check_mesh_integrity("after defragment_triangles()")
.unwrap();
}
}
match self.params.flip_strategy {
FlipStrategy::Disabled => {}
FlipStrategy::Valence => {
did_something |= self.flip_edges_dihedral();
#[cfg(feature = "integrity_check")]
self.check_mesh_integrity("after flip_edges_dihedral()")
.unwrap();
}
FlipStrategy::WeightedQuality {
quality_threshold: quality_weight,
} => {
did_something |= self.flip_edges_aspect_ratio(quality_weight);
#[cfg(feature = "integrity_check")]
self.check_mesh_integrity("after flip_edges_aspect_ratio()")
.unwrap();
}
}
if let Some(weight) = self.params.smooth_weight {
did_something |= self.smooth_project_vertices(
weight,
self.params.max_projection_distance_sq,
self.params.smooth_normal_threshold,
);
#[cfg(feature = "integrity_check")]
self.check_mesh_integrity("after smooth_vertices()")
.unwrap();
}
did_something
}
#[inline(always)]
pub(crate) fn from_glam(v: S::Vec3) -> V {
v.into().into()
}
#[inline(always)]
pub(crate) fn edge_length_sq(&self, v1_idx: VertexIndex, v2_idx: VertexIndex) -> S {
let v1 = self.vertex(v1_idx);
let v2 = self.vertex(v2_idx);
(v2 - v1).magnitude_sq()
}
#[inline(always)]
pub(crate) fn vertex_of_corner(&self, ci: CornerIndex) -> S::Vec3 {
debug_assert!(ci.is_valid());
let vi = self.corner_table.vertex(ci);
debug_assert!(vi.is_valid());
self.vertex(vi)
}
#[inline(always)]
pub(crate) fn vertex_of_corner_f64(&self, ci: CornerIndex) -> DVec3 {
self.vertex_f64(self.corner_table.vertex(ci))
}
#[inline(always)]
pub(crate) fn is_vertex_deleted(&self, vertex: VertexIndex) -> bool {
!vertex.is_valid()
|| if cfg!(any(feature = "integrity_check", debug_assertions)) {
let slow = !self.vertex_pool.is_used(vertex);
let fast = !self.corner_table.corner(vertex).is_valid();
assert_eq!(slow, fast);
fast
} else {
!self.corner_table.corner(vertex).is_valid()
}
}
#[inline(always)]
pub(crate) fn add_vertex(&mut self, v: S::Vec3Simd) -> VertexIndex {
match self.vertex_pool.pop() {
Allocation::New(index) => {
self.vertices.push(S::Vec3::from_simd(v));
self.corner_table.handle_new_vertex(index);
integrity_println!("Added new vertex {index:?}:{:?}", v.into());
index
}
Allocation::Reused(index) => {
self.vertices[index.0 as usize] = S::Vec3::from_simd(v);
self.corner_table.handle_reused_vertex(index);
integrity_println!("Reused vertex {index:?}:{:?}", v.into());
index
}
}
}
#[inline(always)]
pub(crate) fn delete_vertex(&mut self, vertex: VertexIndex) {
integrity_assert!(vertex.is_valid());
integrity_assert!(
!self.is_vertex_deleted(vertex),
"vertex {vertex:?} already deleted"
);
self.vertex_pool.push(vertex);
self.corner_table.delete_vertex(vertex);
}
#[inline(always)]
pub(crate) fn vertex(&self, index: VertexIndex) -> S::Vec3 {
#[cfg(feature = "integrity_check")]
assert!(index.is_valid() && self.vertex_pool.is_used(index));
debug_assert!(index.is_valid(), "{index:?}");
if ENABLE_UNSAFE {
unsafe { *self.vertices.get_unchecked(index.0 as usize) }
} else {
self.vertices[index.0 as usize]
}
}
#[inline(always)]
pub(crate) fn vertex_f64(&self, index: VertexIndex) -> DVec3 {
let v = self.vertex(index);
DVec3::new(v[0].as_(), v[1].as_(), v[2].as_())
}
pub(crate) fn print_input_status(
vertex_limit: usize,
vertices: &[S::Vec3],
indices: &[VertexIndex],
) {
if vertices.len() <= vertex_limit {
println!(
"//******** vertices:{} indices:{}",
vertices.len(),
indices.len()
);
println!(
"let vertices = {:?};",
vertices
.iter()
.map(|v| (*v).into())
.collect::<Vec<[S; 3]>>(),
);
println!(
"let indices = {:?};",
indices.iter().map(|i| i.0).collect::<Vec<u32>>(),
);
} else {
println!("********");
println!("vertices:{} indices:{}", vertices.len(), indices.len());
}
}
}
pub(crate) struct DirtyVertices<const ENABLE_UNSAFE: bool>(Vob);
impl<const ENABLE_UNSAFE: bool> DirtyVertices<ENABLE_UNSAFE> {
pub(crate) fn with_capacity(capacity: usize) -> Self {
Self(Vob::from_elem(false, capacity))
}
#[inline(always)]
pub(crate) fn mark_dirty(&mut self, vertex: VertexIndex) {
integrity_assert!(vertex.is_valid());
if ENABLE_UNSAFE {
unsafe {
let _ = self.0.set_unchecked(vertex.0 as usize, true);
}
} else {
let _ = self.0.set(vertex.0 as usize, true);
}
}
#[inline(always)]
pub(crate) fn is_dirty(&self, vertex: VertexIndex) -> bool {
integrity_assert!(vertex.is_valid());
if ENABLE_UNSAFE {
unsafe { self.0.get_unchecked(vertex.0 as usize) }
} else {
self.0.get(vertex.0 as usize).unwrap()
}
}
#[inline(always)]
pub(crate) fn clear(&mut self) {
self.0.set_all(false);
}
pub(crate) fn prepare(&mut self, vertex_count: usize) {
self.clear();
if self.0.len() < vertex_count {
self.0.resize(vertex_count, false);
}
}
}