use crate::{Aabb3d, MapType, Real, RealConvert, new_map, profile};
use bytemuck_derive::{Pod, Zeroable};
use nalgebra::{Scalar, Unit, Vector3};
use rayon::prelude::*;
use std::borrow::Cow;
use std::cell::RefCell;
use std::collections::BTreeSet;
use std::fmt::Debug;
use thread_local::ThreadLocal;
#[cfg(feature = "vtk_extras")]
use vtkio::model::{Attribute, UnstructuredGridPiece};
#[cfg(feature = "vtk_extras")]
pub use crate::mesh::vtk_helper::{IntoVtkDataSet, IntoVtkUnstructuredGridPiece};
pub fn tri_area<RIn: Real, RComp: Real>(
a: &Vector3<RIn>,
b: &Vector3<RIn>,
c: &Vector3<RIn>,
) -> RComp {
let a = a.convert::<RComp>();
let b = b.convert::<RComp>();
let c = c.convert::<RComp>();
((b - a).cross(&(c - a)))
.norm()
.unscale(RComp::one() + RComp::one())
}
pub fn tri_normal<RIn: Real, RComp: Real>(
a: &Vector3<RIn>,
b: &Vector3<RIn>,
c: &Vector3<RIn>,
) -> Vector3<RComp> {
let a = a.convert::<RComp>();
let b = b.convert::<RComp>();
let c = c.convert::<RComp>();
((b - a).cross(&(c - a))).normalize()
}
pub fn angle_between<RIn: Real, RComp: Real>(
a: &Vector3<RIn>,
b: &Vector3<RIn>,
c: &Vector3<RIn>,
) -> RComp {
let a = a.convert::<RComp>();
let b = b.convert::<RComp>();
let c = c.convert::<RComp>();
((a - b).dot(&(c - b)) / ((a - b).norm() * (c - b).norm())).acos()
}
pub fn tri_min_max_angles<RIn: Real, RComp: Real>(
a: &Vector3<RIn>,
b: &Vector3<RIn>,
c: &Vector3<RIn>,
) -> (RComp, RComp) {
let a = a.convert::<RComp>();
let b = b.convert::<RComp>();
let c = c.convert::<RComp>();
let alpha1: RComp = angle_between(&a, &b, &c);
let alpha2: RComp = angle_between(&b, &c, &a);
let alpha3 = RComp::pi() - alpha1 - alpha2;
(
alpha1.min(alpha2.min(alpha3)),
alpha1.max(alpha2.max(alpha3)),
)
}
pub fn tri_aspect_ratio<RIn: Real, RComp: Real>(
a: &Vector3<RIn>,
b: &Vector3<RIn>,
c: &Vector3<RIn>,
) -> RComp {
let two = RComp::from_i32(2).unwrap();
let sqrt_twelve = RComp::from_i32(12).unwrap().sqrt();
let a = a.convert::<RComp>();
let b = b.convert::<RComp>();
let c = c.convert::<RComp>();
let l0 = (a - b).norm();
let l1 = (b - c).norm();
let l2 = (c - a).norm();
let s = (l0 + l1 + l2) / two;
let area: RComp = tri_area(&a, &b, &c);
let r_in = area / s;
let l_max = l0.max(l1.max(l2));
l_max / (sqrt_twelve * r_in)
}
pub trait TriMesh3dExt<R: Real> {
fn tri_vertices(&self) -> &[Vector3<R>];
fn tri_area_ijk<RComp: Real>(&self, ijk: &[usize; 3]) -> RComp {
let v = self.tri_vertices();
tri_area(&v[ijk[0]], &v[ijk[1]], &v[ijk[2]])
}
fn tri_normal_ijk<RComp: Real>(&self, ijk: &[usize; 3]) -> Vector3<RComp> {
let v = self.tri_vertices();
tri_normal(&v[ijk[0]], &v[ijk[1]], &v[ijk[2]])
}
fn tri_min_max_angles_ijk<RComp: Real>(&self, ijk: &[usize; 3]) -> (RComp, RComp) {
let v = self.tri_vertices();
tri_min_max_angles(&v[ijk[0]], &v[ijk[1]], &v[ijk[2]])
}
fn tri_aspect_ratio<RComp: Real>(&self, ijk: &[usize; 3]) -> RComp {
let v = self.tri_vertices();
tri_aspect_ratio(&v[ijk[0]], &v[ijk[1]], &v[ijk[2]])
}
}
impl<R: Real> TriMesh3dExt<R> for TriMesh3d<R> {
fn tri_vertices(&self) -> &[Vector3<R>] {
&self.vertices
}
}
#[derive(Clone, Debug)]
pub struct MeshAttribute<'a, R: Scalar> {
pub name: String,
pub data: AttributeData<'a, R>,
}
pub type OwnedMeshAttribute<R> = MeshAttribute<'static, R>;
#[derive(Clone, Debug)]
pub enum AttributeData<'a, R: Scalar> {
ScalarU64(Cow<'a, [u64]>),
ScalarReal(Cow<'a, [R]>),
Vector3Real(Cow<'a, [Vector3<R>]>),
}
pub type OwnedAttributeData<R> = AttributeData<'static, R>;
#[derive(Clone, Debug, Default)]
pub struct TriMesh3d<R: Scalar> {
pub vertices: Vec<Vector3<R>>,
pub triangles: Vec<[usize; 3]>,
}
#[derive(Clone, Debug)]
pub enum TriangleOrQuadCell {
Tri([usize; 3]),
Quad([usize; 4]),
}
impl TriangleOrQuadCell {
fn num_vertices(&self) -> usize {
match self {
TriangleOrQuadCell::Tri(_) => 3,
TriangleOrQuadCell::Quad(_) => 4,
}
}
fn vertices(&self) -> &[usize] {
match self {
TriangleOrQuadCell::Tri(v) => v,
TriangleOrQuadCell::Quad(v) => v,
}
}
fn vertices_mut(&mut self) -> &mut [usize] {
match self {
TriangleOrQuadCell::Tri(v) => v,
TriangleOrQuadCell::Quad(v) => v,
}
}
}
#[derive(Clone, Debug, Default)]
pub struct MixedTriQuadMesh3d<R: Scalar> {
pub vertices: Vec<Vector3<R>>,
pub cells: Vec<TriangleOrQuadCell>,
}
#[derive(Clone, Debug, Default)]
pub struct HexMesh3d<R: Scalar> {
pub vertices: Vec<Vector3<R>>,
pub cells: Vec<[usize; 8]>,
}
#[derive(Clone, Debug, Default)]
pub struct PointCloud3d<R: Scalar> {
points: Vec<Vector3<R>>,
indices: Vec<usize>,
}
impl<R: Real> PointCloud3d<R> {
pub fn new(points: impl Into<Vec<Vector3<R>>>) -> Self {
let points = points.into();
let indices = (0..points.len()).collect();
Self { points, indices }
}
}
pub trait Mesh3d<R: Real>
where
Self: Sized,
{
type Cell: CellConnectivity + Clone;
fn vertices(&self) -> &[Vector3<R>];
fn vertices_mut(&mut self) -> &mut [Vector3<R>];
fn cells(&self) -> &[Self::Cell];
fn from_vertices_and_connectivity(
vertices: Vec<Vector3<R>>,
connectivity: Vec<Self::Cell>,
) -> Self;
fn vertex_vertex_connectivity(&self) -> Vec<Vec<usize>> {
profile!("vertex_vertex_connectivity");
let mut connectivity_map: Vec<Vec<usize>> =
vec![Vec::with_capacity(4); self.vertices().len()];
for cell in self.cells() {
for &i in cell.vertices() {
for &j in cell.vertices() {
if i != j && !connectivity_map[i].contains(&j) {
connectivity_map[i].push(j);
}
}
}
}
connectivity_map
}
fn vertex_cell_connectivity(&self) -> Vec<Vec<usize>> {
profile!("vertex_cell_connectivity");
let mut connectivity_map: Vec<Vec<usize>> = vec![Vec::new(); self.vertices().len()];
for (cell_idx, cell) in self.cells().iter().enumerate() {
for &v_i in cell.vertices() {
if !connectivity_map[v_i].contains(&cell_idx) {
connectivity_map[v_i].push(cell_idx);
}
}
}
connectivity_map
}
fn keep_cells(&self, cell_indices: &[usize], keep_vertices: bool) -> Self {
if keep_vertices {
keep_cells_impl(self, cell_indices, &[])
} else {
let vertex_keep_table = vertex_keep_table(self, cell_indices);
keep_cells_impl(self, cell_indices, &vertex_keep_table)
}
}
fn par_clamp_with_aabb(
&self,
aabb: &Aabb3d<R>,
clamp_vertices: bool,
keep_vertices: bool,
) -> Self
where
Self::Cell: Sync,
{
let vertices = self.vertices();
let cells_to_keep = self
.cells()
.par_iter()
.enumerate()
.filter(|(_, cell)| {
cell.vertices()
.iter()
.copied()
.any(|v| aabb.contains_point(&vertices[v]))
})
.map(|(i, _)| i)
.collect::<Vec<_>>();
let mut new_mesh = self.keep_cells(&cells_to_keep, keep_vertices);
if clamp_vertices {
new_mesh.vertices_mut().par_iter_mut().for_each(|v| {
let min = aabb.min();
let max = aabb.max();
v.x = v.x.clamp(min.x, max.x);
v.y = v.y.clamp(min.y, max.y);
v.z = v.z.clamp(min.z, max.z);
});
}
new_mesh
}
}
fn vertex_keep_table<R: Real, MeshT: Mesh3d<R>>(mesh: &MeshT, cell_indices: &[usize]) -> Vec<bool> {
let vertices = mesh.vertices();
let cells = mesh.cells();
{
let mut table = vec![false; vertices.len()];
for cell in cell_indices.iter().copied().map(|c_i| &cells[c_i]) {
for &vertex_index in cell.vertices() {
table[vertex_index] = true;
}
}
table
}
}
fn keep_cells_impl<R: Real, MeshT: Mesh3d<R>>(
mesh: &MeshT,
cell_indices: &[usize],
vertex_keep_table: &[bool],
) -> MeshT {
let vertices = mesh.vertices();
let cells = mesh.cells();
if vertex_keep_table.is_empty() {
MeshT::from_vertices_and_connectivity(
mesh.vertices().to_vec(),
cell_indices.iter().map(|&i| &cells[i]).cloned().collect(),
)
} else {
assert_eq!(mesh.vertices().len(), vertex_keep_table.len());
let old_to_new_label_map = {
let mut label_map = MapType::default();
let mut next_label = 0;
for (i, keep) in vertex_keep_table.iter().enumerate() {
if *keep {
label_map.insert(i, next_label);
next_label += 1;
}
}
label_map
};
let relabeled_cells: Vec<_> = cell_indices
.iter()
.map(|&i| &cells[i])
.cloned()
.map(|mut cell| {
for index in cell.vertices_mut() {
*index = *old_to_new_label_map
.get(index)
.expect("Index must be in map");
}
cell
})
.collect();
let relabeled_vertices: Vec<_> = vertex_keep_table
.iter()
.copied()
.enumerate()
.filter_map(|(i, should_keep)| if should_keep { Some(i) } else { None })
.map(|index| vertices[index])
.collect();
MeshT::from_vertices_and_connectivity(relabeled_vertices, relabeled_cells)
}
}
pub trait CellConnectivity {
fn num_vertices(&self) -> usize {
Self::expected_num_vertices()
}
fn expected_num_vertices() -> usize;
fn vertices(&self) -> &[usize];
fn vertices_mut(&mut self) -> &mut [usize];
}
#[derive(Copy, Clone, Pod, Zeroable)]
#[repr(transparent)]
pub struct TriangleCell(pub [usize; 3]);
#[derive(Copy, Clone, Pod, Zeroable)]
#[repr(transparent)]
pub struct HexCell(pub [usize; 8]);
#[derive(Copy, Clone, Pod, Zeroable)]
#[repr(transparent)]
pub struct PointCell(pub usize);
impl CellConnectivity for TriangleCell {
fn expected_num_vertices() -> usize {
3
}
fn vertices(&self) -> &[usize] {
&self.0[..]
}
fn vertices_mut(&mut self) -> &mut [usize] {
&mut self.0[..]
}
}
impl CellConnectivity for HexCell {
fn expected_num_vertices() -> usize {
8
}
fn vertices(&self) -> &[usize] {
&self.0[..]
}
fn vertices_mut(&mut self) -> &mut [usize] {
&mut self.0[..]
}
}
impl CellConnectivity for TriangleOrQuadCell {
fn expected_num_vertices() -> usize {
4
}
fn num_vertices(&self) -> usize {
self.num_vertices()
}
fn vertices(&self) -> &[usize] {
TriangleOrQuadCell::vertices(self)
}
fn vertices_mut(&mut self) -> &mut [usize] {
TriangleOrQuadCell::vertices_mut(self)
}
}
impl CellConnectivity for PointCell {
fn expected_num_vertices() -> usize {
1
}
fn vertices(&self) -> &[usize] {
std::slice::from_ref(&self.0)
}
fn vertices_mut(&mut self) -> &mut [usize] {
std::slice::from_mut(&mut self.0)
}
}
impl<R: Real> Mesh3d<R> for TriMesh3d<R> {
type Cell = TriangleCell;
fn vertices(&self) -> &[Vector3<R>] {
self.vertices.as_slice()
}
fn vertices_mut(&mut self) -> &mut [Vector3<R>] {
self.vertices.as_mut_slice()
}
fn cells(&self) -> &[TriangleCell] {
self.triangle_cells()
}
fn from_vertices_and_connectivity(
vertices: Vec<Vector3<R>>,
triangles: Vec<Self::Cell>,
) -> Self {
Self {
vertices,
triangles: bytemuck::cast_vec::<TriangleCell, [usize; 3]>(triangles),
}
}
}
impl<R: Real> Mesh3d<R> for HexMesh3d<R> {
type Cell = HexCell;
fn vertices(&self) -> &[Vector3<R>] {
self.vertices.as_slice()
}
fn vertices_mut(&mut self) -> &mut [Vector3<R>] {
self.vertices.as_mut_slice()
}
fn cells(&self) -> &[HexCell] {
bytemuck::cast_slice::<[usize; 8], HexCell>(self.cells.as_slice())
}
fn from_vertices_and_connectivity(vertices: Vec<Vector3<R>>, cells: Vec<HexCell>) -> Self {
Self {
vertices,
cells: bytemuck::cast_vec::<HexCell, [usize; 8]>(cells),
}
}
}
impl<R: Real> Mesh3d<R> for MixedTriQuadMesh3d<R> {
type Cell = TriangleOrQuadCell;
fn vertices(&self) -> &[Vector3<R>] {
self.vertices.as_slice()
}
fn vertices_mut(&mut self) -> &mut [Vector3<R>] {
self.vertices.as_mut_slice()
}
fn cells(&self) -> &[TriangleOrQuadCell] {
&self.cells
}
fn from_vertices_and_connectivity(
vertices: Vec<Vector3<R>>,
cells: Vec<TriangleOrQuadCell>,
) -> Self {
Self { vertices, cells }
}
}
impl<R: Real> Mesh3d<R> for PointCloud3d<R> {
type Cell = PointCell;
fn vertices(&self) -> &[Vector3<R>] {
self.points.as_slice()
}
fn vertices_mut(&mut self) -> &mut [Vector3<R>] {
self.points.as_mut_slice()
}
fn cells(&self) -> &[PointCell] {
bytemuck::cast_slice::<usize, PointCell>(self.indices.as_slice())
}
fn from_vertices_and_connectivity(points: Vec<Vector3<R>>, cells: Vec<PointCell>) -> Self {
Self {
points,
indices: bytemuck::cast_vec::<PointCell, usize>(cells),
}
}
}
impl<R: Real, MeshT: Mesh3d<R> + Clone> Mesh3d<R> for std::borrow::Cow<'_, MeshT> {
type Cell = MeshT::Cell;
fn vertices(&self) -> &[Vector3<R>] {
(*self.as_ref()).vertices()
}
fn vertices_mut(&mut self) -> &mut [Vector3<R>] {
(self.to_mut()).vertices_mut()
}
fn cells(&self) -> &[MeshT::Cell] {
(*self.as_ref()).cells()
}
fn from_vertices_and_connectivity(vertices: Vec<Vector3<R>>, cells: Vec<Self::Cell>) -> Self {
std::borrow::Cow::Owned(MeshT::from_vertices_and_connectivity(vertices, cells).to_owned())
}
}
impl TriangleCell {
pub fn edges(&self) -> impl Iterator<Item = (usize, usize)> + '_ {
(0..3).map(|i| (self.0[i], self.0[(i + 1) % 3]))
}
}
pub struct MeshEdgeInformation {
edge_counts: MapType<[usize; 2], (usize, usize)>,
edge_info: Vec<([usize; 2], usize, usize)>,
}
pub struct EdgeInformation {
pub edge: [usize; 2],
pub edge_sorted: [usize; 2],
pub incident_faces: usize,
pub face: usize,
pub local_edge_index: usize,
}
impl MeshEdgeInformation {
pub fn iter(&self) -> impl Iterator<Item = EdgeInformation> + '_ {
self.edge_counts.iter().map(|(e, (edge_idx, count))| {
let info = &self.edge_info[*edge_idx];
EdgeInformation {
edge: info.0,
edge_sorted: *e,
incident_faces: *count,
face: info.1,
local_edge_index: info.2,
}
})
}
pub fn count_boundary_edges(&self) -> usize {
self.edge_counts
.values()
.filter(|(_, count)| *count == 1)
.count()
}
pub fn count_non_manifold_edges(&self) -> usize {
self.edge_counts
.values()
.filter(|(_, count)| *count > 2)
.count()
}
pub fn boundary_edges(&self) -> impl Iterator<Item = [usize; 2]> + '_ {
self.edge_counts
.values()
.filter(|(_, count)| *count == 1)
.map(move |(edge_idx, _)| self.edge_info[*edge_idx].0)
}
pub fn non_manifold_edges(&self) -> impl Iterator<Item = [usize; 2]> + '_ {
self.edge_counts
.values()
.filter(|(_, count)| *count > 2)
.map(move |(edge_idx, _)| self.edge_info[*edge_idx].0)
}
}
pub struct MeshManifoldInformation {
pub boundary_edges: Vec<[usize; 2]>,
pub non_manifold_edges: Vec<[usize; 2]>,
pub non_manifold_vertices: Vec<usize>,
}
impl MeshManifoldInformation {
pub fn is_closed(&self) -> bool {
self.boundary_edges.is_empty()
}
pub fn is_manifold(&self) -> bool {
self.non_manifold_edges.is_empty() && self.non_manifold_vertices.is_empty()
}
}
impl<R: Real> TriMesh3d<R> {
pub fn triangle_cells(&self) -> &[TriangleCell] {
bytemuck::cast_slice::<[usize; 3], TriangleCell>(self.triangles.as_slice())
}
pub fn clear(&mut self) {
self.vertices.clear();
self.triangles.clear();
}
pub fn append(&mut self, other: &mut TriMesh3d<R>) {
let TriMesh3d {
vertices: new_verts,
triangles: new_tris,
} = other;
let vertex_offset = self.vertices.len();
let tri_offset = self.triangles.len();
self.vertices.append(new_verts);
self.triangles.append(new_tris);
for tri in self.triangles.iter_mut().skip(tri_offset) {
tri[0] += vertex_offset;
tri[1] += vertex_offset;
tri[2] += vertex_offset;
}
}
fn vertex_normal_directions_inplace_assume_zeroed(&self, normal_directions: &mut [Vector3<R>]) {
assert_eq!(normal_directions.len(), self.vertices.len());
for tri_verts in self.triangles.iter() {
let v0 = &self.vertices[tri_verts[0]];
let v1 = &self.vertices[tri_verts[1]];
let v2 = &self.vertices[tri_verts[2]];
let normal = (v1 - v0).cross(&(v2 - v1));
normal_directions[tri_verts[0]] += normal;
normal_directions[tri_verts[1]] += normal;
normal_directions[tri_verts[2]] += normal;
}
}
fn par_vertex_normal_directions_inplace_assume_zeroed(
&self,
normal_directions: &mut [Vector3<R>],
) {
assert_eq!(normal_directions.len(), self.vertices.len());
let tl_normals = ThreadLocal::new();
let init_tl_normals = || RefCell::new(vec![Vector3::zeros(); normal_directions.len()]);
self.triangles.par_chunks(256).for_each(|tri_chunk| {
let mut tl_normals_ref = tl_normals.get_or(init_tl_normals).borrow_mut();
let tl_normals = &mut *tl_normals_ref;
for tri_verts in tri_chunk.iter() {
let v0 = &self.vertices[tri_verts[0]];
let v1 = &self.vertices[tri_verts[1]];
let v2 = &self.vertices[tri_verts[2]];
let normal = (v1 - v0).cross(&(v2 - v1));
tl_normals[tri_verts[0]] += normal;
tl_normals[tri_verts[1]] += normal;
tl_normals[tri_verts[2]] += normal;
}
});
let tl_normals = tl_normals
.into_iter()
.map(|cell| cell.into_inner())
.collect::<Vec<_>>();
normal_directions
.par_iter_mut()
.with_min_len(256)
.enumerate()
.for_each(|(i, out_normal)| {
for tl_normals in tl_normals.iter() {
*out_normal += tl_normals[i];
}
});
}
pub fn vertex_normal_directions_inplace(&self, normal_directions: &mut [Vector3<R>]) {
assert_eq!(normal_directions.len(), self.vertices.len());
for normal in normal_directions.iter_mut() {
normal.fill(R::zero());
}
self.vertex_normal_directions_inplace_assume_zeroed(normal_directions);
}
pub fn vertex_normal_directions(&self) -> Vec<Vector3<R>> {
let mut normal_directions = vec![Vector3::zeros(); self.vertices.len()];
self.vertex_normal_directions_inplace_assume_zeroed(normal_directions.as_mut_slice());
normal_directions
}
fn vertex_normals_inplace_assume_zeroed(&self, normals: &mut [Unit<Vector3<R>>]) {
assert_eq!(normals.len(), self.vertices.len());
{
let normal_directions =
bytemuck::cast_slice_mut::<Unit<Vector3<R>>, Vector3<R>>(normals);
self.vertex_normal_directions_inplace_assume_zeroed(normal_directions);
}
for normal in normals.iter_mut() {
let norm = (normal.x * normal.x + normal.y * normal.y + normal.z * normal.z).sqrt();
*normal.as_mut_unchecked() /= norm;
}
}
fn par_vertex_normals_inplace_assume_zeroed(&self, normals: &mut [Unit<Vector3<R>>]) {
assert_eq!(normals.len(), self.vertices.len());
{
let normal_directions =
bytemuck::cast_slice_mut::<Unit<Vector3<R>>, Vector3<R>>(normals);
self.par_vertex_normal_directions_inplace_assume_zeroed(normal_directions);
}
normals.par_chunks_mut(256).for_each(|normal_chunk| {
for normal in normal_chunk.iter_mut() {
let norm = (normal.x * normal.x + normal.y * normal.y + normal.z * normal.z).sqrt();
*normal.as_mut_unchecked() /= norm;
}
});
}
pub fn vertex_normals_inplace(&self, normals: &mut [Unit<Vector3<R>>]) {
assert_eq!(normals.len(), self.vertices.len());
for normal in normals.iter_mut() {
normal.as_mut_unchecked().fill(R::zero());
}
self.vertex_normals_inplace_assume_zeroed(normals);
}
pub fn vertex_normals(&self) -> Vec<Unit<Vector3<R>>> {
let mut normals = vec![Unit::new_unchecked(Vector3::zeros()); self.vertices.len()];
self.vertex_normals_inplace_assume_zeroed(normals.as_mut_slice());
normals
}
pub fn par_vertex_normals_inplace(&self, normals: &mut [Unit<Vector3<R>>]) {
assert_eq!(normals.len(), self.vertices.len());
normals.par_chunks_mut(256).for_each(|normal_chunk| {
for normal in normal_chunk {
normal.as_mut_unchecked().fill(R::zero())
}
});
self.par_vertex_normals_inplace_assume_zeroed(normals);
}
pub fn par_vertex_normals(&self) -> Vec<Unit<Vector3<R>>> {
let mut normals = vec![Unit::new_unchecked(Vector3::zeros()); self.vertices.len()];
self.par_vertex_normals_inplace_assume_zeroed(normals.as_mut_slice());
normals
}
pub fn compute_edge_information(&self) -> MeshEdgeInformation {
let mut sorted_edges = Vec::new();
let mut edge_info = Vec::new();
let tri_edges: [(usize, usize); 3] = [(0, 1), (1, 2), (2, 0)];
for (tri_idx, tri_conn) in self.triangles.iter().enumerate() {
for (local_idx, (v0, v1)) in tri_edges
.iter()
.copied()
.map(|(i0, i1)| (tri_conn[i0], tri_conn[i1]))
.enumerate()
{
if v0 < v1 {
sorted_edges.push([v0, v1])
} else {
sorted_edges.push([v1, v0])
};
edge_info.push(([v0, v1], tri_idx, local_idx));
}
}
let mut edge_counts = new_map();
for (edge_idx, edge) in sorted_edges.iter().copied().enumerate() {
edge_counts
.entry(edge)
.and_modify(|(_, count)| *count += 1)
.or_insert((edge_idx, 1));
}
MeshEdgeInformation {
edge_counts,
edge_info,
}
}
pub fn find_non_manifold_vertices(&self) -> Vec<usize> {
let mut non_manifold_verts = Vec::new();
let mut tri_fan = Vec::new();
let sort_edge = |edge: (usize, usize)| -> (usize, usize) {
if edge.0 > edge.1 {
(edge.1, edge.0)
} else {
edge
}
};
let is_fan_triangle =
|vert: usize, next_tri: &TriangleCell, current_fan: &[TriangleCell]| -> bool {
let mut is_fan_tri = false;
'edge_loop: for edge in next_tri.edges().map(sort_edge) {
if edge.0 == vert || edge.1 == vert {
for fan_tri in current_fan {
for fan_edge in fan_tri.edges().map(sort_edge) {
if edge == fan_edge {
is_fan_tri = true;
break 'edge_loop;
}
}
}
}
}
is_fan_tri
};
let tris = self.triangle_cells();
let vert_face_connectivity = self.vertex_cell_connectivity();
for vert in 0..self.vertices.len() {
let mut remaining_faces = vert_face_connectivity[vert]
.iter()
.cloned()
.collect::<BTreeSet<_>>();
if remaining_faces.len() > 1 {
tri_fan.push(tris[remaining_faces.pop_first().unwrap()]);
while !remaining_faces.is_empty() {
let mut found_next = None;
for &next_candidate in &remaining_faces {
if is_fan_triangle(vert, &tris[next_candidate], &tri_fan) {
found_next = Some(next_candidate);
break;
}
}
if let Some(next) = found_next {
tri_fan.push(tris[next]);
remaining_faces.remove(&next);
} else {
break;
}
}
if !remaining_faces.is_empty() {
non_manifold_verts.push(vert);
}
tri_fan.clear();
}
}
non_manifold_verts
}
pub fn compute_manifold_information(&self) -> MeshManifoldInformation {
let edges = self.compute_edge_information();
let boundary_edges = edges.boundary_edges().collect();
let non_manifold_edges = edges.non_manifold_edges().collect();
let non_manifold_vertices = self.find_non_manifold_vertices();
MeshManifoldInformation {
boundary_edges,
non_manifold_edges,
non_manifold_vertices,
}
}
}
#[cfg(test)]
mod tri_mesh_tests {
use super::*;
fn mesh_one_tri() -> TriMesh3d<f64> {
TriMesh3d::<f64> {
vertices: vec![
Vector3::new_random(),
Vector3::new_random(),
Vector3::new_random(),
],
triangles: vec![[0, 1, 2]],
}
}
fn mesh_non_manifold_edge() -> TriMesh3d<f64> {
TriMesh3d::<f64> {
vertices: vec![
Vector3::new(0.0, 0.0, 0.0),
Vector3::new(1.0, 0.0, 0.0),
Vector3::new(0.0, 1.0, 0.0),
Vector3::new(1.0, 1.0, 0.0),
Vector3::new(0.0, 0.0, 1.0),
],
triangles: vec![[0, 1, 2], [1, 3, 2], [1, 2, 4]],
}
}
fn mesh_non_manifold_edge_double() -> TriMesh3d<f64> {
TriMesh3d::<f64> {
vertices: vec![
Vector3::new(0.0, 0.0, 0.0),
Vector3::new(1.0, 0.0, 0.0),
Vector3::new(0.0, 1.0, 0.0),
Vector3::new(1.0, 1.0, 0.0),
Vector3::new(0.0, 0.0, 1.0),
],
triangles: vec![[0, 1, 2], [1, 3, 2], [1, 2, 4], [4, 2, 1]],
}
}
fn mesh_non_manifold_vertex() -> TriMesh3d<f64> {
TriMesh3d::<f64> {
vertices: vec![
Vector3::new(1.0, 0.0, 0.0),
Vector3::new(0.0, 1.0, 0.0),
Vector3::new(1.0, 1.0, 0.0),
Vector3::new(2.0, 1.0, 1.0),
Vector3::new(1.0, 2.0, 1.0),
],
triangles: vec![[0, 2, 1], [2, 3, 4]],
}
}
#[test]
fn test_tri_mesh_edge_info() {
let mesh = mesh_non_manifold_edge();
let edges = mesh.compute_edge_information();
for ei in edges.iter() {
if ei.edge_sorted == [1, 2] {
assert_eq!(ei.incident_faces, 3);
} else {
assert_eq!(ei.incident_faces, 1);
}
}
assert_eq!(edges.count_boundary_edges(), 6);
assert_eq!(edges.count_non_manifold_edges(), 1);
}
#[test]
fn test_tri_mesh_non_manifold_vertex_info() {
let mesh = mesh_non_manifold_vertex();
let non_manifold_vertes = mesh.find_non_manifold_vertices();
assert_eq!(non_manifold_vertes, [2]);
}
#[test]
fn test_tri_mesh_manifold_info() {
{
let mesh = mesh_one_tri();
let info = mesh.compute_manifold_information();
assert!(!info.is_closed());
assert!(info.is_manifold());
assert_eq!(info.non_manifold_edges.len(), 0);
assert_eq!(info.non_manifold_vertices.len(), 0);
}
{
let mesh = mesh_non_manifold_edge();
let info = mesh.compute_manifold_information();
assert!(!info.is_closed());
assert!(!info.is_manifold());
assert_eq!(info.non_manifold_edges.len(), 1);
assert_eq!(info.non_manifold_vertices.len(), 0);
}
{
let mesh = mesh_non_manifold_edge_double();
let info = mesh.compute_manifold_information();
assert!(!info.is_closed());
assert!(!info.is_manifold());
assert_eq!(info.non_manifold_edges.len(), 1);
assert_eq!(info.non_manifold_vertices.len(), 0);
}
{
let mesh = mesh_non_manifold_vertex();
let info = mesh.compute_manifold_information();
assert!(!info.is_closed());
assert!(!info.is_manifold());
assert_eq!(info.non_manifold_edges.len(), 0);
assert_eq!(info.non_manifold_vertices.len(), 1);
}
}
}
#[derive(Clone, Debug)]
pub struct MeshWithData<R: Real, MeshT: Mesh3d<R>> {
pub mesh: MeshT,
pub point_attributes: Vec<OwnedMeshAttribute<R>>,
pub cell_attributes: Vec<OwnedMeshAttribute<R>>,
}
impl<R: Real, MeshT: Mesh3d<R>> Mesh3d<R> for MeshWithData<R, MeshT> {
type Cell = MeshT::Cell;
fn vertices(&self) -> &[Vector3<R>] {
self.mesh.vertices()
}
fn vertices_mut(&mut self) -> &mut [Vector3<R>] {
self.mesh.vertices_mut()
}
fn cells(&self) -> &[MeshT::Cell] {
self.mesh.cells()
}
fn from_vertices_and_connectivity(
vertices: Vec<Vector3<R>>,
connectivity: Vec<Self::Cell>,
) -> Self {
MeshWithData::new(MeshT::from_vertices_and_connectivity(
vertices,
connectivity,
))
}
fn keep_cells(&self, cell_indices: &[usize], keep_all_vertices: bool) -> Self {
let mut new_mesh = if keep_all_vertices {
let mut new_mesh = keep_cells_impl(self, cell_indices, &[]);
new_mesh.point_attributes = self.point_attributes.clone();
new_mesh
} else {
let vertex_keep_table = vertex_keep_table(self, cell_indices);
let mut new_mesh = keep_cells_impl(self, cell_indices, &vertex_keep_table);
let vertex_indices = vertex_keep_table
.iter()
.copied()
.enumerate()
.filter_map(|(i, should_keep)| if should_keep { Some(i) } else { None })
.collect::<Vec<_>>();
new_mesh.point_attributes = self
.point_attributes
.iter()
.map(|attr| attr.keep_indices(&vertex_indices))
.collect();
new_mesh
};
let cell_attributes = self
.cell_attributes
.iter()
.map(|attr| attr.keep_indices(cell_indices))
.collect();
new_mesh.cell_attributes = cell_attributes;
new_mesh
}
}
impl<R: Real, MeshT: Mesh3d<R> + Default> Default for MeshWithData<R, MeshT> {
fn default() -> Self {
Self::new(MeshT::default())
}
}
impl<R: Real, MeshT: Mesh3d<R>> MeshWithData<R, MeshT> {
pub fn new(mesh: MeshT) -> Self {
Self {
mesh,
point_attributes: Vec::new(),
cell_attributes: Vec::new(),
}
}
pub fn with_mesh<NewMeshT: Mesh3d<R>>(self, new_mesh: NewMeshT) -> MeshWithData<R, NewMeshT> {
if !self.point_attributes.is_empty() {
assert_eq!(
self.mesh.vertices().len(),
new_mesh.vertices().len(),
"number of vertices should match if there are point attributes"
);
}
if !self.cell_attributes.is_empty() {
assert_eq!(
self.mesh.cells().len(),
new_mesh.cells().len(),
"number of cells should match if there are cell attributes"
)
}
MeshWithData {
mesh: new_mesh,
point_attributes: self.point_attributes,
cell_attributes: self.cell_attributes,
}
}
pub fn with_point_data(mut self, point_attribute: impl Into<OwnedMeshAttribute<R>>) -> Self {
let point_attribute = point_attribute.into();
assert_eq!(point_attribute.data.len(), self.mesh.vertices().len());
self.point_attributes.push(point_attribute);
self
}
pub fn with_cell_data(mut self, cell_attribute: impl Into<OwnedMeshAttribute<R>>) -> Self {
let cell_attribute = cell_attribute.into();
assert_eq!(cell_attribute.data.len(), self.mesh.cells().len());
self.cell_attributes.push(cell_attribute);
self
}
}
impl<'a, R: Real> MeshAttribute<'a, R> {
pub fn new<S: Into<String>>(name: S, data: impl Into<AttributeData<'a, R>>) -> Self {
Self {
name: name.into(),
data: data.into(),
}
}
fn keep_indices(&self, indices: &[usize]) -> OwnedMeshAttribute<R> {
let data: OwnedAttributeData<R> = match &self.data {
AttributeData::ScalarU64(d) => OwnedAttributeData::ScalarU64(Cow::Owned(
indices.iter().copied().map(|i| d[i]).collect(),
)),
AttributeData::ScalarReal(d) => OwnedAttributeData::ScalarReal(Cow::Owned(
indices.iter().copied().map(|i| d[i]).collect(),
)),
AttributeData::Vector3Real(d) => OwnedAttributeData::Vector3Real(Cow::Owned(
indices.iter().copied().map(|i| d[i]).collect::<Vec<_>>(),
)),
};
OwnedMeshAttribute {
name: self.name.clone(),
data,
}
}
}
impl<R: Real> OwnedMeshAttribute<R> {
pub fn new_real_scalar<S: Into<String>>(name: S, data: impl Into<Vec<R>>) -> Self {
Self {
name: name.into(),
data: OwnedAttributeData::ScalarReal(Cow::Owned(data.into())),
}
}
pub fn new_real_vector3<S: Into<String>>(name: S, data: impl Into<Vec<Vector3<R>>>) -> Self {
Self {
name: name.into(),
data: OwnedAttributeData::Vector3Real(Cow::Owned(data.into())),
}
}
#[cfg(feature = "vtk_extras")]
#[cfg_attr(docsrs, doc(cfg(feature = "vtk_extras")))]
fn to_vtk_attribute(&self) -> Attribute {
match &self.data {
OwnedAttributeData::ScalarU64(u64_vec) => {
Attribute::scalars(&self.name, 1).with_data(u64_vec.clone().into_owned())
}
OwnedAttributeData::ScalarReal(real_vec) => {
Attribute::scalars(&self.name, 1).with_data(real_vec.clone().into_owned())
}
OwnedAttributeData::Vector3Real(vec3r_vec) => Attribute::scalars(&self.name, 3)
.with_data(vec3r_vec.iter().flatten().copied().collect::<Vec<R>>()),
}
}
}
impl<'a, R: Real> AttributeData<'a, R> {
fn len(&self) -> usize {
match self {
AttributeData::ScalarU64(v) => v.len(),
AttributeData::ScalarReal(v) => v.len(),
AttributeData::Vector3Real(v) => v.len(),
}
}
}
impl<R: Real, V: Into<Vec<u64>>> From<V> for OwnedAttributeData<R> {
fn from(data: V) -> Self {
Self::ScalarU64(Cow::Owned(data.into()))
}
}
#[cfg(feature = "vtk_extras")]
impl<R, MeshT> MeshWithData<R, MeshT>
where
R: Real,
MeshT: Mesh3d<R>,
for<'a> &'a MeshT: IntoVtkUnstructuredGridPiece,
{
#[cfg_attr(docsrs, doc(cfg(feature = "vtk_extras")))]
fn unstructured_grid(&self) -> UnstructuredGridPiece {
let mut grid_piece: UnstructuredGridPiece = (&self.mesh).into_unstructured_grid();
for point_attribute in &self.point_attributes {
grid_piece
.data
.point
.push(point_attribute.to_vtk_attribute())
}
for cell_attribute in &self.cell_attributes {
grid_piece.data.cell.push(cell_attribute.to_vtk_attribute())
}
grid_piece
}
}
macro_rules! impl_into_vtk {
($name:tt) => {
#[cfg(feature = "vtk_extras")]
#[cfg_attr(docsrs, doc(cfg(feature = "vtk_extras")))]
impl<R: Real> IntoVtkUnstructuredGridPiece for $name<R> {
fn into_unstructured_grid(self) -> UnstructuredGridPiece {
vtk_helper::mesh_to_unstructured_grid(&self)
}
}
#[cfg(feature = "vtk_extras")]
#[cfg_attr(docsrs, doc(cfg(feature = "vtk_extras")))]
impl<R: Real> IntoVtkUnstructuredGridPiece for &$name<R> {
fn into_unstructured_grid(self) -> UnstructuredGridPiece {
vtk_helper::mesh_to_unstructured_grid(self)
}
}
#[cfg(feature = "vtk_extras")]
#[cfg_attr(docsrs, doc(cfg(feature = "vtk_extras")))]
impl<'a, R: Real> IntoVtkUnstructuredGridPiece for std::borrow::Cow<'a, $name<R>> {
fn into_unstructured_grid(self) -> UnstructuredGridPiece {
vtk_helper::mesh_to_unstructured_grid(&self)
}
}
#[cfg(feature = "vtk_extras")]
#[cfg_attr(docsrs, doc(cfg(feature = "vtk_extras")))]
impl<'a, R: Real> IntoVtkUnstructuredGridPiece for &std::borrow::Cow<'a, $name<R>> {
fn into_unstructured_grid(self) -> UnstructuredGridPiece {
vtk_helper::mesh_to_unstructured_grid(self)
}
}
#[cfg(feature = "vtk_extras")]
#[cfg_attr(docsrs, doc(cfg(feature = "vtk_extras")))]
impl<R: Real> IntoVtkUnstructuredGridPiece for &MeshWithData<R, $name<R>> {
fn into_unstructured_grid(self) -> UnstructuredGridPiece {
self.unstructured_grid()
}
}
#[cfg(feature = "vtk_extras")]
#[cfg_attr(docsrs, doc(cfg(feature = "vtk_extras")))]
impl<R: Real> IntoVtkUnstructuredGridPiece for MeshWithData<R, $name<R>> {
fn into_unstructured_grid(self) -> UnstructuredGridPiece {
self.unstructured_grid()
}
}
#[cfg(feature = "vtk_extras")]
#[cfg_attr(docsrs, doc(cfg(feature = "vtk_extras")))]
impl<'a, R: Real> IntoVtkUnstructuredGridPiece
for &MeshWithData<R, std::borrow::Cow<'a, $name<R>>>
{
fn into_unstructured_grid(self) -> UnstructuredGridPiece {
self.unstructured_grid()
}
}
};
}
impl_into_vtk!(TriMesh3d);
impl_into_vtk!(MixedTriQuadMesh3d);
impl_into_vtk!(HexMesh3d);
impl_into_vtk!(PointCloud3d);
#[cfg(feature = "vtk_extras")]
#[cfg_attr(docsrs, doc(cfg(feature = "vtk_extras")))]
pub mod vtk_helper {
use vtkio::IOBuffer;
use vtkio::model::{
Attributes, CellType, Cells, DataSet, UnstructuredGridPiece, VertexNumbers,
};
use super::{
CellConnectivity, HexCell, Mesh3d, PointCell, Real, TriangleCell, TriangleOrQuadCell,
};
#[cfg_attr(docsrs, doc(cfg(feature = "vtk_extras")))]
pub trait HasVtkCellType {
fn vtk_cell_type(&self) -> CellType;
}
#[cfg_attr(docsrs, doc(cfg(feature = "vtk_extras")))]
impl HasVtkCellType for TriangleCell {
fn vtk_cell_type(&self) -> CellType {
CellType::Triangle
}
}
#[cfg_attr(docsrs, doc(cfg(feature = "vtk_extras")))]
impl HasVtkCellType for TriangleOrQuadCell {
fn vtk_cell_type(&self) -> CellType {
match self {
TriangleOrQuadCell::Tri(_) => CellType::Triangle,
TriangleOrQuadCell::Quad(_) => CellType::Quad,
}
}
}
#[cfg_attr(docsrs, doc(cfg(feature = "vtk_extras")))]
impl HasVtkCellType for HexCell {
fn vtk_cell_type(&self) -> CellType {
CellType::Hexahedron
}
}
#[cfg_attr(docsrs, doc(cfg(feature = "vtk_extras")))]
impl HasVtkCellType for PointCell {
fn vtk_cell_type(&self) -> CellType {
CellType::Vertex
}
}
#[cfg_attr(docsrs, doc(cfg(feature = "vtk_extras")))]
pub trait IntoVtkUnstructuredGridPiece {
fn into_unstructured_grid(self) -> UnstructuredGridPiece;
}
#[cfg_attr(docsrs, doc(cfg(feature = "vtk_extras")))]
pub trait IntoVtkDataSet {
fn into_dataset(self) -> DataSet;
}
impl IntoVtkUnstructuredGridPiece for UnstructuredGridPiece {
fn into_unstructured_grid(self) -> UnstructuredGridPiece {
self
}
}
impl<T: IntoVtkUnstructuredGridPiece> IntoVtkDataSet for T {
fn into_dataset(self) -> DataSet {
DataSet::inline(self.into_unstructured_grid())
}
}
impl IntoVtkDataSet for DataSet {
fn into_dataset(self) -> DataSet {
self
}
}
#[cfg_attr(docsrs, doc(cfg(feature = "vtk_extras")))]
pub fn mesh_to_unstructured_grid<'a, R, MeshT>(mesh: &'a MeshT) -> UnstructuredGridPiece
where
R: Real,
MeshT: Mesh3d<R> + 'a,
<MeshT as Mesh3d<R>>::Cell: HasVtkCellType,
{
let points = {
let mut points: Vec<R> = Vec::with_capacity(mesh.vertices().len() * 3);
points.extend(mesh.vertices().iter().flat_map(|p| p.as_slice()));
points
};
let vertices = {
let mut vertices =
Vec::with_capacity(mesh.cells().len() * (MeshT::Cell::expected_num_vertices() + 1));
for cell in mesh.cells().iter() {
vertices.push(cell.num_vertices() as u32);
cell.vertices()
.iter()
.copied()
.for_each(|v| vertices.push(v as u32));
}
vertices
};
let mut cell_types = Vec::with_capacity(mesh.cells().len());
cell_types.extend(mesh.cells().iter().map(|c| c.vtk_cell_type()));
new_unstructured_grid_piece(points, vertices, cell_types)
}
fn new_unstructured_grid_piece<B: Into<IOBuffer>>(
points: B,
vertices: Vec<u32>,
cell_types: Vec<CellType>,
) -> UnstructuredGridPiece {
UnstructuredGridPiece {
points: points.into(),
cells: Cells {
cell_verts: VertexNumbers::Legacy {
num_cells: cell_types.len() as u32,
vertices,
},
types: cell_types,
},
data: Attributes::new(),
}
}
}