#[cfg(test)]
mod test;
use crate::{
geometry::{
Coordinate, Coordinates, Direction,
mesh::{
Connectivity,
tessellation::{D, Tessellation},
},
},
math::{CrossProduct, FxHashMap, FxHashSet, Quantity, Scalar, Tensor},
units::{Area, Dimensionless, Length},
};
use std::array::from_fn;
const CREASE_COSINE: Scalar = 0.866_025_403_784_438_6;
pub struct Features {
corners: Vec<Coordinate<D>>,
creases: Vec<[Coordinate<D>; 2]>,
}
pub struct FeatureIndex<'a> {
features: &'a Features,
corners: FxHashMap<[i64; D], Vec<usize>>,
creases: FxHashMap<[i64; D], Vec<usize>>,
sprawling: Vec<usize>,
spacing: Quantity<Length>,
}
const SPAN: i64 = 64;
fn triangles(tessellation: &Tessellation) -> Vec<[usize; D]> {
match &tessellation.mesh().connectivities()[0] {
Connectivity::Triangular(triangles) => triangles.iter().copied().collect(),
_ => Vec::new(),
}
}
fn key(one: usize, two: usize) -> [usize; 2] {
if one < two { [one, two] } else { [two, one] }
}
pub(crate) fn crease_edges(
triangles: &[[usize; D]],
coordinates: &Coordinates<D>,
) -> Vec<[usize; 2]> {
let normals: Vec<Direction<D>> = triangles
.iter()
.map(|&[a, b, c]| {
(&coordinates[b] - &coordinates[a])
.cross(&(&coordinates[c] - &coordinates[a]))
.normalized()
})
.collect();
let mut incident = FxHashMap::<[usize; 2], Vec<usize>>::default();
triangles
.iter()
.enumerate()
.for_each(|(index, &[a, b, c])| {
[key(a, b), key(b, c), key(c, a)]
.into_iter()
.for_each(|edge| incident.entry(edge).or_default().push(index))
});
let mut sharp: Vec<[usize; 2]> = incident
.iter()
.filter(|(_, triangles)| triangles.len() == 2)
.filter(|(_, triangles)| &normals[triangles[0]] * &normals[triangles[1]] < CREASE_COSINE)
.map(|(&edge, _)| edge)
.collect();
sharp.sort_unstable();
sharp
}
pub(crate) fn crease_nodes(
triangles: &[[usize; D]],
coordinates: &Coordinates<D>,
) -> FxHashSet<usize> {
crease_edges(triangles, coordinates)
.into_iter()
.flatten()
.collect()
}
impl Features {
pub fn corners(&self) -> &[Coordinate<D>] {
&self.corners
}
pub fn creases(&self) -> &[[Coordinate<D>; 2]] {
&self.creases
}
pub(super) fn of(tessellation: &Tessellation) -> Self {
let coordinates = tessellation.mesh().coordinates();
let triangles = triangles(tessellation);
let sharp = crease_edges(&triangles, coordinates);
let mut through = FxHashMap::<usize, Vec<usize>>::default();
sharp.iter().for_each(|&[a, b]| {
through.entry(a).or_default().push(b);
through.entry(b).or_default().push(a)
});
let mut nodes: Vec<usize> = through.keys().copied().collect();
nodes.sort_unstable();
let corners = nodes
.into_iter()
.filter(|node| {
let others = &through[node];
match others.len() {
2 => {
let one = (&coordinates[others[0]] - &coordinates[*node]).normalized();
let two = (&coordinates[others[1]] - &coordinates[*node]).normalized();
&one * &two > -CREASE_COSINE
}
_ => true,
}
})
.map(|node| coordinates[node].clone())
.collect();
let creases = sharp
.into_iter()
.map(|[a, b]| [coordinates[a].clone(), coordinates[b].clone()])
.collect();
Self { corners, creases }
}
pub fn index(&self, radius: Quantity<Length>) -> FeatureIndex<'_> {
let spacing = if radius > Quantity::new(0.0) {
radius
} else {
Quantity::new(1.0)
};
let mut corners = FxHashMap::<[i64; D], Vec<usize>>::default();
self.corners.iter().enumerate().for_each(|(index, point)| {
corners.entry(cell(point, spacing)).or_default().push(index)
});
let mut creases = FxHashMap::<[i64; D], Vec<usize>>::default();
let mut sprawling = Vec::new();
self.creases.iter().enumerate().for_each(|(index, [a, b])| {
let (low, high) = (cell(a, spacing), cell(b, spacing));
let span: [i64; D] = from_fn(|axis| (high[axis] - low[axis]).abs() + 1);
if span.iter().product::<i64>() > SPAN {
return sprawling.push(index);
}
for i in low[0].min(high[0])..=low[0].max(high[0]) {
for j in low[1].min(high[1])..=low[1].max(high[1]) {
for k in low[2].min(high[2])..=low[2].max(high[2]) {
creases.entry([i, j, k]).or_default().push(index)
}
}
}
});
FeatureIndex {
features: self,
corners,
creases,
sprawling,
spacing,
}
}
}
fn cell(point: &Coordinate<D>, spacing: Quantity<Length>) -> [i64; D] {
from_fn(|axis| (point[axis] / spacing).floor().value() as i64)
}
fn closest_on(segment: &[Coordinate<D>; 2], point: &Coordinate<D>) -> Coordinate<D> {
let along = &segment[1] - &segment[0];
let length = &along * &along;
if length == Quantity::<Area>::new(0.0) {
return segment[0].clone();
}
let fraction = Quantity::<Dimensionless>::new(
((point - &segment[0]) * &along / length)
.value()
.clamp(0.0, 1.0),
);
&segment[0] + &(along * fraction)
}
impl FeatureIndex<'_> {
fn about(&self, point: &Coordinate<D>) -> Vec<[i64; D]> {
let middle = cell(point, self.spacing);
(-1..=1)
.flat_map(|i| {
(-1..=1).flat_map(move |j| {
(-1..=1).map(move |k| [middle[0] + i, middle[1] + j, middle[2] + k])
})
})
.collect()
}
pub fn nearest_corner(
&self,
point: &Coordinate<D>,
radius: Quantity<Length>,
) -> Option<(usize, Quantity<Length>)> {
self.about(point)
.iter()
.filter_map(|cell| self.corners.get(cell))
.flatten()
.map(|&index| (index, (&self.features.corners[index] - point).norm()))
.filter(|&(_, distance)| distance < radius)
.min_by(|(_, one), (_, two)| one.total_cmp(two))
}
pub fn nearest_crease(
&self,
point: &Coordinate<D>,
radius: Quantity<Length>,
) -> Option<Coordinate<D>> {
self.about(point)
.iter()
.filter_map(|cell| self.creases.get(cell))
.flatten()
.chain(self.sprawling.iter())
.map(|&index| closest_on(&self.features.creases[index], point))
.map(|closest| {
let distance = (&closest - point).norm();
(closest, distance)
})
.filter(|&(_, distance)| distance < radius)
.min_by(|(_, one), (_, two)| one.total_cmp(two))
.map(|(closest, _)| closest)
}
pub fn corner(&self, index: usize) -> &Coordinate<D> {
&self.features.corners[index]
}
pub fn is_empty(&self) -> bool {
self.features.corners.is_empty() && self.features.creases.is_empty()
}
}