use std::cmp::Ordering;
use std::collections::BTreeMap;
use axiolid_contracts::{GeomError, GeomResult, Sign};
use axiolid_core::{Point2, Point3};
use axiolid_exact::{Arith, Dyadic, Interval};
use axiolid_mesh::TriMesh;
use super::regions::Projection;
use super::{
internal_topology_error, mesh_index, orient_sign, sign_times, Classification, EdgeKey,
ExactSectionPlane, NodeKey,
};
use crate::coplanar::project;
type Homogeneous<T> = [T; 3];
struct ExactPoint {
exact: Homogeneous<Dyadic>,
enclosure: Homogeneous<Interval>,
bounds: [f64; 4],
flat: Option<Point2>,
}
pub(super) struct PlanePoints {
origin: [Dyadic; 3],
normal: [Dyadic; 3],
axis: usize,
frame_sign: Sign,
points: Vec<ExactPoint>,
by_node: BTreeMap<NodeKey, usize>,
values: BTreeMap<u32, Dyadic>,
}
fn not_finite() -> GeomError {
GeomError::Degenerate("cut ring point is not finite".into())
}
fn undecided() -> GeomError {
GeomError::Degenerate("a cut ring sign could not be decided exactly".into())
}
fn exact(value: f64) -> GeomResult<Dyadic> {
Dyadic::try_from_f64(value).ok_or_else(not_finite)
}
fn exact_point(point: Point3) -> GeomResult<[Dyadic; 3]> {
Ok([exact(point.x)?, exact(point.y)?, exact(point.z)?])
}
fn dot<T: Arith>(left: &[T; 3], right: &[T; 3]) -> T {
left[0]
.mul(&right[0])
.add(&left[1].mul(&right[1]))
.add(&left[2].mul(&right[2]))
}
fn minor<T: Arith>(p: &Homogeneous<T>, q: &Homogeneous<T>, i: usize, j: usize) -> T {
p[i].mul(&q[j]).sub(&q[i].mul(&p[j]))
}
fn determinant<T: Arith>(a: &Homogeneous<T>, b: &Homogeneous<T>, c: &Homogeneous<T>) -> T {
a[0].mul(&minor(b, c, 1, 2))
.sub(&a[1].mul(&minor(b, c, 0, 2)))
.add(&a[2].mul(&minor(b, c, 0, 1)))
}
fn difference<T: Arith>(a: &Homogeneous<T>, b: &Homogeneous<T>, axis: usize) -> T {
a[axis].mul(&b[2]).sub(&b[axis].mul(&a[2]))
}
fn ordering(sign: Sign) -> Ordering {
match sign {
Sign::Positive => Ordering::Greater,
Sign::Negative => Ordering::Less,
_ => Ordering::Equal,
}
}
fn coordinate(point: Point2, axis: usize) -> f64 {
if axis == 0 {
point.x
} else {
point.y
}
}
fn sub3(left: &[Dyadic; 3], right: &[Dyadic; 3]) -> [Dyadic; 3] {
[
left[0].sub(&right[0]),
left[1].sub(&right[1]),
left[2].sub(&right[2]),
]
}
fn exact_quotient(numerator: &Dyadic, weight: &Dyadic, hint: Option<f64>) -> Option<f64> {
let candidate = match hint {
Some(hint) => hint,
None if numerator.bits() <= 53 && weight.bits() <= 53 => {
numerator.to_f64() / weight.to_f64()
}
None => return None,
};
let exact_candidate = Dyadic::try_from_f64(candidate)?;
(exact_candidate.mul(weight) == *numerator).then_some(candidate)
}
impl PlanePoints {
pub(super) fn new(plane: &ExactSectionPlane, projection: Projection) -> GeomResult<Self> {
let origin = exact_point(plane.origin)?;
let to_x = sub3(&exact_point(plane.x_point)?, &origin);
let to_y = sub3(&exact_point(plane.y_point)?, &origin);
let normal = [
minor(&to_x, &to_y, 1, 2),
minor(&to_x, &to_y, 2, 0),
minor(&to_x, &to_y, 0, 1),
];
Ok(Self {
origin,
normal,
axis: projection.axis(),
frame_sign: projection.frame_sign(),
points: Vec::new(),
by_node: BTreeMap::new(),
values: BTreeMap::new(),
})
}
fn plane_value(&self, point: Point3) -> GeomResult<Dyadic> {
let offset = sub3(&exact_point(point)?, &self.origin);
Ok(dot(&self.normal, &offset))
}
fn vertex_value(&mut self, index: u32, point: Point3) -> GeomResult<Dyadic> {
if let Some(value) = self.values.get(&index) {
return Ok(value.clone());
}
let value = self.plane_value(point)?;
self.values.insert(index, value.clone());
Ok(value)
}
pub(super) fn node(
&mut self,
mesh: &TriMesh,
classifications: &[Classification],
node: NodeKey,
) -> GeomResult<usize> {
if let Some(&index) = self.by_node.get(&node) {
return Ok(index);
}
let position = |index: u32| {
mesh.positions
.get(mesh_index(index)?)
.copied()
.ok_or_else(internal_topology_error)
};
let index = match node {
NodeKey::Vertex(vertex) => {
let certified = classifications
.get(mesh_index(vertex)?)
.ok_or_else(internal_topology_error)?;
if certified.sign != Sign::Zero {
return Err(internal_topology_error());
}
let point = position(vertex)?;
debug_assert_eq!(self.plane_value(point)?.sign(), Some(Sign::Zero));
self.push_vertex(point)?
}
NodeKey::Edge(EdgeKey(left, right)) => {
let (a, b) = (position(left)?, position(right)?);
let (da, db) = (self.vertex_value(left, a)?, self.vertex_value(right, b)?);
self.crossing_of(&da, &db, a, b)?
}
};
self.by_node.insert(node, index);
Ok(index)
}
#[cfg(test)]
pub(super) fn vertex(&mut self, point: Point3) -> GeomResult<usize> {
if self.plane_value(point)?.sign() != Some(Sign::Zero) {
return Err(internal_topology_error());
}
self.push_vertex(point)
}
fn push_vertex(&mut self, point: Point3) -> GeomResult<usize> {
let flat = project(point, self.axis);
self.push_flat(flat)
}
fn push_flat(&mut self, flat: Point2) -> GeomResult<usize> {
let exact = [exact(flat.x)?, exact(flat.y)?, exact(1.0)?];
let enclosure = [
Interval::point(flat.x),
Interval::point(flat.y),
Interval::point(1.0),
];
self.points.push(ExactPoint {
exact,
enclosure,
bounds: [flat.x, flat.y, flat.x, flat.y],
flat: Some(flat),
});
Ok(self.points.len() - 1)
}
#[cfg(test)]
pub(super) fn crossing(&mut self, a: Point3, b: Point3) -> GeomResult<usize> {
let (da, db) = (self.plane_value(a)?, self.plane_value(b)?);
self.crossing_of(&da, &db, a, b)
}
fn crossing_of(&mut self, da: &Dyadic, db: &Dyadic, a: Point3, b: Point3) -> GeomResult<usize> {
let opposite = matches!(
(da.sign(), db.sign()),
(Some(Sign::Positive), Some(Sign::Negative))
| (Some(Sign::Negative), Some(Sign::Positive))
);
if !opposite {
return Err(internal_topology_error());
}
let (a, b) = (project(a, self.axis), project(b, self.axis));
let mut point = [
da.mul(&exact(b.x)?).sub(&db.mul(&exact(a.x)?)),
da.mul(&exact(b.y)?).sub(&db.mul(&exact(a.y)?)),
da.sub(db),
];
if point[2].sign() == Some(Sign::Negative) {
point = point.map(|value| value.neg());
}
let fixed = |from: f64, to: f64| (from == to).then_some(from);
if let (Some(u), Some(v)) = (
exact_quotient(&point[0], &point[2], fixed(a.x, b.x)),
exact_quotient(&point[1], &point[2], fixed(a.y, b.y)),
) {
return self.push_flat(Point2::new(u, v));
}
let enclosure = [
point[0].enclosure(),
point[1].enclosure(),
point[2].enclosure(),
];
let (u, v) = (
enclosure[0].quotient(enclosure[2]),
enclosure[1].quotient(enclosure[2]),
);
self.points.push(ExactPoint {
exact: point,
enclosure,
bounds: [u.lo(), v.lo(), u.hi(), v.hi()],
flat: None,
});
Ok(self.points.len() - 1)
}
pub(super) fn orient(&self, a: usize, b: usize, c: usize) -> GeomResult<Sign> {
let (a, b, c) = (&self.points[a], &self.points[b], &self.points[c]);
if let (Some(fa), Some(fb), Some(fc)) = (a.flat, b.flat, c.flat) {
return Ok(sign_times(orient_sign(fa, fb, fc)?, self.frame_sign));
}
let sign = match determinant(&a.enclosure, &b.enclosure, &c.enclosure).sign() {
Some(sign) => sign,
None => determinant(&a.exact, &b.exact, &c.exact)
.sign()
.ok_or_else(undecided)?,
};
Ok(sign_times(sign, self.frame_sign))
}
pub(super) fn compare(&self, a: usize, b: usize, axis: usize) -> GeomResult<Ordering> {
let (a, b) = (&self.points[a], &self.points[b]);
if let (Some(fa), Some(fb)) = (a.flat, b.flat) {
return coordinate(fa, axis)
.partial_cmp(&coordinate(fb, axis))
.ok_or_else(not_finite);
}
let sign = match difference(&a.enclosure, &b.enclosure, axis).sign() {
Some(sign) => sign,
None => difference(&a.exact, &b.exact, axis)
.sign()
.ok_or_else(undecided)?,
};
Ok(ordering(sign))
}
pub(super) fn same(&self, a: usize, b: usize) -> GeomResult<bool> {
Ok(a == b
|| (self.compare(a, b, 0)? == Ordering::Equal
&& self.compare(a, b, 1)? == Ordering::Equal))
}
pub(super) fn lexicographic(&self, a: usize, b: usize) -> GeomResult<Ordering> {
match self.compare(a, b, 0)? {
Ordering::Equal => self.compare(a, b, 1),
order => Ok(order),
}
}
pub(super) fn bounds(&self, index: usize) -> [f64; 4] {
self.points[index].bounds
}
}
#[cfg(test)]
mod tests {
use axiolid_core::{Frame3, Point2, Vec3};
use super::super::{classify_vertices, node_point, orient_sign, project_local};
use super::*;
fn plane_of(frame: Frame3) -> (ExactSectionPlane, Projection) {
let plane = ExactSectionPlane::new(frame).expect("plane");
let projection = Projection::new(&plane).expect("projection");
(plane, projection)
}
fn flat_frame() -> Frame3 {
Frame3 {
origin: Point3::new(0.0, 0.0, 0.5),
x: Vec3::X,
y: Vec3::Y,
z: Vec3::Z,
}
}
fn turned(k: u32) -> Frame3 {
let (s, c) = (std::f64::consts::PI * f64::from(k) / 64.0).sin_cos();
Frame3 {
origin: Point3::new(0.0, 0.0, 0.5),
x: Vec3::new(c, s, 0.0),
y: Vec3::new(-s, c, 0.0),
z: Vec3::Z,
}
}
fn column(x: f64) -> (Point3, Point3) {
(Point3::new(x, 0.1, 0.0), Point3::new(x, 0.1, 1.0))
}
#[test]
fn crossings_on_one_line_stay_collinear_where_rounded_points_do_not() {
let xs = [0.3, 0.9, 1.7];
let mut premise = false;
for k in 1..64 {
let frame = turned(k);
let (plane, projection) = plane_of(frame);
let mut points = PlanePoints::new(&plane, projection).expect("points");
let nodes: Vec<usize> = xs
.iter()
.map(|&x| {
let (a, b) = column(x);
points.crossing(a, b).expect("crossing")
})
.collect();
assert_eq!(
points.orient(nodes[0], nodes[1], nodes[2]).expect("orient"),
Sign::Zero,
"frame {k}"
);
let positions: Vec<Point3> = xs
.iter()
.flat_map(|&x| {
let (a, b) = column(x);
[a, b]
})
.collect();
let mesh = TriMesh::new(positions, vec![]);
let classifications = classify_vertices(&mesh, plane).expect("classes");
let rounded: Vec<_> = (0..3u32)
.map(|i| {
let node = NodeKey::Edge(EdgeKey::new(2 * i, 2 * i + 1));
let world = node_point(&mesh, &classifications, node).expect("point");
project_local(frame, world).expect("local")
})
.collect();
premise |= orient_sign(rounded[0], rounded[1], rounded[2]).expect("sign") != Sign::Zero;
}
assert!(premise, "no frame rounds the collinear points off the line");
}
#[test]
fn a_vertex_and_a_crossing_at_the_same_point_are_the_same() {
let (plane, projection) = plane_of(flat_frame());
let mut points = PlanePoints::new(&plane, projection).expect("points");
let crossing = points
.crossing(Point3::new(0.25, 0.5, 1.0), Point3::new(0.25, 0.5, -1.0))
.expect("crossing");
let vertex = points.vertex(Point3::new(0.25, 0.5, 0.5)).expect("vertex");
assert!(points.same(crossing, vertex).expect("same"));
let slanted = points
.crossing(Point3::new(0.0, 0.0, -1.0), Point3::new(1.0, 2.0, 3.0))
.expect("crossing");
let other = points
.vertex(Point3::new(0.375, 0.75, 0.5))
.expect("vertex");
assert!(points.same(slanted, other).expect("same"));
assert!(!points.same(slanted, vertex).expect("same"));
}
#[test]
fn a_crossing_with_a_binary64_position_takes_the_fast_path() {
let (plane, projection) = plane_of(flat_frame());
let mut points = PlanePoints::new(&plane, projection).expect("points");
let crossing = points
.crossing(Point3::new(0.0, 0.0, -1.0), Point3::new(1.0, 2.0, 3.0))
.expect("crossing");
assert_eq!(points.points[crossing].flat, Some(Point2::new(0.375, 0.75)));
let vertex = points
.vertex(Point3::new(0.375, 0.75, 0.5))
.expect("vertex");
assert!(points.same(crossing, vertex).expect("same"));
let third = points
.crossing(Point3::new(0.0, 0.0, 0.0), Point3::new(1.0, 1.0, 1.5))
.expect("crossing");
assert_eq!(points.points[third].flat, None);
assert!(!points.same(third, vertex).expect("same"));
let near = points
.vertex(Point3::new(0.375_f64.next_up(), 0.75, 0.5))
.expect("vertex");
assert!(!points.same(crossing, near).expect("same"));
assert_eq!(
points.lexicographic(crossing, near).expect("order"),
Ordering::Less
);
}
#[test]
fn a_crossing_does_not_depend_on_the_direction_of_its_edge() {
let (plane, projection) = plane_of(turned(5));
let mut points = PlanePoints::new(&plane, projection).expect("points");
let (a, b) = (Point3::new(0.1, 0.7, -0.3), Point3::new(-0.9, 0.2, 1.4));
let forward = points.crossing(a, b).expect("crossing");
let backward = points.crossing(b, a).expect("crossing");
assert!(points.same(forward, backward).expect("same"));
assert_eq!(points.points[forward].exact, points.points[backward].exact);
}
#[test]
fn the_box_of_a_crossing_contains_its_position() {
let frame = Frame3 {
origin: Point3::ZERO,
..flat_frame()
};
let (plane, projection) = plane_of(frame);
let mut points = PlanePoints::new(&plane, projection).expect("points");
let crossing = points
.crossing(Point3::new(0.0, 0.0, -1.0), Point3::new(1.0, 1.0, 2.0))
.expect("crossing");
let [u_lo, v_lo, u_hi, v_hi] = points.bounds(crossing);
let third = 1.0 / 3.0;
assert!(u_lo <= third && third <= u_hi && v_lo <= third && third <= v_hi);
assert!(u_hi - u_lo < 1e-12);
}
#[test]
fn a_mirrored_frame_reverses_the_orientation() {
let counter = flat_frame();
let mirrored = Frame3 {
y: -Vec3::Y,
z: -Vec3::Z,
..counter
};
for (frame, expected) in [(counter, Sign::Positive), (mirrored, Sign::Negative)] {
let (plane, projection) = plane_of(frame);
let mut points = PlanePoints::new(&plane, projection).expect("points");
let nodes: Vec<usize> = [(0.0, 0.0), (1.0, 0.0), (0.0, 1.0)]
.into_iter()
.map(|(x, y)| points.vertex(Point3::new(x, y, 0.5)).expect("vertex"))
.collect();
assert_eq!(
points.orient(nodes[0], nodes[1], nodes[2]).expect("orient"),
expected
);
}
}
fn slanted(points: &mut PlanePoints, thirds: &[f64]) -> Vec<usize> {
thirds
.iter()
.map(|&x| {
points
.crossing(Point3::new(0.0, 0.1, 0.0), Point3::new(x, 0.1, 1.5))
.expect("crossing")
})
.collect()
}
#[test]
fn homogeneous_points_decide_collinearity_exactly_in_either_frame_sense() {
let counter = flat_frame();
let mirrored = Frame3 {
y: -Vec3::Y,
z: -Vec3::Z,
..counter
};
for (frame, turn) in [(counter, Sign::Positive), (mirrored, Sign::Negative)] {
let (plane, projection) = plane_of(frame);
let mut points = PlanePoints::new(&plane, projection).expect("points");
let line = slanted(&mut points, &[1.0, 2.0, 4.0]);
assert!(line.iter().all(|&i| points.points[i].flat.is_none()));
assert_eq!(
points.orient(line[0], line[1], line[2]).expect("orient"),
Sign::Zero
);
let above = points.vertex(Point3::new(0.5, 0.5, 0.5)).expect("vertex");
assert_eq!(
points.orient(line[0], line[1], above).expect("orient"),
turn
);
}
}
#[test]
fn equal_homogeneous_points_compare_equal() {
let (plane, projection) = plane_of(flat_frame());
let mut points = PlanePoints::new(&plane, projection).expect("points");
let first = slanted(&mut points, &[1.0])[0];
let second = points
.crossing(Point3::new(0.0, 0.1, 0.0), Point3::new(2.0, 0.1, 3.0))
.expect("crossing");
assert!(points.same(first, second).expect("same"));
assert_eq!(
points.lexicographic(first, second).expect("order"),
Ordering::Equal
);
}
#[test]
fn an_edge_that_does_not_move_crosses_at_its_own_coordinates() {
let (plane, projection) = plane_of(flat_frame());
let mut points = PlanePoints::new(&plane, projection).expect("points");
let column = points
.crossing(Point3::new(0.25, 0.5, 1.0), Point3::new(0.25, 0.5, -1.0))
.expect("crossing");
assert_eq!(points.points[column].flat, Some(Point2::new(0.25, 0.5)));
}
#[test]
fn a_vertex_node_needs_a_certified_plane_side_of_zero() {
let (plane, projection) = plane_of(flat_frame());
let mut points = PlanePoints::new(&plane, projection).expect("points");
let mesh = TriMesh::new(vec![Point3::new(0.0, 0.0, 1.0)], vec![]);
let classifications = classify_vertices(&mesh, plane).expect("classes");
let off = points.node(&mesh, &classifications, NodeKey::Vertex(0));
assert!(matches!(
off,
Err(GeomError::BackendContractViolation { .. })
));
}
#[test]
fn a_vertex_off_the_plane_is_a_contract_violation() {
let (plane, projection) = plane_of(flat_frame());
let mut points = PlanePoints::new(&plane, projection).expect("points");
let off = points.vertex(Point3::new(0.0, 0.0, 0.5 + f64::EPSILON));
assert!(matches!(
off,
Err(GeomError::BackendContractViolation { .. })
));
let same_side = points.crossing(Point3::new(0.0, 0.0, 1.0), Point3::new(1.0, 0.0, 2.0));
assert!(matches!(
same_side,
Err(GeomError::BackendContractViolation { .. })
));
}
}