quadrs 1.0.0

Experimental quad remeshing library and tools based on Instant Meshes
Documentation
use crate::meshio::Vec3;

pub const INVALID: usize = usize::MAX;

#[derive(Clone)]
pub struct TriMesh {
    pub vertices: Vec<Vec3>,
    pub faces: Vec<[usize; 3]>,
}

#[derive(Clone)]
pub struct DirectedEdges {
    pub v2e: Vec<usize>,
    pub e2e: Vec<usize>,
    pub boundary: Vec<bool>,
    pub nonmanifold: Vec<bool>,
}

pub fn dedge_prev_3(edge: usize) -> usize {
    if edge % 3 == 0 {
        edge + 2
    } else {
        edge - 1
    }
}

pub fn dedge_next_3(edge: usize) -> usize {
    if edge % 3 == 2 {
        edge - 2
    } else {
        edge + 1
    }
}

pub fn build_directed_edges(mesh: &TriMesh) -> DirectedEdges {
    let mut v2e = vec![INVALID; mesh.vertices.len()];
    let mut next_from_vertex = vec![INVALID; mesh.faces.len() * 3];

    for (face_index, face) in mesh.faces.iter().enumerate() {
        for corner in 0..3 {
            let current = face[corner];
            let next = face[(corner + 1) % 3];
            if current == next {
                continue;
            }
            let edge_id = face_index * 3 + corner;
            next_from_vertex[edge_id] = v2e[current];
            v2e[current] = edge_id;
        }
    }

    let mut e2e = vec![INVALID; mesh.faces.len() * 3];
    let mut nonmanifold = vec![false; mesh.vertices.len()];
    for (face_index, face) in mesh.faces.iter().enumerate() {
        for corner in 0..3 {
            let current = face[corner];
            let next = face[(corner + 1) % 3];
            if current == next {
                continue;
            }
            let edge_id = face_index * 3 + corner;
            let mut candidate = v2e[next];
            let mut opposite = INVALID;
            while candidate != INVALID {
                let candidate_face = mesh.faces[candidate / 3];
                let candidate_next = candidate_face[candidate % 3];
                let candidate_to = candidate_face[(candidate % 3 + 1) % 3];
                if candidate_next == next && candidate_to == current {
                    if opposite == INVALID {
                        opposite = candidate;
                    } else {
                        nonmanifold[current] = true;
                        nonmanifold[next] = true;
                        opposite = INVALID;
                        break;
                    }
                }
                candidate = next_from_vertex[candidate];
            }
            if opposite != INVALID && edge_id < opposite {
                e2e[edge_id] = opposite;
                e2e[opposite] = edge_id;
            }
        }
    }

    let mut boundary = vec![false; mesh.vertices.len()];
    for vertex in 0..mesh.vertices.len() {
        let edge = v2e[vertex];
        if edge == INVALID || nonmanifold[vertex] {
            v2e[vertex] = INVALID;
            continue;
        }
        let mut cursor = edge;
        let start = edge;
        let mut best = edge;
        loop {
            best = best.min(cursor);
            let previous = e2e[dedge_prev_3(cursor)];
            if previous == INVALID {
                best = cursor;
                boundary[vertex] = true;
                break;
            }
            cursor = previous;
            if cursor == start {
                break;
            }
        }
        v2e[vertex] = best;
    }

    DirectedEdges {
        v2e,
        e2e,
        boundary,
        nonmanifold,
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn builds_opposites_for_two_triangles() {
        let mesh = TriMesh {
            vertices: vec![
                Vec3::new(0.0, 0.0, 0.0),
                Vec3::new(1.0, 0.0, 0.0),
                Vec3::new(1.0, 1.0, 0.0),
                Vec3::new(0.0, 1.0, 0.0),
            ],
            faces: vec![[0, 1, 2], [0, 2, 3]],
        };
        let dedge = build_directed_edges(&mesh);
        assert_ne!(dedge.e2e[2], INVALID);
        assert_eq!(dedge.e2e[dedge.e2e[2]], 2);
    }
}