1use std::collections::HashMap;
13
14use crate::format::CadFormat;
15use crate::geom::{BBox, Mesh, MeshDiff, MeshLayers};
16
17type Vec3 = [f64; 3];
18type Tri = [Vec3; 3];
19
20pub fn diff(content_a: &[u8], content_b: &[u8], format: CadFormat) -> MeshDiff {
22 let a = match parse(content_a, format) {
23 Some(t) => t,
24 None => return error("could not parse the old mesh"),
25 };
26 let b = match parse(content_b, format) {
27 Some(t) => t,
28 None => return error("could not parse the new mesh"),
29 };
30
31 let mut bb = BBoxAccum::new();
33 for t in a.iter().chain(b.iter()) {
34 for v in t {
35 bb.add(*v);
36 }
37 }
38 let (min, max) = match bb.finish() {
39 Some(mm) => mm,
40 None => return ok_empty(),
41 };
42 let diag = dist(min, max);
43 let tol = (0.02 * diag).max(1e-9);
44 let cell = tol;
45 let tol2 = tol * tol;
46
47 let grid_a = TriGrid::build(&a, cell);
48 let grid_b = TriGrid::build(&b, cell);
49
50 let mut unchanged = MeshBuilder::new();
51 let mut added = MeshBuilder::new();
52 let mut removed = MeshBuilder::new();
53
54 for t in &b {
58 if grid_a.surface_within(centroid(t), tol2) {
59 unchanged.push(t);
60 } else {
61 added.push(t);
62 }
63 }
64 for t in &a {
66 if !grid_b.surface_within(centroid(t), tol2) {
67 removed.push(t);
68 }
69 }
70
71 MeshDiff {
72 status: "ok".to_string(),
73 error: None,
74 bbox: Some(BBox { min, max }),
75 layers: Some(MeshLayers {
76 unchanged: unchanged.finish(),
77 added: added.finish(),
78 removed: removed.finish(),
79 }),
80 }
81}
82
83fn error(msg: &str) -> MeshDiff {
84 MeshDiff {
85 status: "error".to_string(),
86 error: Some(msg.to_string()),
87 bbox: None,
88 layers: None,
89 }
90}
91
92fn ok_empty() -> MeshDiff {
93 MeshDiff {
94 status: "ok".to_string(),
95 error: None,
96 bbox: None,
97 layers: Some(MeshLayers {
98 unchanged: Mesh {
99 positions: vec![],
100 normals: vec![],
101 },
102 added: Mesh {
103 positions: vec![],
104 normals: vec![],
105 },
106 removed: Mesh {
107 positions: vec![],
108 normals: vec![],
109 },
110 }),
111 }
112}
113
114fn centroid(t: &Tri) -> Vec3 {
117 [
118 (t[0][0] + t[1][0] + t[2][0]) / 3.0,
119 (t[0][1] + t[1][1] + t[2][1]) / 3.0,
120 (t[0][2] + t[1][2] + t[2][2]) / 3.0,
121 ]
122}
123
124fn dist(a: Vec3, b: Vec3) -> f64 {
125 let (dx, dy, dz) = (a[0] - b[0], a[1] - b[1], a[2] - b[2]);
126 (dx * dx + dy * dy + dz * dz).sqrt()
127}
128
129fn normal(t: &Tri) -> [f32; 3] {
130 let u = [t[1][0] - t[0][0], t[1][1] - t[0][1], t[1][2] - t[0][2]];
131 let v = [t[2][0] - t[0][0], t[2][1] - t[0][1], t[2][2] - t[0][2]];
132 let n = [
133 u[1] * v[2] - u[2] * v[1],
134 u[2] * v[0] - u[0] * v[2],
135 u[0] * v[1] - u[1] * v[0],
136 ];
137 let len = (n[0] * n[0] + n[1] * n[1] + n[2] * n[2]).sqrt();
138 if len > 1e-12 {
139 [
140 (n[0] / len) as f32,
141 (n[1] / len) as f32,
142 (n[2] / len) as f32,
143 ]
144 } else {
145 [0.0, 0.0, 1.0]
146 }
147}
148
149struct BBoxAccum {
150 min: Vec3,
151 max: Vec3,
152 any: bool,
153}
154impl BBoxAccum {
155 fn new() -> Self {
156 BBoxAccum {
157 min: [f64::INFINITY; 3],
158 max: [f64::NEG_INFINITY; 3],
159 any: false,
160 }
161 }
162 fn add(&mut self, p: Vec3) {
163 self.any = true;
164 for (m, &v) in self.min.iter_mut().zip(p.iter()) {
165 *m = m.min(v);
166 }
167 for (m, &v) in self.max.iter_mut().zip(p.iter()) {
168 *m = m.max(v);
169 }
170 }
171 fn finish(self) -> Option<(Vec3, Vec3)> {
172 self.any.then_some((self.min, self.max))
173 }
174}
175
176struct MeshBuilder {
178 positions: Vec<f32>,
179 normals: Vec<f32>,
180}
181impl MeshBuilder {
182 fn new() -> Self {
183 MeshBuilder {
184 positions: Vec::new(),
185 normals: Vec::new(),
186 }
187 }
188 fn push(&mut self, t: &Tri) {
189 let n = normal(t);
190 for v in t {
191 self.positions.push(v[0] as f32);
192 self.positions.push(v[1] as f32);
193 self.positions.push(v[2] as f32);
194 self.normals.extend_from_slice(&n);
195 }
196 }
197 fn finish(self) -> Mesh {
198 Mesh {
199 positions: self.positions,
200 normals: self.normals,
201 }
202 }
203}
204
205fn cell_of(cell: f64, x: f64) -> i64 {
206 (x / cell).floor() as i64
207}
208
209struct TriGrid<'a> {
214 cell: f64,
215 tris: &'a [Tri],
216 map: HashMap<[i64; 3], Vec<usize>>,
217}
218impl<'a> TriGrid<'a> {
219 fn build(tris: &'a [Tri], cell: f64) -> TriGrid<'a> {
220 let cell = if cell > 0.0 { cell } else { 1.0 };
221 let mut map: HashMap<[i64; 3], Vec<usize>> = HashMap::new();
222 for (i, t) in tris.iter().enumerate() {
223 let (mut lo, mut hi) = (t[0], t[0]);
224 for v in &t[1..] {
225 for d in 0..3 {
226 lo[d] = lo[d].min(v[d]);
227 hi[d] = hi[d].max(v[d]);
228 }
229 }
230 for cx in cell_of(cell, lo[0])..=cell_of(cell, hi[0]) {
231 for cy in cell_of(cell, lo[1])..=cell_of(cell, hi[1]) {
232 for cz in cell_of(cell, lo[2])..=cell_of(cell, hi[2]) {
233 map.entry([cx, cy, cz]).or_default().push(i);
234 }
235 }
236 }
237 }
238 TriGrid { cell, tris, map }
239 }
240
241 fn surface_within(&self, p: Vec3, tol2: f64) -> bool {
243 let k = [
244 cell_of(self.cell, p[0]),
245 cell_of(self.cell, p[1]),
246 cell_of(self.cell, p[2]),
247 ];
248 for dx in -1..=1 {
249 for dy in -1..=1 {
250 for dz in -1..=1 {
251 if let Some(ids) = self.map.get(&[k[0] + dx, k[1] + dy, k[2] + dz]) {
252 for &i in ids {
253 let t = &self.tris[i];
254 if dist2_point_tri(p, t[0], t[1], t[2]) <= tol2 {
255 return true;
256 }
257 }
258 }
259 }
260 }
261 }
262 false
263 }
264}
265
266fn sub(a: Vec3, b: Vec3) -> Vec3 {
269 [a[0] - b[0], a[1] - b[1], a[2] - b[2]]
270}
271fn dot(a: Vec3, b: Vec3) -> f64 {
272 a[0] * b[0] + a[1] * b[1] + a[2] * b[2]
273}
274
275fn dist2_point_tri(p: Vec3, a: Vec3, b: Vec3, c: Vec3) -> f64 {
278 let ab = sub(b, a);
279 let ac = sub(c, a);
280 let ap = sub(p, a);
281 let d1 = dot(ab, ap);
282 let d2 = dot(ac, ap);
283 if d1 <= 0.0 && d2 <= 0.0 {
284 return dot(ap, ap);
285 }
286 let bp = sub(p, b);
287 let d3 = dot(ab, bp);
288 let d4 = dot(ac, bp);
289 if d3 >= 0.0 && d4 <= d3 {
290 return dot(bp, bp);
291 }
292 let vc = d1 * d4 - d3 * d2;
293 if vc <= 0.0 && d1 >= 0.0 && d3 <= 0.0 {
294 let v = d1 / (d1 - d3);
295 let q = [a[0] + v * ab[0], a[1] + v * ab[1], a[2] + v * ab[2]];
296 return dot(sub(p, q), sub(p, q));
297 }
298 let cp = sub(p, c);
299 let d5 = dot(ab, cp);
300 let d6 = dot(ac, cp);
301 if d6 >= 0.0 && d5 <= d6 {
302 return dot(cp, cp);
303 }
304 let vb = d5 * d2 - d1 * d6;
305 if vb <= 0.0 && d2 >= 0.0 && d6 <= 0.0 {
306 let w = d2 / (d2 - d6);
307 let q = [a[0] + w * ac[0], a[1] + w * ac[1], a[2] + w * ac[2]];
308 return dot(sub(p, q), sub(p, q));
309 }
310 let va = d3 * d6 - d5 * d4;
311 if va <= 0.0 && (d4 - d3) >= 0.0 && (d5 - d6) >= 0.0 {
312 let w = (d4 - d3) / ((d4 - d3) + (d5 - d6));
313 let bc = sub(c, b);
314 let q = [b[0] + w * bc[0], b[1] + w * bc[1], b[2] + w * bc[2]];
315 return dot(sub(p, q), sub(p, q));
316 }
317 let denom = 1.0 / (va + vb + vc);
318 let v = vb * denom;
319 let w = vc * denom;
320 let q = [
321 a[0] + ab[0] * v + ac[0] * w,
322 a[1] + ab[1] * v + ac[1] * w,
323 a[2] + ab[2] * v + ac[2] * w,
324 ];
325 dot(sub(p, q), sub(p, q))
326}
327
328fn parse(content: &[u8], format: CadFormat) -> Option<Vec<Tri>> {
331 match format {
332 CadFormat::Stl => Some(parse_stl(content)),
333 CadFormat::Obj => Some(parse_obj(content)),
334 _ => None,
335 }
336}
337
338fn parse_stl(content: &[u8]) -> Vec<Tri> {
339 if content.len() >= 84 {
340 let count =
341 u32::from_le_bytes([content[80], content[81], content[82], content[83]]) as usize;
342 if content.len() == 84 + count * 50 {
343 return parse_stl_binary(content, count);
344 }
345 }
346 parse_stl_ascii(content)
347}
348
349fn parse_stl_binary(content: &[u8], count: usize) -> Vec<Tri> {
350 let read = |b: &[u8]| f32::from_le_bytes([b[0], b[1], b[2], b[3]]) as f64;
351 let mut tris = Vec::with_capacity(count);
352 for t in 0..count {
353 let base = 84 + t * 50 + 12; let mut tri = [[0.0; 3]; 3];
355 for (v, slot) in tri.iter_mut().enumerate() {
356 let o = base + v * 12;
357 *slot = [
358 read(&content[o..o + 4]),
359 read(&content[o + 4..o + 8]),
360 read(&content[o + 8..o + 12]),
361 ];
362 }
363 tris.push(tri);
364 }
365 tris
366}
367
368fn parse_stl_ascii(content: &[u8]) -> Vec<Tri> {
369 let text = String::from_utf8_lossy(content);
370 let mut tris = Vec::new();
371 let mut current: Vec<Vec3> = Vec::new();
372 for line in text.lines() {
373 let line = line.trim_start();
374 if let Some(rest) = line.strip_prefix("vertex") {
375 if let Some(p) = parse_xyz(rest) {
376 current.push(p);
377 }
378 } else if line.starts_with("endfacet") {
379 if current.len() >= 3 {
380 tris.push([current[0], current[1], current[2]]);
381 }
382 current.clear();
383 }
384 }
385 tris
386}
387
388fn parse_obj(content: &[u8]) -> Vec<Tri> {
389 let text = String::from_utf8_lossy(content);
390 let mut verts: Vec<Vec3> = Vec::new();
391 let mut tris = Vec::new();
392 for line in text.lines() {
393 let line = line.trim_start();
394 if let Some(rest) = line.strip_prefix("v ") {
395 if let Some(p) = parse_xyz(rest) {
396 verts.push(p);
397 }
398 } else if let Some(rest) = line.strip_prefix("f ") {
399 let idx: Vec<usize> = rest
402 .split_whitespace()
403 .filter_map(|tok| obj_index(tok.split('/').next().unwrap_or(""), verts.len()))
404 .collect();
405 for w in 1..idx.len().saturating_sub(1) {
407 tris.push([verts[idx[0]], verts[idx[w]], verts[idx[w + 1]]]);
408 }
409 }
410 }
411 tris
412}
413
414fn obj_index(tok: &str, n: usize) -> Option<usize> {
415 let i: i64 = tok.parse().ok()?;
416 if i > 0 {
417 Some((i - 1) as usize).filter(|&x| x < n)
418 } else if i < 0 {
419 let x = n as i64 + i;
420 (x >= 0).then_some(x as usize)
421 } else {
422 None
423 }
424}
425
426fn parse_xyz(s: &str) -> Option<Vec3> {
427 let mut it = s.split_whitespace();
428 let x = it.next()?.parse().ok()?;
429 let y = it.next()?.parse().ok()?;
430 let z = it.next()?.parse().ok()?;
431 Some([x, y, z])
432}
433
434#[cfg(test)]
435mod tests {
436 use super::*;
437
438 #[test]
441 fn classifies_moved_triangle() {
442 let a = "v 0 0 0\nv 1 0 0\nv 0 1 0\nv 9 9 0\nv 10 9 0\nv 9 10 0\nf 1 2 3\nf 4 5 6\n";
443 let b = "v 0 0 0\nv 1 0 0\nv 0 1 0\nv 9 9 50\nv 10 9 50\nv 9 10 50\nf 1 2 3\nf 4 5 6\n";
445 let d = diff(a.as_bytes(), b.as_bytes(), CadFormat::Obj);
446 assert!(d.is_ok());
447 let l = d.layers.unwrap();
448 assert_eq!(l.unchanged.triangle_count(), 1);
449 assert_eq!(l.added.triangle_count(), 1);
450 assert_eq!(l.removed.triangle_count(), 1);
451 }
452
453 #[test]
454 fn identical_meshes_are_all_unchanged() {
455 let m = "v 0 0 0\nv 1 0 0\nv 0 1 0\nf 1 2 3\n";
456 let d = diff(m.as_bytes(), m.as_bytes(), CadFormat::Obj);
457 let l = d.layers.unwrap();
458 assert_eq!(l.unchanged.triangle_count(), 1);
459 assert_eq!(l.added.triangle_count(), 0);
460 assert_eq!(l.removed.triangle_count(), 0);
461 }
462}