1use std::time::Instant;
12
13use honeycomb::{
14 kernels::{
15 grisubal::Clip,
16 remeshing::{
17 EdgeCollapseError, EdgeSwapError, capture_geometry, classify_capture, collapse_edge,
18 cut_inner_edge, cut_outer_edge, move_vertex_to_average, swap_edge,
19 },
20 triangulation::{TriangulateError, earclip_cell_countercw},
21 utils::{EdgeAnchor, FaceAnchor, VertexAnchor, is_orbit_orientation_consistent},
22 },
23 prelude::{CMap2, CoordsFloat, DartIdType, NULL_DART_ID, OrbitPolicy, SewError, Vertex2},
24 stm::{StmClosureResult, Transaction, abort, atomically, atomically_with_err, retry},
25};
26use rayon::{iter::Either, prelude::*};
27
28use crate::{
29 cli::RemeshArgs,
30 prof_start, prof_stop,
31 utils::{get_num_threads, hash_file},
32};
33
34#[allow(clippy::print_literal)]
35pub fn bench_remesh<T: CoordsFloat>(args: RemeshArgs) -> CMap2<T> {
36 let input_map = args.input.to_str().unwrap();
37 let target_len = T::from(args.target_length).unwrap();
38
39 let n_threads = get_num_threads().unwrap_or(
40 std::thread::available_parallelism()
41 .map(|n| n.get())
42 .unwrap_or(1),
43 );
44
45 let input_hash = hash_file(input_map).expect("E: could not compute input hash"); let mut instant = Instant::now();
50 let mut map: CMap2<T> = capture_geometry(
51 input_map,
52 [T::from(args.lx).unwrap(), T::from(args.ly).unwrap()],
53 Clip::from(args.clip),
54 )
55 .unwrap();
56 let capture_time = instant.elapsed();
57
58 instant = Instant::now();
60 classify_capture(&map).unwrap();
61 let classification_time = instant.elapsed();
62
63 instant = Instant::now();
65 prof_start!("HCBENCH_REMESH_TRIANGULATION");
66 let n_tot = map
67 .iter_faces()
68 .map(|id| (map.orbit(OrbitPolicy::Face, id as DartIdType).count() - 3) * 2)
69 .sum();
70 let start = map.allocate_used_darts(n_tot);
71 map.iter_faces()
73 .scan(start, |state, f| {
74 let n_d = (map.orbit(OrbitPolicy::Face, f as DartIdType).count() - 3) * 2;
76 *state += n_d as DartIdType;
77 Some((f, n_d, *state - n_d as DartIdType))
78 })
79 .filter(|(_, n_d, _)| *n_d != 0)
80 .for_each(|(f, n_d, start)| {
81 let new_darts = (start..start + n_d as DartIdType).collect::<Vec<_>>();
82 let anchor = map.force_remove_attribute::<FaceAnchor>(f);
83 if let Some(a) = anchor {
85 atomically(|t| {
86 for &d in &new_darts {
87 map.write_attribute(t, d, EdgeAnchor::from(a))?;
88 }
89 Ok(())
90 });
91 }
92 while let Err(e) =
93 atomically_with_err(|t| earclip_cell_countercw(t, &map, f, &new_darts))
94 {
95 match e {
96 TriangulateError::UndefinedFace(_) | TriangulateError::OpFailed(_) => continue,
97 TriangulateError::NoEar => panic!("E: cannot triangulate the geometry capture"),
98 TriangulateError::AlreadyTriangulated
99 | TriangulateError::NonFannable
100 | TriangulateError::NotEnoughDarts(_)
101 | TriangulateError::TooManyDarts(_) => {
102 unreachable!()
103 }
104 }
105 }
106 if let Some(a) = anchor {
108 atomically(|t| {
109 for &d in &new_darts {
110 let fid = map.face_id_tx(t, d)?;
111 map.write_attribute(t, fid, a)?;
112 }
113 Ok(())
114 });
115 }
116 });
117 let triangulation_time = instant.elapsed();
118 prof_stop!("HCBENCH_REMESH_TRIANGULATION");
119
120 debug_assert!(
122 map.par_iter_faces()
123 .all(|f| map.orbit(OrbitPolicy::Face, f as DartIdType).count() == 3)
124 );
125 debug_assert!(map.par_iter_faces().all(|f| {
126 map.orbit(OrbitPolicy::FaceLinear, f).count() == 3
127 && atomically(|t| check_tri_orientation(t, &map, f as DartIdType))
128 }));
129 debug_assert!(
130 map.par_iter_vertices()
131 .all(|v| map.force_read_attribute::<VertexAnchor>(v).is_some())
132 );
133 debug_assert!(
134 map.par_iter_edges()
135 .all(|e| map.force_read_attribute::<EdgeAnchor>(e).is_some())
136 );
137 debug_assert!(
138 map.par_iter_faces()
139 .all(|f| map.force_read_attribute::<FaceAnchor>(f).is_some())
140 );
141
142 println!("| remesh benchmark");
144 println!("|-> input : {input_map} (hash: {input_hash:#0x})");
145 println!(
146 "|-> backend : {:?} with {n_threads} thread(s)",
147 args.backend
148 );
149 println!("|-> target size: {target_len:?}");
150 println!("|-> capture time : {}ms", capture_time.as_millis());
151 println!(
152 "|-> triangulation time : {}ms",
153 triangulation_time.as_millis()
154 );
155 println!(
156 "|-> classification time : {}ms",
157 classification_time.as_millis()
158 );
159
160 println!(
161 "Round | {} | {} | {} | {} | {} | {} | {} | {} | {} | {} | {} | {}",
162 "# of used darts", "# of unused darts", "Graph compute (s)", "Relax (tot, s)", "Ret cond (s)", "Batch compute (s)", "Dart prealloc (s)", "Cut batch size", "Cut edges (s)", "Collapse batch size", "Collapse edges (s)", "Swap edges (s)", );
175 prof_start!("HCBENCH_REMESH_MAINLOOP");
181 let mut n = 0;
182 let mut r;
183 loop {
184 print!("{n:>5}");
185 let n_unused = (1..map.n_darts() as DartIdType)
187 .into_par_iter()
188 .filter(|d| map.is_unused(*d))
189 .count();
190 print!(" | {:>15}", map.n_darts() - n_unused);
191 print!(" | {n_unused:>17}");
192
193 prof_start!("HCBENCH_REMESH_GRAPH");
195 instant = Instant::now();
196 let nodes: Vec<(_, Vec<_>)> = map
197 .par_iter_vertices()
198 .filter_map(|v| {
199 let mut neigh = Vec::with_capacity(10);
200 for d in map.orbit(OrbitPolicy::Vertex, v as DartIdType) {
201 let b2d = map.beta::<2>(d);
202 if b2d == NULL_DART_ID {
203 return None; } else {
205 neigh.push(map.vertex_id(b2d));
206 }
207 }
208 Some((v, neigh))
209 })
210 .collect();
211 prof_stop!("HCBENCH_REMESH_GRAPH");
212 print!(" | {:>17.6e}", instant.elapsed().as_secs_f64());
213
214 prof_start!("HCBENCH_REMESH_RELAX");
216 instant = Instant::now();
217 r = 0;
218 loop {
219 nodes.par_iter().for_each(|(vid, neighbors)| {
220 let _ = atomically_with_err(|t| {
221 move_vertex_to_average(t, &map, *vid, neighbors)?;
222 if !is_orbit_orientation_consistent(t, &map, *vid)? {
223 abort("E: resulting geometry is inverted")?;
224 }
225 Ok(())
226 });
227 });
228
229 r += 1;
230 if r >= args.n_relax_rounds.get() {
231 break;
232 }
233 }
234 prof_stop!("HCBENCH_REMESH_RELAX");
235 print!(" | {:>14.6e}", instant.elapsed().as_secs_f64());
236
237 debug_assert!(
238 map.par_iter_faces()
239 .all(|f| { atomically(|t| check_tri_orientation(t, &map, f as DartIdType)) })
240 );
241
242 instant = Instant::now();
244 let (long_edges, short_edges): (Vec<_>, Vec<_>) = map
245 .par_iter_edges()
246 .filter_map(|e| {
247 let (l, r) = (e as DartIdType, map.beta::<1>(e as DartIdType));
248 let diff =
249 atomically(|t| compute_diff_to_target(t, &map, l, r, args.target_length));
250 if diff.abs() > args.target_tolerance {
251 Some((e, diff))
252 } else {
253 None
254 }
255 })
256 .partition_map(|(e, diff)| {
257 if diff.is_sign_positive() {
258 Either::Left(e)
259 } else {
260 Either::Right(e)
261 }
262 });
263 let batch_time = instant.elapsed().as_secs_f64();
264 if args.enable_er {
266 instant = Instant::now();
267 let n_e = map.par_iter_edges().count();
268 let n_e_outside_tol = long_edges.len() + short_edges.len();
269 if (n_e_outside_tol as f64 / n_e as f64) < args.target_tolerance {
271 print!(" | {:>12.6e}", instant.elapsed().as_millis());
272 print!(" | {:>13}", "n/a");
273 print!(" | {:>12}", "n/a");
274 println!(" | {:>8}", "n/a");
275 break;
276 }
277 print!(" | {:>12.6e}", instant.elapsed().as_secs_f64());
278 } else {
279 print!(" | {:>12}", "n/a");
280 }
281
282 instant = Instant::now();
284 let n_e = long_edges.len();
285 let n_darts = 6 * n_e;
286 let new_darts = if n_unused < n_darts {
287 let tmp = map.allocate_unused_darts(n_darts);
288 (tmp..tmp + n_darts as DartIdType).collect::<Vec<_>>()
289 } else {
290 (1..map.n_darts() as DartIdType)
291 .into_par_iter()
292 .filter(|&d| map.is_unused(d))
293 .take_any(n_darts)
294 .collect()
295 };
296 let alloc_time = instant.elapsed().as_secs_f64();
297 print!(" | {batch_time:>17.6e}");
298 print!(" | {alloc_time:>17.6e}");
299
300 prof_start!("HCBENCH_REMESH_CUT");
302 print!(" | {n_e:>14}");
303 instant = Instant::now();
304 long_edges
305 .into_par_iter()
306 .zip(new_darts.par_chunks_exact(6))
307 .for_each(|(e, sl)| {
308 let &[d1, d2, d3, d4, d5, d6] = sl else {
309 unreachable!()
310 };
311
312 if map.is_i_free::<2>(e as DartIdType) {
313 while let Err(er) = atomically_with_err(|t| {
314 let nds = [d1, d2, d3];
315 for d in nds {
316 map.claim_dart_tx(t, d)?;
317 }
318 cut_outer_edge(t, &map, e, nds)?;
319
320 if !is_orbit_orientation_consistent(t, &map, d1)? {
321 abort(SewError::BadGeometry(1, 0, 0))?;
322 }
323
324 Ok(())
325 }) {
326 match er {
327 SewError::BadGeometry(1, _, _) => {
329 break;
330 }
331 SewError::BadGeometry(_, _, _)
333 | SewError::FailedLink(_)
334 | SewError::FailedAttributeOp(_) => continue,
335 }
336 }
337 } else {
338 while let Err(er) = atomically_with_err(|t| {
339 let nds = [d1, d2, d3, d4, d5, d6];
340 for d in nds {
341 map.claim_dart_tx(t, d)?;
342 }
343
344 cut_inner_edge(t, &map, e, nds)?;
345
346 if !is_orbit_orientation_consistent(t, &map, d1)? {
347 abort(SewError::BadGeometry(1, 0, 0))?;
348 }
349
350 Ok(())
351 }) {
352 match er {
353 SewError::BadGeometry(1, _, _) => {
355 break;
356 }
357 SewError::BadGeometry(_, _, _)
359 | SewError::FailedLink(_)
360 | SewError::FailedAttributeOp(_) => continue,
361 }
362 }
363 }
364 });
365 prof_stop!("HCBENCH_REMESH_CUT");
366 print!(" | {:>13.6e}", instant.elapsed().as_secs_f64());
367
368 prof_start!("HCBENCH_REMESH_COLLAPSE");
370 print!(" | {:>19}", short_edges.len());
371 instant = Instant::now();
372 short_edges.into_par_iter().for_each(|e| {
373 while let Err(er) = atomically_with_err(|t| {
374 if map.is_unused_tx(t, e as DartIdType)? {
375 return Ok(());
377 }
378 let e = map.edge_id_tx(t, e)?;
379
380 let (l, r) = (e as DartIdType, map.beta_tx::<1>(t, e as DartIdType)?);
381 let diff = compute_diff_to_target(t, &map, l, r, args.target_length)?;
382 if diff.abs() < args.target_tolerance {
383 return Ok(());
385 }
386
387 collapse_edge(t, &map, e)?;
388 Ok(())
389 }) {
390 match er {
391 EdgeCollapseError::FailedCoreOp(SewError::BadGeometry(_, _, _))
393 | EdgeCollapseError::NonCollapsibleEdge(_)
394 | EdgeCollapseError::InvertedOrientation => break,
395 EdgeCollapseError::FailedCoreOp(_)
397 | EdgeCollapseError::FailedDartRelease(_)
398 | EdgeCollapseError::BadTopology => continue,
399 EdgeCollapseError::NullEdge => unreachable!(),
401 }
402 }
403 });
404 prof_stop!("HCBENCH_REMESH_COLLAPSE");
405 print!(" | {:>18.6e}", instant.elapsed().as_secs_f64());
406
407 prof_start!("HCBENCH_REMESH_SWAP");
409 instant = Instant::now();
410 map.par_iter_edges()
411 .map(|e| {
412 let (l, r) = (e as DartIdType, map.beta::<1>(e as DartIdType));
413 let diff =
414 atomically(|t| compute_diff_to_target(t, &map, l, r, args.target_length));
415 (e, diff)
416 })
417 .filter(|(_, diff)| diff.abs() > args.target_tolerance)
418 .for_each(|(e, diff)| {
419 let (l, r) = (e as DartIdType, map.beta::<2>(e as DartIdType));
420 if r != NULL_DART_ID {
421 debug_assert!(
422 map.force_read_attribute::<FaceAnchor>(map.face_id(l))
423 .is_some()
424 );
425 debug_assert!(
426 map.force_read_attribute::<FaceAnchor>(map.face_id(r))
427 .is_some()
428 );
429 if let Err(er) = atomically_with_err(|t| {
430 let (b0l, b0r) = (map.beta_tx::<0>(t, l)?, map.beta_tx::<0>(t, r)?);
431 let new_diff =
432 compute_diff_to_target(t, &map, b0l, b0r, args.target_length)?;
433
434 if new_diff.abs() < diff.abs() {
436 swap_edge(t, &map, e)?;
437 }
438
439 if !check_tri_orientation(t, &map, l)?
441 || !check_tri_orientation(t, &map, r)?
442 {
443 abort(EdgeSwapError::NotSwappable("swap inverts orientation"))?;
444 }
445
446 Ok(())
447 }) {
448 match er {
449 EdgeSwapError::NotSwappable(_)
450 | EdgeSwapError::FailedCoreOp(_)
451 | EdgeSwapError::BadTopology => {} EdgeSwapError::NullEdge | EdgeSwapError::IncompleteEdge => {
453 unreachable!()
454 }
455 }
456 }
457
458 debug_assert!(
459 map.force_read_attribute::<FaceAnchor>(map.face_id(l))
460 .is_some()
461 );
462 debug_assert!(
463 map.force_read_attribute::<FaceAnchor>(map.face_id(r))
464 .is_some()
465 );
466 }
467 });
468 prof_stop!("HCBENCH_REMESH_SWAP");
469 println!(" | {:>14.6e}", instant.elapsed().as_secs_f64());
470
471 debug_assert!(map.par_iter_faces().all(|f| {
472 map.orbit(OrbitPolicy::FaceLinear, f).count() == 3
473 && atomically(|t| check_tri_orientation(t, &map, f as DartIdType))
474 }));
475
476 n += 1;
477 if n >= args.n_rounds.get() {
478 break;
479 }
480 }
481 prof_stop!("HCBENCH_REMESH_MAINLOOP");
482
483 debug_assert!(map.par_iter_faces().all(|f| {
484 map.orbit(OrbitPolicy::FaceLinear, f).count() == 3
485 && atomically(|t| check_tri_orientation(t, &map, f as DartIdType))
486 }));
487
488 map
489}
490
491#[inline]
492fn compute_diff_to_target<T: CoordsFloat>(
493 t: &mut Transaction,
494 map: &CMap2<T>,
495 l: DartIdType,
496 r: DartIdType,
497 target: f64,
498) -> StmClosureResult<f64> {
499 let (vid1, vid2) = (map.vertex_id_tx(t, l)?, map.vertex_id_tx(t, r)?);
500 let (v1, v2) =
501 if let (Some(v1), Some(v2)) = (map.read_vertex(t, vid1)?, map.read_vertex(t, vid2)?) {
502 (v1, v2)
503 } else {
504 retry()?
505 };
506 Ok(((v2 - v1).norm().to_f64().unwrap() - target) / target)
507}
508
509#[inline]
510fn check_tri_orientation<T: CoordsFloat>(
511 t: &mut Transaction,
512 map: &CMap2<T>,
513 d: DartIdType,
514) -> StmClosureResult<bool> {
515 let vid1 = map.vertex_id_tx(t, d)?;
516 let b1 = map.beta_tx::<1>(t, d)?;
517 let vid2 = map.vertex_id_tx(t, b1)?;
518 let b1b1 = map.beta_tx::<1>(t, b1)?;
519 let vid3 = map.vertex_id_tx(t, b1b1)?;
520 let v1 = if let Ok(Some(v)) = map.read_vertex(t, vid1) {
521 v
522 } else {
523 return retry()?;
524 };
525 let v2 = if let Ok(Some(v)) = map.read_vertex(t, vid2) {
526 v
527 } else {
528 return retry()?;
529 };
530 let v3 = if let Ok(Some(v)) = map.read_vertex(t, vid3) {
531 v
532 } else {
533 return retry()?;
534 };
535 Ok(Vertex2::cross_product_from_vertices(&v1, &v2, &v3) > T::zero())
536}