1use alloc::collections::BTreeMap;
2use alloc::vec::Vec;
3
4use he3d_math::Vec3;
5
6use crate::aabb::Aabb;
7
8#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Debug)]
12pub struct StaticMeshId {
13 index: u32,
14 generation: u32,
15}
16
17impl StaticMeshId {
18 pub(crate) const fn new(index: u32, generation: u32) -> Self {
21 Self { index, generation }
22 }
23
24 #[must_use]
27 pub const fn index(self) -> u32 {
28 self.index
29 }
30
31 #[must_use]
34 pub const fn generation(self) -> u32 {
35 self.generation
36 }
37}
38
39#[derive(Clone, Debug)]
42pub(crate) struct StaticMesh {
43 pub(crate) positions: Vec<Vec3>,
46 pub(crate) triangle_bounds: Vec<Aabb>,
49 pub(crate) welded: Vec<[u32; 3]>,
52 pub(crate) bounds: Aabb,
55}
56
57impl StaticMesh {
58 pub(crate) fn triangle_count(&self) -> usize {
60 self.triangle_bounds.len()
61 }
62
63 pub(crate) fn triangle(&self, index: usize) -> [Vec3; 3] {
65 let base = index * 3;
66 [
67 self.positions[base],
68 self.positions[base + 1],
69 self.positions[base + 2],
70 ]
71 }
72}
73
74#[derive(Clone, Debug, Default)]
86pub(crate) struct StaticTopology {
87 welded: BTreeMap<[u32; 3], u32>,
89 vertex_use: BTreeMap<u32, u32>,
91 edge_use: BTreeMap<(u32, u32), u32>,
93 next_vertex: u32,
95}
96
97impl StaticTopology {
98 pub(crate) fn weld(&mut self, position: Vec3) -> u32 {
100 let key = position_key(position);
101 if let Some(&id) = self.welded.get(&key) {
102 return id;
103 }
104 let id = self.next_vertex;
105 self.next_vertex = self.next_vertex.wrapping_add(1);
106 self.welded.insert(key, id);
107 id
108 }
109
110 pub(crate) fn add_triangle(&mut self, welded: [u32; 3]) {
112 for vertex in welded {
113 *self.vertex_use.entry(vertex).or_insert(0) += 1;
114 }
115 for edge in edges_of(welded) {
116 *self.edge_use.entry(edge).or_insert(0) += 1;
117 }
118 }
119
120 pub(crate) fn remove_triangle(&mut self, welded: [u32; 3]) {
122 for vertex in welded {
123 if let Some(count) = self.vertex_use.get_mut(&vertex) {
124 *count = count.saturating_sub(1);
125 if *count == 0 {
126 self.vertex_use.remove(&vertex);
127 }
128 }
129 }
130 for edge in edges_of(welded) {
131 if let Some(count) = self.edge_use.get_mut(&edge) {
132 *count = count.saturating_sub(1);
133 if *count == 0 {
134 self.edge_use.remove(&edge);
135 }
136 }
137 }
138 }
139
140 pub(crate) fn is_internal_edge(&self, first: u32, second: u32) -> bool {
150 let key = if first <= second {
151 (first, second)
152 } else {
153 (second, first)
154 };
155 self.edge_use.get(&key).copied().unwrap_or(0) >= 2
156 }
157
158 #[cfg(test)]
160 pub(crate) fn vertex_count(&self) -> usize {
161 self.welded.len()
162 }
163}
164
165fn edges_of(welded: [u32; 3]) -> [(u32, u32); 3] {
167 let ordered = |first: u32, second: u32| {
168 if first <= second {
169 (first, second)
170 } else {
171 (second, first)
172 }
173 };
174 [
175 ordered(welded[0], welded[1]),
176 ordered(welded[1], welded[2]),
177 ordered(welded[2], welded[0]),
178 ]
179}
180
181fn position_key(position: Vec3) -> [u32; 3] {
184 let canonical = |value: f32| {
185 if value == 0.0 {
186 0.0_f32.to_bits()
187 } else {
188 value.to_bits()
189 }
190 };
191 [
192 canonical(position.x),
193 canonical(position.y),
194 canonical(position.z),
195 ]
196}
197
198#[cfg(test)]
199mod tests {
200 use super::StaticTopology;
201 use he3d_math::Vec3;
202
203 #[test]
204 fn identical_positions_weld_to_one_vertex() {
205 let mut topology = StaticTopology::default();
206 let first = topology.weld(Vec3::new(1.0, 2.0, 3.0));
207 let second = topology.weld(Vec3::new(1.0, 2.0, 3.0));
208 assert_eq!(first, second);
209 assert_eq!(topology.vertex_count(), 1);
210 }
211
212 #[test]
213 fn negative_zero_welds_with_positive_zero() {
214 let mut topology = StaticTopology::default();
215 assert_eq!(
216 topology.weld(Vec3::new(0.0, 0.0, 0.0)),
217 topology.weld(Vec3::new(-0.0, -0.0, -0.0))
218 );
219 }
220
221 #[test]
222 fn an_edge_shared_by_two_triangles_is_internal() {
223 let mut topology = StaticTopology::default();
224 topology.add_triangle([0, 1, 2]);
226 assert!(
227 !topology.is_internal_edge(1, 2),
228 "one triangle is a boundary"
229 );
230 topology.add_triangle([1, 3, 2]);
231 assert!(topology.is_internal_edge(1, 2));
232 assert!(!topology.is_internal_edge(0, 1));
233 }
234
235 #[test]
236 fn edges_weld_across_separate_meshes() {
237 let mut topology = StaticTopology::default();
238 topology.add_triangle([0, 1, 2]);
240 topology.add_triangle([1, 3, 2]);
242 assert!(
243 topology.is_internal_edge(2, 1),
244 "the order of the pair must not matter"
245 );
246 }
247
248 #[test]
249 fn removing_a_triangle_releases_its_edges() {
250 let mut topology = StaticTopology::default();
251 topology.add_triangle([0, 1, 2]);
252 topology.add_triangle([1, 3, 2]);
253 assert!(topology.is_internal_edge(1, 2));
254 topology.remove_triangle([1, 3, 2]);
255 assert!(!topology.is_internal_edge(1, 2));
256 }
257
258 #[test]
259 fn welding_is_stable_across_add_and_remove() {
260 let mut topology = StaticTopology::default();
261 let id = topology.weld(Vec3::X);
262 topology.add_triangle([id, id, id]);
263 topology.remove_triangle([id, id, id]);
264 assert_eq!(topology.weld(Vec3::X), id);
266 assert_eq!(topology.vertex_count(), 1);
267 }
268}