honeycomb_benches/
remesh.rs

1use std::time::Instant;
2
3use honeycomb::{
4    kernels::{
5        grisubal::Clip,
6        remeshing::{
7            EdgeCollapseError, EdgeSwapError, capture_geometry, classify_capture, collapse_edge,
8            cut_inner_edge, cut_outer_edge, move_vertex_to_average, swap_edge,
9        },
10        triangulation::{TriangulateError, earclip_cell_countercw},
11        utils::{EdgeAnchor, FaceAnchor, VertexAnchor, is_orbit_orientation_consistent},
12    },
13    prelude::{CMap2, CoordsFloat, DartIdType, NULL_DART_ID, OrbitPolicy, SewError, Vertex2},
14    stm::{StmClosureResult, Transaction, abort, atomically, atomically_with_err, retry},
15};
16
17use crate::{cli::RemeshArgs, utils::hash_file};
18
19pub fn bench_remesh<T: CoordsFloat>(args: RemeshArgs) -> CMap2<T> {
20    let input_map = args.input.to_str().unwrap();
21    let target_len = T::from(args.target_length).unwrap();
22
23    let n_threads = std::thread::available_parallelism()
24        .map(|n| n.get())
25        .unwrap_or(1);
26
27    // load map from file
28    let input_hash = hash_file(input_map).expect("E: could not compute input hash"); // file id for posterity
29
30    // -- capture via grid overlap
31    let mut instant = Instant::now();
32    let mut map: CMap2<T> = capture_geometry(
33        input_map,
34        [T::from(args.lx).unwrap(), T::from(args.ly).unwrap()],
35        Clip::from(args.clip),
36    )
37    .unwrap();
38    let capture_time = instant.elapsed();
39
40    // -- classification
41    instant = Instant::now();
42    classify_capture(&map).unwrap();
43    let classification_time = instant.elapsed();
44
45    // -- triangulation
46    instant = Instant::now();
47    let n_tot = map
48        .iter_faces()
49        .map(|id| (map.orbit(OrbitPolicy::Face, id as DartIdType).count() - 3) * 2)
50        .sum();
51    let start = map.add_free_darts(n_tot);
52    // use a prefix sum starting from the newly allocated darts to associate free darts to each face
53    map.iter_faces()
54        .scan(start, |state, f| {
55            // compute the number of dart needed to triangulate this face
56            let n_d = (map.orbit(OrbitPolicy::Face, f as DartIdType).count() - 3) * 2;
57            *state += n_d as DartIdType;
58            Some((f, n_d, *state - n_d as DartIdType))
59        })
60        .filter(|(_, n_d, _)| *n_d != 0)
61        .for_each(|(f, n_d, start)| {
62            let new_darts = (start..start + n_d as DartIdType).collect::<Vec<_>>();
63            let anchor = map.force_remove_attribute::<FaceAnchor>(f);
64            // make sure new edges are anchored
65            if let Some(a) = anchor {
66                atomically(|t| {
67                    for &d in &new_darts {
68                        map.write_attribute(t, d, EdgeAnchor::from(a))?;
69                    }
70                    Ok(())
71                });
72            }
73            while let Err(e) =
74                atomically_with_err(|t| earclip_cell_countercw(t, &map, f, &new_darts))
75            {
76                match e {
77                    TriangulateError::UndefinedFace(_) | TriangulateError::OpFailed(_) => continue,
78                    TriangulateError::NoEar => panic!("E: cannot triangulate the geometry capture"),
79                    TriangulateError::AlreadyTriangulated
80                    | TriangulateError::NonFannable
81                    | TriangulateError::NotEnoughDarts(_)
82                    | TriangulateError::TooManyDarts(_) => {
83                        unreachable!()
84                    }
85                }
86            }
87            // make sure new faces are anchored
88            if let Some(a) = anchor {
89                atomically(|t| {
90                    for &d in &new_darts {
91                        let fid = map.face_id_transac(t, d)?;
92                        map.write_attribute(t, fid, a)?;
93                    }
94                    Ok(())
95                });
96            }
97        });
98    let triangulation_time = instant.elapsed();
99
100    // check that the mesh is triangular, consistently oriented and fully classified
101    debug_assert!(
102        map.iter_faces()
103            .all(|f| map.orbit(OrbitPolicy::Face, f as DartIdType).count() == 3)
104    );
105    debug_assert!(map.iter_faces().all(|f| {
106        map.orbit(OrbitPolicy::FaceLinear, f).count() == 3
107            && atomically(|t| check_tri_orientation(t, &map, f as DartIdType))
108    }));
109    debug_assert!(
110        map.iter_vertices()
111            .all(|v| map.force_read_attribute::<VertexAnchor>(v).is_some())
112    );
113    debug_assert!(
114        map.iter_edges()
115            .all(|e| map.force_read_attribute::<EdgeAnchor>(e).is_some())
116    );
117    debug_assert!(
118        map.iter_faces()
119            .all(|f| map.force_read_attribute::<FaceAnchor>(f).is_some())
120    );
121
122    // TODO: print the whole config / args
123    println!("| remesh benchmark");
124    println!("|-> input      : {input_map} (hash: {input_hash:#0x})");
125    println!(
126        "|-> backend    : {:?} with {n_threads} thread(s)",
127        args.backend
128    );
129    println!("|-> target size: {target_len:?}");
130    println!("|-> capture time  : {}ms", capture_time.as_millis());
131    println!(
132        "|-> triangulation time  : {}ms",
133        triangulation_time.as_millis()
134    );
135    println!(
136        "|-> classification time  : {}ms",
137        classification_time.as_millis()
138    );
139
140    println!(
141        "Round | Relax (tot, s) | Ret cond (s) | Batch compute (s) | Cut/collapse (s) | Swap (s)"
142    );
143    // -- main remeshing loop
144    // a. relax
145    // b. cut / collapse
146    // c. swap
147    // check for ending condition after each relax
148    let mut n = 0;
149    let mut r;
150    loop {
151        print!("{:>5}", n);
152
153        // -- relax
154        instant = Instant::now();
155        r = 0;
156        loop {
157            map.iter_vertices()
158                .filter_map(|v| {
159                    let mut neigh = Vec::with_capacity(10);
160                    for d in map.orbit(OrbitPolicy::Vertex, v as DartIdType) {
161                        let b2d = map.beta::<2>(d);
162                        if b2d == NULL_DART_ID {
163                            return None; // filter out vertices on the boundary
164                        } else {
165                            neigh.push(map.vertex_id(b2d));
166                        }
167                    }
168                    Some((v, neigh))
169                })
170                .for_each(|(vid, neighbors)| {
171                    let _ = atomically_with_err(|t| {
172                        move_vertex_to_average(t, &map, vid, &neighbors)?;
173                        if !is_orbit_orientation_consistent(t, &map, vid)? {
174                            abort("E: resulting geometry is inverted")?;
175                        }
176                        Ok(())
177                    });
178                });
179
180            r += 1;
181            if r >= args.n_relax_rounds.get() {
182                break;
183            }
184        }
185        print!(" | {:>14.6e}", instant.elapsed().as_secs_f64());
186
187        debug_assert!(
188            map.iter_faces()
189                .all(|f| { atomically(|t| check_tri_orientation(t, &map, f as DartIdType)) })
190        );
191
192        // -- check early return conds if enabled
193        if args.enable_er {
194            instant = Instant::now();
195            let n_e = map.iter_edges().count();
196            let n_e_outside_tol = map
197                .iter_edges()
198                .map(|e| {
199                    let (v1, v2) = (
200                        map.force_read_vertex(map.vertex_id(e as DartIdType))
201                            .unwrap(),
202                        map.force_read_vertex(map.vertex_id(map.beta::<1>(e as DartIdType)))
203                            .unwrap(),
204                    );
205                    (v2 - v1).norm()
206                })
207                .filter(|l| {
208                    (l.to_f64().unwrap() - args.target_length).abs() / args.target_length
209                        > args.target_tolerance
210                })
211                .count();
212            // if 95%+ edges are in the target length tolerance range, finish early
213            if ((n_e_outside_tol as f32 - n_e as f32).abs() / n_e as f32) < 0.05 {
214                print!(" | {:>12.6e}", instant.elapsed().as_millis());
215                print!(" | {:>17}", "n/a");
216                print!(" | {:>16}", "n/a");
217                println!(" | {:>8}", "n/a");
218                break;
219            }
220            print!(" | {:>12.6e}", instant.elapsed().as_secs_f64());
221        } else {
222            print!(" | {:>12}", "n/a");
223        }
224
225        // -- get edges to process
226        instant = Instant::now();
227        let edges_to_process = map.iter_edges().collect::<Vec<_>>();
228        print!(" | {:>17.6e}", instant.elapsed().as_secs_f64());
229
230        // -- cut / collapse
231        instant = Instant::now();
232        for e in edges_to_process {
233            if map.is_unused(e as DartIdType) {
234                // needed as some operations may remove some edges besides the one processed
235                continue;
236            }
237
238            // filter out
239            let (l, r) = (e as DartIdType, map.beta::<1>(e as DartIdType));
240            let diff = atomically(|t| compute_diff_to_target(t, &map, l, r, args.target_length));
241            if diff.abs() < args.target_tolerance {
242                // edge is within target length tolerance; skip the cut/process phase
243                continue;
244            }
245            let e = map.edge_id(e);
246            // process
247            if diff.is_sign_positive() {
248                // edge is longer than target length => cut
249                if map.is_i_free::<2>(e as DartIdType) {
250                    let nd = map.add_free_darts(3);
251                    let nds: [DartIdType; 3] = std::array::from_fn(|i| nd + i as DartIdType);
252                    while let Err(er) = atomically_with_err(|t| {
253                        cut_outer_edge(t, &map, e, nds)?;
254                        let new_vid = nds[0];
255                        if !is_orbit_orientation_consistent(t, &map, new_vid)? {
256                            abort(SewError::BadGeometry(1, nds[0], nds[2]))?;
257                        }
258                        Ok(())
259                    }) {
260                        match er {
261                            SewError::BadGeometry(1, _, _) => {
262                                for d in nds {
263                                    map.remove_free_dart(d);
264                                }
265                                break;
266                            }
267                            SewError::BadGeometry(_, _, _)
268                            | SewError::FailedLink(_)
269                            | SewError::FailedAttributeOp(_) => continue,
270                        }
271                    }
272                } else {
273                    let nd = map.add_free_darts(6);
274                    let nds: [DartIdType; 6] = std::array::from_fn(|i| nd + i as DartIdType);
275                    while let Err(er) = atomically_with_err(|t| {
276                        cut_inner_edge(t, &map, e, nds)?;
277                        let new_vid = nds[0];
278                        if !is_orbit_orientation_consistent(t, &map, new_vid)? {
279                            abort(SewError::BadGeometry(1, nds[0], nds[3]))?;
280                        }
281                        Ok(())
282                    }) {
283                        match er {
284                            SewError::BadGeometry(1, _, _) => {
285                                for d in nds {
286                                    map.remove_free_dart(d);
287                                }
288                                break;
289                            }
290                            SewError::BadGeometry(_, _, _)
291                            | SewError::FailedLink(_)
292                            | SewError::FailedAttributeOp(_) => continue,
293                        }
294                    }
295                }
296            } else {
297                // edge is shorter than target length => collapse
298                while let Err(er) = atomically_with_err(|t| collapse_edge(t, &map, e)) {
299                    match er {
300                        EdgeCollapseError::FailedCoreOp(SewError::BadGeometry(_, _, _))
301                        | EdgeCollapseError::NonCollapsibleEdge(_)
302                        | EdgeCollapseError::InvertedOrientation => break,
303                        EdgeCollapseError::FailedCoreOp(_) | EdgeCollapseError::BadTopology => {
304                            continue;
305                        }
306                        EdgeCollapseError::NullEdge => unreachable!(),
307                    }
308                }
309            }
310        }
311        print!(" | {:>16.6e}", instant.elapsed().as_secs_f64());
312
313        // -- swap
314        instant = Instant::now();
315        for (e, diff) in map
316            .iter_edges()
317            .map(|e| {
318                let (l, r) = (e as DartIdType, map.beta::<1>(e as DartIdType));
319                let diff =
320                    atomically(|t| compute_diff_to_target(t, &map, l, r, args.target_length));
321                (e, diff)
322            })
323            .filter(|(_, diff)| diff.abs() > args.target_tolerance)
324        {
325            let (l, r) = (e as DartIdType, map.beta::<2>(e as DartIdType));
326            if r != NULL_DART_ID {
327                debug_assert!(
328                    map.force_read_attribute::<FaceAnchor>(map.face_id(l))
329                        .is_some()
330                );
331                debug_assert!(
332                    map.force_read_attribute::<FaceAnchor>(map.face_id(r))
333                        .is_some()
334                );
335                if let Err(er) = atomically_with_err(|t| {
336                    let (b0l, b0r) = (map.beta_transac::<0>(t, l)?, map.beta_transac::<0>(t, r)?);
337                    let new_diff = atomically(|t| {
338                        compute_diff_to_target(t, &map, b0l, b0r, args.target_length)
339                    });
340
341                    // if the swap gets the edge length closer to target value, do it
342                    if new_diff.abs() < diff.abs() {
343                        swap_edge(t, &map, e)?;
344                    }
345
346                    // ensure the swap doesn't invert geometry
347                    if !check_tri_orientation(t, &map, l)? || !check_tri_orientation(t, &map, r)? {
348                        abort(EdgeSwapError::NotSwappable("swap inverts orientation"))?;
349                    }
350
351                    Ok(())
352                }) {
353                    match er {
354                        EdgeSwapError::NotSwappable(_) => {} // continue
355                        EdgeSwapError::NullEdge
356                        | EdgeSwapError::IncompleteEdge
357                        | EdgeSwapError::FailedCoreOp(_)
358                        | EdgeSwapError::BadTopology => unreachable!(),
359                    }
360                }
361
362                debug_assert!(
363                    map.force_read_attribute::<FaceAnchor>(map.face_id(l))
364                        .is_some()
365                );
366                debug_assert!(
367                    map.force_read_attribute::<FaceAnchor>(map.face_id(r))
368                        .is_some()
369                );
370            }
371        }
372        println!(" | {:>8.6e}", instant.elapsed().as_secs_f64());
373
374        debug_assert!(map.iter_faces().all(|f| {
375            map.orbit(OrbitPolicy::FaceLinear, f).count() == 3
376                && atomically(|t| check_tri_orientation(t, &map, f as DartIdType))
377        }));
378
379        n += 1;
380        if n >= args.n_rounds.get() {
381            break;
382        }
383    }
384
385    debug_assert!(map.iter_faces().all(|f| {
386        map.orbit(OrbitPolicy::FaceLinear, f).count() == 3
387            && atomically(|t| check_tri_orientation(t, &map, f as DartIdType))
388    }));
389
390    map
391}
392
393#[inline]
394fn compute_diff_to_target<T: CoordsFloat>(
395    t: &mut Transaction,
396    map: &CMap2<T>,
397    l: DartIdType,
398    r: DartIdType,
399    target: f64,
400) -> StmClosureResult<f64> {
401    let (vid1, vid2) = (map.vertex_id_transac(t, l)?, map.vertex_id_transac(t, r)?);
402    let (v1, v2) =
403        if let (Some(v1), Some(v2)) = (map.read_vertex(t, vid1)?, map.read_vertex(t, vid2)?) {
404            (v1, v2)
405        } else {
406            retry()?
407        };
408    Ok(((v2 - v1).norm().to_f64().unwrap() - target) / target)
409}
410
411#[inline]
412fn check_tri_orientation<T: CoordsFloat>(
413    t: &mut Transaction,
414    map: &CMap2<T>,
415    d: DartIdType,
416) -> StmClosureResult<bool> {
417    let vid1 = map.vertex_id_transac(t, d)?;
418    let b1 = map.beta_transac::<1>(t, d)?;
419    let vid2 = map.vertex_id_transac(t, b1)?;
420    let b1b1 = map.beta_transac::<1>(t, b1)?;
421    let vid3 = map.vertex_id_transac(t, b1b1)?;
422    let v1 = map.read_vertex(t, vid1)?.unwrap();
423    let v2 = map.read_vertex(t, vid2)?.unwrap();
424    let v3 = map.read_vertex(t, vid3)?.unwrap();
425    Ok(Vertex2::cross_product_from_vertices(&v1, &v2, &v3) > T::zero())
426}