1use crate::{Bounds, IntensityTable, Mesh, PackedMesh};
2
3#[derive(Clone, Copy, Debug, PartialEq, Eq)]
4pub struct FilamentTopology {
5 branches: u32,
6 terminals: u32,
7 junctions: u32,
8 nodes: u32,
9}
10
11impl FilamentTopology {
12 pub fn new(branches: u32, terminals: u32, junctions: u32, nodes: u32) -> Self {
13 assert!(branches > 0, "a filament holds at least one branch");
14 assert!(
15 terminals <= 2 * branches && junctions < branches,
16 "a filament counts its terminals and junctions"
17 );
18 assert!(nodes > 0, "a filament holds at least one node");
19 Self {
20 branches,
21 terminals,
22 junctions,
23 nodes,
24 }
25 }
26
27 pub fn branches(self) -> u32 {
28 self.branches
29 }
30
31 pub fn terminals(self) -> u32 {
32 self.terminals
33 }
34
35 pub fn junctions(self) -> u32 {
36 self.junctions
37 }
38
39 pub fn nodes(self) -> u32 {
40 self.nodes
41 }
42}
43
44#[derive(Clone, Copy, Debug, PartialEq)]
45pub struct FilamentStatistics {
46 length: f64,
47 diameter: f64,
48 volume: f64,
49 topology: FilamentTopology,
50 centroid: [f32; 3],
51 bounds: Bounds,
52}
53
54impl FilamentStatistics {
55 pub fn new(
56 length: f64,
57 diameter: f64,
58 volume: f64,
59 topology: FilamentTopology,
60 centroid: [f32; 3],
61 bounds: Bounds,
62 ) -> Self {
63 assert!(
64 length.is_finite() && length > 0.0,
65 "a filament length is a positive length"
66 );
67 assert!(
68 diameter.is_finite() && diameter > 0.0,
69 "a filament diameter is a positive length"
70 );
71 assert!(
72 volume.is_finite() && volume > 0.0,
73 "a filament volume is a positive volume"
74 );
75 assert!(
76 centroid.iter().all(|value| value.is_finite()),
77 "a filament centroid must be finite"
78 );
79 Self {
80 length,
81 diameter,
82 volume,
83 topology,
84 centroid,
85 bounds,
86 }
87 }
88
89 pub fn length(self) -> f64 {
90 self.length
91 }
92
93 pub fn diameter(self) -> f64 {
94 self.diameter
95 }
96
97 pub fn volume(self) -> f64 {
98 self.volume
99 }
100
101 pub fn topology(self) -> FilamentTopology {
102 self.topology
103 }
104
105 pub fn branches(self) -> u32 {
106 self.topology.branches()
107 }
108
109 pub fn terminals(self) -> u32 {
110 self.topology.terminals()
111 }
112
113 pub fn junctions(self) -> u32 {
114 self.topology.junctions()
115 }
116
117 pub fn nodes(self) -> u32 {
118 self.topology.nodes()
119 }
120
121 pub fn centroid(self) -> [f32; 3] {
122 self.centroid
123 }
124
125 pub fn bounds(self) -> Bounds {
126 self.bounds
127 }
128}
129
130#[derive(Clone, Debug, PartialEq)]
131pub struct FilamentSet {
132 level: usize,
133 geometry: PackedMesh,
134 objects: Vec<FilamentStatistics>,
135 intensities: IntensityTable,
136}
137
138impl FilamentSet {
139 pub fn new(
140 level: usize,
141 mesh: Mesh,
142 objects: Vec<FilamentStatistics>,
143 intensities: IntensityTable,
144 ) -> Self {
145 let geometry = PackedMesh::new(mesh);
146 assert_eq!(
147 geometry.object_count(),
148 objects.len(),
149 "every packed filament names an object"
150 );
151 assert_eq!(
152 geometry.is_empty(),
153 objects.is_empty(),
154 "the mesh and the object list agree on emptiness"
155 );
156 assert_eq!(
157 intensities.objects(),
158 objects.len(),
159 "every filament is measured in every channel"
160 );
161 Self {
162 level,
163 geometry,
164 objects,
165 intensities,
166 }
167 }
168
169 pub fn from_parts(
170 level: usize,
171 geometry: PackedMesh,
172 objects: Vec<FilamentStatistics>,
173 intensities: IntensityTable,
174 ) -> Option<Self> {
175 if geometry.object_count() != objects.len()
176 || geometry.is_empty() != objects.is_empty()
177 || intensities.objects() != objects.len()
178 {
179 return None;
180 }
181 Some(Self {
182 level,
183 geometry,
184 objects,
185 intensities,
186 })
187 }
188
189 pub fn level(&self) -> usize {
190 self.level
191 }
192
193 pub fn geometry(&self) -> &PackedMesh {
194 &self.geometry
195 }
196
197 pub fn objects(&self) -> &[FilamentStatistics] {
198 &self.objects
199 }
200
201 pub fn intensities(&self) -> &IntensityTable {
202 &self.intensities
203 }
204
205 pub fn object(&self, index: usize) -> Option<FilamentStatistics> {
206 self.objects.get(index).copied()
207 }
208
209 pub fn object_count(&self) -> usize {
210 self.objects.len()
211 }
212
213 pub fn is_empty(&self) -> bool {
214 self.objects.is_empty()
215 }
216}