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::VertexIndex;
use crate::common::macros::{
    integrity_assert, integrity_assert_eq, integrity_assert_ne, integrity_println,
};
use crate::corner_table::{CornerIndex, VertexFan};
use crate::isotropic_remesh::IsotropicRemeshAlgo;
use std::fmt::Debug;
use vector_traits::num_traits::AsPrimitive;

#[derive(Copy, Clone)]
pub(crate) struct CollapseCandidate<S>
where
    S: crate::common::sealed::ScalarType,
    f64: AsPrimitive<S>,
{
    pub c0p: CornerIndex,
    /// if new_pos==None: => collapse (`c0p -> c0p.next`) edge, keep V(`c0p`), delete(V(`c0p.next`))
    /// else: collapse (`c0p` -> `c0p.next`) edge, keep V(`c0p`), delete(V(`c0p.next`)), then move V(`c0p`) to `pos`
    pub new_pos: Option<S::Vec3>,
}

impl<S> CollapseCandidate<S>
where
    S: crate::common::sealed::ScalarType,
    f64: AsPrimitive<S>,
{
    pub fn keep_pos(c0p: CornerIndex) -> Self {
        Self { c0p, new_pos: None }
    }

    pub fn move_pos(c0p: CornerIndex, new_pos: S::Vec3) -> Self {
        Self {
            c0p,
            new_pos: Some(new_pos),
        }
    }
}

