axiolid_mesh_compile_contract/polyline.rs
1//! Curves compiled to polylines, with a certified chord bound (#311).
2//!
3//! A curve node has no area, so it has no triangle mesh: compiling it with
4//! [`crate::MeshCompiler::compile_mesh`] is refused. It is a separate
5//! operation instead of a second output kind of `compile_mesh`, so the mesh
6//! path's signature and every consumer of it stay as they are.
7
8use axiolid_contracts::{Backend, ExecutionOptions, GeomResult};
9use axiolid_core::{Point3, Scalar};
10use axiolid_model::{GeometryGraph, NodeId};
11
12/// How far the exact curve may lie from its polyline, reported the way a
13/// compiled mesh's deviation is: the chord budget asked for, and a
14/// certified upper bound on the distance from every point of the exact
15/// curve to the nearest point of the polyline, both in world units.
16///
17/// The bound is one-sided, exact curve to polyline, and it is always
18/// present: a compiler that cannot bound a curve refuses it rather than
19/// returning a polyline with no bound.
20#[non_exhaustive]
21#[derive(Debug, Clone, Copy, PartialEq)]
22pub struct ChordDeviation {
23 /// The chord budget the compilation was asked for:
24 /// `ExecutionOptions::chord_error`, else the linear tolerance.
25 pub requested: Scalar,
26 /// An upper bound on the distance from every point of the exact curve
27 /// to the polyline.
28 pub bound: Scalar,
29}
30
31impl ChordDeviation {
32 /// A bound against the budget it was asked for.
33 #[must_use]
34 pub const fn new(requested: Scalar, bound: Scalar) -> Self {
35 Self { requested, bound }
36 }
37
38 /// Whether the polyline is certified within the requested budget.
39 ///
40 /// `false` means "not certified", not "missed": the bound is an upper
41 /// bound, and the true deviation may still be below the budget.
42 #[must_use]
43 pub fn meets_requested(&self) -> bool {
44 self.bound <= self.requested
45 }
46}
47
48/// A curve compiled to a 3D polyline.
49#[non_exhaustive]
50#[derive(Debug, Clone, PartialEq)]
51pub struct PolylineOutcome {
52 /// The vertices in the curve's direction of travel. A closed curve
53 /// ends on the vertex it starts on.
54 pub points: Vec<Point3>,
55 /// Whether the curve is closed: the last vertex is the first.
56 pub closed: bool,
57 /// How far the exact curve may lie from the polyline.
58 pub deviation: ChordDeviation,
59}
60
61impl PolylineOutcome {
62 /// A polyline and its deviation. `closed` is read from the points: at
63 /// least three of them, the last equal to the first.
64 #[must_use]
65 pub fn new(points: Vec<Point3>, deviation: ChordDeviation) -> Self {
66 let closed = points.len() > 2 && points.first() == points.last();
67 Self {
68 points,
69 closed,
70 deviation,
71 }
72 }
73}
74
75/// Backend that compiles a curve node of a graph to a 3D polyline.
76///
77/// Implementations must return [`axiolid_contracts::GeomError::Unsupported`]
78/// or [`axiolid_contracts::GeomError::UnsupportedInput`] for a node they
79/// cannot compile or cannot bound, naming it; they never return a polyline
80/// whose [`ChordDeviation::bound`] is not an upper bound.
81pub trait PolylineCompiler: Backend {
82 /// Compile one root to a polyline within the options' chord budget.
83 fn compile_polyline(
84 &self,
85 graph: &GeometryGraph,
86 root: NodeId,
87 options: &ExecutionOptions,
88 ) -> GeomResult<PolylineOutcome>;
89}