use crate::math::Vector;
#[cfg(feature = "alloc")]
use crate::query::details::NormalConstraints;
#[derive(Clone, Debug)]
pub struct SegmentPseudoNormals {
pub face: Vector,
pub edges: [Vector; 2],
}
#[cfg(feature = "alloc")]
impl NormalConstraints for SegmentPseudoNormals {
fn project_local_normal_mut(&self, dir: &mut Vector) -> bool {
let closest_edge = if dir.dot(self.edges[0]) >= dir.dot(self.edges[1]) {
self.edges[0]
} else {
self.edges[1]
};
crate::shape::pseudo_normals::project_into_cone(self.face, closest_edge, dir)
}
}
#[cfg(test)]
#[cfg(all(feature = "dim2", feature = "alloc"))]
mod test {
use super::{NormalConstraints, SegmentPseudoNormals};
use crate::math::Vector;
fn bisector(v1: Vector, v2: Vector) -> Vector {
(v1 + v2).normalize()
}
#[test]
fn degenerate_cone_collapses_to_face() {
let pn = SegmentPseudoNormals {
face: Vector::Y,
edges: [Vector::Y, Vector::Y],
};
assert_eq!(
pn.project_local_normal(Vector::new(1.0, 1.0)),
Some(Vector::Y)
);
assert!(pn.project_local_normal(-Vector::Y).is_none());
}
#[test]
fn clamps_into_the_outward_cone() {
let ends = [
bisector(Vector::Y, Vector::X),
bisector(Vector::Y, -Vector::X),
];
let edges = [bisector(Vector::Y, ends[0]), bisector(Vector::Y, ends[1])];
let pn = SegmentPseudoNormals {
face: Vector::Y,
edges,
};
assert_eq!(pn.project_local_normal(Vector::Y), Some(Vector::Y));
for edge in edges {
assert_eq!(pn.project_local_normal(edge), Some(edge));
}
let inside = Vector::new(0.2, 1.0).normalize();
assert!(pn
.project_local_normal(inside)
.unwrap()
.abs_diff_eq(inside, 1.0e-5));
let sideways = Vector::new(1.0, 0.2).normalize();
let clamped = pn.project_local_normal(sideways).unwrap();
assert!(clamped.dot(Vector::Y) > sideways.dot(Vector::Y));
assert!(clamped.abs_diff_eq(ends[0], 1.0e-5));
assert!(pn.project_local_normal(-Vector::Y).is_none());
assert!(pn
.project_local_normal(Vector::new(0.3, -1.0).normalize())
.is_none());
}
}