pub(crate) struct CollapseData {
    pub(crate) c0: CornerIndex,
    pub(crate) c0p: CornerIndex,
    pub(crate) vic0: VertexIndex,  // R
    pub(crate) vic0p: VertexIndex, // V₀
    // fan goes around c0 CCW, c0_fan[0] == c0
    pub(crate) c0_fan: VertexFan,
}

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(crate) fn collapse_edge_candidate(&mut self, candidate: CollapseCandidate<S>) {
        if let Some(new_pos) = candidate.new_pos {
            let vi = self.corner_table.vertex(candidate.c0p);
            integrity_println!(
                "moving V₀:{vi:?} from {:?} to {:?}",
                self.vertices[vi.0 as usize].into(),
                new_pos.into()
            );
            self.vertices[vi.0 as usize] = new_pos;
            self.collapse_manifold_edge(self.collapse_data_from_c0p(candidate.c0p));
        } else {
            self.collapse_manifold_edge(self.collapse_data_from_c0p(candidate.c0p));
        }
    }

    pub(crate) fn collapse_manifold_edge(&mut self, collapse_data: CollapseData) {
        integrity_assert!(
            collapse_data.c0_fan.len() >= 3,
            "fan size was only {}",
            collapse_data.c0_fan.len()
        );
        if collapse_data.c0_fan.len() == 3 {
            self.collapse_manifold_edge_valence_3(collapse_data);
        } else {
            self.collapse_manifold_edge_valence_4_plus(collapse_data);
        }
    }

    /// ```text
    /// Removes the R vertex by collapsing the RV₀ edge (R will take the value of V₀ in the node graph)
    /// while the triangles t₀ and t₁ are removed.
    ///     Vₖ ─────────────── Vₖ₋₁
    ///     │ \             / │
    ///     │   \    tₖ    /  │
    ///     │     \      /    │
    ///     │       \cₖ/ tₖ₋₁ │
    /// t₀o │ t₀ c₀  R ────── V₂
    ///     │      / c₁ \c₂   │
    ///     │c₀p /       \ t₂ │
    ///     │  /     t₁   \   │
    ///     │/              \ │
    ///     V₀ ────────────── V₁
    ///              t₁o
    /// Input: c₀: The corner index at R in the RVₖV₀ triangle.
    /// V(c₀)==R, V(c₀.prev())==V₀, V(c₀.next())==Vₖ
    ///
    /// Triangles:
    /// t₀: RVₖV₀ (delete)
    /// t₁: RV₀V₁ (delete)
    /// t₀o: opposite t₀ across VₖV₀ edge (update opposite)
    /// t₁o: opposite t₁ across V₀V₁ edge (update opposite)
    /// t₁: next after t₀ in R fan (update vertex + opposite)
    /// t₂: next after t₁ in R fan (update vertex + opposite)
    /// tₖ: last triangle in R fan (update vertex + opposite)
    /// t₂..tₖ₋₁: other triangles in R fan (update vertex only)
    /// ```
    fn collapse_manifold_edge_valence_4_plus(&mut self, cd: CollapseData) {
        let c0 = cd.c0;

        integrity_println!(
            "before manifold edge collapse c0p:{}, c0:{}",
            self.dbg_edge(cd.c0p),
            self.dbg_edge(c0)
        );

        //#[cfg(feature = "integrity_check")]
        //self.print_debug_stats();

        //integrity_println!("{c0} cop:{}", self.coplanarity(c0));

        let vr = cd.vic0;
        integrity_assert!(vr.is_valid());

        // Get V₀ (the vertex R will collapse into)
        let v0 = cd.vic0p;
        integrity_assert!(v0.is_valid());

        integrity_println!(
            "collapse manifold edge c₀p:{:?} R:{vr:?}{:?} V₀:{v0:?}{:?}",
            self.dbg_edge(cd.c0p),
            self.vertex(vr).into(),
            self.vertex(v0).into()
        );

        // All corner indices CCW around R
        let r_fan = cd.c0_fan.as_slice();

        #[cfg(feature = "integrity_check")]
        let r_fan_str = self.dbg_fan_prev(r_fan);

        integrity_assert!(r_fan.len() >= 3);

        let k = r_fan.len().saturating_sub(1);
        integrity_assert_eq!(r_fan[0], c0);

        let c1 = r_fan[1];
        let c2 = r_fan[2];
        let ck = r_fan[k];

        integrity_println!("c₀:{c0:?} c₁:{c1:?} cₖ:{ck:?}");
        integrity_println!("c0/R surrounding vertices: {}", r_fan_str);
        integrity_println!("c0/R fan: {}", self.dbg_vertex_fan(r_fan));
        integrity_println!(
            "c0p/V₀ fan: {}",
            self.dbg_vertex_fan(&self.corner_table.ccw_vertex_fan(cd.c0p))
        );
        integrity_println!(
            "c0p/c₀ surrounding vertices: {}",
            self.dbg_fan_prev(&self.corner_table.ccw_vertex_fan(cd.c0p))
        );

        // Set (potential) new default corners of vertex Vₖ and V₁
        let c2n = self.corner_table.next(c2);
        let v1 = self.corner_table.vertex(c2n);
        self.corner_table.data.set_default_corner_of_vertex(v1, c2n);

        let ckn = self.corner_table.next(ck);
        let vk = self.corner_table.vertex(ckn);
        self.corner_table.data.set_default_corner_of_vertex(vk, ckn);
        integrity_assert_ne!(vk, v1);
        integrity_assert_ne!(c2n, ckn);
        integrity_assert!(vk.is_valid());
        integrity_assert!(v1.is_valid());
        integrity_assert!(c2n.is_valid());
        integrity_assert!(ckn.is_valid());

        // Opposite corner c₀ across VₖV₀
        let t0o = self.corner_table.opposite(c0);
        // Opposite corner c₁ across V₀V₁
        let t1o = self.corner_table.opposite(c1);

        integrity_println!("t₀o:{t0o:?} t₁o:{t1o:?}");

        // Set vertex of all the interior triangles (t₂..tₖ) to V₀
        for &ci in &r_fan[2..] {
            self.corner_table.link_corner_vertex(ci, v0);
        }

        // Stitch opposites at the ends of the fan
        //   - triangle before t₀ (cₖ) ↔ t₀o
        self.corner_table
            .set_dual_opposite(self.corner_table.next(ck), t0o);
        //   - triangle after t₁ (c₂) ↔ t₁o
        self.corner_table
            .set_dual_opposite(self.corner_table.prev(c2), t1o);

        // Release the two removed triangles t₀ and t₁
        self.corner_table
            .free_two_adjacent_triangles(c0.triangle(), c1.triangle());

        // Delete the R vertex
        self.delete_vertex(vr);

        // Set c₂ as default corner of V₀
        self.corner_table.data.set_default_corner_of_vertex(v0, c2);

        self.dirty_vertices.mark_dirty(vr);
        self.dirty_vertices.mark_dirty(v0);

        integrity_println!("updated cₖ:{}", self.corner_table.dbg_triangle(ck));
        integrity_println!(
            "t₀o:{} t₁o:{}",
            self.corner_table.dbg_triangle(t0o),
            self.corner_table.dbg_triangle(t1o)
        );
        integrity_println!("updated c₂:{}", self.corner_table.dbg_triangle(c2));

        integrity_println!("{}", r_fan_str);
        integrity_assert_eq!(self.check_vertex_fan_dbg(v0), Ok(()));
    }

    /// ```text
    /// Specialized collapse for valence 3 case: removes R vertex by collapsing RV₀ edge.
    /// Only three triangles exist in the R fan: t₀, t₁, and t₂.
    /// We delete t₀ and t₂, and repurpose t₁ to become the V₀V₁V₂ triangle.
    ///     V₂ ────────────
    ///     │ \ c₂p        \    c₂o
    ///     │c₀n\      t₂   \
    ///     │     \          \
    ///     │       \  c₂     │
    /// c₀o │ t₀ c₀  R        │
    ///     │      / c₁ \     │
    ///     │c₀p /       \c₂n │
    ///     │  /     t₁   \   │
    ///     │/ c₁n      c₁p \ │
    ///     V₀ ────────────── V₁
    ///              c₁o
    /// Input: c₀: The corner index at R in the RV₂V₀ triangle.
    /// V(c₀)==V(c₁)==V(c₂)==R, V(c₀.prev())==V₀, V(c₀.next())==V₂
    ///
    /// Triangles:
    /// t₀: RV₂V₀ (delete)
    /// t₁: RV₀V₁ (repurpose to V₂V₀V₁)
    /// t₂: RV₁V₂ (delete)
    /// Opposites:
    /// c₀o: opposite c₀ across V₂V₀ edge (update opposite to point to c₁p)
    /// c₁o: opposite c₁ across V₀V₁ edge (keep as is)
    /// c₂o: opposite c₂ across V₂V₁ edge (update opposite to point to c₁n)
    /// ```
    fn collapse_manifold_edge_valence_3(&mut self, cd: CollapseData) {
        let c0 = cd.c0;
        let c0n = self.corner_table.next(c0);
        #[cfg(any(feature = "integrity_check", debug_assertions))]
        let c0p = cd.c0p;
        let vr = cd.vic0;
        let v0 = cd.vic0p;

        // All corner indices CCW around R (should have exactly 3 elements)
        let r_fan = cd.c0_fan.as_slice();
        debug_assert_eq!(r_fan.len(), 3, "This specialization requires valence == 3");

        let c1 = r_fan[1];
        let c2 = r_fan[2];

        #[cfg(feature = "integrity_check")]
        let dbg_c1_triangle = self.corner_table.dbg_triangle(c1);
        #[cfg(feature = "integrity_check")]
        let dbg_c0p_triangle = self.corner_table.dbg_triangle(c0p);

        // Get the vertices
        let (c1n, c1p) = self.corner_table.next_prev(c1); //  V₀,V₁ in t₁
        let v1 = self.corner_table.vertex(c1p);

        #[cfg(any(feature = "integrity_check", debug_assertions))]
        let (c2n, c2p) = self.corner_table.next_prev(c2); // corner at V₁,V₂ in t₂

        let v2 = self.corner_table.vertex(c0n);
        integrity_assert_eq!(v0, self.corner_table.vertex(c0p));
        integrity_assert_eq!(v0, self.corner_table.vertex(c1n));
        integrity_assert_eq!(v1, self.corner_table.vertex(c2n));
        integrity_assert_eq!(v2, self.corner_table.vertex(c2p));

        integrity_assert_ne!(v0, v1);
        integrity_assert_ne!(v0, v2);
        integrity_assert_ne!(v1, v2);

        // Set default corners for V₁ and V₂ (to corners in the repurposed t₁)
        // After repurposing, t₁ will be V₀V₁V₂ with corners at c₁, c₁n, c₁p

        self.corner_table.data.set_default_corner_of_vertex(v0, c1n);
        self.corner_table.data.set_default_corner_of_vertex(v1, c1p);
        self.corner_table.data.set_default_corner_of_vertex(v2, c1);

        // Get opposite corners
        let c0o = self.corner_table.opposite(c0); // opposite across V₂V₀
        #[cfg(feature = "integrity_check")]
        let c1o = self.corner_table.opposite(c1); // opposite across V₀V₁ (unchanged)
        let c2o = self.corner_table.opposite(c2); // opposite across V₁Vₖ

        // Repurpose t₁: change c₁ from R to V₂, making triangle V₂V₀V₁
        // Currently t₁ is: c₁(R),  c₁n(V₀), c₁p(V₁)
        // We want:         c₁(V₂), c₁n(V₀), c₁p(V₁)
        self.corner_table.link_corner_vertex(c1, v2);

        // Update opposites for the repurposed t₁
        // - c₀o (opposite across V₂V₀) now connects to c₁p (V₂V₀ edge in repurposed t₁)
        self.corner_table.set_dual_opposite(c1p, c0o);
        // - c₂o (opposite across V₁V₂) now connects to c₁n (V₁V₂ edge in repurposed t₁)
        self.corner_table.set_dual_opposite(c1n, c2o);
        // - t₁o (opposite across V₀V₁) remains connected to c₁ (V₀V₁ edge in repurposed t₁)
        // (no change needed as c₁ still represents this edge, just different orientation)
        #[cfg(feature = "integrity_check")]
        self.corner_table.assert_dual_opposite(c1, c1o);

        // Delete triangles t₀ and t₂ (we have already set default vertices)
        self.corner_table.free_triangle_some_cleanup(c0.triangle());
        self.corner_table.free_triangle_some_cleanup(c2.triangle());

        // Delete vertex R
        self.delete_vertex(vr);

        integrity_println!(
            "after collapse_manifold_edge_valence3 c1:before:{dbg_c1_triangle} after:{}, c0p:before{dbg_c0p_triangle} after:{}",
            self.corner_table.dbg_triangle(c1),
            self.corner_table.dbg_triangle(c0p)
        );

        // Track modifications
        self.dirty_vertices.mark_dirty(vr);
        self.dirty_vertices.mark_dirty(v0);
        self.dirty_vertices.mark_dirty(v1);
        self.dirty_vertices.mark_dirty(v2);
    }

    pub(super) fn evaluate_valence_for_collapse(
        &self,
        corner_valence: i16,
        twin_valence: i16,
    ) -> (bool, bool) {
        let max_val = self.params.max_valence;

        if (4..=max_val).contains(&corner_valence) && (4..=max_val).contains(&twin_valence) {
            (true, true)
        } else if (4..=max_val).contains(&corner_valence) && twin_valence == 3 {
            (true, false)
        } else if corner_valence == 3 && (4..=max_val).contains(&twin_valence) {
            (false, true)
        } else {
            (false, false)
        }
    }
}