Wildmeshing Toolkit
Loading...
Searching...
No Matches
TopoOffsetTriMesh.h
1#pragma once
2#include <wmtk/TriMesh.h>
3#include <wmtk/TriOptimizerMesh.h>
4#include <algorithm>
5#include <array>
6#include <atomic>
7#include <cstdint>
8#include <functional>
9#include <map>
10#include <mutex>
11#include <set>
12#include <wmtk/optimization/EnergySum.hpp>
13#include <wmtk/optimization/solver.hpp>
14#include <wmtk/threading/enumerable_thread_specific.hpp>
15#include "OffsetPotential.hpp"
16#include "Parameters.h"
17#include "SimplicialComplexBVH.hpp"
18#include "TagEnvelopes.hpp"
19
20using CellTag = std::set<int64_t>;
21
22namespace wmtk::components::topological_offset {
23
24
25const int64_t TEMP_OFFSET_TRI_TAG = -1;
26const CellTag TEMP_OFFSET_TRI_TAG_SET{TEMP_OFFSET_TRI_TAG};
27
37{
38public:
39 int label = 0;
40 bool m_is_on_input = false; // on the input complex
41 bool m_is_on_offset = false; // on the offset boundary itself
42 bool m_is_on_region = false; // on some OTHER tag region's boundary
47 Vector2d m_turn_start = Vector2d::Zero();
48 bool m_turn_start_valid = false;
49
58 uint64_t m_boundary_mask = 0;
59
64 uint32_t m_born_epoch = 0;
65};
66
67
71{
72public:
73 int label = 0;
77 bool on_curve = false;
78};
79
80
84{
85public:
86 int label = 0;
94 bool rest_valid = false;
95 std::array<Eigen::Vector2d, 3> rest_pos;
96};
97
98
112{
113public: // mode for splitting in marching tets
114 enum class EdgeSplitMode {
115 Midpoint = 0, // construction: simplicial embedding AND marching_tris
116 Optimization = 2 // the optimization phase; the shared engine places the vertex
117 };
118
119public:
120 int m_vtu_counter = 0;
121 std::array<size_t, 3> m_init_counts = {{0, 0, 0}};
122 size_t m_tags_count;
126 int64_t m_curve_tag = -1;
128 MatrixXd m_curve_V;
129 MatrixXi m_curve_E;
157 std::shared_ptr<SimplicialComplexBVH> m_input_complex_bvh;
158
168 std::shared_ptr<OffsetPotential2D> m_offset_potential;
169
199 int m_n_regions = 0;
200 std::vector<std::shared_ptr<OffsetPotential2D>> m_region_potentials;
201 std::vector<int64_t> m_phi_vert_region;
202 std::vector<int64_t> m_phi_seg_region;
203 std::vector<int64_t> m_phi_face_region;
204 std::vector<int64_t> m_phi_point_region;
205 std::vector<int> m_face_region;
206 std::vector<int> m_vertex_region;
207 void init_region_potentials(double delta, double effective_factor);
208 void assign_band_regions();
210 void log_front_profile(size_t vid);
211 int vertex_region(const size_t vid) const
212 {
213 return vid < m_vertex_region.size() ? m_vertex_region[vid] : -1;
214 }
215 int edge_region(const size_t va, const size_t vb) const
216 {
217 const int a = vertex_region(va), b = vertex_region(vb);
218 return (a >= 0 && a == b) ? a : -1;
219 }
220 const OffsetPotential2D& potential_for_region(const int region) const
221 {
222 return (region >= 0 && size_t(region) < m_region_potentials.size())
223 ? *m_region_potentials[size_t(region)]
225 }
226 const OffsetPotential2D& potential_for(const size_t vid) const
227 {
228 return potential_for_region(vertex_region(vid));
229 }
232 std::shared_ptr<const OffsetPotential2D> potential_ptr_for(const size_t vid) const
233 {
234 const int r = vertex_region(vid);
235 return (r >= 0 && size_t(r) < m_region_potentials.size()) ? m_region_potentials[size_t(r)]
237 }
238 const OffsetPotential2D& potential_for_edge(const size_t va, const size_t vb) const
239 {
240 return potential_for_region(edge_region(va, vb));
241 }
242 const OffsetPotential2D& potential_for_face(const size_t fid) const
243 {
244 return potential_for_region(fid < m_face_region.size() ? m_face_region[fid] : -1);
245 }
246
263 std::map<int64_t, std::shared_ptr<SampleEnvelope>> m_tag_envelopes;
264
279 {
280 std::vector<Eigen::Vector2i> E;
281 std::vector<std::vector<int>> at_vertex;
282 };
286 std::vector<Eigen::Vector2d> m_env_polyline_V;
287 std::map<int64_t, TagPolyline2d> m_tag_polyline;
288
291 std::map<int64_t, int> m_tag_bit;
292
295 mutable std::map<uint64_t, std::shared_ptr<SampleEnvelope>> m_isect_cache;
296 mutable std::mutex m_isect_mutex;
297
309 mutable std::map<uint64_t, std::shared_ptr<SampleEnvelope>> m_offset_isect_cache;
310
321 std::shared_ptr<SampleEnvelope> containment_for(uint64_t region_mask, bool on_offset) const;
322
343 bool project_into_containment(size_t vid, Vector2d& x) const;
344
354 int64_t tangent_curve_tag(size_t vid, const Vector2d& x) const;
355
370 bool walk_along_curve(int64_t tag, const Vector2d& x, double s, Vector2d& out) const;
371
379 bool curve_tangent(int64_t tag, const Vector2d& x, const Vector2d& prefer, Vector2d& tau) const;
380
403 enum class OptPhase { A, B, Single };
404
407 bool phase_places_front() const { return m_phase != OptPhase::A; }
408
410 OptPhase m_phase = OptPhase::A;
411
412 // Phase B is one sweep over every vertex per pass: a front vertex is placed by
413 // smooth_offset_vertex_backtracking(), an interior one relaxed by the shared smoother, in
414 // mesh order -- see smooth_before().
415
417 bool m_freeze_front = false;
418
437 enum class EnvelopeSetup { PerTag, WallComplex };
438 EnvelopeSetup envelope_setup() const
439 {
440 return m_offset_params.deform_others ? EnvelopeSetup::WallComplex : EnvelopeSetup::PerTag;
441 }
442 static constexpr int64_t m_wall_tag = -2;
443 static constexpr int64_t m_complex_tag = -3;
445 std::string envelope_key_name(int64_t tag) const;
448 bool edge_is_complex_boundary(const Tuple& e) const;
454 void build_boundary_envelopes(const char* when, EnvelopeSetup setup);
455
456 // No Phi test gates collapse and swap: the envelope is the constraint, and the offset
457 // criterion belongs to Phase B's own placement. The coarsening bar is applied where 3D
458 // applies it -- absolute, and only in m_coarsen_mode.
459
467 std::shared_ptr<SampleEnvelope> m_offset_envelope;
468
473
476 void check_no_vertex_on_both_surfaces(const char* when) const;
477
480
487
488 EdgeSplitMode m_edge_split_mode = EdgeSplitMode::Midpoint;
489
490 // tag name maps
491 std::map<std::string, int64_t> m_tag_name_to_id;
492 std::map<int64_t, std::string> m_tag_id_to_name;
493 CellTag m_offset_output_tag_ids;
494
495 // if in 'singlebody' mode
496 bool m_singlebody = false;
497 int64_t m_single_tag;
498
499 // just for retaining in output. dont actually use
500 bool m_has_envelope = false;
501 MatrixXd m_V_envelope;
502 MatrixXi m_F_envelope;
503
515 static constexpr int INPUT_SURFACE_CLASS = 0;
516 static constexpr int OFFSET_SURFACE_CLASS = 1;
517
520
524 // m_vertex_attribute, m_edge_attribute and m_face_attribute are the base's; these three
525 // are registered alongside them in its attribute groups.
526 VertexExtraCol m_vertex_extra;
527 EdgeExtraCol m_edge_extra;
528 FaceExtraCol m_face_extra;
529
530 TopoOffsetTriMesh(Parameters& _m_offset_params, int _num_threads = 0)
531 : wmtk::TriOptimizerMesh(_m_offset_params)
532 , m_offset_params(_m_offset_params)
533 {
534 NUM_THREADS = _num_threads;
535 m_vertex_attr_group.add(&m_vertex_extra);
536 m_edge_attr_group.add(&m_edge_extra);
537 m_face_attr_group.add(&m_face_extra);
538
539 // As in 3D. The per-vertex Newton solver logs a line per smoothing attempt at info level,
540 // which is one line per vertex per pass and buries the run's own output.
541 optimization::deactivate_opt_logger();
542 }
543
544 ~TopoOffsetTriMesh() override = default;
545
552 void set_vertex_position(const size_t vid, const Vector2d& p)
553 {
554 m_vertex_attribute[vid].m_posf = p;
555 m_vertex_attribute[vid].m_pos = to_rational(p);
556 m_vertex_attribute[vid].m_is_rounded = true;
557 }
558
561 bool edge_is_offset(const size_t eid) const
562 {
563 return m_edge_attribute[eid].m_is_surface_fs &&
564 m_edge_attribute[eid].m_surface_class == OFFSET_SURFACE_CLASS;
565 }
569 bool edge_is_region(const size_t eid) const
570 {
571 return m_edge_attribute[eid].m_is_surface_fs &&
572 m_edge_attribute[eid].m_surface_class != OFFSET_SURFACE_CLASS;
573 }
581 {
582 EdgeAttributes tags;
583 EdgeExtra2d extra;
584 };
586 {
587 FaceAttributes attrs;
588 FaceExtra2d extra;
589 };
590 EdgeSnapshot2d edge_snapshot(const size_t eid) const
591 {
592 return EdgeSnapshot2d{m_edge_attribute[eid], m_edge_extra[eid]};
593 }
594 void restore_edge(const size_t eid, const EdgeSnapshot2d& s)
595 {
596 m_edge_attribute[eid] = s.tags;
597 m_edge_extra[eid] = s.extra;
598 }
599 FaceSnapshot2d face_snapshot(const size_t fid) const
600 {
601 return FaceSnapshot2d{m_face_attribute[fid], m_face_extra[fid]};
602 }
603 void restore_face(const size_t fid, const FaceSnapshot2d& s)
604 {
605 m_face_attribute[fid] = s.attrs;
606 m_face_extra[fid] = s.extra;
607 }
608
618
628 bool face_in_region(const size_t fid) const;
629
632 bool face_is_input_complex(const size_t fid) const;
633
643 bool vertex_is_on_surface(const size_t vid) const override;
644 bool edge_is_on_surface(const std::array<size_t, 2>& vids) const override;
645
647 void optimize_offset(const std::filesystem::path& output_file);
648
652 bool face_is_offset_band(const size_t fid) const;
653
654
663 std::pair<double, double> compute_distance_deviation() const;
664
667 mutable size_t m_worst_dist_vid = static_cast<size_t>(-1);
668 void log_worst_dist_vertex() const;
669
672 bool edge_is_offset_surface_live(const Tuple& e) const;
673
678
684 std::vector<std::array<double, 8>> optimization_metrics;
687 std::vector<std::array<int, 3>> churn_counts;
692 std::vector<std::array<int, 3>> op_counts;
695 int m_ab_round = 0;
697 mutable size_t m_debug_seq = 0;
700 mutable int m_debug_pass = 0;
701 mutable int m_debug_last_round = -1;
702 mutable char m_debug_last_phase = '?';
707 bool m_converged = false;
712 double m_quality_max_amips = 0.;
713
716 std::atomic<int> iter_cnt_split_born{0};
717 std::atomic<int> iter_cnt_recollapsed{0};
718 std::atomic<int> iter_cnt_recollapsed_same_pass{0};
719 std::atomic<int> iter_cnt_split = 0, iter_cnt_collapse = 0, iter_cnt_swap = 0;
720 std::atomic<int> iter_cnt_collapse_offset_removed{0};
723 std::atomic<int> iter_cnt_swap_offset_reject{0};
725 std::atomic<int> iter_cnt_split_offset_before{0};
726 std::atomic<int> iter_cnt_split_offset{0};
732 {
733 std::map<size_t, int> face_label;
734 size_t v1_id = 0;
735 size_t v2_id = 0;
741 uint64_t edge_bits = 0;
744 double parent_q_max = -1.;
747 double parent_flatness = 1.;
748 };
750
751 bool marching_split_edge_before(const Tuple& t);
752 bool marching_split_edge_after(const Tuple& t);
753
762 bool collapse_edge_before(const Tuple& t) override;
763
771 bool swap_edge_before(const Tuple& t) override;
772
775 bool swap_edge_after(const Tuple& t) override;
776 bool collapse_before_vertex(size_t v1, size_t v2) override;
777 void collapse_after_vertex(size_t v1, size_t v2) override;
778 void split_after_vertex(size_t v_new) override;
779
795 bool split_adjust_position(size_t v_new, const std::vector<Tuple>& children) override;
796
797 bool smooth_before(const Tuple& t) override;
798 bool smooth_after(const Tuple& t) override;
799
817 bool vertex_is_on_domain_boundary(const size_t vid) const
818 {
819 return !m_vertex_attribute[vid].on_bbox_faces.empty();
820 }
821 bool edge_is_on_domain_boundary(const size_t eid) const
822 {
823 return m_edge_attribute[eid].m_is_bbox_fs >= 0;
824 }
825
841
846
860
871 {
872 std::atomic<int> attempted{0};
873 std::atomic<int> before_bbox{0};
874 std::atomic<int> before_unrounded{0};
876 0};
879 std::atomic<int> offset_attempted{0};
880 std::atomic<int> offset_accepted{0};
881 std::atomic<int> interior_attempted{0};
882 std::atomic<int> region_attempted{0};
889 std::atomic<long long> res_before_nano{0};
890 std::atomic<long long> res_after_nano{0};
891 std::atomic<long long> res_max_before_nano{0};
892 std::atomic<long long> res_max_after_nano{0};
893
894 void reset()
895 {
896 for (std::atomic<int>* c :
897 {&attempted,
907 c->store(0);
908 }
909 for (std::atomic<long long>* c :
910 {&res_before_nano, &res_after_nano, &res_max_before_nano, &res_max_after_nano}) {
911 c->store(0);
912 }
913 }
914 };
915 SmoothTrace m_smooth_trace;
916
933 mutable std::atomic<size_t> m_needle_smooth_offered{0};
934 mutable std::atomic<size_t> m_needle_smooth_reached{0};
935 mutable std::atomic<size_t> m_needle_smooth_fixed{0};
936 mutable std::atomic<size_t> m_needle_smooth_stationary{0};
938 mutable std::atomic<size_t> m_needle_smooth_reports{0};
939
941 double ring_max_quality(size_t vid) const;
942
951 double face_flatness(size_t fid) const;
952
963 void needle_forensics() const;
964
966 void record_flatness(const char* op, double parent_flat, size_t child_fid) const;
967 mutable std::atomic<size_t> m_flat_created_split{0};
968 mutable std::atomic<size_t> m_flat_created_collapse{0};
969 mutable std::atomic<size_t> m_flat_worsened_split{0};
970 mutable std::atomic<size_t> m_flat_genesis_reports{0};
971 static constexpr double kFlatThreshold = 1e-3;
977 void log_smooth_trace() const;
978
979
989 void log_region_edge_mask_health(const std::string& when) const;
990
1007 void audit_surface_containment(const std::string& when) const;
1008
1012 mutable std::atomic<int> m_placement_env_entry_outside{0};
1013
1018 mutable std::atomic<int> m_placement_projected{0};
1019
1024 mutable std::atomic<int> m_placement_tangential{0};
1025
1027
1035 bool vertex_is_on_region(const size_t vid) const
1036 {
1037 return m_vertex_extra[vid].m_is_on_region || !m_vertex_attribute[vid].on_bbox_faces.empty();
1038 }
1039
1042 uint64_t tag_bits(const CellTag& tags) const
1043 {
1044 uint64_t bits = 0;
1045 for (const int64_t t : tags) {
1046 const auto it = m_tag_bit.find(t);
1047 if (it != m_tag_bit.end()) bits |= (uint64_t(1) << it->second);
1048 }
1049 return bits;
1050 }
1051
1062 uint64_t vertex_boundary_mask(const size_t vid) const
1063 {
1064 return vertex_is_on_region(vid) ? m_vertex_extra[vid].m_boundary_mask : uint64_t(0);
1065 }
1066
1069 uint64_t edge_mask(const std::array<size_t, 2>& vids) const
1070 {
1071 return vertex_boundary_mask(vids[0]) & vertex_boundary_mask(vids[1]);
1072 }
1073
1086 uint64_t edge_boundary_bits(const Tuple& e) const
1087 {
1088 const std::optional<Tuple> opp = e.switch_face(*this);
1089 if (!opp) {
1090 return tag_bits(m_face_attribute[e.fid(*this)].tags); // domain wall
1091 }
1092 const auto& t0 = m_face_attribute[e.fid(*this)].tags;
1093 const auto& t1 = m_face_attribute[opp->fid(*this)].tags;
1094 CellTag diff;
1095 std::set_symmetric_difference(
1096 t0.begin(),
1097 t0.end(),
1098 t1.begin(),
1099 t1.end(),
1100 std::inserter(diff, diff.begin()));
1101 return tag_bits(diff);
1102 }
1103
1113 std::shared_ptr<SampleEnvelope> envelope_for_mask(uint64_t mask) const;
1114
1131 std::shared_ptr<SampleEnvelope> surface_envelope_for_edge(
1132 const std::array<size_t, 2>& vids) const override
1133 {
1134 // Boundary geometry first, in both phases: a segment on any tag-region boundary may not
1135 // drift out of that boundary's tube, and one on several boundaries -- a junction -- is
1136 // held in their intersection. The mask carries the input complex too, since every complex
1137 // simplex lies on tag boundaries, and E_t is built from the input mesh before construction
1138 // touches it, so the per-tag tubes hold the as-loaded geometry.
1139 //
1140 // Keyed on the vertices, not the edge: every caller is an operation asking about a segment
1141 // it is about to create or has just created, whose own edge attributes are not written
1142 // yet. The endpoints' masks are, maintained by the operations themselves (AND at a split,
1143 // OR at a collapse).
1144 uint64_t mask = edge_mask(vids);
1145 bool all_offset = true;
1146 for (const size_t v : vids) {
1147 all_offset = all_offset && m_vertex_extra[v].m_is_on_offset;
1148 }
1149
1150 // The ambiguous case: both endpoints can be on region boundaries and on the offset front
1151 // at once -- a real state, and the point of tracking the two families separately. The
1152 // endpoint-mask AND is then necessary but not sufficient for the segment lying on a shared
1153 // boundary: two vertices on different junctions can share a tag bit by coincidence and be
1154 // joined by an offset chord nowhere near that tag's curve, which a container it was never
1155 // meant to satisfy then refuses to refine. Ask the edge's own class, the only record that
1156 // distinguishes a chord from a boundary.
1157 //
1158 // Reading the slot is safe here and would not be unconditionally: the base checks a
1159 // split's two child segments before writing their attributes, so their slots still hold a
1160 // recycled edge's class. A split child never reaches this branch -- its mask and its
1161 // offset flag are mutually exclusive by construction, so one of `mask` and `all_offset` is
1162 // always empty -- and the m_is_surface_fs guard leaves both constraints standing rather
1163 // than dropping one when a slot is illegible.
1164 if (mask != 0 && all_offset) {
1165 if (const auto found = try_tuple_from_edge(vids)) {
1166 const size_t eid = std::get<1>(*found);
1167 if (m_edge_attribute[eid].m_is_surface_fs) {
1168 if (edge_is_offset(eid)) {
1169 mask = 0; // an offset edge lies on no region boundary
1170 } else {
1171 all_offset = false; // a region edge is not the offset front
1172 }
1173 }
1174 }
1175 }
1176 // Both families compose: whatever holds this segment holds it at once. Phase A holds the
1177 // offset where Phase B left it; Phase B is what moves it, so it contributes nothing there
1178 // -- and null before the offset exists at all, which is the pre-pass.
1179 const std::shared_ptr<SampleEnvelope> base = containment_for(mask, all_offset);
1180 if (base || m_deform_tags.empty()) return base;
1181 // deform_others' ops-only tube: a released boundary is held by no mask -- its vertices
1182 // were freed so smoothing can carry the object -- which would leave the operations free
1183 // to decimate and reposition it. A segment the masks and the offset class do not claim,
1184 // but which lies on a released boundary by its incident faces' current tags, is held to
1185 // the tube around the boundary's current shape. A fresh split child can misread its
1186 // recycled face slots for this one check, and is at worst skipped or over-held once.
1187 if (const auto found = try_tuple_from_edge(vids)) {
1188 if (edge_borders_released_boundary(std::get<0>(*found))) return released_envelope();
1189 }
1190 return nullptr;
1191 }
1192
1205 bool smoothing_position_is_allowed(const size_t, const Vector2d&) const override
1206 {
1207 return true;
1208 }
1209
1223 std::shared_ptr<SampleEnvelope> smoothing_energy_envelope(const size_t vid) const override
1224 {
1225 if (m_vertex_extra[vid].m_is_on_offset && !vertex_is_on_region(vid)) {
1226 return nullptr;
1227 }
1228 const uint64_t mask = vertex_boundary_mask(vid);
1229 if (mask == 0) {
1230 // Reachable only for a construction artefact (a wall-chord midpoint flagged
1231 // on-surface with disjoint endpoint masks); its containment is vacuous too, so no
1232 // pull is behavior-neutral. Not an error.
1233 return nullptr;
1234 }
1235 std::shared_ptr<SampleEnvelope> best;
1236 double worst_d2 = -1.;
1237 for (const auto& [tag, env] : m_tag_envelopes) {
1238 const auto it = m_tag_bit.find(tag);
1239 if (it == m_tag_bit.end() || !(mask & (uint64_t(1) << it->second))) continue;
1240 if (!best) {
1241 best = env;
1242 if ((mask & (mask - 1)) == 0) break; // single bit: no violation contest to run
1243 worst_d2 = env->squared_distance(m_vertex_attribute[vid].m_posf);
1244 continue;
1245 }
1246 const double d2 = env->squared_distance(m_vertex_attribute[vid].m_posf);
1247 if (d2 > worst_d2) {
1248 worst_d2 = d2;
1249 best = env;
1250 }
1251 }
1252 return best;
1253 }
1254
1266 std::shared_ptr<SampleEnvelope> smoothing_containment_envelope(const size_t vid) const override
1267 {
1268 // Both families, composed -- not a choice between them. A vertex where the offset front
1269 // meets one or more region boundaries is on all of them at once, and the three cases fall
1270 // out of one expression: pure-offset (mask 0) gives the offset tube in Phase A and null in
1271 // Phase B, pure-region gives its tubes' intersection in both, and a junction of the two
1272 // gives the intersection of everything.
1273 return containment_for(vertex_boundary_mask(vid), m_vertex_extra[vid].m_is_on_offset);
1274 }
1275
1284 bool smooth_front_vertex_phase_b(const Tuple& t);
1287 double front_vertex_normal_gradient(size_t vid) const;
1290 Vector2d front_vertex_move_direction(size_t vid) const;
1294 bool front_vertex_alignment_traps_1d_solve(size_t vid) const;
1297 double front_vertex_conv_ratio(size_t vid) const;
1299 double edge_conv_ratio(const Tuple& e) const;
1301 static_cast<size_t>(-1);
1303 Vector2d front_vertex_normal(size_t vid) const;
1306 std::shared_ptr<polysolve::nonlinear::Problem> phase_b_front_objective(
1307 size_t vid,
1308 const Vector2d& x) const;
1313 std::shared_ptr<polysolve::nonlinear::Problem> smoothing_extra_energy(
1314 const size_t vid) const override
1315 {
1316 std::shared_ptr<polysolve::nonlinear::Problem> front;
1317 if (phase_places_front() && m_offset_potential && m_vertex_extra[vid].m_is_on_offset &&
1318 vertex_boundary_mask(vid) == 0) {
1319 front = phase_b_front_energy(vid, potential_ptr_for(vid));
1320 }
1321 const std::shared_ptr<polysolve::nonlinear::Problem> rest = rest_energy_for_vertex(vid);
1322 if (!front) return rest;
1323 if (!rest) return front;
1324 auto sum = std::make_shared<optimization::EnergySum>();
1325 sum->add_energy(front);
1326 sum->add_energy(rest);
1327 return sum;
1328 }
1329
1330 // ------- deform_others: other input regions deform instead of being envelope-held -------
1331
1333 std::set<int64_t> m_deform_tags;
1336 std::set<int64_t> m_source_tags;
1343 mutable std::shared_ptr<SampleEnvelope> m_released_envelope;
1344 mutable std::atomic<bool> m_released_tube_dirty{false};
1345 mutable std::mutex m_released_mutex;
1348 std::shared_ptr<SampleEnvelope> released_envelope() const;
1351 bool edge_borders_released_boundary(const Tuple& e) const;
1353 bool face_is_deformable(size_t fid) const;
1359 bool m_plastic_active = false;
1360 bool face_is_plastic(size_t fid) const
1361 {
1362 // Everything outside the band: ambient, the other objects and the input complex's
1363 // interior alike -- one material. The complex's boundary is what its tube holds.
1364 return m_plastic_active && m_face_extra[fid].label != 2;
1365 }
1367 void stamp_plastic_rests();
1370 bool smooth_plastic_vertex(const Tuple& t);
1374 bool face_is_released_band(size_t fid) const;
1377 void stamp_rest_face(size_t fid);
1384 std::shared_ptr<polysolve::nonlinear::Problem> rest_energy_for_vertex(size_t vid) const;
1389 std::shared_ptr<polysolve::nonlinear::Problem> phase_b_front_energy(
1390 size_t vid,
1391 const std::shared_ptr<const OffsetPotential2D>& pot) const;
1392
1405 std::tuple<double, double> optimization_quality_stats() override;
1406
1418 double optimization_stop_metric() const override
1419 {
1420 // Single is TriWild's loop, so its stop metric is TriWild's.
1421 return m_phase != OptPhase::B ? wmtk::TriOptimizerMesh::optimization_stop_metric() : 1.;
1422 }
1423
1426 int offset_residual_samples() const { return m_offset_params.offset_residual_samples; }
1427
1435 {
1436 // Derived from the tolerance, not from the knob. grad E . n = 2 (Phi - c) (grad Phi . n),
1437 // so with |grad Phi| = s at the level set a bound g on the normal gradient is the length
1438 // bound |Phi - c| <= g / (2 s^2). The tolerance is rel x a measured maximum, so this must
1439 // follow it rather than reading the knob.
1440 const double s = m_offset_potential ? m_offset_potential->level_set_slope() : 1.;
1441 return std::max(0.5 * offset_gradient_tolerance() / (s * s), 1e-16);
1442 }
1443
1457 {
1458 // A fraction of a measured maximum, not of an analytic estimate. m_gradient_reference is
1459 // max |2 (Phi - c) grad Phi . n| over the offset-surface vertices as constructed, taken
1460 // once before the A/B loop starts; everything this bar serves -- the convergence criterion
1461 // and every Phase B local stop -- compares the full gradient norm at those same vertices
1462 // against it, so both sides come from one measurement pass and no per-field calibration.
1463 //
1464 // Never measured on the single-phase path, so this sits at the 1e-16 floor there; the
1465 // single-phase convergence bar uses m_front_gradient_reference instead.
1466 return std::max(m_offset_params.front_conv_rel * m_gradient_reference, 1e-16);
1467 }
1468
1472 double gradient_reference() const { return m_gradient_reference; }
1473
1484 void check_offset_within_support(const char* when) const;
1485
1499 {
1500 double max_reachable = 0., avg_reachable = 0.;
1501 double max_pinned = 0.;
1502 size_t n_reachable = 0, n_pinned = 0;
1507 double max_at_vertex = 0., max_in_edge = 0.;
1512 size_t worst_outside_vid = static_cast<size_t>(-1);
1513 double worst_outside_dist = 0.;
1514 };
1515 DistanceSplit distance_deviation_split() const;
1516
1522 std::vector<bool> band_vertex_mask() const;
1523
1526 double max_band_vertex_distance() const;
1529 double band_vertex_distance_error(const size_t vid) const;
1530
1533 double band_vertex_residual(const size_t vid) const;
1534
1537 {
1538 double max = 0.;
1539 double sum = 0.;
1540 size_t n = 0;
1541 };
1542
1560 EdgeSamples offset_edge_samples(const Tuple& e) const;
1561
1569 template <typename Visit>
1570 void for_each_offset_edge_sample(const Tuple& e, Visit&& visit) const
1571 {
1572 const int k = m_offset_params.offset_residual_samples;
1573 if (k <= 0) return;
1574 const Vector2d p0 = m_vertex_attribute[e.vid(*this)].m_posf;
1575 const Vector2d p1 = m_vertex_attribute[e.switch_vertex(*this).vid(*this)].m_posf;
1576 for (int i = 1; i <= k; ++i) {
1577 const double t = double(i) / double(k + 1);
1578 visit(Vector2d((1. - t) * p0 + t * p1));
1579 }
1580 }
1581
1600 {
1601 double max_reachable = 0., avg_reachable = 0.;
1602 double max_pinned = 0.;
1603 size_t n_reachable = 0, n_pinned = 0;
1608 double max_at_vertex = 0., max_in_edge = 0.;
1615 size_t n_edge_samples = 0;
1618 size_t n_skipped_inverted = 0, n_skipped_unrounded = 0;
1621 size_t worst_vid = static_cast<size_t>(-1);
1622 };
1625 GradientSplit gradient_split(bool include_edge_samples = true) const;
1626
1647 {
1648 double
1649 max_vertex = 0.,
1651 0.;
1652 double bar = 1.;
1653 size_t n_vertices = 0, n_edges = 0, n_unmeasurable = 0;
1654 size_t n_pressed = 0, n_edges_pressed = 0;
1655 size_t worst_vid = static_cast<size_t>(-1);
1656 Vector2d worst_edge_mid = Vector2d::Zero();
1657 double worst_edge_len = 0.;
1663 size_t n_edges_over = 0, n_edges_over_on_level = 0;
1664 double max_edge_on_level = 0.;
1665 Vector2d worst_on_level_mid = Vector2d::Zero();
1683 double tube = 0.;
1684 size_t n_placed = 0, n_travelling = 0, n_pressed_on = 0, n_stuck = 0;
1686 size_t n_at_floor = 0;
1687 size_t worst_stuck_vid = static_cast<size_t>(-1);
1688 double worst_stuck_rho = 0.;
1690 {
1691 size_t a, b;
1692 double sag, len;
1693 };
1694 std::vector<Refinable> refinable;
1695 bool vertices_ok() const { return max_vertex <= bar; }
1696 bool edges_ok() const { return max_edge <= bar; }
1698 bool converged() const { return vertices_ok() && n_unmeasurable == 0; }
1704 bool converged_single() const { return converged() && refinable.empty(); }
1705 double ratio() const { return bar > 0. ? std::max(max_vertex, max_edge) / bar : 0.; }
1706 };
1707 EnergyCriterion energy_criterion();
1710 bool front_vertex_touches_other(size_t vid) const;
1715 double front_chord_target(size_t va, size_t vb, double len, double sag, double tube) const;
1716
1719 size_t refine_front_from_sag(const std::vector<EnergyCriterion::Refinable>& edges);
1722 std::optional<EnergyCriterion> m_energy_verdict;
1724 double edge_interpolation_residual(const Tuple& e) const;
1725
1738 Vector2d offset_vertex_normal(const size_t vid) const;
1739
1740
1743 void report_outside_support(const char* when, const DistanceSplit& s) const;
1751 bool band_vertex_is_reachable(const size_t vid) const
1752 {
1753 // An envelope-held offset vertex is pinned: it must stay within envelope_size of the input
1754 // region boundary AND sit on Phi = c, target_distance away. Those are not simultaneously
1755 // satisfiable, so its residual gradient is a statement about the constraint rather than
1756 // about the offset's quality, and leaving it in max_reachable makes convergence impossible
1757 // by construction. Structural, not left to whether the tolerance happens to hide it.
1758 //
1759 // Still reported, never silent: the pinned half of every band measure is logged beside the
1760 // reachable half, the same contract vertex_is_on_domain_boundary() gets below. This is a
1761 // retreat, to be reverted the moment a placement that works exists.
1762 if (m_vertex_extra[vid].m_is_on_offset && vertex_boundary_mask(vid) != 0) return false;
1763
1764 return !vertex_is_on_domain_boundary(vid);
1765 }
1766
1771 std::vector<char> m_placement_pressed;
1772
1784 size_t refine_sizing_around_worst(double max_metric) override;
1785
1807 void log_stuck_refine_census(double max_metric, double filter_energy);
1808
1842 void log_refine_block_census(const std::string& when, double filter_energy) const;
1843
1855 bool collapse_quality_allowed(size_t v1, size_t v2, double q, double ring_max) const override;
1856
1857 mutable std::atomic<size_t> m_deg_split_created{0};
1858 mutable std::atomic<size_t> m_deg_collapse_offered{0};
1859 mutable std::atomic<size_t> m_deg_collapse_allowed{0};
1860 mutable std::atomic<size_t> m_deg_collapse_by_ringmax{0};
1861 mutable std::atomic<size_t> m_deg_collapse_by_stop{0};
1862 mutable std::atomic<size_t> m_deg_collapse_by_unrounded{0};
1864 std::array<size_t, 6> m_deg_prev_counts{{0, 0, 0, 0, 0, 0}};
1865
1878 void report_needle(const char* op, size_t fid, double parent_q) const;
1879 static constexpr size_t kNeedleReports = 12;
1888 static constexpr double kNeedleQuality = 1e6;
1889 mutable std::atomic<size_t> m_needle_reports{0};
1890
1893 void needle_scan(const char* when) const;
1896 void collapse_pass_begin() override;
1897
1900 std::set<std::pair<long, long>> m_stuck_prev_cells;
1901 size_t m_stuck_calls = 0;
1902
1910 bool optimization_bare_coarsen_passes() const override { return false; }
1911
1921 bool collapse_edge_after(const Tuple& t) override;
1925 double face_criterion_rel(const size_t fid) const;
1944 void write_smoothing_debug_output(const std::string& path) const override
1945 {
1946 const char ph = (m_phase == OptPhase::A) ? 'A' : (m_phase == OptPhase::B ? 'B' : 'S');
1947 if (m_ab_round != m_debug_last_round || ph != m_debug_last_phase) {
1948 m_debug_last_round = m_ab_round;
1949 m_debug_last_phase = ph;
1950 m_debug_pass = 0;
1951 }
1952 // What this frame is, as a compact token: round, phase, pass within the phase, and the
1953 // operation it follows (from m_debug_pass_name -- the shared driver writes several frames
1954 // per operation group). Phase B and the single phase write one per sweep.
1955 std::string label = path;
1956 if (path.rfind("debug_", 0) == 0) {
1957 label = fmt::format(
1958 "r{}{}{}{}",
1959 m_ab_round,
1960 ph,
1961 ++m_debug_pass,
1962 m_debug_pass_name.empty() ? std::string() : "_" + m_debug_pass_name);
1963 } else if (path.rfind("phase_", 0) == 0) {
1964 label = fmt::format("r{}{}_end", m_ab_round, ph);
1965 }
1966 // The file name is the sequence number and nothing else, so ParaView groups the frames into
1967 // one time series: it needs the digits last, right before the extension, and every frame
1968 // of a series to share its prefix. The label goes to <output>_frames.txt, one
1969 // "NNNNN<tab>label" line per frame, which is what the polyscope viewer reads for its
1970 // slider.
1971 const size_t idx = m_debug_seq++;
1972 append_frame_label(idx, label);
1973 const_cast<TopoOffsetTriMesh*>(this)->write_vtu(
1974 m_offset_params.output_path + fmt::format("_{:05d}", idx));
1975 }
1976
1979 void append_frame_label(size_t idx, const std::string& label) const;
1980
1989 void init_from_image(
1990 const MatrixXd& V,
1991 const MatrixXi& F,
1992 const MatrixSi& F_tags,
1993 const MatrixXd& V_env,
1994 const MatrixXi& F_env,
1995 const std::vector<std::string>& tag_names,
1996 const std::string& curve_name = "");
1997
2001 bool ambient_assert();
2002
2006 void label_input_complex();
2007
2012 bool empty_input_complex();
2013
2023
2031 void init_offset_potential();
2032
2035 MatrixXd m_phi_V;
2036 MatrixXi m_phi_E;
2037 MatrixXi m_phi_F;
2038 std::vector<int> m_phi_P;
2039
2041 bool split_edge_before(const Tuple& t) override;
2042 bool split_edge_after(const Tuple& t) override;
2043 bool split_face_before(const Tuple& t) override;
2044 bool split_face_after(const Tuple& t) override;
2045 bool invariants(const std::vector<Tuple>& tris) override;
2047
2068
2069 void execute_offset(const std::filesystem::path& output_file);
2070
2079 void marching_tris();
2080
2081
2083
2087 bool is_simplicially_embedded() const;
2088
2093 bool tri_is_simp_emb(const Tuple& t) const;
2094
2098 void simplicial_embedding();
2100
2105 void set_offset_tri_tags();
2106
2111 bool offset_is_manifold();
2112
2114
2125 void write_phi_grid(const std::string& path, int n) const;
2126
2127 void write_input_complex(const std::string& path);
2128 void write_vtu(const std::string& path);
2129 // void write_msh(const std::string& file);
2130 void write_msh_groups(const std::string& file);
2132
2133private:
2140 {
2141 size_t v1_id;
2142 size_t v2_id;
2143 Vector2d new_v_pos;
2144 VertexExtra2d new_v_extra;
2145
2146 // cache edge attributes
2147 EdgeSnapshot2d split_eattr;
2148 std::map<simplex::Edge, EdgeSnapshot2d> existing_eattr;
2149
2150 // cache face attributes
2151 std::map<size_t, FaceSnapshot2d> opp_v_fattr;
2152 };
2154
2156 {
2157 size_t v1_id;
2158 size_t v2_id;
2159 size_t v3_id;
2160 Vector2d new_v_pos;
2161 VertexExtra2d new_v_extra;
2162
2163 std::map<simplex::Edge, EdgeSnapshot2d> existing_eattr; // 3 orig edges
2164 FaceSnapshot2d split_fattr; // split face attributes
2165 };
2167
2168private: // helpers
2172 bool any_tag_present(const CellTag& tag1, const CellTag& tag2) const
2173 {
2174 for (const int64_t& i : tag1) {
2175 if (tag2.find(i) != tag2.end()) {
2176 return true;
2177 }
2178 }
2179 return false;
2180 }
2181
2185 void sort_edges_by_length(std::vector<simplex::Edge>& edges)
2186 {
2187 std::sort(
2188 edges.begin(),
2189 edges.end(),
2190 [this](const simplex::Edge& e1, const simplex::Edge& e2) {
2191 double len1 = (m_vertex_attribute[e1.vertices()[0]].m_posf -
2192 m_vertex_attribute[e1.vertices()[1]].m_posf)
2193 .squaredNorm();
2194 double len2 = (m_vertex_attribute[e2.vertices()[0]].m_posf -
2195 m_vertex_attribute[e2.vertices()[1]].m_posf)
2196 .squaredNorm();
2197 return len1 > len2;
2198 });
2199 }
2200
2201public: // helpers
2206 {
2207 const auto& verts = e.vertices();
2208 const auto incident = simplex_incident_triangles(e);
2209 const auto& faces = incident.faces();
2210
2211 assert(!faces.empty()); // throw error here otherwise
2212
2213 const size_t f_id = tuple_from_simplex(faces.front()).fid(*this);
2214 const Tuple t_edge = tuple_from_edge(verts[0], verts[1], f_id);
2215 return t_edge.eid(*this);
2216 }
2217
2222 {
2223 const auto& v = e.vertices();
2224 const auto faces = simplex_incident_triangles(e).faces();
2225 assert(!faces.empty());
2226 const size_t fid = tuple_from_simplex(faces.front()).fid(*this);
2227 return tuple_from_edge(v[0], v[1], fid);
2228 }
2229
2233 std::vector<Tuple> get_edge_adjacent_faces(const Tuple& f) const
2234 {
2235 std::vector<Tuple> adj_tris;
2236 auto tri_1 = f.switch_face(*this);
2237 if (tri_1) {
2238 adj_tris.push_back(tri_1.value());
2239 }
2240 auto tri_2 = f.switch_edge(*this).switch_face(*this);
2241 if (tri_2) {
2242 adj_tris.push_back(tri_2.value());
2243 }
2244 auto tri_3 = f.switch_vertex(*this).switch_edge(*this).switch_face(*this);
2245 if (tri_3) {
2246 adj_tris.push_back(tri_3.value());
2247 }
2248 return adj_tris;
2249 }
2250};
2251
2252
2253} // namespace wmtk::components::topological_offset
Whether a codimension-1 simplex is tracked surface, and which bbox side it lies on.
Definition SurfaceTagAttributes.h:15
Definition TriMesh.h:32
size_t fid(const TriMesh &) const
Definition TriMesh.h:80
Tuple switch_vertex(const TriMesh &m) const
Definition TriMesh.cpp:158
Tuple switch_edge(const TriMesh &m) const
Definition TriMesh.cpp:187
std::optional< Tuple > switch_face(const TriMesh &m) const
Definition TriMesh.cpp:214
std::optional< std::tuple< Tuple, size_t > > try_tuple_from_edge(const std::array< size_t, 2 > &vids) const
tuple_from_edge for callers where a missing edge is an answer, not a bug.
Definition TriMesh.cpp:2042
Tuple tuple_from_edge(size_t vid1, size_t vid2, size_t fid) const
Definition TriMesh.cpp:2202
What triwild and simwild's 2D mesh share.
Definition TriOptimizerMesh.h:39
AttributeContainerGroup m_edge_attr_group
What p_edge_attrs points at, so a derived class can register more.
Definition TriOptimizerMesh.h:111
AttributeContainerGroup m_vertex_attr_group
What p_vertex_attrs points at, so a derived class can register more.
Definition TriOptimizerMesh.h:96
AttributeContainerGroup m_face_attr_group
What p_face_attrs points at, so a derived class can register more.
Definition TriOptimizerMesh.h:122
std::string m_debug_pass_name
Definition TriOptimizerMesh.h:158
Definition TopoOffsetTriMesh.h:71
bool on_curve
Definition TopoOffsetTriMesh.h:77
Definition TopoOffsetTriMesh.h:84
bool rest_valid
Definition TopoOffsetTriMesh.h:94
The offset potential: the scalar field on space whose level set the front is placed on.
Definition OffsetPotential.hpp:37
The offset's 2D mesh, on the shared 2D optimizer.
Definition TopoOffsetTriMesh.h:112
void rebuild_offset_envelope()
Definition Optimize2d.cpp:3661
std::pair< double, double > compute_distance_deviation() const
How far the offset boundary is from where it should be: {max, avg} over vertices.
Definition Optimize2d.cpp:3213
void stamp_plastic_rests()
Stamp rest := current for every plastic face; called before every operation group.
Definition Optimize2d.cpp:348
void stamp_rest_face(size_t fid)
Definition Optimize2d.cpp:339
int m_n_regions
One field per connected piece of the input complex, and which one each band vertex is placed on.
Definition TopoOffsetTriMesh.h:199
int64_t tangent_curve_tag(size_t vid, const Vector2d &x) const
Which tag's boundary curve a vertex slides along, or -1.
Definition Optimize2d.cpp:495
int m_debug_pass
Definition TopoOffsetTriMesh.h:700
void label_input_complex()
label input complex simplices as per boolean expression (or single body mode)
Definition TopoOffsetTriMesh.cpp:406
double edge_interpolation_residual(const Tuple &e) const
The interpolation residual of front edge e, see EnergyCriterion. -1 when unmeasurable.
Definition Optimize2d.cpp:2704
void set_vertex_position(const size_t vid, const Vector2d &p)
Place a vertex, keeping its exact and rounded coordinates in step.
Definition TopoOffsetTriMesh.h:552
std::string envelope_key_name(int64_t tag) const
The name a tag or pseudo-tag prints under.
Definition TopoOffsetTriMesh.cpp:210
Parameters & m_offset_params
The base holds only wmtk::OptimizerParameters; this is the same object, typed.
Definition TopoOffsetTriMesh.h:519
double band_vertex_distance_error(const size_t vid) const
Definition Optimize2d.cpp:2497
std::vector< char > m_placement_pressed
Definition TopoOffsetTriMesh.h:1771
void write_smoothing_debug_output(const std::string &path) const override
Put the frames beside the run's own output, and rename them into one timeline.
Definition TopoOffsetTriMesh.h:1944
std::atomic< int > m_placement_tangential
Definition TopoOffsetTriMesh.h:1024
void log_front_profile(size_t vid)
Diagnostic: the front objective of one vertex along its normal, offset term vs total.
Definition Optimize2d.cpp:3044
void audit_surface_containment(const std::string &when) const
Which tracked edges are outside their envelope, and by how much.
Definition Optimize2d.cpp:1067
double optimization_stop_metric() const override
1.0 in Phase B, where the metric is normalized; the base's stop_energy in Phase A.
Definition TopoOffsetTriMesh.h:1418
void needle_scan(const char *when) const
Definition Optimize2d.cpp:2206
void log_stuck_refine_census(double max_metric, double filter_energy)
Why Phase A is stuck: a census of the faces stuck-refine is about to chase.
Definition Optimize2d.cpp:1924
std::shared_ptr< polysolve::nonlinear::Problem > smoothing_extra_energy(const size_t vid) const override
Definition TopoOffsetTriMesh.h:1313
bool split_face_before(const Tuple &t) override
User specified preparations and desideratas for a face split.
Definition EdgeSplittingTri.cpp:196
std::set< int64_t > m_source_tags
Definition TopoOffsetTriMesh.h:1336
Vector2d front_vertex_move_direction(size_t vid) const
Definition FrontSmooth2d.cpp:173
int offset_residual_samples() const
Definition TopoOffsetTriMesh.h:1426
void set_offset_tri_tags()
update 'tags' data for triangles in the offset region (tris labelled 2) based on the given offset tag...
Definition TopoOffsetTriMesh.cpp:1201
std::map< int64_t, int > m_tag_bit
Definition TopoOffsetTriMesh.h:291
bool front_vertex_alignment_traps_1d_solve(size_t vid) const
Definition FrontSmooth2d.cpp:212
bool vertex_is_on_surface(const size_t vid) const override
The substructure the link condition is evaluated against, derived not cached.
Definition Optimize2d.cpp:50
std::vector< int64_t > m_phi_point_region
per m_phi_P entry: region index, -1 unknown
Definition TopoOffsetTriMesh.h:204
std::atomic< int > m_placement_env_entry_outside
Definition TopoOffsetTriMesh.h:1012
bool band_vertex_is_reachable(const size_t vid) const
Definition TopoOffsetTriMesh.h:1751
bool collapse_quality_allowed(size_t v1, size_t v2, double q, double ring_max) const override
Instrumentation only: which operation manufactures the MAX_ENERGY needles.
Definition Optimize2d.cpp:2124
size_t refine_front_from_sag(const std::vector< EnergyCriterion::Refinable > &edges)
Definition Optimize2d.cpp:3150
bool offset_is_manifold()
verify that the closed offset region (simplices labelled 1 or 2) form a manifold region....
Definition TopoOffsetTriMesh.cpp:1236
void report_needle(const char *op, size_t fid, double parent_q) const
Where the first needles come from – a tripwire, not a census.
Definition Optimize2d.cpp:2153
void optimize_offset(const std::filesystem::path &output_file)
The 2D optimization phase: split / collapse / swap / smooth on the shared driver.
Definition Optimize2d.cpp:3922
void report_outside_support(const char *when, const DistanceSplit &s) const
Definition Optimize2d.cpp:3192
std::shared_ptr< SampleEnvelope > m_released_envelope
Definition TopoOffsetTriMesh.h:1343
double offset_residual_tolerance() const
Definition TopoOffsetTriMesh.h:1434
GradientSplit gradient_split(bool include_edge_samples=true) const
Definition Optimize2d.cpp:2589
std::shared_ptr< SampleEnvelope > released_envelope() const
Definition Optimize2d.cpp:3607
double m_gradient_reference
See offset_gradient_tolerance(). Nothing sets it on the single-phase path; it stays 0.
Definition TopoOffsetTriMesh.h:704
std::vector< int64_t > m_phi_vert_region
per m_phi_V row: region index
Definition TopoOffsetTriMesh.h:201
size_t m_worst_dist_vid
Definition TopoOffsetTriMesh.h:667
std::atomic< int > iter_cnt_split_offset_before
Splits of an offset-boundary edge: offered, accepted.
Definition TopoOffsetTriMesh.h:725
std::array< size_t, 6 > m_deg_prev_counts
Values at the previous census, so each census can report deltas rather than totals.
Definition TopoOffsetTriMesh.h:1864
size_t m_debug_seq
Monotonic frame counter for the debug timeline. Mutable because the write hook is const.
Definition TopoOffsetTriMesh.h:697
EdgeSamples offset_edge_samples(const Tuple &e) const
The Phi residual at offset_residual_samples interior points of band edge e.
Definition Optimize2d.cpp:2513
bool empty_input_complex()
check if the input complex is empty. Only valid after calling init_from_image(...)....
Definition TopoOffsetTriMesh.cpp:588
void build_boundary_envelopes(const char *when, EnvelopeSetup setup)
Definition TopoOffsetTriMesh.cpp:230
bool smooth_plastic_vertex(const Tuple &t)
Definition Optimize2d.cpp:364
OptPhase m_phase
Which phase is running. Read by every hook that differs between them; see OptPhase.
Definition TopoOffsetTriMesh.h:410
std::shared_ptr< SimplicialComplexBVH > m_input_complex_bvh
The input complex as loaded. Built once, never rebuilt.
Definition TopoOffsetTriMesh.h:157
void init_input_complex_bvh()
Build the input complex's BVH and its smooth offset potential, from one extraction.
Definition TopoOffsetTriMesh.cpp:601
bool face_is_deformable(size_t fid) const
Under deform_others the same set as face_is_plastic(): every face outside the band.
Definition Optimize2d.cpp:313
double front_chord_target(size_t va, size_t vb, double len, double sag, double tube) const
Definition Optimize2d.cpp:3109
bool phase_places_front() const
Definition TopoOffsetTriMesh.h:407
size_t edge_id_from_simplex(const simplex::Edge &e) const
get global id of edge from simplex::Edge object
Definition TopoOffsetTriMesh.h:2205
std::atomic< int > iter_cnt_collapse_offset_reject
Operations refused because they would have left an offset-boundary face over tolerance.
Definition TopoOffsetTriMesh.h:722
void log_refine_block_census(const std::string &when, double filter_energy) const
For every element above filter_energy, why its edges cannot be split.
Definition Optimize2d.cpp:1747
std::map< uint64_t, std::shared_ptr< SampleEnvelope > > m_offset_isect_cache
Memoized "region tubes AND the offset envelope", keyed by the region mask.
Definition TopoOffsetTriMesh.h:309
bool face_is_offset_band(const size_t fid) const
Definition Optimize2d.cpp:2454
Tuple get_tuple_from_edge(const simplex::Edge &e) const
get Tuple simplex::Edge object
Definition TopoOffsetTriMesh.h:2221
std::vector< int > m_vertex_region
per vertex: region of its band faces, -1 / -2 as above
Definition TopoOffsetTriMesh.h:206
Vector2d offset_vertex_normal(const size_t vid) const
The normal at an offset vertex. Every caller that needs one goes through here, so switching the defin...
Definition Optimize2d.cpp:1034
void check_no_vertex_on_both_surfaces(const char *when) const
Definition Optimize2d.cpp:3528
void marching_tris()
execute simplistic marching tris. All edges with one vertex labelled 0 and the other 1/2 are split,...
Definition TopoOffsetTriMesh.cpp:1136
uint64_t tag_bits(const CellTag &tags) const
Definition TopoOffsetTriMesh.h:1042
double gradient_reference() const
Definition TopoOffsetTriMesh.h:1472
void classify_curve_edges()
Mark the mesh edges that lie on the input's curve group (EdgeExtra2d::on_curve).
Definition TopoOffsetTriMesh.cpp:370
double face_flatness(size_t fid) const
Scale-invariant flatness: 2*area / longest_edge^2.
Definition Optimize2d.cpp:2265
bool face_in_region(const size_t fid) const
Whether face fid belongs to the closed offset region, read from its label.
Definition Optimize2d.cpp:60
void sort_edges_by_length(std::vector< simplex::Edge > &edges)
sort vector of edge simplices in place by decreasing length
Definition TopoOffsetTriMesh.h:2185
bool swap_edge_after(const Tuple &t) override
Definition Optimize2d.cpp:652
bool m_freeze_front
The final Phase A: front vertices are not smoothed (see smooth_before()).
Definition TopoOffsetTriMesh.h:417
uint64_t vertex_boundary_mask(const size_t vid) const
The tag boundaries this vertex lies on – the raw mask gated on the vertex still being region geometry...
Definition TopoOffsetTriMesh.h:1062
double front_vertex_normal_gradient(size_t vid) const
Definition Optimize2d.cpp:1050
double max_band_vertex_distance() const
Definition TopoOffsetTriMesh.cpp:937
MatrixXi m_phi_F
the complex faces, in the same vertex index space (for per-region BVHs)
Definition TopoOffsetTriMesh.h:2037
DistanceSplit residual_split() const
Definition Optimize2d.cpp:2534
std::vector< bool > band_vertex_mask() const
Definition Optimize2d.cpp:2460
void for_each_offset_edge_sample(const Tuple &e, Visit &&visit) const
Visit the same interior sample points offset_edge_samples() measures on.
Definition TopoOffsetTriMesh.h:1570
std::set< int64_t > m_deform_tags
The released tags. Filled by release_deformable_regions(); empty = feature inactive.
Definition TopoOffsetTriMesh.h:1333
std::shared_ptr< SampleEnvelope > envelope_for_mask(uint64_t mask) const
The envelope a simplex with this boundary mask is contained in, or null.
Definition Optimize2d.cpp:197
bool m_converged
Definition TopoOffsetTriMesh.h:707
void init_offset_sizing_field()
Definition Optimize2d.cpp:1624
std::vector< std::array< int, 3 > > churn_counts
Definition TopoOffsetTriMesh.h:687
void warn_if_offset_reaches_domain_boundary() const
Warn if the offset band has grown into the domain boundary.
Definition Optimize2d.cpp:164
double edge_conv_ratio(const Tuple &e) const
The edge test divided by its bar (1 = bar), per front_conv_criterion; -1 unmeasurable.
Definition Optimize2d.cpp:2909
double offset_gradient_tolerance() const
The convergence tolerance: the bound on |grad (Phi - c)^2| at a band vertex.
Definition TopoOffsetTriMesh.h:1456
bool edge_is_offset_surface_live(const Tuple &e) const
Definition Optimize2d.cpp:3514
bool split_edge_after(const Tuple &t) override
User specified modifications and desideratas after an edge split.
Definition EdgeSplittingTri.cpp:120
uint64_t edge_boundary_bits(const Tuple &e) const
Diagnostic only: which tag boundaries the incident faces say this edge lies on right now – the same s...
Definition TopoOffsetTriMesh.h:1086
bool vertex_is_on_domain_boundary(const size_t vid) const
Identification only – no operation refuses the domain wall through these.
Definition TopoOffsetTriMesh.h:817
std::optional< EnergyCriterion > m_energy_verdict
Definition TopoOffsetTriMesh.h:1722
bool invariants(const std::vector< Tuple > &tris) override
User specified invariants that can't be violated.
Definition TopoOffsetTriMesh.cpp:1286
double face_criterion_rel(const size_t fid) const
Definition Optimize2d.cpp:3291
double phase_b_front_gradient_linf()
Definition Optimize2d.cpp:2856
std::shared_ptr< const OffsetPotential2D > potential_ptr_for(const size_t vid) const
Definition TopoOffsetTriMesh.h:232
void optimize_offset_single_phase()
TriWild's loop, the front placed inside its smoothing passes.
Definition Optimize2d.cpp:3731
std::vector< int > m_face_region
per face: band's region, -1 none, -2 reached from two
Definition TopoOffsetTriMesh.h:205
std::set< std::pair< long, long > > m_stuck_prev_cells
Definition TopoOffsetTriMesh.h:1900
void init_from_image(const MatrixXd &V, const MatrixXi &F, const MatrixSi &F_tags, const MatrixXd &V_env, const MatrixXi &F_env, const std::vector< std::string > &tag_names, const std::string &curve_name="")
initialize TriMesh from vertex, face, tag data
Definition TopoOffsetTriMesh.cpp:13
void check_offset_within_support(const char *when) const
Stop the run if any reachable band vertex has left the potential's support.
Definition Optimize2d.cpp:3187
bool project_into_containment(size_t vid, Vector2d &x) const
Move x back inside every region tube this vertex lies on. True if it ended up inside all of them.
Definition Optimize2d.cpp:266
std::shared_ptr< OffsetPotential2D > m_offset_potential
The smooth offset potential, and with it the definition of the offset itself.
Definition TopoOffsetTriMesh.h:168
MatrixXd m_curve_V
The curve group as loaded, kept because the classification below is redone on demand.
Definition TopoOffsetTriMesh.h:128
std::vector< int64_t > m_phi_seg_region
per m_phi_E row: region index, -1 unknown
Definition TopoOffsetTriMesh.h:202
int64_t m_curve_tag
Definition TopoOffsetTriMesh.h:126
static constexpr int INPUT_SURFACE_CLASS
SurfaceTagAttributes::m_surface_class: which of the two tracked surfaces an edge belongs to....
Definition TopoOffsetTriMesh.h:515
bool smooth_before(const Tuple &t) override
User specified preparations and desideratas for an edge smooth.
Definition Optimize2d.cpp:905
MatrixXd m_phi_V
Definition TopoOffsetTriMesh.h:2035
std::shared_ptr< SampleEnvelope > surface_envelope_for_edge(const std::array< size_t, 2 > &vids) const override
Class-0 segments – every region boundary, the input complex and the domain wall included – carry a co...
Definition TopoOffsetTriMesh.h:1131
uint64_t edge_mask(const std::array< size_t, 2 > &vids) const
Definition TopoOffsetTriMesh.h:1069
bool smoothing_position_is_allowed(const size_t, const Vector2d &) const override
No per-vertex positional constraint. The per-tag envelopes close that hole structurally – the same de...
Definition TopoOffsetTriMesh.h:1205
static constexpr int64_t m_wall_tag
pseudo-tag: the domain wall's tube
Definition TopoOffsetTriMesh.h:442
bool split_edge_before(const Tuple &t) override
User specified preparations and desideratas for an edge split.
Definition EdgeSplittingTri.cpp:9
bool split_face_after(const Tuple &t) override
User specified modifications and desideratas after a face split.
Definition EdgeSplittingTri.cpp:230
bool face_is_released_band(size_t fid) const
Definition Optimize2d.cpp:320
void record_flatness(const char *op, double parent_flat, size_t child_fid) const
Genesis: flatness transitions recorded at the operation hooks. {op, parent, child}.
Definition Optimize2d.cpp:2277
void mark_input_complex_vertices()
Definition TopoOffsetTriMesh.cpp:339
std::shared_ptr< polysolve::nonlinear::Problem > phase_b_front_objective(size_t vid, const Vector2d &x) const
Definition FrontSmooth2d.cpp:284
std::atomic< size_t > m_needle_smooth_reports
Worst-case record: the best (lowest) ring max any needle-adjacent smooth achieved.
Definition TopoOffsetTriMesh.h:938
double m_front_gradient_reference
Definition TopoOffsetTriMesh.h:486
std::shared_ptr< SampleEnvelope > containment_for(uint64_t region_mask, bool on_offset) const
The containment a simplex with this region mask, on/off the offset front, must satisfy – the intersec...
Definition Optimize2d.cpp:237
std::vector< std::shared_ptr< OffsetPotential2D > > m_region_potentials
one per piece
Definition TopoOffsetTriMesh.h:200
std::atomic< int > iter_cnt_split_born
Definition TopoOffsetTriMesh.h:716
bool vertex_is_on_region(const size_t vid) const
Is this vertex on a region boundary – a tag boundary, or the domain wall.
Definition TopoOffsetTriMesh.h:1035
wmtk::threading::enumerable_thread_specific< double > m_collapse_parent_flatness
The flattest face in the collapse's ring before it ran, for record_flatness().
Definition TopoOffsetTriMesh.h:973
double front_vertex_conv_ratio(size_t vid) const
Definition Optimize2d.cpp:2879
bool edge_is_on_surface(const std::array< size_t, 2 > &vids) const override
Is an edge part of the substructure.
Definition Optimize2d.cpp:33
bool any_tag_present(const CellTag &tag1, const CellTag &tag2) const
determine if any tag from tag1 is also present in tag2.
Definition TopoOffsetTriMesh.h:2172
Vector2d front_vertex_normal(size_t vid) const
The field's outward unit direction at front vertex vid (zero where grad Phi vanishes).
Definition FrontSmooth2d.cpp:205
size_t refine_sizing_around_worst(double max_metric) override
TriWild's stall-driven sizing refinement, verbatim.
Definition Optimize2d.cpp:3313
std::vector< int64_t > m_phi_face_region
per m_phi_F row: region index, -1 unknown
Definition TopoOffsetTriMesh.h:203
static constexpr double kNeedleQuality
What counts as a needle for the tripwire – deliberately far below MAX_ENERGY.
Definition TopoOffsetTriMesh.h:1888
std::shared_ptr< polysolve::nonlinear::Problem > phase_b_front_energy(size_t vid, const std::shared_ptr< const OffsetPotential2D > &pot) const
Definition FrontSmooth2d.cpp:332
std::map< int64_t, std::shared_ptr< SampleEnvelope > > m_tag_envelopes
One containment envelope per input tag, ambient included. Both phases.
Definition TopoOffsetTriMesh.h:263
bool front_vertex_touches_other(size_t vid) const
Definition Optimize2d.cpp:3084
std::map< uint64_t, std::shared_ptr< SampleEnvelope > > m_isect_cache
Definition TopoOffsetTriMesh.h:295
bool optimization_bare_coarsen_passes() const override
Definition TopoOffsetTriMesh.h:1910
void label_offset_boundary()
Tag the two tracked surfaces for the optimization phase.
Definition Optimize2d.cpp:70
bool edge_borders_released_boundary(const Tuple &e) const
Definition Optimize2d.cpp:3583
bool edge_is_region(const size_t eid) const
Definition TopoOffsetTriMesh.h:569
bool curve_tangent(int64_t tag, const Vector2d &x, const Vector2d &prefer, Vector2d &tau) const
The unit tangent of tag tag's curve at x's foot, or false if there is none.
Definition Optimize2d.cpp:514
void append_frame_label(size_t idx, const std::string &label) const
Definition Optimize2d.cpp:3721
double band_vertex_residual(const size_t vid) const
Definition Optimize2d.cpp:2506
std::vector< std::array< double, 8 > > optimization_metrics
Definition TopoOffsetTriMesh.h:684
void simplicial_embedding()
make mesh a simplicial embedding of the input complex (simplices labelled 1)
Definition TopoOffsetTriMesh.cpp:1073
bool edge_is_offset(const size_t eid) const
Definition TopoOffsetTriMesh.h:561
static constexpr int64_t m_complex_tag
Definition TopoOffsetTriMesh.h:443
std::tuple< double, double > optimization_quality_stats() override
The loop's convergence metric, normalized so that 1.0 means "done".
Definition Optimize2d.cpp:3259
bool swap_edge_before(const Tuple &t) override
Reject a flip whose new edge already exists.
Definition Optimize2d.cpp:129
bool face_is_input_complex(const size_t fid) const
Definition Optimize2d.cpp:65
void log_region_edge_mask_health(const std::string &when) const
Are the tracked region boundaries actually contained by anything?
Definition Optimize2d.cpp:1232
void pre_optimize_input_mesh()
TriWild over the input mesh, before any of the offset exists.
Definition Optimize2d.cpp:1450
bool smooth_after(const Tuple &t) override
User specified modifications and desideras after an edge smooth.
Definition Optimize2d.cpp:937
bool collapse_edge_after(const Tuple &t) override
A collapse is accepted by the same criterion the smoothing minimises.
Definition Optimize2d.cpp:666
void needle_forensics() const
The full post-mortem on why nothing removes the flat faces.
Definition Optimize2d.cpp:2329
bool walk_along_curve(int64_t tag, const Vector2d &x, double s, Vector2d &out) const
March s of arclength along tag tag's boundary polyline from x's foot on it.
Definition Optimize2d.cpp:562
bool split_adjust_position(size_t v_new, const std::vector< Tuple > &children) override
Carry each parent's region label onto the two children it became.
Definition Optimize2d.cpp:852
wmtk::threading::enumerable_thread_specific< double > m_collapse_survivor_sizing
Definition TopoOffsetTriMesh.h:976
std::vector< Tuple > get_edge_adjacent_faces(const Tuple &f) const
get faces (as Tuples) that are edge-adjacent to the given face (as Tuple)
Definition TopoOffsetTriMesh.h:2233
std::vector< std::array< int, 3 > > op_counts
Definition TopoOffsetTriMesh.h:692
std::vector< Eigen::Vector2d > m_env_polyline_V
Definition TopoOffsetTriMesh.h:286
wmtk::threading::enumerable_thread_specific< std::pair< double, Vector2d > > m_needle_pre
Why smoothing does not lift a sliver's apex off its opposite edge.
Definition TopoOffsetTriMesh.h:932
void collapse_pass_begin() override
Definition Optimize2d.cpp:776
int m_ab_round
Definition TopoOffsetTriMesh.h:695
void init_surfaces_and_boundaries()
Classify every region boundary, build the per-tag containment envelopes, and tag the domain wall – on...
Definition TopoOffsetTriMesh.cpp:140
void release_deformable_regions()
Definition Optimize2d.cpp:426
bool m_quality_converged
Definition TopoOffsetTriMesh.h:711
bool m_plastic_active
set in optimize_offset() when deform_others
Definition TopoOffsetTriMesh.h:1359
double ring_max_quality(size_t vid) const
Max AMIPS over the faces incident to vid. -1 if it has none.
Definition Optimize2d.cpp:2255
void write_phi_grid(const std::string &path, int n) const
Sample the smooth offset potential on a dense grid and write it as <path>_phi.vtu.
Definition TopoOffsetTriMesh.cpp:1445
EnvelopeSetup
Which boundaries the region-class envelopes hold, and how they are built.
Definition TopoOffsetTriMesh.h:437
bool ambient_assert()
ensure ambient tag does not overlap any other tags in mesh.
Definition TopoOffsetTriMesh.cpp:356
std::shared_ptr< SampleEnvelope > smoothing_containment_envelope(const size_t vid) const override
... and it is not contained by one either, except in Phase A.
Definition TopoOffsetTriMesh.h:1266
size_t m_front_gradient_worst_vid
Definition TopoOffsetTriMesh.h:1300
bool edge_is_complex_boundary(const Tuple &e) const
Definition TopoOffsetTriMesh.cpp:218
OptPhase
Which half of the alternating optimization is running.
Definition TopoOffsetTriMesh.h:403
bool is_simplicially_embedded() const
check if the input complex (simplices labelled 1) are simplicially embedded w.r.t....
Definition TopoOffsetTriMesh.cpp:1028
std::shared_ptr< SampleEnvelope > smoothing_energy_envelope(const size_t vid) const override
The offset boundary is the one tracked surface with no envelope, in either role.
Definition TopoOffsetTriMesh.h:1223
bool collapse_edge_before(const Tuple &t) override
Reject any collapse that violates the substructure link condition.
Definition Optimize2d.cpp:700
std::shared_ptr< polysolve::nonlinear::Problem > rest_energy_for_vertex(size_t vid) const
Definition Optimize2d.cpp:459
std::shared_ptr< SampleEnvelope > m_offset_envelope
The tube the offset boundary may not leave during Phase A, of half-width offset_envelope_rel x target...
Definition TopoOffsetTriMesh.h:467
bool tri_is_simp_emb(const Tuple &t) const
check if a triangle satisfies simpicial embedding criteria w.r.t. input complex (simplices labelled 1...
Definition TopoOffsetTriMesh.cpp:1047
std::atomic< int > m_placement_projected
Definition TopoOffsetTriMesh.h:1018
void init_offset_potential()
Build the smooth offset potential from the extraction init_input_complex_bvh() kept.
Definition TopoOffsetTriMesh.cpp:820
bool smooth_front_vertex_phase_b(const Tuple &t)
Phase B placement of a front vertex: the shared smoother with the offset's options.
Definition FrontSmooth2d.cpp:77
Per-vertex data the shared 2D optimizer knows nothing about.
Definition TopoOffsetTriMesh.h:37
uint64_t m_boundary_mask
Which tag boundaries this vertex lies on – one bit per input tag, ambient included....
Definition TopoOffsetTriMesh.h:58
uint32_t m_born_epoch
Definition TopoOffsetTriMesh.h:64
Vector2d m_turn_start
Definition TopoOffsetTriMesh.h:47
Definition Simplex.hpp:46
Definition enumerable_thread_specific.hpp:26
Definition TriOptimizerMesh.h:74
What the offset needs on top of the parameters every wmtk optimizer shares.
Definition Parameters.h:20
bool deform_others
Definition Parameters.h:83
The band's distance error, split by whether the optimizer can do anything about it.
Definition TopoOffsetTriMesh.h:1499
The residual sampled at points along a band edge – see offset_edge_samples().
Definition TopoOffsetTriMesh.h:1537
An edge's / face's shared attributes together with the offset's own label.
Definition TopoOffsetTriMesh.h:581
The "energy_gradient" criterion: the front is at a critical point of Phase B's energy,...
Definition TopoOffsetTriMesh.h:1647
double bar
the ratios' bar, 1
Definition TopoOffsetTriMesh.h:1652
bool converged_single() const
Definition TopoOffsetTriMesh.h:1704
size_t n_at_floor
chords over the tube whose ends are already at the sizing floor
Definition TopoOffsetTriMesh.h:1686
bool converged() const
The alternating loop's test (and 3D's): the vertex test alone.
Definition TopoOffsetTriMesh.h:1698
double max_edge
RATIOS to the bar (1 = bar): the vertex measure per front_conv_criterion; the edge test.
Definition TopoOffsetTriMesh.h:1650
size_t n_pressed_touching
of the pressed, those touching another front or a wall
Definition TopoOffsetTriMesh.h:1685
size_t n_edges_pressed
skipped: pressed (see m_placement_pressed)
Definition TopoOffsetTriMesh.h:1654
The convergence criterion's own split: ||grad (Phi - c)^2|| at band vertices – the deciding measure –...
Definition TopoOffsetTriMesh.h:1600
double max_in_edge_pinned
Definition TopoOffsetTriMesh.h:1611
size_t n_skipped_inverted
Definition TopoOffsetTriMesh.h:1618
size_t n_edge_samples
Edge-interior samples measured into max_in_edge (not part of n_reachable).
Definition TopoOffsetTriMesh.h:1615
double max_normal_aligned
max |2 (Phi - c) grad Phi . n| at band vertices, reachable AND pinned.
Definition TopoOffsetTriMesh.h:1613
What smoothing did with each class of vertex, per pass.
Definition TopoOffsetTriMesh.h:871
std::atomic< int > offset_accepted
... and the smoother kept the new position
Definition TopoOffsetTriMesh.h:880
std::atomic< int > offset_attempted
reached the smoother with the offset term
Definition TopoOffsetTriMesh.h:879
std::atomic< int > interior_attempted
reached it without one
Definition TopoOffsetTriMesh.h:881
std::atomic< int > before_phase_b_not_offset
Phase B: on an input surface, neither placed nor relaxed.
Definition TopoOffsetTriMesh.h:875
std::atomic< int > attempted
smooth_before() entered
Definition TopoOffsetTriMesh.h:872
std::atomic< int > region_attempted
Definition TopoOffsetTriMesh.h:882
std::atomic< int > before_phase_b_enveloped_background
Phase B: envelope-held.
Definition TopoOffsetTriMesh.h:877
std::atomic< long long > res_before_nano
Definition TopoOffsetTriMesh.h:889
std::atomic< int > before_unrounded
base smooth_before said no: could not round
Definition TopoOffsetTriMesh.h:874
std::atomic< int > before_bbox
base smooth_before said no: on the bounding box
Definition TopoOffsetTriMesh.h:873
std::atomic< int > before_phase_b_enveloped_offset
Phase B: on-offset AND held.
Definition TopoOffsetTriMesh.h:878
The per-tag boundary polyline, with the adjacency an arclength walk needs.
Definition TopoOffsetTriMesh.h:279
std::vector< std::vector< int > > at_vertex
polyline vertex -> incident segment ids
Definition TopoOffsetTriMesh.h:281
std::vector< Eigen::Vector2i > E
segments, indexing m_env_polyline_V
Definition TopoOffsetTriMesh.h:280