use crate::mesh::elements::tri::mapping as tri3;
#[derive(Clone, Copy, Debug)]
pub struct TriangleMeshView<'a> {
nodes: (&'a [f64], &'a [f64], &'a [f64]),
triangles: (&'a [usize], &'a [usize], &'a [usize]),
}
#[inline]
fn triangle_area_from_indices(nodes: (&[f64], &[f64], &[f64]), idx: [usize; 3]) -> f64 {
let n0 = [nodes.0[idx[0]], nodes.1[idx[0]], nodes.2[idx[0]]];
let n1 = [nodes.0[idx[1]], nodes.1[idx[1]], nodes.2[idx[1]]];
let n2 = [nodes.0[idx[2]], nodes.1[idx[2]], nodes.2[idx[2]]];
tri3::area(n0, n1, n2)
}
#[inline]
fn triangle_quality_from_indices(nodes: (&[f64], &[f64], &[f64]), idx: [usize; 3]) -> f64 {
let n0 = [nodes.0[idx[0]], nodes.1[idx[0]], nodes.2[idx[0]]];
let n1 = [nodes.0[idx[1]], nodes.1[idx[1]], nodes.2[idx[1]]];
let n2 = [nodes.0[idx[2]], nodes.1[idx[2]], nodes.2[idx[2]]];
let edge_sq = |a: [f64; 3], b: [f64; 3]| -> f64 {
let dx = a[0] - b[0];
let dy = a[1] - b[1];
let dz = a[2] - b[2];
dx * dx + dy * dy + dz * dz
};
let area = tri3::area(n0, n1, n2);
let edge_sum = edge_sq(n0, n1) + edge_sq(n1, n2) + edge_sq(n2, n0);
4.0 * 3.0_f64.sqrt() * area / edge_sum
}
fn validate_triangle_mesh_geometry(
nodes: (&[f64], &[f64], &[f64]),
triangles: (&[usize], &[usize], &[usize]),
) -> Result<(usize, usize), &'static str> {
let nnode = nodes.0.len();
if nodes.1.len() != nnode || nodes.2.len() != nnode {
return Err("Node coordinate dimension mismatch");
}
let ntri = triangles.0.len();
if triangles.1.len() != ntri || triangles.2.len() != ntri {
return Err("Triangle index dimension mismatch");
}
if triangles
.0
.iter()
.chain(triangles.1.iter())
.chain(triangles.2.iter())
.any(|&idx| idx >= nnode)
{
return Err("Triangle refers to non-existent node");
}
let mut low_quality_triangles = Vec::new();
for i in 0..ntri {
let idx = [triangles.0[i], triangles.1[i], triangles.2[i]];
let area = triangle_area_from_indices(nodes, idx);
if area < 1e-14 {
return Err("Triangle has zero area");
}
let quality = triangle_quality_from_indices(nodes, idx);
if quality < 1e-3 {
low_quality_triangles.push((i, quality, idx));
}
}
if !low_quality_triangles.is_empty() {
let entries = low_quality_triangles
.iter()
.map(|(i, quality, idx)| format!("{i} (quality={quality:.3e}, indices={idx:?})"))
.collect::<Vec<_>>()
.join(", ");
eprintln!(
"warning: {} triangles have very poor aspect ratio: {entries}",
low_quality_triangles.len()
);
}
Ok((nnode, ntri))
}
impl<'a> TriangleMeshView<'a> {
pub fn new(
nodes: (&'a [f64], &'a [f64], &'a [f64]),
triangles: (&'a [usize], &'a [usize], &'a [usize]),
) -> Result<Self, &'static str> {
validate_triangle_mesh_geometry(nodes, triangles)?;
Ok(Self { nodes, triangles })
}
#[inline]
pub fn nnode(&self) -> usize {
self.nodes.0.len()
}
#[inline]
pub fn len(&self) -> usize {
self.triangles.0.len()
}
#[inline]
pub fn triangle_indices(&self, i: usize) -> [usize; 3] {
[
self.triangles.0[i],
self.triangles.1[i],
self.triangles.2[i],
]
}
#[inline]
pub fn triangle_nodes(&self, i: usize) -> [[f64; 3]; 3] {
let idx = self.triangle_indices(i);
idx.map(|k| [self.nodes.0[k], self.nodes.1[k], self.nodes.2[k]])
}
#[inline]
pub fn triangle_nodes_and_indices(&self, i: usize) -> ([[f64; 3]; 3], [usize; 3]) {
let idx = self.triangle_indices(i);
let nodes = idx.map(|k| [self.nodes.0[k], self.nodes.1[k], self.nodes.2[k]]);
(nodes, idx)
}
#[inline]
pub fn validate_nodal_values(&self, s: &[f64]) -> Result<(), &'static str> {
if s.len() != self.nnode() {
return Err("Nodal scalar dimension mismatch");
}
Ok(())
}
#[inline]
pub fn triangle_scalars(&self, i: usize, s: &[f64]) -> [f64; 3] {
let idx = self.triangle_indices(i);
idx.map(|k| s[k])
}
}
#[cfg(test)]
mod tests {
use super::TriangleMeshView;
#[test]
fn test_triangle_mesh_view_valid() {
let nodes = (
&[0.0, 1.0, 0.0][..],
&[0.0, 0.0, 1.0][..],
&[0.0, 0.0, 0.0][..],
);
let triangles = (&[0usize][..], &[1usize][..], &[2usize][..]);
let s = &[1.0, -0.5, 0.25][..];
let view = TriangleMeshView::new(nodes, triangles).unwrap();
assert_eq!(view.len(), 1);
assert_eq!(view.nnode(), 3);
view.validate_nodal_values(s).unwrap();
let tri_nodes = view.triangle_nodes(0);
let tri_s = view.triangle_scalars(0, s);
assert_eq!(
tri_nodes,
[[0.0, 0.0, 0.0], [1.0, 0.0, 0.0], [0.0, 1.0, 0.0]]
);
assert_eq!(tri_s, [1.0, -0.5, 0.25]);
}
#[test]
fn test_triangle_mesh_view_rejects_zero_area_triangle() {
let nodes = (
&[0.0, 1.0, 2.0][..],
&[0.0, 0.0, 0.0][..],
&[0.0, 0.0, 0.0][..],
);
let triangles = (&[0usize][..], &[1usize][..], &[2usize][..]);
let err = TriangleMeshView::new(nodes, triangles).unwrap_err();
assert_eq!(err, "Triangle has zero area");
}
#[test]
fn test_triangle_mesh_view_rejects_repeated_vertex_triangle() {
let nodes = (
&[0.0, 1.0, 0.0][..],
&[0.0, 0.0, 1.0][..],
&[0.0, 0.0, 0.0][..],
);
let triangles = (&[0usize][..], &[1usize][..], &[1usize][..]);
let err = TriangleMeshView::new(nodes, triangles).unwrap_err();
assert_eq!(err, "Triangle has zero area");
}
}