use crate::float_types::{EPSILON, Real};
use crate::mesh::polygon::Polygon;
use crate::mesh::vertex::Vertex;
use nalgebra::{Isometry3, Matrix4, Point3, Rotation3, Translation3, Vector3};
use robust::{Coord3D, orient3d};
pub const COPLANAR: i8 = 0;
pub const FRONT: i8 = 1;
pub const BACK: i8 = 2;
pub const SPANNING: i8 = 3;
#[derive(Debug, Clone)]
pub struct Plane {
pub point_a: Point3<Real>,
pub point_b: Point3<Real>,
pub point_c: Point3<Real>,
}
impl PartialEq for Plane {
fn eq(&self, other: &Self) -> bool {
if self.point_a == other.point_a
&& self.point_b == other.point_b
&& self.point_c == other.point_c
{
true
} else {
robust::orient3d(
point_to_coord3d(self.point_a),
point_to_coord3d(self.point_b),
point_to_coord3d(self.point_c),
point_to_coord3d(other.point_a),
) == 0.0
&& robust::orient3d(
point_to_coord3d(self.point_a),
point_to_coord3d(self.point_b),
point_to_coord3d(self.point_c),
point_to_coord3d(other.point_b),
) == 0.0
&& robust::orient3d(
point_to_coord3d(self.point_a),
point_to_coord3d(self.point_b),
point_to_coord3d(self.point_c),
point_to_coord3d(other.point_c),
) == 0.0
}
}
}
fn point_to_coord3d(point: Point3<Real>) -> robust::Coord3D<Real> {
robust::Coord3D {
x: point.coords.x,
y: point.coords.y,
z: point.coords.z,
}
}
impl Plane {
pub fn from_vertices(vertices: Vec<Vertex>) -> Plane {
let n = vertices.len();
let reference_plane = Plane {
point_a: vertices[0].pos,
point_b: vertices[1].pos,
point_c: vertices[2].pos,
};
if n == 3 {
return reference_plane;
}
let Some((i0, i1, _)) = (0..n)
.flat_map(|i| (i + 1..n).map(move |j| (i, j)))
.map(|(i, j)| {
let d2 = (vertices[i].pos - vertices[j].pos).norm_squared();
(i, j, d2)
})
.max_by(|a, b| a.2.total_cmp(&b.2))
else {
return reference_plane;
};
let p0 = vertices[i0].pos;
let p1 = vertices[i1].pos;
let dir = p1 - p0;
if dir.norm_squared() < EPSILON * EPSILON {
return reference_plane; }
let Some((i2, max_area2)) = vertices
.iter()
.enumerate()
.filter(|(idx, _)| *idx != i0 && *idx != i1)
.map(|(idx, v)| {
let a2 = (v.pos - p0).cross(&dir).norm_squared(); (idx, a2)
})
.max_by(|a, b| a.1.total_cmp(&b.1))
else {
return reference_plane;
};
let i2 = if max_area2 > EPSILON * EPSILON {
i2
} else {
return reference_plane; };
let p2 = vertices[i2].pos;
let mut plane_hq = Plane {
point_a: p0,
point_b: p1,
point_c: p2,
};
let reference_normal = vertices.iter().zip(vertices.iter().cycle().skip(1)).fold(
Vector3::zeros(),
|acc, (curr, next)| {
acc + (curr.pos - Point3::origin()).cross(&(next.pos - Point3::origin()))
},
);
if plane_hq.normal().dot(&reference_normal) < 0.0 {
plane_hq.flip(); }
plane_hq
}
pub fn from_normal(normal: Vector3<Real>, offset: Real) -> Self {
let n2 = normal.norm_squared();
if n2 < EPSILON * EPSILON {
panic!(); }
let p0 = Point3::from(normal * (offset / n2));
let mut u = if normal.z.abs() > normal.x.abs() || normal.z.abs() > normal.y.abs() {
Vector3::x().cross(&normal)
} else {
Vector3::z().cross(&normal)
};
u.normalize_mut();
let v = normal.cross(&u).normalize();
Self {
point_a: p0,
point_b: p0 + u,
point_c: p0 + v,
}
}
#[inline]
pub fn orient_plane(&self, other: &Plane) -> i8 {
let test_point = other.point_a + other.normal();
self.orient_point(&test_point)
}
#[inline]
pub fn orient_point(&self, point: &Point3<Real>) -> i8 {
let sign = orient3d(
Coord3D {
x: self.point_a.x,
y: self.point_a.y,
z: self.point_a.z,
},
Coord3D {
x: self.point_b.x,
y: self.point_b.y,
z: self.point_b.z,
},
Coord3D {
x: self.point_c.x,
y: self.point_c.y,
z: self.point_c.z,
},
Coord3D {
x: point.x,
y: point.y,
z: point.z,
},
);
#[allow(clippy::useless_conversion)]
if sign > EPSILON.into() {
BACK
} else if sign < (-EPSILON).into() {
FRONT
} else {
COPLANAR
}
}
#[inline]
pub fn normal(&self) -> Vector3<Real> {
let n = (self.point_b - self.point_a).cross(&(self.point_c - self.point_a));
let len = n.norm();
if len < EPSILON {
Vector3::zeros()
} else {
n / len
}
}
#[inline]
pub fn offset(&self) -> Real {
self.normal().dot(&self.point_a.coords)
}
pub const fn flip(&mut self) {
std::mem::swap(&mut self.point_a, &mut self.point_b);
}
pub fn classify_polygon<S: Clone>(&self, polygon: &Polygon<S>) -> i8 {
let mut polygon_type: i8 = 0;
for vertex in &polygon.vertices {
polygon_type |= self.orient_point(&vertex.pos);
}
polygon_type
}
#[allow(clippy::type_complexity)]
pub fn split_polygon<S: Clone + Send + Sync>(
&self,
polygon: &Polygon<S>,
) -> (
Vec<Polygon<S>>,
Vec<Polygon<S>>,
Vec<Polygon<S>>,
Vec<Polygon<S>>,
) {
let mut coplanar_front = Vec::new();
let mut coplanar_back = Vec::new();
let mut front = Vec::new();
let mut back = Vec::new();
let normal = self.normal();
let types: Vec<i8> = polygon
.vertices
.iter()
.map(|v| self.orient_point(&v.pos))
.collect();
let polygon_type = types.iter().fold(0, |acc, &t| acc | t);
match polygon_type {
COPLANAR => {
if normal.dot(&polygon.plane.normal()) > 0.0 {
coplanar_front.push(polygon.clone());
} else {
coplanar_back.push(polygon.clone());
}
},
FRONT => front.push(polygon.clone()),
BACK => back.push(polygon.clone()),
_ => {
let mut split_front = Vec::<Vertex>::new();
let mut split_back = Vec::<Vertex>::new();
for i in 0..polygon.vertices.len() {
let j = (i + 1) % polygon.vertices.len();
let type_i = types[i];
let type_j = types[j];
let vertex_i = &polygon.vertices[i];
let vertex_j = &polygon.vertices[j];
if type_i != BACK {
split_front.push(vertex_i.clone());
}
if type_i != FRONT {
split_back.push(vertex_i.clone());
}
if (type_i | type_j) == SPANNING {
let denom = normal.dot(&(vertex_j.pos - vertex_i.pos));
if denom.abs() > EPSILON {
let intersection =
(self.offset() - normal.dot(&vertex_i.pos.coords)) / denom;
let vertex_new = vertex_i.interpolate(vertex_j, intersection);
split_front.push(vertex_new.clone());
split_back.push(vertex_new);
}
}
}
if split_front.len() >= 3 {
front.push(Polygon::new(split_front, polygon.metadata.clone()));
}
if split_back.len() >= 3 {
back.push(Polygon::new(split_back, polygon.metadata.clone()));
}
},
}
(coplanar_front, coplanar_back, front, back)
}
pub fn to_xy_transform(&self) -> (Matrix4<Real>, Matrix4<Real>) {
let n = self.normal();
let n_len = n.norm();
if n_len < EPSILON {
return (Matrix4::identity(), Matrix4::identity());
}
let norm_dir = n / n_len;
let rot = Rotation3::rotation_between(&norm_dir, &Vector3::z())
.unwrap_or_else(Rotation3::identity);
let iso_rot = Isometry3::from_parts(Translation3::identity(), rot.into());
let denom = n.dot(&n);
let p0_3d = norm_dir * (self.offset() / denom);
let p0_rot = iso_rot.transform_point(&Point3::from(p0_3d));
let shift_z = -p0_rot.z;
let iso_trans = Translation3::new(0.0, 0.0, shift_z);
let transform_to_xy = iso_trans.to_homogeneous() * iso_rot.to_homogeneous();
let transform_from_xy = transform_to_xy
.try_inverse()
.unwrap_or_else(Matrix4::identity);
(transform_to_xy, transform_from_xy)
}
}
#[test]
fn test_plane_orientation() {
let vertices = [
Vertex {
pos: Point3::new(1152.0, 256.0, 512.0),
normal: Vector3::new(0., 1., 0.),
},
Vertex {
pos: Point3::new(1152.0, 256.0, 256.0),
normal: Vector3::new(0., 1., 0.),
},
Vertex {
pos: Point3::new(768.0, 256.0, 256.0),
normal: Vector3::new(0., 1., 0.),
},
Vertex {
pos: Point3::new(768.0, 256.0, 512.0),
normal: Vector3::new(0., 1., 0.),
},
Vertex {
pos: Point3::new(896.0, 256.0, 512.0),
normal: Vector3::new(0., 1., 0.),
},
Vertex {
pos: Point3::new(896.0, 256.0, 384.0),
normal: Vector3::new(0., 1., 0.),
},
Vertex {
pos: Point3::new(1024.0, 256.0, 384.0),
normal: Vector3::new(0., 1., 0.),
},
Vertex {
pos: Point3::new(1024.0, 256.0, 512.0),
normal: Vector3::new(0., 1., 0.),
},
];
for cycle_rotation in 0..vertices.len() {
let mut vertices = vertices.clone();
vertices.rotate_right(cycle_rotation);
let plane = Plane::from_vertices(vertices.to_vec());
assert!(
plane.normal() == Vector3::new(0., 1., 0.),
"the vertices {vertices:?} form a plane with unexpected normal {}, \
expected (0., 1., 0.); \
point list obtained by rotating {cycle_rotation} times",
plane.normal(),
);
}
}