Wildmeshing Toolkit
Loading...
Searching...
No Matches
SmoothVertex.hpp
1#pragma once
2
3#include <wmtk/optimization/AMIPSEnergy.hpp>
4#include <wmtk/optimization/EnergySum.hpp>
5#include <wmtk/optimization/EnvelopeEnergy.hpp>
6#include <wmtk/optimization/solver.hpp>
7
8#include <wmtk/Types.hpp>
9#include <wmtk/envelope/Envelope.hpp>
10#include <wmtk/simplex/SimplexCollection.hpp>
11#include <wmtk/utils/Logger.hpp>
12#include <wmtk/utils/TetraQualityUtils.hpp>
13
14#include <array>
15#include <atomic>
16#include <limits>
17#include <memory>
18#include <utility>
19#include <vector>
20
21namespace wmtk::optimization {
22
32{
33 std::atomic<size_t> already_inverted{0};
34 std::atomic<size_t> envelope{0};
35 std::atomic<size_t> inverted{0};
36 std::atomic<size_t> quality{0};
37 std::atomic<size_t> accepted{0};
38
39 void reset()
40 {
42 envelope = 0;
43 inverted = 0;
44 quality = 0;
45 accepted = 0;
46 }
47
48 std::string to_string() const
49 {
50 return fmt::format(
51 "accepted {} | rejected: pre-inverted {}, envelope {}, inverted {}, quality {}",
52 accepted.load(),
53 already_inverted.load(),
54 envelope.load(),
55 inverted.load(),
56 quality.load());
57 }
58};
59
76{
78 static constexpr size_t kIterBuckets = 12;
80 static constexpr size_t kStatuses = 14;
81 static_assert(
82 int(polysolve::nonlinear::Status::UpdateDirectionFailed) == int(kStatuses) - 2,
83 "polysolve's Status enum changed; update kStatuses and status_name()");
84
85 std::array<std::atomic<size_t>, kIterBuckets> iterations{};
86 std::array<std::atomic<size_t>, kStatuses> status{};
87 std::atomic<size_t> iterations_total{0};
88 std::atomic<size_t> threw{0};
89
91 void record(const polysolve::nonlinear::Solver& s, const bool did_throw)
92 {
93 const size_t it = s.current_criteria().iterations;
94 iterations[std::min(it, kIterBuckets - 1)].fetch_add(1, std::memory_order_relaxed);
95 iterations_total.fetch_add(it, std::memory_order_relaxed);
96 const int st = int(s.status()) + 1;
97 if (st >= 0 && size_t(st) < kStatuses) {
98 status[size_t(st)].fetch_add(1, std::memory_order_relaxed);
99 }
100 if (did_throw) threw.fetch_add(1, std::memory_order_relaxed);
101 }
102
103 size_t solves() const
104 {
105 size_t n = 0;
106 for (const auto& c : iterations) n += c.load();
107 return n;
108 }
109
110 void reset()
111 {
112 for (auto& c : iterations) c = 0;
113 for (auto& c : status) c = 0;
114 iterations_total = 0;
115 threw = 0;
116 }
117
118 static const char* status_name(const size_t i)
119 {
120 static constexpr std::array<const char*, kStatuses> names = {
121 {"NotStarted",
122 "Continue",
123 "IterationLimit",
124 "XDeltaTolerance",
125 "RelXDeltaTolerance",
126 "FDeltaTolerance",
127 "GradNormTolerance",
128 "RelGradNormTolerance",
129 "NewtonDecrementTolerance",
130 "ObjectiveCustomStop",
131 "NanEncountered",
132 "NotDescentDirection",
133 "LineSearchFailed",
134 "UpdateDirectionFailed"}};
135 return i < kStatuses ? names[i] : "?";
136 }
137
141 std::string to_string() const
142 {
143 const size_t n = solves();
144 std::string out = fmt::format(
145 "solves={} mean_iterations={:.3g} | iterations",
146 n,
147 n > 0 ? double(iterations_total.load()) / double(n) : 0.);
148 for (size_t i = 0; i < kIterBuckets; ++i) {
149 out += i + 1 < kIterBuckets ? fmt::format(" {}={}", i, iterations[i].load())
150 : fmt::format(" >{}={}", i - 1, iterations[i].load());
151 }
152 out += " | stop";
153 for (size_t i = 0; i < kStatuses; ++i) {
154 if (status[i].load() != 0)
155 out += fmt::format(" {}={}", status_name(i), status[i].load());
156 }
157 out += fmt::format(" | threw={}", threw.load());
158 return out;
159 }
160};
161
173{
174 double w_amips = 1e-4;
175 double w_envelope = 1.0 - 1e-4;
176 double s_amips = 1.0;
177 double s_envelope = 1.0;
178
181 bool two_stage = true;
182
211
226 enum class SmoothingMode { Projected, Exact };
227 SmoothingMode smoothing_mode = SmoothingMode::Projected;
228
232
250 bool quality_veto = true;
251
252
269
277 double max_step = std::numeric_limits<double>::infinity();
278};
279
310template <class Mesh>
311bool smooth_vertex_3d(
312 Mesh& m,
313 const typename Mesh::Tuple& t,
314 const SmoothVertexOptions& opts,
315 std::unique_ptr<polysolve::nonlinear::Solver>& solver,
316 SmoothRejectCounters* counters = nullptr,
317 NewtonCounters* newton = nullptr)
318{
319 using Tuple = typename Mesh::Tuple;
320
321 const size_t vid = t.vid(m);
322 auto& VA = m.m_vertex_attribute;
323 // Every OTHER vertex is read through this const view. A non-const AttributeCollection access
324 // made while the operation is protected records the entry, and a rejected smooth writes every
325 // recorded entry back -- so reading a neighbour through `VA` would also write it. Under the
326 // one-ring lock that was only wasted copying; when non-adjacent vertices smooth concurrently
327 // (colored smoothing), two of them can share a neighbour and would write it back at once.
328 const auto& VA_read = std::as_const(m.m_vertex_attribute);
329 const auto locs = m.get_one_ring_tets_for_vertex(t);
330 assert(!locs.empty());
331
332 // Here and below, every cell's inversion test runs before any cell's energy is read: an
333 // application's smoothing_cell_energy() or smoothing_ring_energy() may read cells beyond the
334 // one it scores, which must all be valid when it does. Measured: a field of the topological
335 // offset (since removed) that walked from a front face through other cells, at a non-front
336 // vertex's trial position, walked into a later cell of the same ring, inverted there, before
337 // that cell's test (one-slot model, stage 1). A refused move rolls back the stored qualities
338 // either way, so the order changes no result.
339 for (const Tuple& tet : locs) {
340 if (m.is_inverted_f(tet)) {
341 // A neighbour that is not rounded can leave a tet inverted in floats even
342 // though it is fine in exact arithmetic; there is nothing to optimize from.
343 if (counters) ++counters->already_inverted;
344 return false;
345 }
346 }
347 double max_quality = 0.;
348 for (const Tuple& tet : locs) {
349 max_quality =
350 std::max(max_quality, m.smoothing_cell_energy(tet.tid(m), m.cell_quality(tet.tid(m))));
351 }
352
353 // AMIPS wants each tet as 12 doubles with the moving vertex first, so that the solver
354 // can overwrite the leading xyz.
355 std::vector<std::array<double, 12>> assembles(locs.size());
356 for (size_t i = 0; i < locs.size(); ++i) {
357 std::array<size_t, 4> local_verts = m.oriented_tet_vids(locs[i].tid(m));
358 local_verts = wmtk::orient_preserve_tet_reorder(local_verts, vid);
359 for (int k = 0; k < 4; k++) {
360 for (int j = 0; j < 3; j++) {
361 assembles[i][k * 3 + j] = VA_read[local_verts[k]].m_posf[j];
362 }
363 }
364 }
365
366 if (!solver) {
367 solver = create_basic_solver();
368 }
369
370 const double amips_w = opts.w_amips > 0 ? opts.s_amips * opts.w_amips : 1.0;
371 auto amips_energy = std::make_shared<AMIPSEnergy3D>(assembles, amips_w);
372
373 // An application-supplied term for this vertex. It carries its own weight, exactly as
374 // ExactDistanceEnergy3D below does, so it is summed here at 1. Null for TetWild and SimWild,
375 // which leaves `base_energy` as `amips_energy` and every expression below exactly what it
376 // was.
377 const std::shared_ptr<polysolve::nonlinear::Problem> extra_energy =
378 m.smoothing_extra_energy(vid);
379 std::shared_ptr<polysolve::nonlinear::Problem> base_energy = amips_energy;
380 if (extra_energy) {
381 auto sum = std::make_shared<EnergySum>();
382 if (opts.w_amips > 0) sum->add_energy(amips_energy);
383 sum->add_energy(extra_energy);
384 base_energy = sum;
385 }
386 std::shared_ptr<polysolve::nonlinear::Problem> total_energy = base_energy;
387
388 // opts.max_step is measured from here, the position before any solve of this call.
389 const Vector3d x_entry = VA_read[vid].m_posf;
390 auto solve = [&]() {
391 VectorXd x = VA[vid].m_posf;
392 bool threw = false;
393 try {
394 solver->minimize(*total_energy, x);
395 } catch (const std::exception&) {
396 // polysolve reports a failed line search by throwing; the position it reached
397 // is still the best it found, and the checks below decide whether to keep it.
398 threw = true;
399 }
400 if (newton) newton->record(*solver, threw);
401 const Vector3d step = Vector3d(x) - x_entry;
402 const double len = step.norm();
403 if (len > opts.max_step) x = x_entry + step * (opts.max_step / len);
404 VA[vid].m_posf = x;
405 };
406
407 // A feature-curve vertex needs pulling and containment even when it is INTERIOR: the
408 // surface flag alone would smooth it as free and let it walk off its curve. Derived from
409 // the edge tags, so it cannot go stale.
410 const bool on_feature_curve = m.m_track_feature_edges && m.vertex_has_feature_edge(vid);
411 const std::shared_ptr<Envelope> pull_env =
412 (VA[vid].m_is_on_surface || on_feature_curve) ? m.smoothing_energy_envelope(vid) : nullptr;
413
414 if (pull_env && opts.smoothing_mode == SmoothVertexOptions::SmoothingMode::Projected) {
415 // Smooth as if the vertex were interior, then walk back onto the input. The ring's value
416 // before the move is read here, before the solve moves the vertex (see
417 // smoothing_ring_energy()); by default it is max_quality.
418 const Vector3d x_orig = VA[vid].m_posf;
419 const double ring_before = m.smoothing_ring_energy(locs, true);
420 total_energy = base_energy;
421 solve();
422 const Vector3d x_new = VA[vid].m_posf;
423
424 // Place a candidate and report the ring's value there (smoothing_ring_energy()), or
425 // infinity if it inverts. is_inverted is exact, so the rational position tracks every
426 // candidate.
427 const auto worst_at = [&](const Vector3d& p) {
428 if ((p - x_orig).norm() > opts.max_step) return std::numeric_limits<double>::infinity();
429 VA[vid].m_posf = p;
430 VA[vid].set_pos_to_posf();
431 for (const Tuple& loc : locs) {
432 if (m.is_inverted(loc)) {
433 return std::numeric_limits<double>::infinity();
434 }
435 }
436 return m.smoothing_ring_energy(locs, false);
437 };
438
439 bool accepted = false;
440 std::vector<Vector3d> interp, proj; // kept for the nested pass below
441 interp.reserve(opts.project_line_search_steps);
442 proj.reserve(opts.project_line_search_steps);
443 for (int k = 0; k < opts.project_line_search_steps; ++k) {
444 const Vector3d p = x_orig + std::pow(0.5, k) * (x_new - x_orig);
445 Vector3d q;
446 pull_env->nearest_point(p, q);
447 interp.push_back(p);
448 proj.push_back(q);
449 if (worst_at(q) < ring_before) {
450 accepted = true;
451 break;
452 }
453 }
454
455 // Nothing ON the input was acceptable anywhere, so settle for getting as close to
456 // it as the one-ring allows. For each candidate, bisect the segment from the
457 // interpolated point (s = 0) to its projection (s = 1) for the LARGEST acceptable s.
458 // s = 1 is already known to fail -- that is what the first pass just established --
459 // so this brackets the boundary and converges up to it from below. Halving s down
460 // from 1 instead would cap the result at the midpoint and leave the vertex needlessly
461 // far from the input.
462 if (!accepted && opts.project_line_search_nested_steps > 0) {
463 for (size_t k = 0; k < proj.size() && !accepted; ++k) {
464 double lo = 0.0, hi = 1.0; // lo: best acceptable so far, hi: known bad
465 Vector3d best;
466 bool found = false;
467 for (int j = 0; j < opts.project_line_search_nested_steps; ++j) {
468 const double mid = 0.5 * (lo + hi);
469 const Vector3d cand = interp[k] + mid * (proj[k] - interp[k]);
470 if (worst_at(cand) < ring_before) {
471 lo = mid;
472 best = cand;
473 found = true;
474 } else {
475 hi = mid;
476 }
477 }
478 if (found) {
479 // worst_at left the vertex at the last candidate tried, which is not
480 // necessarily the best one.
481 worst_at(best);
482 accepted = true;
483 }
484 }
485 }
486 if (!accepted) {
487 VA[vid].m_posf = x_orig;
488 VA[vid].set_pos_to_posf();
489 if (counters) ++counters->quality;
490 return false;
491 }
492 } else if (pull_env) {
493 auto envelope_energy =
494 std::make_shared<ExactDistanceEnergy3D>(pull_env, opts.s_envelope * opts.w_envelope);
495
496 if (opts.two_stage) {
497 auto warmup = std::make_shared<EnergySum>();
498 if (opts.w_amips > 0) warmup->add_energy(amips_energy, 1. / opts.w_amips);
499 if (opts.w_envelope > 0) warmup->add_energy(envelope_energy, 1. / opts.w_envelope);
500 if (extra_energy) warmup->add_energy(extra_energy);
501 total_energy = warmup;
502 solve();
503 }
504
505 auto weighted = std::make_shared<EnergySum>();
506 if (opts.w_amips > 0) weighted->add_energy(amips_energy);
507 if (opts.w_envelope > 0) weighted->add_energy(envelope_energy);
508 if (extra_energy) weighted->add_energy(extra_energy);
509 total_energy = weighted;
510 solve();
511 } else {
512 solve();
513 }
514
515 // Per-vertex positional constraint (a 0-dimensional feature anchor's ball). Same hook
516 // and same placement as the 2D path.
517 if (!m.smoothing_position_is_allowed(vid, VA[vid].m_posf)) {
518 if (counters) ++counters->envelope;
519 return false;
520 }
521
522 // Feature containment: every tagged edge at this vertex must stay inside the feature
523 // tube, at the candidate position (already written into the vertex attribute here).
524 if (on_feature_curve && !m.feature_edges_at_vertex_inside(vid)) {
525 if (counters) ++counters->envelope;
526 return false;
527 }
528
529 // Open-boundary containment, the same test for the DERIVED order-2 curves: every open
530 // boundary edge at this vertex must stay inside the order-2 tube. Without it smoothing
531 // walks boundary vertices along the boundary -- each step legal for the vertex, no check
532 // on the edges -- and the coarsening composite's mass smoothing contracts an open sheet
533 // boundary far past its tube (measured: a corner walked 0.5, 14% of the sheet gone).
534 if (!m.boundary_edges_at_vertex_inside(vid)) {
535 if (counters) ++counters->envelope;
536 return false;
537 }
538
539 // Containment: every surface triangle at this vertex must still be inside -- each in the
540 // envelope smoothing_containment_envelope() gives that face, by default its own input set's,
541 // which is not necessarily the one the vertex was pulled to.
542 if (VA[vid].m_is_on_surface) {
543 const auto& FA_read = std::as_const(m.m_face_attribute);
544 const simplex::SimplexCollection surf = m.get_surface_faces_for_vertex(vid);
545 for (const simplex::Face& f : surf.faces()) {
546 const auto& fv = f.vertices();
547 const size_t fid = std::get<1>(m.tuple_from_face(fv));
548 const std::shared_ptr<Envelope> check_env =
549 m.smoothing_containment_envelope(vid, fv, FA_read[fid]);
550 if (!check_env) continue;
551 const std::array<Eigen::Vector3d, 3> face = {
552 {VA_read[fv[0]].m_posf, VA_read[fv[1]].m_posf, VA_read[fv[2]].m_posf}};
553 if (check_env->is_outside(face)) {
554 if (counters) ++counters->envelope;
555 return false;
556 }
557 }
558 }
559
560 // The rational position must be current before the exact inversion test.
561 VA[vid].set_pos_to_posf();
562
563 for (const Tuple& loc : locs) {
564 if (m.is_inverted(loc)) {
565 if (counters) ++counters->inverted;
566 return false;
567 }
568 }
569 double max_after_quality = 0.;
570 for (const Tuple& loc : locs) {
571 const size_t tid = loc.tid(m);
572 const double quality = m.get_quality(loc);
573 m.set_cell_quality(tid, quality);
574 max_after_quality = std::max(max_after_quality, m.smoothing_cell_energy(tid, quality));
575 }
576
577 if (opts.quality_veto && (!VA[vid].m_is_on_surface || opts.quality_veto_on_surface)) {
578 if (max_after_quality > max_quality) {
579 if (counters) ++counters->quality;
580 return false;
581 }
582 }
583
584 if (counters) ++counters->accepted;
585 return true;
586}
587
588
609template <class Mesh>
610bool smooth_vertex_2d(
611 Mesh& m,
612 const typename Mesh::Tuple& t,
613 const SmoothVertexOptions& opts,
614 std::unique_ptr<polysolve::nonlinear::Solver>& solver,
615 SmoothRejectCounters* counters = nullptr)
616{
617 const size_t vid = t.vid(m);
618 auto& VA = m.m_vertex_attribute;
619 auto& FA = m.m_face_attribute;
620
621 const std::vector<size_t>& locs = m.get_one_ring_fids_for_vertex(t);
622 assert(!locs.empty());
623
624 // Every cell's inversion test before any cell's energy, as in smooth_vertex_3d().
625 for (const size_t fid : locs) {
626 if (m.is_inverted_f(fid)) {
627 // Nothing to optimize from: a neighbour that is not rounded can leave a face
628 // inverted in floats even when it is fine exactly.
629 if (counters) ++counters->already_inverted;
630 return false;
631 }
632 }
633 double max_quality = 0.;
634 for (const size_t fid : locs) {
635 max_quality = std::max(max_quality, m.smoothing_cell_energy(fid, FA[fid].m_quality));
636 }
637
638 // AMIPS wants each face as 6 doubles with the moving vertex first, keeping the winding.
639 std::vector<std::array<double, 6>> assembles;
640 assembles.reserve(locs.size());
641 for (const size_t fid : locs) {
642 std::array<size_t, 3> vs = m.oriented_tri_vids(fid);
643 size_t v_loc = 0;
644 for (size_t i = 0; i < 3; ++i) {
645 if (vs[i] == vid) {
646 v_loc = i;
647 break;
648 }
649 }
650 const std::array<size_t, 3> buf = vs;
651 vs[0] = buf[v_loc];
652 vs[1] = buf[(v_loc + 1) % 3];
653 vs[2] = buf[(v_loc + 2) % 3];
654
655 std::array<double, 6> T;
656 for (int i = 0; i < 3; i++) {
657 const Vector2d p = m.smoothing_position(vs[i]);
658 T[i * 2] = p[0];
659 T[i * 2 + 1] = p[1];
660 }
661 assembles.push_back(T);
662 }
663
664 if (!solver) {
665 solver = create_basic_solver();
666 }
667
668 const double amips_w = opts.w_amips > 0 ? opts.s_amips * opts.w_amips : 1.0;
669 auto amips_energy = std::make_shared<AMIPSEnergy2D>(assembles, amips_w);
670
671 // An application-supplied term for this vertex. It carries its own weight, exactly as
672 // ExactDistanceEnergy2D below does, so it is summed here at 1. Null for TriWild and
673 // SimWild, which leaves `base_energy` as `amips_energy` and every expression below exactly
674 // what it was.
675 const std::shared_ptr<polysolve::nonlinear::Problem> extra_energy =
676 m.smoothing_extra_energy(vid);
677 std::shared_ptr<polysolve::nonlinear::Problem> base_energy = amips_energy;
678 if (extra_energy) {
679 auto sum = std::make_shared<EnergySum>();
680 if (opts.w_amips > 0) sum->add_energy(amips_energy);
681 sum->add_energy(extra_energy);
682 base_energy = sum;
683 }
684 std::shared_ptr<polysolve::nonlinear::Problem> total_energy = base_energy;
685
686 auto solve = [&]() {
687 VectorXd x = m.smoothing_position(vid);
688 try {
689 solver->minimize(*total_energy, x);
690 } catch (const std::exception&) {
691 // A failed line search is reported by throwing; the position reached is still
692 // the best found, and the checks below decide whether to keep it.
693 }
694 m.set_smoothing_position(vid, Vector2d(x));
695 };
696
697 // Neighbours along the incident surface edges, captured before the solve. Only `vid`
698 // moves, so their positions are the same either way, but taking them first matches what
699 // both applications did and keeps the assert below meaningful.
700 // The segments themselves are kept too, for the per-segment containment below.
701 std::vector<Vector2d> surf_neighbors;
702 std::vector<std::array<size_t, 2>> surf_edges;
703 if (VA[vid].m_is_on_surface) {
704 const simplex::SimplexCollection es = m.get_surface_edges_for_vertex(vid);
705 surf_neighbors.reserve(es.edges().size());
706 surf_edges.reserve(es.edges().size());
707 for (const simplex::Edge& e : es.edges()) {
708 const auto& evs = e.vertices();
709 surf_neighbors.push_back(m.smoothing_position(evs[0] != vid ? evs[0] : evs[1]));
710 surf_edges.push_back(evs);
711 }
712 assert(!surf_neighbors.empty());
713 }
714
715 // Per-vertex rather than the mesh's single m_envelope, and split into the PULL and the
716 // CONTAINER exactly as the 3D path above: see TriOptimizerMesh::smoothing_energy_envelope.
717 // The defaults return the old expression, so TriWild and SimWild are unaffected.
718 const std::shared_ptr<Envelope> envelope = m.smoothing_energy_envelope(vid);
719
720 if (envelope && opts.smoothing_mode == SmoothVertexOptions::SmoothingMode::Projected) {
721 const Vector2d x_orig = m.smoothing_position(vid);
722 total_energy = base_energy;
723 solve();
724 const Vector2d x_new = m.smoothing_position(vid);
725
726 // set_smoothing_position keeps the rational position in step, which the exact
727 // is_inverted below depends on.
728 const auto worst_at = [&](const Vector2d& p) {
729 m.set_smoothing_position(vid, p);
730 for (const size_t fid : locs) {
731 if (m.is_inverted(fid)) {
732 return std::numeric_limits<double>::infinity();
733 }
734 }
735 double mq = 0.;
736 for (const size_t fid : locs) {
737 mq = std::max(mq, m.smoothing_cell_energy(fid, m.get_quality(fid)));
738 }
739 return mq;
740 };
741
742 bool accepted = false;
743 std::vector<Vector2d> interp, proj; // kept for the nested pass below
744 interp.reserve(opts.project_line_search_steps);
745 proj.reserve(opts.project_line_search_steps);
746 for (int k = 0; k < opts.project_line_search_steps; ++k) {
747 const Vector2d p = x_orig + std::pow(0.5, k) * (x_new - x_orig);
748 Vector2d q;
749 envelope->nearest_point(p, q);
750 interp.push_back(p);
751 proj.push_back(q);
752 if (worst_at(q) < max_quality) {
753 accepted = true;
754 break;
755 }
756 }
757
758 // Nothing ON the input was acceptable anywhere, so settle for getting as close to
759 // it as the one-ring allows. For each candidate, bisect the segment from the
760 // interpolated point (s = 0) to its projection (s = 1) for the LARGEST acceptable s.
761 // s = 1 is already known to fail -- that is what the first pass just established --
762 // so this brackets the boundary and converges up to it from below. Halving s down
763 // from 1 instead would cap the result at the midpoint and leave the vertex needlessly
764 // far from the input.
765 if (!accepted && opts.project_line_search_nested_steps > 0) {
766 for (size_t k = 0; k < proj.size() && !accepted; ++k) {
767 double lo = 0.0, hi = 1.0; // lo: best acceptable so far, hi: known bad
768 Vector2d best;
769 bool found = false;
770 for (int j = 0; j < opts.project_line_search_nested_steps; ++j) {
771 const double mid = 0.5 * (lo + hi);
772 const Vector2d cand = interp[k] + mid * (proj[k] - interp[k]);
773 if (worst_at(cand) < max_quality) {
774 lo = mid;
775 best = cand;
776 found = true;
777 } else {
778 hi = mid;
779 }
780 }
781 if (found) {
782 // worst_at left the vertex at the last candidate tried, which is not
783 // necessarily the best one.
784 worst_at(best);
785 accepted = true;
786 }
787 }
788 }
789 if (!accepted) {
790 m.set_smoothing_position(vid, x_orig);
791 if (counters) ++counters->quality;
792 return false;
793 }
794 } else if (envelope) {
795 auto envelope_energy =
796 std::make_shared<ExactDistanceEnergy2D>(envelope, opts.s_envelope * opts.w_envelope);
797
798 if (opts.two_stage) {
799 auto warmup = std::make_shared<EnergySum>();
800 if (opts.w_amips > 0) warmup->add_energy(amips_energy, 1. / opts.w_amips);
801 if (opts.w_envelope > 0) warmup->add_energy(envelope_energy, 1. / opts.w_envelope);
802 if (extra_energy) warmup->add_energy(extra_energy);
803 total_energy = warmup;
804 solve();
805 }
806
807 auto weighted = std::make_shared<EnergySum>();
808 if (opts.w_amips > 0) weighted->add_energy(amips_energy);
809 if (opts.w_envelope > 0) weighted->add_energy(envelope_energy);
810 if (extra_energy) weighted->add_energy(extra_energy);
811 total_energy = weighted;
812 solve();
813 } else {
814 solve();
815 }
816
817 // Per-vertex positional constraint, on top of the envelope. A mesh uses this to pin a
818 // vertex to a 0-dimensional feature it stands for -- within a ball, so the vertex is
819 // still free to move and improve quality, it just cannot walk away from the feature.
820 // Meshes with no such features answer true unconditionally.
821 if (!m.smoothing_position_is_allowed(vid, m.smoothing_position(vid))) {
822 if (counters) ++counters->envelope;
823 return false;
824 }
825
826 // Containment, edge by edge rather than face by face as in 3D -- each segment against its
827 // own CONTAINMENT envelope, which is not necessarily the one the vertex was pulled to.
828 {
829 const auto& EA_read = std::as_const(m.m_edge_attribute);
830 const Vector2d p = m.smoothing_position(vid);
831 for (size_t i = 0; i < surf_neighbors.size(); ++i) {
832 const size_t eid = std::get<1>(m.tuple_from_edge(surf_edges[i]));
833 const std::shared_ptr<Envelope> hold_env =
834 m.smoothing_containment_envelope(vid, surf_edges[i], EA_read[eid]);
835 if (!hold_env) continue;
836 const std::array<Eigen::Vector2d, 2> edge = {{p, surf_neighbors[i]}};
837 if (hold_env->is_outside(edge)) {
838 if (counters) ++counters->envelope;
839 return false;
840 }
841 }
842 }
843
844 for (const size_t fid : locs) {
845 if (m.is_inverted(fid)) {
846 if (counters) ++counters->inverted;
847 return false;
848 }
849 }
850 double max_after_quality = 0.;
851 for (const size_t fid : locs) {
852 const double q = m.get_quality(fid);
853 FA[fid].m_quality = q;
854 max_after_quality = std::max(max_after_quality, m.smoothing_cell_energy(fid, q));
855 }
856
857 if (opts.quality_veto && (!VA[vid].m_is_on_surface || opts.quality_veto_on_surface)) {
858 if (max_after_quality > max_quality) {
859 if (counters) ++counters->quality;
860 return false;
861 }
862 }
863
864 if (counters) ++counters->accepted;
865 return true;
866}
867
868} // namespace wmtk::optimization
How the vertex solves of a smoothing pass ended: Newton iterations taken and the status polysolve sto...
Definition SmoothVertex.hpp:76
void record(const polysolve::nonlinear::Solver &s, const bool did_throw)
One solve that just returned from minimize(), or threw out of it (did_throw).
Definition SmoothVertex.hpp:91
std::string to_string() const
Definition SmoothVertex.hpp:141
static constexpr size_t kStatuses
polysolve::nonlinear::Status, NotStarted (-1) .. UpdateDirectionFailed (12), shifted by 1.
Definition SmoothVertex.hpp:80
static constexpr size_t kIterBuckets
0 .. 10 iterations, and one bucket for more than 10.
Definition SmoothVertex.hpp:78
Why a smoothing attempt was refused, counted per pass.
Definition SmoothVertex.hpp:32
std::atomic< size_t > envelope
a surface triangle left the envelope
Definition SmoothVertex.hpp:34
std::atomic< size_t > already_inverted
incident tet inverted in floats on entry
Definition SmoothVertex.hpp:33
std::atomic< size_t > quality
the move made the worst incident tet worse
Definition SmoothVertex.hpp:36
std::atomic< size_t > inverted
the move inverted a tet (exact predicate)
Definition SmoothVertex.hpp:35
Weights and policy for smooth_vertex_3d.
Definition SmoothVertex.hpp:173
bool quality_veto_on_surface
Definition SmoothVertex.hpp:210
double max_step
Definition SmoothVertex.hpp:277
int project_line_search_steps
Definition SmoothVertex.hpp:231
int project_line_search_nested_steps
Definition SmoothVertex.hpp:268
bool quality_veto
Definition SmoothVertex.hpp:250
bool two_stage
Definition SmoothVertex.hpp:181
SmoothingMode
Definition SmoothVertex.hpp:226