axiolid_pointcloud_reconstruction_contract/contract.rs
1//! The provider trait: what implementing reconstruction obliges you to.
2
3use axiolid_contracts::{
4 Backend, CancellationGranularity, Determinism, ExecutionOptions, GeomResult, ScratchRequirement,
5};
6use axiolid_core::Scalar;
7use axiolid_pointcloud::PointCloud;
8
9use crate::{ReconstructionOutcome, ReconstructionRequest};
10
11/// Why a reconstruction could not be performed.
12///
13/// Every variant is a *typed refusal*: the provider knows why it cannot
14/// answer and says so. A provider must never return an empty mesh in place
15/// of one of these — an empty result and "I cannot do this" are different
16/// answers, and a caller that cannot tell them apart will treat a failure as
17/// an object that is not there.
18#[derive(Debug, Clone, PartialEq)]
19#[non_exhaustive]
20pub enum ReconstructionRefusal {
21 /// Fewer points than the method needs.
22 TooFewPoints {
23 /// Points supplied.
24 supplied: usize,
25 /// Minimum this provider needs.
26 required: usize,
27 },
28 /// The points are degenerate: all coincident, collinear, or coplanar.
29 ///
30 /// A surface cannot be reconstructed from a set with no volume, and
31 /// producing a zero-thickness sheet would misrepresent the input.
32 DegenerateExtent {
33 /// What the provider measured.
34 detail: String,
35 },
36 /// The request demanded a closed result the data cannot support.
37 ///
38 /// Raised instead of inventing surface across a gap the capture never
39 /// covered.
40 CannotClose {
41 /// Why closure is impossible for this input.
42 detail: String,
43 },
44 /// The requested resolution is finer than the samples resolve.
45 ///
46 /// Honouring it would interpolate detail that was never measured and
47 /// present it as geometry.
48 ResolutionExceedsData {
49 /// Edge length the caller asked for.
50 requested: Scalar,
51 /// Spacing the samples actually resolve.
52 sample_spacing: Scalar,
53 },
54 /// The provider does not implement some part of the request.
55 Unsupported {
56 /// What is unsupported.
57 detail: String,
58 },
59}
60
61impl core::fmt::Display for ReconstructionRefusal {
62 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
63 match self {
64 Self::TooFewPoints { supplied, required } => write!(
65 f,
66 "reconstruction needs at least {required} points, got {supplied}"
67 ),
68 Self::DegenerateExtent { detail } => {
69 write!(f, "point set has no reconstructable extent: {detail}")
70 }
71 Self::CannotClose { detail } => {
72 write!(f, "a closed result was required but is not supported by the data: {detail}")
73 }
74 Self::ResolutionExceedsData {
75 requested,
76 sample_spacing,
77 } => write!(
78 f,
79 "requested edge length {requested} is finer than the sample spacing {sample_spacing}"
80 ),
81 Self::Unsupported { detail } => write!(f, "unsupported request: {detail}"),
82 }
83 }
84}
85
86impl core::error::Error for ReconstructionRefusal {}
87
88/// Either a reconstruction or a typed reason there is none.
89///
90/// Deliberately not `Option<TriMesh>`: an absent surface always has a
91/// reason, and this type makes the reason impossible to drop.
92#[derive(Debug, Clone, PartialEq)]
93pub enum Reconstruction {
94 /// A surface was produced.
95 Surface(Box<ReconstructionOutcome>),
96 /// No surface was produced, for this stated reason.
97 Refused(ReconstructionRefusal),
98}
99
100impl Reconstruction {
101 /// The outcome, if one was produced.
102 pub fn outcome(&self) -> Option<&ReconstructionOutcome> {
103 match self {
104 Self::Surface(outcome) => Some(outcome),
105 Self::Refused(_) => None,
106 }
107 }
108
109 /// The refusal, if there was one.
110 pub fn refusal(&self) -> Option<&ReconstructionRefusal> {
111 match self {
112 Self::Surface(_) => None,
113 Self::Refused(reason) => Some(reason),
114 }
115 }
116
117 /// Whether a surface was produced.
118 pub fn is_surface(&self) -> bool {
119 matches!(self, Self::Surface(_))
120 }
121}
122
123/// Pointcloud-to-surface reconstruction provider.
124///
125/// Implementing this trait is the capability declaration: a type that
126/// implements it claims it can turn an unstructured point set into a
127/// surface. Providers that cannot must not implement it.
128pub trait PointcloudReconstruction: Backend {
129 /// Scratch this provider needs beyond its inputs and result.
130 ///
131 /// Defaults to [`ScratchRequirement::Unbounded`] so an unaudited
132 /// provider is treated as unbudgetable rather than assumed cheap.
133 /// Reconstruction is memory-hungry — an octree over a hundred million
134 /// points is not a detail a caller should discover by being killed.
135 fn scratch_requirement(&self) -> ScratchRequirement {
136 ScratchRequirement::Unbounded
137 }
138
139 /// Reproducibility this provider guarantees.
140 ///
141 /// Defaults to the weakest level so a provider that has not audited
142 /// itself cannot silently satisfy a stronger request.
143 fn determinism(&self) -> Determinism {
144 Determinism::BestEffort
145 }
146
147 /// How finely this provider observes cancellation.
148 ///
149 /// Defaults to [`CancellationGranularity::None`]: a provider that has
150 /// not audited its own polling must not advertise responsiveness it
151 /// cannot deliver. Reconstruction runs long, so overstating this is
152 /// exactly the case a caller would feel.
153 fn cancellation_granularity(&self) -> CancellationGranularity {
154 CancellationGranularity::None
155 }
156
157 /// Minimum points this provider needs to attempt a reconstruction.
158 ///
159 /// Callers can check before paying for a large request. A provider
160 /// still refuses by name if given fewer.
161 fn minimum_points(&self) -> usize {
162 4
163 }
164
165 /// Reconstruct a surface from a point set.
166 ///
167 /// Returns [`Reconstruction::Refused`] with a typed reason when the
168 /// request cannot be honoured. An `Err` is reserved for a failure of
169 /// the machinery itself — a refusal is an answer, not an error.
170 fn reconstruct(
171 &self,
172 cloud: &PointCloud,
173 request: &ReconstructionRequest,
174 options: &ExecutionOptions,
175 ) -> GeomResult<Reconstruction>;
176}