use crate::{
geometry::{
Coordinate, Coordinates,
mesh::{Connectivity, Mesh, tessellation::Tessellation},
},
math::{Quantity, Tensor, TensorVec},
};
use std::collections::HashMap;
fn cube(minimum: [f64; 3], maximum: [f64; 3]) -> Tessellation {
let [x0, y0, z0] = minimum;
let [x1, y1, z1] = maximum;
let coordinates = vec![
[x0, y0, z0],
[x1, y0, z0],
[x1, y1, z0],
[x0, y1, z0],
[x0, y0, z1],
[x1, y0, z1],
[x1, y1, z1],
[x0, y1, z1],
];
let quads: [[usize; 4]; 6] = [
[0, 1, 5, 4],
[1, 2, 6, 5],
[2, 3, 7, 6],
[3, 0, 4, 7],
[0, 3, 2, 1],
[4, 5, 6, 7],
];
let faces: Vec<[usize; 3]> = quads
.iter()
.flat_map(|&[a, b, c, d]| [[a, b, c], [a, c, d]])
.collect();
Tessellation::from(Mesh::from((
vec![Connectivity::Triangular(faces.into())],
Coordinates::from(coordinates),
)))
}
fn sphere(refinements: usize) -> Tessellation {
let mut coordinates = vec![
[1.0, 0.0, 0.0],
[-1.0, 0.0, 0.0],
[0.0, 1.0, 0.0],
[0.0, -1.0, 0.0],
[0.0, 0.0, 1.0],
[0.0, 0.0, -1.0],
];
let mut faces = vec![
[0, 2, 4],
[2, 1, 4],
[1, 3, 4],
[3, 0, 4],
[2, 0, 5],
[1, 2, 5],
[3, 1, 5],
[0, 3, 5],
];
(0..refinements).for_each(|_| {
let mut cache = HashMap::new();
faces = faces
.iter()
.flat_map(|&[a, b, c]| {
let mut midpoint = |one: usize, two: usize| {
let key = if one < two { [one, two] } else { [two, one] };
*cache.entry(key).or_insert_with(|| {
let point: Vec<f64> = (0..3)
.map(|d| 0.5 * (coordinates[one][d] + coordinates[two][d]))
.collect();
let norm = point.iter().map(|entry| entry * entry).sum::<f64>().sqrt();
coordinates.push([point[0] / norm, point[1] / norm, point[2] / norm]);
coordinates.len() - 1
})
};
let ab = midpoint(a, b);
let bc = midpoint(b, c);
let ca = midpoint(c, a);
[[a, ab, ca], [ab, b, bc], [ca, bc, c], [ab, bc, ca]]
})
.collect()
});
Tessellation::from(Mesh::from((
vec![Connectivity::Triangular(faces.into())],
Coordinates::from(coordinates),
)))
}
#[test]
fn a_cube_has_eight_corners_and_twelve_creases() {
let tessellation = cube([0.0, 0.0, 0.0], [1.0, 1.0, 1.0]);
let features = tessellation.features();
assert_eq!(features.corners().len(), 8);
assert_eq!(features.creases().len(), 12);
}
#[test]
fn the_corners_of_a_cube_are_its_vertices() {
let tessellation = cube([0.0, 0.0, 0.0], [1.0, 1.0, 1.0]);
let features = tessellation.features();
features.corners().iter().for_each(|corner| {
(0..3).for_each(|d| {
assert!(
corner[d] == Quantity::new(0.0) || corner[d] == Quantity::new(1.0),
"{corner}"
)
})
});
}
#[test]
fn a_sphere_has_no_features() {
let tessellation = sphere(3);
let features = tessellation.features();
assert!(features.corners().is_empty());
assert!(features.creases().is_empty());
}
#[test]
fn a_corner_is_found_only_within_the_radius() {
let tessellation = cube([0.0, 0.0, 0.0], [1.0, 1.0, 1.0]);
let index = tessellation.features().index(Quantity::new(0.2));
let point = Coordinate::const_from([0.1, 0.1, 0.1]);
let (corner, distance) = index.nearest_corner(&point, Quantity::new(0.2)).unwrap();
assert_eq!(index.corner(corner), &Coordinate::const_from([0.0; 3]));
assert!((distance.value() - 3.0_f64.sqrt() * 0.1).abs() < 1.0e-12);
assert!(index.nearest_corner(&point, Quantity::new(0.1)).is_none());
}
#[test]
fn a_crease_is_found_at_its_closest_point() {
let tessellation = cube([0.0, 0.0, 0.0], [1.0, 1.0, 1.0]);
let index = tessellation.features().index(Quantity::new(0.3));
let point = Coordinate::const_from([0.05, 0.05, 0.5]);
let closest = index.nearest_crease(&point, Quantity::new(0.3)).unwrap();
assert!(
(&closest - &Coordinate::const_from([0.0, 0.0, 0.5])).norm() < Quantity::new(1.0e-12),
"{closest}"
);
}
#[test]
fn triangles_that_share_no_nodes_have_no_features() {
let tessellation = cube([0.0, 0.0, 0.0], [1.0, 1.0, 1.0]);
let triangles = match &tessellation.mesh().connectivities()[0] {
Connectivity::Triangular(triangles) => triangles.iter().copied().collect::<Vec<_>>(),
_ => panic!(),
};
let mut coordinates = Coordinates::zero(0);
let mut unshared = Vec::new();
triangles.iter().for_each(|&[a, b, c]| {
let first = coordinates.len();
[a, b, c]
.into_iter()
.for_each(|node| coordinates.push(tessellation.mesh().coordinates()[node].clone()));
unshared.push([first, first + 1, first + 2])
});
let soup = Tessellation::from(Mesh::from((
vec![Connectivity::Triangular(unshared.into())],
coordinates,
)));
assert!(soup.features().corners().is_empty());
assert!(soup.features().creases().is_empty());
}