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};
26
27use crate::{cli::RemeshArgs, prof_start, prof_stop, utils::hash_file};
28
29pub fn bench_remesh<T: CoordsFloat>(args: RemeshArgs) -> CMap2<T> {
30 let input_map = args.input.to_str().unwrap();
31 let target_len = T::from(args.target_length).unwrap();
32
33 let n_threads = std::thread::available_parallelism()
34 .map(|n| n.get())
35 .unwrap_or(1);
36
37 let input_hash = hash_file(input_map).expect("E: could not compute input hash"); let mut instant = Instant::now();
42 let mut map: CMap2<T> = capture_geometry(
43 input_map,
44 [T::from(args.lx).unwrap(), T::from(args.ly).unwrap()],
45 Clip::from(args.clip),
46 )
47 .unwrap();
48 let capture_time = instant.elapsed();
49
50 instant = Instant::now();
52 classify_capture(&map).unwrap();
53 let classification_time = instant.elapsed();
54
55 instant = Instant::now();
57 prof_start!("HCBENCH_REMESH_TRIANGULATION");
58 let n_tot = map
59 .iter_faces()
60 .map(|id| (map.orbit(OrbitPolicy::Face, id as DartIdType).count() - 3) * 2)
61 .sum();
62 let start = map.allocate_used_darts(n_tot);
63 map.iter_faces()
65 .scan(start, |state, f| {
66 let n_d = (map.orbit(OrbitPolicy::Face, f as DartIdType).count() - 3) * 2;
68 *state += n_d as DartIdType;
69 Some((f, n_d, *state - n_d as DartIdType))
70 })
71 .filter(|(_, n_d, _)| *n_d != 0)
72 .for_each(|(f, n_d, start)| {
73 let new_darts = (start..start + n_d as DartIdType).collect::<Vec<_>>();
74 let anchor = map.force_remove_attribute::<FaceAnchor>(f);
75 if let Some(a) = anchor {
77 atomically(|t| {
78 for &d in &new_darts {
79 map.write_attribute(t, d, EdgeAnchor::from(a))?;
80 }
81 Ok(())
82 });
83 }
84 while let Err(e) =
85 atomically_with_err(|t| earclip_cell_countercw(t, &map, f, &new_darts))
86 {
87 match e {
88 TriangulateError::UndefinedFace(_) | TriangulateError::OpFailed(_) => continue,
89 TriangulateError::NoEar => panic!("E: cannot triangulate the geometry capture"),
90 TriangulateError::AlreadyTriangulated
91 | TriangulateError::NonFannable
92 | TriangulateError::NotEnoughDarts(_)
93 | TriangulateError::TooManyDarts(_) => {
94 unreachable!()
95 }
96 }
97 }
98 if let Some(a) = anchor {
100 atomically(|t| {
101 for &d in &new_darts {
102 let fid = map.face_id_transac(t, d)?;
103 map.write_attribute(t, fid, a)?;
104 }
105 Ok(())
106 });
107 }
108 });
109 let triangulation_time = instant.elapsed();
110 prof_stop!("HCBENCH_REMESH_TRIANGULATION");
111
112 debug_assert!(
114 map.iter_faces()
115 .all(|f| map.orbit(OrbitPolicy::Face, f as DartIdType).count() == 3)
116 );
117 debug_assert!(map.iter_faces().all(|f| {
118 map.orbit(OrbitPolicy::FaceLinear, f).count() == 3
119 && atomically(|t| check_tri_orientation(t, &map, f as DartIdType))
120 }));
121 debug_assert!(
122 map.iter_vertices()
123 .all(|v| map.force_read_attribute::<VertexAnchor>(v).is_some())
124 );
125 debug_assert!(
126 map.iter_edges()
127 .all(|e| map.force_read_attribute::<EdgeAnchor>(e).is_some())
128 );
129 debug_assert!(
130 map.iter_faces()
131 .all(|f| map.force_read_attribute::<FaceAnchor>(f).is_some())
132 );
133
134 println!("| remesh benchmark");
136 println!("|-> input : {input_map} (hash: {input_hash:#0x})");
137 println!(
138 "|-> backend : {:?} with {n_threads} thread(s)",
139 args.backend
140 );
141 println!("|-> target size: {target_len:?}");
142 println!("|-> capture time : {}ms", capture_time.as_millis());
143 println!(
144 "|-> triangulation time : {}ms",
145 triangulation_time.as_millis()
146 );
147 println!(
148 "|-> classification time : {}ms",
149 classification_time.as_millis()
150 );
151
152 println!(
153 "Round | Relax (tot, s) | Ret cond (s) | Batch compute (s) | Cut/collapse (s) | Swap (s)"
154 );
155 prof_start!("HCBENCH_REMESH_MAINLOOP");
161 let mut n = 0;
162 let mut r;
163 loop {
164 print!("{:>5}", n);
165
166 prof_start!("HCBENCH_REMESH_RELAX");
168 instant = Instant::now();
169 r = 0;
170 loop {
171 map.iter_vertices()
172 .filter_map(|v| {
173 let mut neigh = Vec::with_capacity(10);
174 for d in map.orbit(OrbitPolicy::Vertex, v as DartIdType) {
175 let b2d = map.beta::<2>(d);
176 if b2d == NULL_DART_ID {
177 return None; } else {
179 neigh.push(map.vertex_id(b2d));
180 }
181 }
182 Some((v, neigh))
183 })
184 .for_each(|(vid, neighbors)| {
185 let _ = atomically_with_err(|t| {
186 move_vertex_to_average(t, &map, vid, &neighbors)?;
187 if !is_orbit_orientation_consistent(t, &map, vid)? {
188 abort("E: resulting geometry is inverted")?;
189 }
190 Ok(())
191 });
192 });
193
194 r += 1;
195 if r >= args.n_relax_rounds.get() {
196 break;
197 }
198 }
199 prof_stop!("HCBENCH_REMESH_RELAX");
200 print!(" | {:>14.6e}", instant.elapsed().as_secs_f64());
201
202 debug_assert!(
203 map.iter_faces()
204 .all(|f| { atomically(|t| check_tri_orientation(t, &map, f as DartIdType)) })
205 );
206
207 if args.enable_er {
209 instant = Instant::now();
210 let n_e = map.iter_edges().count();
211 let n_e_outside_tol = map
212 .iter_edges()
213 .map(|e| {
214 let (v1, v2) = (
215 map.force_read_vertex(map.vertex_id(e as DartIdType))
216 .unwrap(),
217 map.force_read_vertex(map.vertex_id(map.beta::<1>(e as DartIdType)))
218 .unwrap(),
219 );
220 (v2 - v1).norm()
221 })
222 .filter(|l| {
223 (l.to_f64().unwrap() - args.target_length).abs() / args.target_length
224 > args.target_tolerance
225 })
226 .count();
227 if ((n_e_outside_tol as f32 - n_e as f32).abs() / n_e as f32) < 0.05 {
229 print!(" | {:>12.6e}", instant.elapsed().as_millis());
230 print!(" | {:>17}", "n/a");
231 print!(" | {:>16}", "n/a");
232 println!(" | {:>8}", "n/a");
233 break;
234 }
235 print!(" | {:>12.6e}", instant.elapsed().as_secs_f64());
236 } else {
237 print!(" | {:>12}", "n/a");
238 }
239
240 instant = Instant::now();
242 let edges_to_process = map.iter_edges().collect::<Vec<_>>();
243 print!(" | {:>17.6e}", instant.elapsed().as_secs_f64());
244
245 prof_start!("HCBENCH_REMESH_CC");
247 instant = Instant::now();
248 for e in edges_to_process {
249 if map.is_unused(e as DartIdType) {
250 continue;
252 }
253
254 let (l, r) = (e as DartIdType, map.beta::<1>(e as DartIdType));
256 let diff = atomically(|t| compute_diff_to_target(t, &map, l, r, args.target_length));
257 if diff.abs() < args.target_tolerance {
258 continue;
260 }
261 let e = map.edge_id(e);
262 if diff.is_sign_positive() {
264 if map.is_i_free::<2>(e as DartIdType) {
266 let nd = map.allocate_used_darts(3);
267 let nds: [DartIdType; 3] = std::array::from_fn(|i| nd + i as DartIdType);
268 while let Err(er) = atomically_with_err(|t| {
269 cut_outer_edge(t, &map, e, nds)?;
270 let new_vid = nds[0];
271 if !is_orbit_orientation_consistent(t, &map, new_vid)? {
272 abort(SewError::BadGeometry(1, nds[0], nds[2]))?;
273 }
274 Ok(())
275 }) {
276 match er {
277 SewError::BadGeometry(1, _, _) => {
278 for d in nds {
279 map.release_dart(d).expect("E: unreachable");
280 }
281 break;
282 }
283 SewError::BadGeometry(_, _, _)
284 | SewError::FailedLink(_)
285 | SewError::FailedAttributeOp(_) => continue,
286 }
287 }
288 } else {
289 let nd = map.allocate_used_darts(6);
290 let nds: [DartIdType; 6] = std::array::from_fn(|i| nd + i as DartIdType);
291 while let Err(er) = atomically_with_err(|t| {
292 cut_inner_edge(t, &map, e, nds)?;
293 let new_vid = nds[0];
294 if !is_orbit_orientation_consistent(t, &map, new_vid)? {
295 abort(SewError::BadGeometry(1, nds[0], nds[3]))?;
296 }
297 Ok(())
298 }) {
299 match er {
300 SewError::BadGeometry(1, _, _) => {
301 for d in nds {
302 map.release_dart(d).expect("E: unreachable");
303 }
304 break;
305 }
306 SewError::BadGeometry(_, _, _)
307 | SewError::FailedLink(_)
308 | SewError::FailedAttributeOp(_) => continue,
309 }
310 }
311 }
312 } else {
313 while let Err(er) = atomically_with_err(|t| collapse_edge(t, &map, e)) {
315 match er {
316 EdgeCollapseError::FailedCoreOp(SewError::BadGeometry(_, _, _))
317 | EdgeCollapseError::NonCollapsibleEdge(_)
318 | EdgeCollapseError::InvertedOrientation => break,
319 EdgeCollapseError::FailedCoreOp(_)
320 | EdgeCollapseError::FailedDartRelease(_)
321 | EdgeCollapseError::BadTopology => continue,
322 EdgeCollapseError::NullEdge => unreachable!(),
323 }
324 }
325 }
326 }
327 prof_stop!("HCBENCH_REMESH_CC");
328 print!(" | {:>16.6e}", instant.elapsed().as_secs_f64());
329
330 prof_start!("HCBENCH_REMESH_SWAP");
332 instant = Instant::now();
333 for (e, diff) in map
334 .iter_edges()
335 .map(|e| {
336 let (l, r) = (e as DartIdType, map.beta::<1>(e as DartIdType));
337 let diff =
338 atomically(|t| compute_diff_to_target(t, &map, l, r, args.target_length));
339 (e, diff)
340 })
341 .filter(|(_, diff)| diff.abs() > args.target_tolerance)
342 {
343 let (l, r) = (e as DartIdType, map.beta::<2>(e as DartIdType));
344 if r != NULL_DART_ID {
345 debug_assert!(
346 map.force_read_attribute::<FaceAnchor>(map.face_id(l))
347 .is_some()
348 );
349 debug_assert!(
350 map.force_read_attribute::<FaceAnchor>(map.face_id(r))
351 .is_some()
352 );
353 if let Err(er) = atomically_with_err(|t| {
354 let (b0l, b0r) = (map.beta_transac::<0>(t, l)?, map.beta_transac::<0>(t, r)?);
355 let new_diff = compute_diff_to_target(t, &map, b0l, b0r, args.target_length)?;
356
357 if new_diff.abs() < diff.abs() {
359 swap_edge(t, &map, e)?;
360 }
361
362 if !check_tri_orientation(t, &map, l)? || !check_tri_orientation(t, &map, r)? {
364 abort(EdgeSwapError::NotSwappable("swap inverts orientation"))?;
365 }
366
367 Ok(())
368 }) {
369 match er {
370 EdgeSwapError::NotSwappable(_) => {} EdgeSwapError::NullEdge
372 | EdgeSwapError::IncompleteEdge
373 | EdgeSwapError::FailedCoreOp(_)
374 | EdgeSwapError::BadTopology => unreachable!(),
375 }
376 }
377
378 debug_assert!(
379 map.force_read_attribute::<FaceAnchor>(map.face_id(l))
380 .is_some()
381 );
382 debug_assert!(
383 map.force_read_attribute::<FaceAnchor>(map.face_id(r))
384 .is_some()
385 );
386 }
387 }
388 prof_stop!("HCBENCH_REMESH_SWAP");
389 println!(" | {:>8.6e}", instant.elapsed().as_secs_f64());
390
391 debug_assert!(map.iter_faces().all(|f| {
392 map.orbit(OrbitPolicy::FaceLinear, f).count() == 3
393 && atomically(|t| check_tri_orientation(t, &map, f as DartIdType))
394 }));
395
396 n += 1;
397 if n >= args.n_rounds.get() {
398 break;
399 }
400 }
401 prof_stop!("HCBENCH_REMESH_MAINLOOP");
402
403 debug_assert!(map.iter_faces().all(|f| {
404 map.orbit(OrbitPolicy::FaceLinear, f).count() == 3
405 && atomically(|t| check_tri_orientation(t, &map, f as DartIdType))
406 }));
407
408 map
409}
410
411#[inline]
412fn compute_diff_to_target<T: CoordsFloat>(
413 t: &mut Transaction,
414 map: &CMap2<T>,
415 l: DartIdType,
416 r: DartIdType,
417 target: f64,
418) -> StmClosureResult<f64> {
419 let (vid1, vid2) = (map.vertex_id_transac(t, l)?, map.vertex_id_transac(t, r)?);
420 let (v1, v2) =
421 if let (Some(v1), Some(v2)) = (map.read_vertex(t, vid1)?, map.read_vertex(t, vid2)?) {
422 (v1, v2)
423 } else {
424 retry()?
425 };
426 Ok(((v2 - v1).norm().to_f64().unwrap() - target) / target)
427}
428
429#[inline]
430fn check_tri_orientation<T: CoordsFloat>(
431 t: &mut Transaction,
432 map: &CMap2<T>,
433 d: DartIdType,
434) -> StmClosureResult<bool> {
435 let vid1 = map.vertex_id_transac(t, d)?;
436 let b1 = map.beta_transac::<1>(t, d)?;
437 let vid2 = map.vertex_id_transac(t, b1)?;
438 let b1b1 = map.beta_transac::<1>(t, b1)?;
439 let vid3 = map.vertex_id_transac(t, b1b1)?;
440 let v1 = map.read_vertex(t, vid1)?.unwrap();
441 let v2 = map.read_vertex(t, vid2)?.unwrap();
442 let v3 = map.read_vertex(t, vid3)?.unwrap();
443 Ok(Vertex2::cross_product_from_vertices(&v1, &v2, &v3) > T::zero())
444}