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