use crate::common::VertexIndex;
use crate::corner_table::{CornerIndex, TriangleIndex};
use crate::isotropic_remesh::IsotropicRemeshAlgo;
use crate::isotropic_remesh::collapse_edges::CollapseCandidate;
use faer::linalg::solvers::Solve;
use faer::prelude::*;
use rayon::iter::IntoParallelIterator;
#[allow(unused_imports)]
use rayon::prelude::*;
use std::fmt::Debug;
use vector_traits::glam::DVec3;
use vector_traits::num_traits::AsPrimitive;
use vector_traits::prelude::GenericVector3;
fn calculate_quadric_error_faer(q: &Mat<f64>, pos: DVec3) -> f64 {
let v = mat![[pos.x], [pos.y], [pos.z], [1.0]];
let qv = q * &v;
let result = v.transpose() * qv;
result[(0, 0)]
}
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,
{
fn triangle_quadric_faer(&self, t: TriangleIndex) -> Mat<f64> {
let (x, y, z, w) = {
let [c, cn, cp] = t.corners();
let vc = self.vertex_of_corner_f64(c);
let vcn = self.vertex_of_corner_f64(cn);
let vcp = self.vertex_of_corner_f64(cp);
let edge1 = vcn - vc;
let edge2 = vcp - vc;
let normal = edge1.cross(edge2).normalize();
(normal.x, normal.y, normal.z, -normal.dot(vc))
};
mat![
[x * x, x * y, x * z, x * w],
[x * y, y * y, y * z, y * w],
[x * z, y * z, z * z, z * w],
[x * w, y * w, z * w, w * w],
]
}
pub(super) fn build_qem_quadratics_faer(&self) -> Vec<Mat<f64>> {
if self.vertices.len() > 500 {
let triangle_quadrics: Vec<_> = (0..self.corner_table.total_triangle_count())
.into_par_iter()
.with_min_len(100)
.map(TriangleIndex)
.map(|t| {
if !self.corner_table.is_triangle_deleted(t) {
self.triangle_quadric_faer(t)
} else {
Mat::zeros(4, 4)
}
})
.collect();
(0..self.vertices.len() as u32)
.into_par_iter()
.with_min_len(100)
.map(VertexIndex)
.map(|vertex| {
let mut q = Mat::<f64>::zeros(4, 4);
if !self.is_vertex_deleted(vertex) {
let corner_of_vertex = self.corner_table.corner(vertex);
for corner in self.corner_table.iter_ccw_swing(corner_of_vertex) {
q += &triangle_quadrics[corner.triangle().0 as usize];
}
}
q
})
.collect()
} else {
let triangle_quadrics: Vec<_> = (0..self.corner_table.total_triangle_count())
.map(TriangleIndex)
.map(|t| {
if !self.corner_table.is_triangle_deleted(t) {
self.triangle_quadric_faer(t)
} else {
Mat::zeros(4, 4)
}
})
.collect();
(0..self.vertices.len() as u32)
.map(VertexIndex)
.map(|vertex| {
let mut q = Mat::<f64>::zeros(4, 4);
if !self.is_vertex_deleted(vertex) {
let corner_of_vertex = self.corner_table.corner(vertex);
for corner in self.corner_table.iter_ccw_swing(corner_of_vertex) {
q += &triangle_quadrics[corner.triangle().0 as usize];
}
}
q
})
.collect()
}
}
pub(super) fn calculate_edge_collapse_qem_cost_faer(
&self,
corner: CornerIndex, vertex_quadrics: &[Mat<f64>],
) -> Option<(CollapseCandidate<S>, f64, u64)> {
let twin = self.corner_table.twin(corner);
let corner_fan = self.corner_table.ccw_vertex_fan(corner);
let twin_fan = self.corner_table.ccw_vertex_fan(twin);
let (can_collapse_corner, can_collapse_twin) =
self.evaluate_valence_for_collapse(corner_fan.len() as i16, twin_fan.len() as i16);
if !can_collapse_corner && !can_collapse_twin {
return None;
}
let vi0 = self.corner_table.vertex(corner);
let vi1 = self.corner_table.vertex(twin);
let v0 = self.vertex_f64(vi0);
let v1 = self.vertex_f64(vi1);
let mut q_combined = Mat::zeros(4, 4);
q_combined += &vertex_quadrics[vi0.0 as usize];
q_combined += &vertex_quadrics[vi1.0 as usize];
let q_3x3 = q_combined.submatrix(0, 0, 3, 3);
let q_3x1 = q_combined.submatrix(0, 3, 3, 1);
let optimal_pos = {
let lu = q_3x3.partial_piv_lu();
let solution = lu.solve(-q_3x1);
let solution = DVec3::new(solution[(0, 0)], solution[(1, 0)], solution[(2, 0)]);
solution.is_finite().then_some(solution).filter(|solution| {
let d0 = solution.distance_sq(v0);
let d1 = solution.distance_sq(v1);
d0 >= self.params.collapse_qem_min_dist_sq
&& d1 >= self.params.collapse_qem_min_dist_sq
&& !(d0 > self.params.collapse_qem_max_dist_sq
&& d1 > self.params.collapse_qem_max_dist_sq)
})
};
let mut candidates: heapless::Vec<(CollapseCandidate<S>, f64, u64), 4> =
heapless::Vec::new();
if can_collapse_twin {
let cost = calculate_quadric_error_faer(&q_combined, v0);
if cost < self.params.collapse_qem_threshold_sq {
let _ = candidates.push((
CollapseCandidate::keep_pos(corner),
cost,
self.corner_table.canonical_edge_hash(corner),
));
}
}
if can_collapse_corner {
let cost = calculate_quadric_error_faer(&q_combined, v1);
if cost < self.params.collapse_qem_threshold_sq {
let _ = candidates.push((
CollapseCandidate::keep_pos(twin),
cost,
self.corner_table.canonical_edge_hash(twin),
));
}
}
if let Some(optimal_pos) = optimal_pos {
let optimal_cost = calculate_quadric_error_faer(&q_combined, optimal_pos);
if optimal_cost < self.params.collapse_qem_threshold_sq {
let optimal_s_pos = S::Vec3::new(
optimal_pos.x.as_(),
optimal_pos.y.as_(),
optimal_pos.z.as_(),
);
let _ = candidates.push((
CollapseCandidate::move_pos(corner, optimal_s_pos),
optimal_cost,
self.corner_table.canonical_edge_hash(corner),
));
let _ = candidates.push((
CollapseCandidate::move_pos(twin, optimal_s_pos),
optimal_cost,
self.corner_table.canonical_edge_hash(twin),
));
}
}
candidates.sort_unstable_by(|(_, a, ah), (_, b, bh)| {
(a, ah)
.partial_cmp(&(b, bh)) .unwrap_or(std::cmp::Ordering::Equal)
});
for (candidate, cost, hash) in candidates.into_iter() {
let (fan1, fan2) = if candidate.c0p != corner {
(&twin_fan, &corner_fan)
} else {
(&corner_fan, &twin_fan)
};
if let Some(candidate) =
self.evaluate_collapse_candidate_qem::<true>(candidate, fan1, fan2)
{
return Some((candidate, cost, hash));
}
}
None
}
}