use crate::math::{ComplexField, Real, RealField, Vector};
use crate::shape::{FeatureId, PackedFeatureId, PolygonalFeature, PolygonalFeatureMap, SupportMap};
use crate::utils;
use alloc::vec::Vec;
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[cfg_attr(
feature = "rkyv",
derive(rkyv::Archive, rkyv::Deserialize, rkyv::Serialize)
)]
#[derive(Clone, Debug)]
pub struct ConvexPolygon {
points: Vec<Vector>,
normals: Vec<Vector>,
}
impl ConvexPolygon {
pub fn from_convex_hull(points: &[Vector]) -> Option<Self> {
let vertices = crate::transformation::convex_hull(points);
Self::from_convex_polyline(vertices)
}
pub fn from_convex_polyline(mut points: Vec<Vector>) -> Option<Self> {
if points.is_empty() {
return None;
}
let eps = <Real as ComplexField>::sqrt(crate::math::DEFAULT_EPSILON);
let mut normals = Vec::with_capacity(points.len());
for i1 in 0..points.len() {
let i2 = (i1 + 1) % points.len();
normals.push(utils::ccw_face_normal([points[i1], points[i2]])?);
}
let mut nremoved = 0;
if normals[0].dot(normals[normals.len() - 1]) > 1.0 - eps {
nremoved = 1;
}
for i2 in 1..points.len() {
let i1 = i2 - 1;
if normals[i1].dot(normals[i2]) > 1.0 - eps {
nremoved += 1;
} else {
points[i2 - nremoved] = points[i2];
normals[i2 - nremoved] = normals[i2];
}
}
let new_length = points.len() - nremoved;
points.truncate(new_length);
normals.truncate(new_length);
if points.len() > 2 {
Some(ConvexPolygon { points, normals })
} else {
None
}
}
pub fn from_convex_polyline_unmodified(points: Vec<Vector>) -> Option<Self> {
if points.len() <= 2 {
return None;
}
let mut normals = Vec::with_capacity(points.len());
for i1 in 0..points.len() {
let i2 = (i1 + 1) % points.len();
normals.push(utils::ccw_face_normal([points[i1], points[i2]])?);
}
Some(ConvexPolygon { points, normals })
}
#[inline]
pub fn points(&self) -> &[Vector] {
&self.points
}
#[inline]
pub fn normals(&self) -> &[Vector] {
&self.normals
}
pub fn scaled(mut self, scale: Vector) -> Option<Self> {
self.points.iter_mut().for_each(|pt| *pt *= scale);
for n in &mut self.normals {
let scaled = *n * scale;
*n = scaled.try_normalize()?;
}
Some(self)
}
pub fn offsetted(&self, amount: Real) -> Self {
if !amount.is_finite() || amount < 0. {
panic!(
"Offset amount must be a non-negative finite number, got {}.",
amount
);
}
let mut points = Vec::with_capacity(self.points.len());
let normals = self.normals.clone();
for i2 in 0..self.points.len() {
let i1 = if i2 == 0 {
self.points.len() - 1
} else {
i2 - 1
};
let normal_a = normals[i1];
let direction = normal_a + normals[i2];
points.push(self.points[i2] + (amount / direction.dot(normal_a)) * direction);
}
ConvexPolygon { points, normals }
}
pub fn support_feature_id_toward(&self, local_dir: Vector) -> FeatureId {
let eps: Real = Real::pi() / 180.0;
let ceps = <Real as ComplexField>::cos(eps);
for i in 0..self.normals.len() {
let normal = &self.normals[i];
if normal.dot(local_dir) >= ceps {
return FeatureId::Face(i as u32);
}
}
FeatureId::Vertex(utils::point_cloud_support_point_id(local_dir, &self.points) as u32)
}
pub fn feature_normal(&self, feature: FeatureId) -> Option<Vector> {
match feature {
FeatureId::Face(id) => Some(self.normals[id as usize]),
FeatureId::Vertex(id2) => {
let id1 = if id2 == 0 {
self.normals.len() - 1
} else {
id2 as usize - 1
};
let sum = self.normals[id1] + self.normals[id2 as usize];
sum.try_normalize()
}
_ => None,
}
}
}
impl SupportMap for ConvexPolygon {
#[inline]
fn local_support_point(&self, dir: Vector) -> Vector {
utils::point_cloud_support_point(dir, self.points())
}
}
impl PolygonalFeatureMap for ConvexPolygon {
fn local_support_feature(&self, dir: Vector, out_feature: &mut PolygonalFeature) {
let cuboid = crate::shape::Cuboid::new(self.points[2]);
cuboid.local_support_feature(dir, out_feature);
let mut best_face = 0;
let mut max_dot = self.normals[0].dot(dir);
for i in 1..self.normals.len() {
let dot = self.normals[i].dot(dir);
if dot > max_dot {
max_dot = dot;
best_face = i;
}
}
let i1 = best_face;
let i2 = (best_face + 1) % self.points.len();
*out_feature = PolygonalFeature {
vertices: [self.points[i1], self.points[i2]],
vids: PackedFeatureId::vertices([i1 as u32 * 2, i2 as u32 * 2]),
fid: PackedFeatureId::face(i1 as u32 * 2 + 1),
num_vertices: 2,
};
}
}
#[cfg(feature = "dim2")]
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_dilation() {
let polygon = ConvexPolygon::from_convex_polyline(vec![
Vector::new(1., 0.),
Vector::new(-1., 0.),
Vector::new(0., -1.),
])
.unwrap();
let offsetted = polygon.offsetted(0.5);
let expected = vec![
Vector::new(2.207, 0.5),
Vector::new(-2.207, 0.5),
Vector::new(0., -1.707),
];
assert_eq!(offsetted.points().len(), 3);
assert!(offsetted
.points()
.iter()
.zip(expected.iter())
.all(|(a, b)| (a - b).length() < 0.001));
}
}