Skip to main content

axioval_engine/
triangle_count.rs

1//! Triangle counts: how many triangles the mesh a host produced for an
2//! object holds.
3//!
4//! ADR 0004: this seam counts. Whether an element has too many polygons is a
5//! rule's judgement over the count.
6//!
7//! The count is of the mesh the host registered, not of anything the source
8//! states: an extruded box has no triangles of its own, and a curved face
9//! has as many as the host's chord budget made of it. Another host, or the
10//! same host with another budget, may count differently. The evidence is
11//! therefore exact only when the mesh is the object's exact shape (every
12//! face planar); a tessellation of curved faces is counted exactly but
13//! reported with approximate evidence, since its count depends on the
14//! host's tessellation.
15
16use std::sync::Arc;
17
18use axioval_ir::{Evidence, ObjectId};
19use thiserror::Error;
20
21/// Failure to count an object's triangles.
22#[derive(Clone, Debug, Error, PartialEq, Eq)]
23pub enum TriangleCountError {
24    /// The service holds no geometry for this object.
25    #[error("no geometry for `{0}`")]
26    UnknownObject(ObjectId),
27    /// The object has a body the host could not mesh, or a mesh that cannot
28    /// be read.
29    #[error("triangle count unavailable: {0}")]
30    Unavailable(String),
31    /// The count names another object, or its evidence is not reviewable.
32    #[error("triangle count is invalid")]
33    InvalidMeasurement,
34}
35
36/// The number of triangles in one object's mesh, with evidence.
37#[derive(Clone, Debug, PartialEq, Eq)]
38pub struct TriangleCount {
39    object: ObjectId,
40    triangles: u64,
41    evidence: Evidence,
42}
43
44impl TriangleCount {
45    /// `triangles` in the mesh of `object`. The evidence must name where
46    /// the count comes from; it is exact when the mesh is the object's
47    /// exact shape.
48    pub fn try_new(
49        object: ObjectId,
50        triangles: u64,
51        evidence: Evidence,
52    ) -> Result<Self, TriangleCountError> {
53        if evidence.locator.trim().is_empty() {
54            return Err(TriangleCountError::InvalidMeasurement);
55        }
56        Ok(Self {
57            object,
58            triangles,
59            evidence,
60        })
61    }
62
63    /// The counted object.
64    #[must_use]
65    pub fn object(&self) -> &ObjectId {
66        &self.object
67    }
68
69    /// The number of triangles in the host's mesh; zero for an object the
70    /// host declared bodiless.
71    #[must_use]
72    pub fn triangles(&self) -> u64 {
73        self.triangles
74    }
75
76    /// Whether the mesh is the object's exact shape rather than a
77    /// tessellation whose count depends on the host.
78    #[must_use]
79    pub fn is_exact(&self) -> bool {
80        self.evidence.exact
81    }
82
83    /// Reviewable provenance of the count.
84    #[must_use]
85    pub fn evidence(&self) -> &Evidence {
86        &self.evidence
87    }
88}
89
90/// Counts the triangles of model objects' meshes.
91pub trait TriangleCountService: Send + Sync + 'static {
92    /// The number of triangles in `object`'s mesh.
93    fn count_triangles(&self, object: &ObjectId) -> Result<TriangleCount, TriangleCountError>;
94}
95
96/// Registry handle for a [`TriangleCountService`].
97#[derive(Clone)]
98pub struct TriangleCountServiceHandle(Arc<dyn TriangleCountService>);
99
100impl TriangleCountServiceHandle {
101    /// Wraps a trusted triangle-count service.
102    #[must_use]
103    pub fn new(service: Arc<dyn TriangleCountService>) -> Self {
104        Self(service)
105    }
106
107    /// The triangle count of `object`. A count naming another object is
108    /// refused.
109    pub fn count_triangles(&self, object: &ObjectId) -> Result<TriangleCount, TriangleCountError> {
110        let count = self.0.count_triangles(object)?;
111        if count.object() != object {
112            return Err(TriangleCountError::InvalidMeasurement);
113        }
114        Ok(count)
115    }
116}
117
118#[cfg(test)]
119mod tests {
120    use super::*;
121    use axioval_ir::SourceId;
122
123    fn id(local: &str) -> ObjectId {
124        ObjectId::new(SourceId::new("cad", "m").unwrap(), local).unwrap()
125    }
126
127    fn evidence() -> Evidence {
128        Evidence::exact(SourceId::new("cad", "m").unwrap(), "triangle-count:a")
129    }
130
131    #[test]
132    fn a_count_needs_a_locator() {
133        assert!(TriangleCount::try_new(id("a"), 12, evidence()).is_ok());
134        let unlocated = Evidence::exact(SourceId::new("cad", "m").unwrap(), " ");
135        assert_eq!(
136            TriangleCount::try_new(id("a"), 12, unlocated),
137            Err(TriangleCountError::InvalidMeasurement)
138        );
139    }
140
141    struct Other;
142    impl TriangleCountService for Other {
143        fn count_triangles(&self, _: &ObjectId) -> Result<TriangleCount, TriangleCountError> {
144            TriangleCount::try_new(id("b"), 12, evidence())
145        }
146    }
147
148    #[test]
149    fn a_count_of_another_object_is_refused() {
150        let handle = TriangleCountServiceHandle::new(Arc::new(Other));
151        assert_eq!(
152            handle.count_triangles(&id("a")),
153            Err(TriangleCountError::InvalidMeasurement)
154        );
155        assert_eq!(handle.count_triangles(&id("b")).unwrap().triangles(), 12);
156    }
157}