use crate::transform::{AABB2D, AABB3D};
use crate::Result;
use u_geometry::nalgebra_types::RealField;
#[cfg(feature = "serde")]
use serde::{Deserialize, Serialize};
pub type GeometryId = String;
#[derive(Debug, Clone, PartialEq)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[derive(Default)]
pub enum RotationConstraint<S> {
#[default]
None,
Discrete(Vec<S>),
}
impl<S: RealField + Copy> RotationConstraint<S> {
pub fn axis_aligned() -> Self {
let pi = S::pi();
let half_pi = pi / (S::one() + S::one());
Self::Discrete(vec![S::zero(), half_pi, pi, pi + half_pi])
}
pub fn steps(n: usize) -> Self {
if n == 0 {
return Self::None;
}
let two_pi = S::two_pi();
let step =
two_pi / S::from_usize(n).expect("n exceeds scalar precision (use n < 2^24 for f32)");
let angles: Vec<S> = (0..n)
.map(|i| step * S::from_usize(i).expect("index exceeds scalar precision"))
.collect();
Self::Discrete(angles)
}
pub fn is_fixed(&self) -> bool {
matches!(self, Self::None)
}
pub fn angles(&self) -> Vec<S> {
match self {
Self::None => vec![S::zero()],
Self::Discrete(angles) => angles.clone(),
}
}
}
pub trait Geometry: Clone + Send + Sync {
type Scalar: RealField + Copy;
fn id(&self) -> &GeometryId;
fn quantity(&self) -> usize;
fn measure(&self) -> Self::Scalar;
fn aabb(&self) -> ([Self::Scalar; 2], [Self::Scalar; 2]) {
let (min, max) = self.aabb_vec();
([min[0], min[1]], [max[0], max[1]])
}
fn aabb_vec(&self) -> (Vec<Self::Scalar>, Vec<Self::Scalar>);
fn centroid(&self) -> Vec<Self::Scalar>;
fn validate(&self) -> Result<()>;
fn rotation_constraint(&self) -> &RotationConstraint<Self::Scalar>;
fn allow_mirror(&self) -> bool {
false
}
fn priority(&self) -> i32 {
0
}
}
pub trait Geometry2DExt: Geometry {
fn aabb_2d(&self) -> AABB2D<Self::Scalar>;
fn outer_ring(&self) -> &[(Self::Scalar, Self::Scalar)];
fn holes(&self) -> &[Vec<(Self::Scalar, Self::Scalar)>];
fn has_holes(&self) -> bool {
!self.holes().is_empty()
}
fn is_convex(&self) -> bool;
fn convex_hull(&self) -> Vec<(Self::Scalar, Self::Scalar)>;
fn perimeter(&self) -> Self::Scalar;
}
pub trait Geometry3DExt: Geometry {
fn aabb_3d(&self) -> AABB3D<Self::Scalar>;
fn surface_area(&self) -> Self::Scalar;
fn mass(&self) -> Option<Self::Scalar>;
fn center_of_mass(&self) -> (Self::Scalar, Self::Scalar, Self::Scalar);
fn stackable(&self) -> bool {
true
}
fn max_stack_load(&self) -> Option<Self::Scalar> {
None
}
}
pub trait Boundary: Clone + Send + Sync {
type Scalar: RealField + Copy;
fn measure(&self) -> Self::Scalar;
fn aabb(&self) -> ([Self::Scalar; 2], [Self::Scalar; 2]) {
let (min, max) = self.aabb_vec();
([min[0], min[1]], [max[0], max[1]])
}
fn aabb_vec(&self) -> (Vec<Self::Scalar>, Vec<Self::Scalar>);
fn validate(&self) -> Result<()>;
fn contains_point(&self, point: &[Self::Scalar]) -> bool;
}
pub trait Boundary2DExt: Boundary {
fn aabb_2d(&self) -> AABB2D<Self::Scalar>;
fn vertices(&self) -> &[(Self::Scalar, Self::Scalar)];
fn contains_polygon(&self, polygon: &[(Self::Scalar, Self::Scalar)]) -> bool;
fn effective_area(&self, margin: Self::Scalar) -> Self::Scalar;
}
pub trait Boundary3DExt: Boundary {
fn aabb_3d(&self) -> AABB3D<Self::Scalar>;
fn max_mass(&self) -> Option<Self::Scalar>;
fn contains_box(&self, min: &[Self::Scalar; 3], max: &[Self::Scalar; 3]) -> bool;
fn effective_volume(&self, margin: Self::Scalar) -> Self::Scalar;
}
pub fn ensure_unique_ids<G: Geometry>(geometries: &[G]) -> Result<()> {
let mut first_at = std::collections::HashMap::with_capacity(geometries.len());
for (position, geometry) in geometries.iter().enumerate() {
if let Some(first) = first_at.insert(geometry.id(), position) {
return Err(crate::Error::InvalidGeometry(format!(
"the id '{}' is given twice, at positions {first} and {position} of \
geometries (counting from 0); placements name geometries by id, so \
every geometry needs its own",
geometry.id()
)));
}
}
Ok(())
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_rotation_constraint_axis_aligned() {
let constraint: RotationConstraint<f64> = RotationConstraint::axis_aligned();
let angles = constraint.angles();
assert_eq!(angles.len(), 4);
}
#[test]
fn test_rotation_constraint_steps() {
let constraint: RotationConstraint<f64> = RotationConstraint::steps(8);
let angles = constraint.angles();
assert_eq!(angles.len(), 8);
}
}