axiolid_pointcloud_reconstruction_contract/
evidence.rs

1//! What a reconstruction was asked to do, and what it actually did.
2//!
3//! A reconstruction is an *estimate*: a point set does not determine a unique
4//! surface, so every result carries the assumptions that produced it. The
5//! request records what the caller asked for; the evidence records what the
6//! provider did, including where it had to guess.
7
8use axiolid_core::Scalar;
9use axiolid_mesh::TriMesh;
10
11/// How densely the surface should be reconstructed.
12///
13/// Not an algorithm choice: a caller states the resolution it needs in model
14/// units and the provider maps that onto whatever its method uses (octree
15/// depth, ball radius, alpha value). Stating it in units keeps the request
16/// portable across providers that share no parameters.
17#[derive(Debug, Clone, Copy, PartialEq)]
18pub enum Resolution {
19    /// Target edge length of the reconstructed surface, in model units.
20    ///
21    /// The provider is expected to approach this, not to guarantee it: the
22    /// achieved value is reported in [`ReconstructionEvidence`].
23    TargetEdgeLength(Scalar),
24    /// Let the provider choose from the sample spacing it measures.
25    ///
26    /// Honest default for a caller with no resolution requirement. What was
27    /// chosen is reported rather than left implicit.
28    FromSampleSpacing,
29}
30
31/// What the caller wants reconstructed.
32#[derive(Debug, Clone, PartialEq)]
33#[non_exhaustive]
34pub struct ReconstructionRequest {
35    /// Surface density to aim for.
36    pub resolution: Resolution,
37    /// Whether the result must be a closed solid.
38    ///
39    /// A scan of a room's interior has no back faces, so demanding closure
40    /// forces the provider to invent surface where it has no data. Set this
41    /// only when the capture genuinely covers the whole object; a provider
42    /// that cannot honour it must refuse rather than fabricate.
43    pub require_closed: bool,
44    /// Whether per-point normals may be used when present.
45    ///
46    /// Normals dramatically improve most methods, but a capture's normals
47    /// can be wrong. Turning this off asks the provider to work from
48    /// positions alone.
49    pub use_normals: bool,
50}
51
52impl Default for ReconstructionRequest {
53    fn default() -> Self {
54        Self {
55            resolution: Resolution::FromSampleSpacing,
56            require_closed: false,
57            use_normals: true,
58        }
59    }
60}
61
62impl ReconstructionRequest {
63    /// A request at a stated resolution.
64    pub fn at_edge_length(edge_length: Scalar) -> Self {
65        Self {
66            resolution: Resolution::TargetEdgeLength(edge_length),
67            ..Self::default()
68        }
69    }
70
71    /// Demand a closed result, refusing rather than inventing surface.
72    pub fn requiring_closed(mut self) -> Self {
73        self.require_closed = true;
74        self
75    }
76
77    /// Ignore per-point normals even when the cloud carries them.
78    pub fn ignoring_normals(mut self) -> Self {
79        self.use_normals = false;
80        self
81    }
82}
83
84/// Counters describing one reconstruction.
85///
86/// Every field is a fact about the computation, never a quality verdict:
87/// whether 12% unsupported area is acceptable is the caller's decision, not
88/// the kernel's.
89#[derive(Debug, Clone, PartialEq, Default)]
90#[non_exhaustive]
91pub struct ReconstructionEvidence {
92    /// Points supplied.
93    pub input_points: usize,
94    /// Points the provider actually used.
95    ///
96    /// Lower than `input_points` when the provider discarded outliers or
97    /// duplicates. A large gap is worth a caller's attention.
98    pub used_points: usize,
99    /// Triangles in the result.
100    pub output_triangles: usize,
101    /// Connected components in the result.
102    ///
103    /// A capture with gaps often reconstructs into several shells. Reporting
104    /// it lets a caller detect the split here rather than downstream.
105    pub output_components: usize,
106    /// Whether the result is a closed two-manifold solid.
107    pub closed: bool,
108    /// Edge length the provider achieved, in model units.
109    ///
110    /// Reported whether or not the caller stated one, so a
111    /// `FromSampleSpacing` request still learns what it got.
112    pub achieved_edge_length: Scalar,
113    /// Median distance between neighbouring input samples.
114    ///
115    /// The scale the capture actually resolves. A result finer than this is
116    /// interpolation, not measurement.
117    pub sample_spacing: Scalar,
118    /// Whether per-point normals were used.
119    ///
120    /// False when the cloud carried none, or the request declined them, or
121    /// the provider could not use them. Distinguishes a normal-driven
122    /// result from a positions-only one.
123    pub used_normals: bool,
124    /// Triangles whose vertices came from interpolation across a gap in the
125    /// data rather than from measured samples.
126    ///
127    /// This is the honest measure of how much of the surface is invented.
128    /// Zero means every triangle rests on real points.
129    pub interpolated_triangles: usize,
130}
131
132impl ReconstructionEvidence {
133    /// Start from a cleared record and fill in what the provider measured.
134    ///
135    /// `#[non_exhaustive]` keeps the struct additive, so a provider outside
136    /// this crate cannot build one with a literal. This constructor is the
137    /// supported route: take the default and set the fields you measured.
138    /// A field left at its default is honestly "not measured" rather than a
139    /// value invented to satisfy the type.
140    pub fn measured() -> Self {
141        Self::default()
142    }
143}
144
145/// A reconstruction and the evidence that produced it.
146#[derive(Debug, Clone, PartialEq)]
147pub struct ReconstructionOutcome {
148    /// The reconstructed surface. An empty mesh is a legitimate value only
149    /// when the provider also explains it in the evidence.
150    pub mesh: TriMesh,
151    /// What the provider did.
152    pub evidence: ReconstructionEvidence,
153}
154
155impl ReconstructionOutcome {
156    /// Pair a mesh with its evidence.
157    pub const fn new(mesh: TriMesh, evidence: ReconstructionEvidence) -> Self {
158        Self { mesh, evidence }
159    }
160}