remesh 0.0.5

Isotropic remeshing library
Documentation
// SPDX-License-Identifier: MIT OR Apache-2.0
// Copyright (c) 2025 lacklustr@protonmail.com https://github.com/eadf

use crate::common::macros::{integrity_assert_eq, integrity_println};
use crate::corner_table::CornerIndex;
use crate::isotropic_remesh::IsotropicRemeshAlgo;
use std::fmt::Debug;
use vector_traits::num_traits::AsPrimitive;
use vector_traits::prelude::GenericVector3;

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,
{
    /// Split an edge in a manifold mesh (everything is CCW)
    ///
    /// ```text
    /// Notation: cₓ = corner, Vₓ = vertex, tₓ = triangle
    /// cₓn = cₓ.next, cₓp = cₓ.prev, cₓo = cₓ.opposite
    ///
    /// Input: corner 'c₀' with vertex 'V₀', where edge to split is V₀->V₂
    ///              c₀o
    ///      V₃ ─────────────── V₂        V₃ ─────────────── V₂
    ///      │c₀p         c₀n / │         │ \ c₃p      c₃n / │
    ///      │              /   │         │c₀p\    t₃    /c₂p│
    ///      │     t₀     / c₁  │         │     \      /     │
    ///      │          /       │    =>   │  t₀   \c₃/   t₂  │
    ///  c₀no│        /         │c₁no     │   c₀n  M c₂      │
    ///      │      /           │         │       /  \       │
    ///      │    /      t₁     │         │     /  c₁  \     │
    ///      │c₀/               │         │c₀ /    t₁    \c₂n│
    ///      │/ c₁n         c₁p │         │ / c₁n      c₁p \ │
    ///      V₀ ────────────── V₁         V₀ ────────────── V₁
    ///              c₁o
    /// ```
    pub(crate) fn split_edges_dihedral(&mut self, adjusted_target_length_sq: S) -> bool {
        let original_num_triangles = self.corner_table.active_triangles();
        let relaxed_max_valence = self.params.max_valence + 1;
        // Split with relaxed valence for the longest edges
        const RELAXED_EDGE_COUNT: usize = 3;

        //#[cfg(feature = "integrity_check")]
        //self.dump_status("pre-split_long_edges()");

        // Collect only long edges that need splitting
        let mut edges_to_split = Vec::new();

        integrity_println!(
            "####### split_edges_dihedral threshold:{}",
            adjusted_target_length_sq.sqrt()
        );

        // Iterate over all corners, filtering for valid vertices
        for (corner, vertex) in self
            .corner_table
            .vertex_of_corners()
            .iter()
            .cloned()
            .enumerate()
            .filter_map(|(c, v)| v.is_valid().then_some((CornerIndex(c as u32), v)))
        {
            // Only process each edge once by comparing with twin corner
            let twin_corner = self.corner_table.twin(corner);
            if corner.0 < twin_corner.0 {
                let v1 = self.vertex(vertex);
                let v2 = self.vertex_of_corner(twin_corner);

                // Check if edge is too long - only add to list if it needs splitting
                let edge_length_sq = (v2 - v1).magnitude_sq();
                if edge_length_sq > adjusted_target_length_sq {
                    edges_to_split.push((
                        corner,
                        edge_length_sq,
                        self.corner_table.canonical_edge_hash(corner),
                    ));
                }
            }
        }

        // Sort by edge length (longest first) - now we only sort the edges that need splitting
        edges_to_split.sort_unstable_by(|a, &b| {
            (b.1, b.2)
                .partial_cmp(&(a.1, a.2))
                .unwrap_or(std::cmp::Ordering::Equal)
        });

        #[cfg(feature = "integrity_check")]
        if edges_to_split.len() > 2 {
            let last = edges_to_split.len().saturating_sub(1);
            debug_assert!(
                edges_to_split[0].1 >= edges_to_split[last].1,
                "first {:.3} < last {:.3}",
                edges_to_split[0].1,
                edges_to_split[last].1
            );
        }

        // Split edges: longest first
        for (count, (c0, _, _)) in edges_to_split.into_iter().enumerate() {
            // Re-verify edge still exists and is still long (topology might have changed)
            let (c0n, c0p) = self.corner_table.next_prev(c0);

            let v1 = self.vertex_of_corner(c0);
            let v2 = self.vertex_of_corner(c0n);
            let edge_length_sq = (v2 - v1).magnitude_sq();
            if edge_length_sq > adjusted_target_length_sq {
                // Check valence of the four vertices that will be affected by the split

                // Get opposite triangle (c1)
                let c1p = self.corner_table.opposite(c0p);
                integrity_assert_eq!(c1p, self.corner_table.prev(self.corner_table.twin(c0)));

                // Check valence of all four surrounding vertices
                let valence_v1 = self.corner_table.valence(c1p);
                let valence_v3 = self.corner_table.valence(c0p);

                // After split:
                // - V0 and V2 will have their valence unchanged (edge removed, edge added)
                // - V1 and V3 will have their valence increased by 1 (new edge to M)
                let will_exceed_valence = if count < RELAXED_EDGE_COUNT {
                    valence_v1 >= relaxed_max_valence || valence_v3 >= relaxed_max_valence
                } else {
                    valence_v1 >= self.params.max_valence || valence_v3 >= self.params.max_valence
                };

                if will_exceed_valence {
                    #[cfg(feature = "integrity_check")]
                    {
                        let v1 = self.corner_table.vertex(c1p);
                        let v2 = self.corner_table.vertex(c0n);
                        let valence_v0 = self.corner_table.valence(c0);
                        let valence_v2 = self.corner_table.valence(c0n);
                        println!(
                            "Skipping edge split {:?}-{:?}: would exceed max valence (v0:{}, v1:{}, v2:{}, v3:{})",
                            v1, v2, valence_v0, valence_v1, valence_v2, valence_v3
                        );
                    }
                    continue;
                }

                #[cfg(feature = "integrity_check")]
                {
                    let valence_v0 = self.corner_table.valence(c0);
                    let valence_v2 = self.corner_table.valence(c0n);
                    println!(
                        "Edge length²:{edge_length_sq} split_threshold_sq:{adjusted_target_length_sq} (valences: v0:{}, v1:{}, v2:{}, v3:{})",
                        valence_v0, valence_v1, valence_v2, valence_v3
                    );
                }

                self.split_manifold_edge(c0);

                #[cfg(feature = "integrity_check")]
                self.check_mesh_integrity(
                    format!(
                        "after manifold edge split {:?}-{:?}",
                        self.corner_table.vertex(c0),
                        self.corner_table.vertex(c0n)
                    )
                    .as_str(),
                )
                .unwrap();
            }
        }
        //println!("split_long_edges() finished. Changes made:{}", original_num_triangles != self.corner_table.active_triangles());
        original_num_triangles != self.corner_table.active_triangles()
    }
}