use std::collections::HashMap;
use crate::error::{Error, Result};
use crate::geometry::{Geometry, Patch, Topology};
use crate::points::Point;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub enum Shape {
#[default]
Auto,
Points,
Lines,
Surface,
Volume,
Outline,
Centroid,
}
impl Shape {
pub fn name(&self) -> &'static str {
match self {
Shape::Auto => "auto",
Shape::Points => "points",
Shape::Lines => "lines",
Shape::Surface => "surface",
Shape::Volume => "volume",
Shape::Outline => "outline",
Shape::Centroid => "centroid",
}
}
pub fn all() -> [Shape; 7] {
[
Shape::Auto,
Shape::Points,
Shape::Lines,
Shape::Surface,
Shape::Volume,
Shape::Outline,
Shape::Centroid,
]
}
pub fn is_summary(&self) -> bool {
matches!(self, Shape::Outline | Shape::Centroid)
}
pub fn parse(text: &str) -> Option<Shape> {
match text.trim().to_ascii_lowercase().as_str() {
"auto" | "default" | "natural" => Some(Shape::Auto),
"point" | "points" | "pointcloud" | "multipoint" => Some(Shape::Points),
"line" | "lines" | "polyline" | "polylines" | "multilinestring" => Some(Shape::Lines),
"surface" | "surfaces" | "polygon" | "polygons" | "multipolygon" | "face" | "faces" => {
Some(Shape::Surface)
}
"volume" | "volumes" | "cell" | "cells" | "solid" => Some(Shape::Volume),
"outline" | "hull" | "footprint" | "extent" | "area" => Some(Shape::Outline),
"centroid" | "centre" | "center" | "middle" => Some(Shape::Centroid),
_ => None,
}
}
}
impl std::fmt::Display for Shape {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.write_str(self.name())
}
}
impl Topology {
pub fn natural_shape(&self) -> Shape {
match self {
Topology::Points => Shape::Points,
Topology::Polylines(_) | Topology::Segments(_) => Shape::Lines,
Topology::Triangles(_) | Topology::Quads(_) | Topology::Polygons(_) => Shape::Surface,
Topology::Grid { .. } => Shape::Surface,
Topology::Hexahedra { .. } | Topology::Polyhedra(_) => Shape::Volume,
}
}
pub fn is_already(&self, shape: Shape) -> bool {
if shape.is_summary() || matches!(self, Topology::Grid { .. }) {
return false;
}
matches!(shape, Shape::Auto) || shape == self.natural_shape()
}
pub fn describe(&self) -> &'static str {
match self {
Topology::Points => "a point set",
Topology::Triangles(_) => "a triangulated surface",
Topology::Quads(_) => "a quadrilateral surface",
Topology::Polygons(_) => "a polygonal surface",
Topology::Polylines(_) => "a set of polylines",
Topology::Segments(_) => "a wireframe of segments",
Topology::Grid { .. } => "a 2D grid",
Topology::Hexahedra { .. } => "a hexahedral grid",
Topology::Polyhedra(_) => "an unstructured grid",
}
}
}
impl Geometry {
pub fn can_be(&self, shape: Shape) -> bool {
self.patches
.iter()
.all(|patch| reshape(&patch.topology, &patch.points, shape).is_ok())
}
pub fn as_shape(&self, shape: Shape) -> Result<Geometry> {
if shape.is_summary() {
return self.summarise(shape);
}
let mut patches = Vec::with_capacity(self.patches.len());
for patch in &self.patches {
patches.push(Patch {
index: patch.index,
node_offset: patch.node_offset,
points: patch.points.clone(),
topology: reshape(&patch.topology, &patch.points, shape)?,
closed: Vec::new(),
});
}
Ok(Geometry {
kind: self.kind,
patches,
})
}
pub fn wireframe(&self) -> Result<Vec<[u32; 2]>> {
let holed = Geometry {
kind: self.kind,
patches: self
.patches
.iter()
.map(|patch| Patch {
index: patch.index,
node_offset: patch.node_offset,
points: crate::geometry::orphans_as_holes(&patch.points, &patch.topology).into_owned(),
topology: patch.topology.clone(),
closed: Vec::new(),
})
.collect(),
};
Ok(holed.as_shape(Shape::Lines)?.drawable().lines)
}
fn summarise(&self, shape: Shape) -> Result<Geometry> {
let nodes: Vec<Point> = self
.patches
.iter()
.flat_map(|patch| patch.points.iter().copied())
.filter(crate::defined::is_defined)
.collect();
if nodes.is_empty() {
return Err(Error::shape("an object with no placed node", "a summary"));
}
let (points, topology) = match shape {
Shape::Centroid => {
let count = nodes.len() as f64;
let mean = |axis: usize| nodes.iter().map(|p| p[axis]).sum::<f64>() / count;
(vec![[mean(0), mean(1), mean(2)]], Topology::Points)
}
_ => {
let ring = hull(&nodes);
let indices: Vec<u32> = (0..ring.len() as u32).collect();
let topology = if ring.len() >= 3 {
Topology::Polygons(vec![indices])
} else {
Topology::Points
};
(ring, topology)
}
};
Ok(Geometry {
kind: self.kind,
patches: vec![Patch {
index: 0,
node_offset: 0,
points,
topology,
closed: Vec::new(),
}],
})
}
}
fn hull(points: &[Point]) -> Vec<Point> {
let mut sorted = points.to_vec();
sorted.sort_by(|a, b| a[0].total_cmp(&b[0]).then(a[1].total_cmp(&b[1])));
sorted.dedup_by(|a, b| a[0] == b[0] && a[1] == b[1]);
if sorted.len() < 3 {
return sorted;
}
let cross =
|o: &Point, a: &Point, b: &Point| (a[0] - o[0]) * (b[1] - o[1]) - (a[1] - o[1]) * (b[0] - o[0]);
let mut chain: Vec<Point> = Vec::with_capacity(sorted.len() * 2);
for pass in 0..2 {
let start = chain.len();
let iter: Box<dyn Iterator<Item = &Point>> = if pass == 0 {
Box::new(sorted.iter())
} else {
Box::new(sorted.iter().rev())
};
for point in iter {
while chain.len() >= start + 2
&& cross(&chain[chain.len() - 2], &chain[chain.len() - 1], point) <= 0.0
{
chain.pop();
}
chain.push(*point);
}
chain.pop();
}
chain
}
fn unique_edges<'a>(cells: impl Iterator<Item = &'a [u32]>) -> Vec<[u32; 2]> {
let mut seen = std::collections::HashSet::new();
let mut out = Vec::new();
for ring in cells {
for at in 0..ring.len() {
let (a, b) = (ring[at], ring[(at + 1) % ring.len()]);
if a == b {
continue;
}
if seen.insert((a.min(b), a.max(b))) {
out.push([a, b]);
}
}
}
out
}
pub(crate) fn reshape(topology: &Topology, points: &[Point], shape: Shape) -> Result<Topology> {
let wanted = match shape {
Shape::Auto => topology.natural_shape(),
other => other,
};
if topology.is_already(wanted) {
return Ok(topology.clone());
}
match wanted {
Shape::Points => Ok(Topology::Points),
Shape::Lines => match topology {
Topology::Polylines(_) | Topology::Segments(_) => Ok(topology.clone()),
Topology::Triangles(cells) => Ok(Topology::Segments(unique_edges(
cells.iter().map(|cell| cell.as_slice()),
))),
Topology::Quads(cells) => Ok(Topology::Segments(unique_edges(
cells.iter().map(|cell| cell.as_slice()),
))),
Topology::Polygons(rings) => Ok(Topology::Segments(unique_edges(
rings.iter().map(|ring| ring.as_slice()),
))),
Topology::Grid { fastest, slowest } => {
let cells = grid_cells(*fastest, *slowest, points);
Ok(Topology::Segments(unique_edges(
cells.iter().map(|cell| cell.as_slice()),
)))
}
Topology::Hexahedra { cells, .. } => {
let boundary = boundary_of(cells, points);
Ok(Topology::Segments(unique_edges(
boundary.iter().map(|cell| cell.as_slice()),
)))
}
Topology::Polyhedra(cells) => {
let boundary = cells.boundary();
Ok(Topology::Segments(unique_edges(
boundary.iter().map(|ring| ring.as_slice()),
)))
}
_ => Err(refuse(topology, "lines")),
},
Shape::Surface => match topology {
Topology::Triangles(_) | Topology::Quads(_) | Topology::Polygons(_) => Ok(topology.clone()),
Topology::Grid { fastest, slowest } => {
Ok(Topology::Quads(grid_cells(*fastest, *slowest, points)))
}
Topology::Hexahedra { cells, .. } => Ok(Topology::Quads(boundary_of(cells, points))),
Topology::Polyhedra(cells) => Ok(Topology::Polygons(cells.boundary())),
_ => Err(refuse(topology, "a surface")),
},
Shape::Volume => match topology {
Topology::Hexahedra { .. } | Topology::Polyhedra(_) => Ok(topology.clone()),
_ => Err(refuse(topology, "a volume")),
},
Shape::Auto | Shape::Outline | Shape::Centroid => Ok(topology.clone()),
}
}
fn refuse(topology: &Topology, wanted: &'static str) -> Error {
Error::shape(topology.describe(), wanted)
}
fn grid_cells(fastest: usize, slowest: usize, points: &[Point]) -> Vec<[u32; 4]> {
if fastest < 2 || slowest < 2 {
return Vec::new();
}
let mut cells = Vec::with_capacity((fastest - 1) * (slowest - 1));
for j in 0..slowest - 1 {
for i in 0..fastest - 1 {
let corner = |di: usize, dj: usize| ((j + dj) * fastest + i + di) as u32;
let ring = [corner(0, 0), corner(1, 0), corner(1, 1), corner(0, 1)];
if ring.iter().any(|c| *c as usize >= points.len()) {
continue;
}
cells.push(wound(ring, points));
}
}
cells
}
fn wound(ring: [u32; 4], points: &[Point]) -> [u32; 4] {
let mut area = 0.0;
for corner in 0..4 {
let a = points[ring[corner] as usize];
let b = points[ring[(corner + 1) % 4] as usize];
area += a[0] * b[1] - b[0] * a[1];
}
if area < 0.0 {
[ring[3], ring[2], ring[1], ring[0]]
} else {
ring
}
}
fn boundary_of(cells: &[[u32; 8]], points: &[Point]) -> Vec<[u32; 4]> {
const FACES: [[usize; 4]; 6] = [
[0, 3, 2, 1],
[4, 5, 6, 7],
[0, 1, 5, 4],
[1, 2, 6, 5],
[2, 3, 7, 6],
[3, 0, 4, 7],
];
let mut seen: HashMap<[u32; 4], (usize, [u32; 4])> = HashMap::new();
for cell in cells {
for face in FACES {
let ring = [cell[face[0]], cell[face[1]], cell[face[2]], cell[face[3]]];
let mut key = ring;
key.sort_unstable();
let entry = seen.entry(key).or_insert((0, ring));
entry.0 += 1;
}
}
seen.into_values()
.filter(|(count, _)| *count == 1)
.map(|(_, ring)| wound(ring, points))
.collect()
}