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