Wildmeshing Toolkit
Loading...
Searching...
No Matches
Classes | Public Types | Public Member Functions | Public Attributes | Static Public Attributes | Private Member Functions | Private Attributes | List of all members
wmtk::components::topological_offset::TopoOffsetTriMesh Class Reference

The offset's 2D mesh, on the shared 2D optimizer. More...

#include <TopoOffsetTriMesh.h>

Inheritance diagram for wmtk::components::topological_offset::TopoOffsetTriMesh:
wmtk::TriOptimizerMesh wmtk::TriMesh wmtk::RationalPositions wmtk::components::manifold_extraction::ManExtractTriMesh

Classes

struct  CollapseCells
 
struct  DistanceSplit
 The band's distance error, split by whether the optimizer can do anything about it. More...
 
struct  EdgeSamples
 
struct  EdgeSnapshot2d
 An edge's / face's shared attributes together with the offset's own label. More...
 
struct  EdgeSplitCache
 
struct  EnergyCriterion
 The front's measures against the one bar and the refinable chords. The 2D twin of TopoOffsetTetMesh::EnergyCriterion, front chords in place of offset faces. More...
 
struct  FaceSnapshot2d
 
struct  FaceSplitCache
 
struct  FrontSolveRecord
 DEBUG_output only: every front solve since the last debug frame, so the frames show per vertex what m_newton_front counts per pass (frame fields front_newton_iters and front_newton_status, see write_vtu()). Appended by smooth_front_vertex(), emptied by write_debug_frame() once the frame is written. As in 3D. More...
 
struct  GradientSplit
 The convergence criterion's own split: ||grad (Phi - c)^2|| at band vertices – the deciding measure – plus the edge-interior chord diagnostic and the normal-aligned reference quantity. More...
 
struct  OptSplitCache2d
 
struct  SmoothingProgress
 
struct  SmoothTrace
 What smoothing did with each class of vertex, per pass. Same fields as 3D. More...
 

Public Types

enum class  EdgeSplitMode { Midpoint = 0 , SphereTrace = 1 , Optimization = 2 }
 
enum class  EnvelopeSetup { PerTag , WallComplex }
 Which boundaries the region-class envelopes hold, and how they are built. More...
 
using VertexExtraCol = wmtk::AttributeCollection< VertexExtra2d >
 
using EdgeExtraCol = wmtk::AttributeCollection< EdgeExtra2d >
 
using FaceExtraCol = wmtk::AttributeCollection< FaceExtra2d >
 
- Public Types inherited from wmtk::TriOptimizerMesh
using EdgeAttributes = wmtk::SurfaceTagAttributes
 
using VertAttCol = AttributeCollection< VertexAttributes >
 
using EdgeAttCol = AttributeCollection< EdgeAttributes >
 
using FaceAttCol = AttributeCollection< FaceAttributes >
 
using CurveSources = std::map< std::array< size_t, 2 >, std::vector< uint64_t > >
 
using FeaturePointAttCol = AttributeCollection< FeaturePointAttributes >
 
- Public Types inherited from wmtk::TriMesh
template<typename T >
using vector = std::vector< T >
 
using VertexMutex = wmtk::threading::VertexMutex
 

Public Member Functions

void classify_curve_edges ()
 Mark the mesh edges that lie on the input's curve group (EdgeExtra2d::on_curve).
 
void init_region_potentials (double delta, double effective_factor)
 
void assign_band_regions (bool log=true)
 
void log_front_profile (size_t vid)
 Diagnostic: the front objective of one vertex along its normal, offset term vs total.
 
int vertex_region (const size_t vid) const
 
int edge_region (const size_t va, const size_t vb) const
 
const OffsetPotential2D & potential_for_region (const int region) const
 
const OffsetPotential2D & potential_for (const size_t vid) const
 
std::shared_ptr< const OffsetPotential2D > potential_ptr_for (const size_t vid) const
 
const OffsetPotential2D & potential_for_edge (const size_t va, const size_t vb) const
 
const OffsetPotential2D & potential_for_face (const size_t fid) const
 
std::shared_ptr< Envelope > 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 intersection of everything that holds it, or null if nothing does.
 
EnvelopeSetup envelope_setup () const
 
std::string envelope_key_name (int64_t tag) const
 The name a tag or pseudo-tag prints under.
 
bool edge_is_complex_boundary (const Tuple &e) const
 
void build_boundary_envelopes (const char *when, EnvelopeSetup setup)
 
void rebuild_offset_envelope ()
 
void refresh_released_envelope ()
 
void check_no_vertex_on_both_surfaces (const char *when) const
 
void optimize_offset_loop (bool final_stage=true, bool refine=true, const std::string &label=std::string())
 
double front_gradient_linf ()
 
 TopoOffsetTriMesh (Parameters &_m_offset_params, int _num_threads=0)
 
double tri_energy (size_t fid) const
 THE per-cell energy, read from the mesh as it is – labels, neighbours and positions, nothing hypothetical. The 2D twin of TopoOffsetTetMesh::tet_energy().
 
double tri_energy (size_t fid, double amips) const
 tri_energy() with the face's AMIPS given, as the shared smoother has just computed it.
 
double smoothing_cell_energy (const size_t fid, const double quality) const override
 
double weighted_amips (const double amips) const
 
double max_tri_energy (const std::vector< size_t > &fids) const
 Max of tri_energy() over fids (0 for none): the number every rule above compares.
 
double offset_term_weight () const
 
void set_vertex_position (const size_t vid, const Vector2d &p)
 Place a vertex, keeping its exact and rounded coordinates in step.
 
bool edge_is_offset (const size_t eid) const
 
bool edge_is_region (const size_t eid) const
 
EdgeSnapshot2d edge_snapshot (const size_t eid) const
 
void restore_edge (const size_t eid, const EdgeSnapshot2d &s)
 
FaceSnapshot2d face_snapshot (const size_t fid) const
 
void restore_face (const size_t fid, const FaceSnapshot2d &s)
 
void label_offset_boundary ()
 Tag the two tracked surfaces for the optimization phase.
 
bool face_in_region (const size_t fid) const
 Whether face fid belongs to the closed offset region, read from its label.
 
bool face_is_input_complex (const size_t fid) const
 
bool vertex_is_on_surface (const size_t vid) const override
 The substructure the link condition is evaluated against, derived not cached.
 
bool edge_is_on_surface (const std::array< size_t, 2 > &vids) const override
 
void optimize_offset (const std::filesystem::path &output_file)
 The 2D optimization phase: split / collapse / swap / smooth on the shared driver.
 
bool face_is_offset_band (const size_t fid) const
 
std::pair< double, double > compute_distance_deviation () const
 How far the offset boundary is from where it should be: {max, avg} over vertices.
 
void log_worst_dist_vertex () const
 
bool edge_is_offset_surface_live (const Tuple &e) const
 
void write_debug_pvd () const
 
bool marching_split_edge_before (const Tuple &t)
 
bool marching_split_edge_after (const Tuple &t)
 
bool edge_split_sphere_trace (const Vector2d &p_in, const Vector2d &p_out, Vector2d &p_new, size_t &steps) const
 Construction placement in the marching: sphere tracing along the edge from p_in (the endpoint in the input complex, label != 0) towards p_out (the background endpoint) for the point where d(x) = D, D = m_construction_distance and d(x) the distance to the input complex through m_input_complex_bvh. From t = 0 the trace evaluates d at the current point and steps forward by D - d, the largest step that cannot cross the level set (d is 1-Lipschitz); it stops when |d - D| <= sphere_trace_target_rel_tol x D and returns true with p_new there. It returns false, p_new untouched, as soon as the current point reaches or passes p_out (t >= L: the level set is not on the edge) or would move behind p_in (d(p_in) already beyond the target); the caller then places the plain midpoint. Every step taken is longer than the tolerance, so the trace ends within L / (tol x D) steps; steps returns how many it took. No snapping away from the endpoints: a point found arbitrarily close to p_out is used as is. Same as 3D.
 
bool collapse_edge_before (const Tuple &t) override
 Reject any collapse that violates the substructure link condition, and remember the survivor's sizing for sizing_collapse_min = false.
 
bool collapse_edge_after (const Tuple &t) override
 THE ENERGY RULE for a collapse (see tri_energy()), on the real mesh once the connectivity is committed, then the base's after-hook, the coarsening bar, the sizing restore and the rest re-stamp. A refusal returns false, which the engine rolls back.
 
bool swap_edge_before (const Tuple &t) override
 Reject a flip whose new edge already exists, and cache the energy rule's before-half.
 
bool swap_edge_after (const Tuple &t) override
 
bool collapse_before_vertex (size_t v1, size_t v2) override
 
void collapse_after_vertex (size_t v1, size_t v2) override
 
void split_after_vertex (size_t v_new) override
 
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.
 
bool smooth_before (const Tuple &t) override
 User specified preparations and desideratas for an edge smooth.
 
bool smooth_after (const Tuple &t) override
 User specified modifications and desideras after an edge smooth.
 
bool vertex_is_on_domain_boundary (const size_t vid) const
 Identification only – no operation refuses the domain wall through these.
 
void init_surfaces_and_boundaries ()
 Classify every region boundary, build the per-tag containment envelopes, and tag the domain wall – once, from the input mesh, before offset construction runs.
 
void mark_input_complex_vertices ()
 
void warn_if_offset_reaches_domain_boundary () const
 Warn if the offset band has grown into the domain boundary.
 
polysolve::nonlinear::Solver & smoothing_solver ()
 
bool smooth_vertex_2d_counted (const Tuple &t, const optimization::SmoothVertexOptions &opts, optimization::NewtonCounters &newton, bool *solved=nullptr)
 
bool is_marched_edge (const size_t a, const size_t b) const
 The edges marching_tris() splits: exactly one end in the input complex (label != 0).
 
bool is_repulsion_vertex (const size_t vid) const
 
double repulsion_term_at (const size_t vid) const
 
double repulsion_cell_term (const std::array< size_t, 3 > &vids) const
 
size_t repulsion_cells_at (const size_t vid) const
 
std::shared_ptr< OffsetEnergy2D > repulsion_energy (const size_t vid) const
 O(v) for vid's smoothing objective, charged once per carrying face.
 
bool repulsion_embedding_kept (const std::vector< size_t > &fids) const
 
bool simplex_in_input_complex (size_t a, size_t b) const
 
std::vector< double > front_ring_measures () const
 The exit test's ring measure of every vertex (energy_criterion()), chord for chord.
 
double ring_measure_at (size_t vid) const
 One vertex's ring measure, from its own live front chords (NaN if it has none).
 
void crossing_snapshot (const std::string &pass, bool compare, bool match_positions)
 
void update_attributes () override
 
double ring_max_quality (size_t vid) const
 Max AMIPS over the faces incident to vid. -1 if it has none.
 
double face_flatness (size_t fid) const
 Scale-invariant flatness: 2*area / longest_edge^2.
 
void needle_forensics () const
 The full post-mortem on why nothing removes the flat faces.
 
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}.
 
std::vector< Tuple > offset_surface_edges_live_at (size_t vid) const
 
std::vector< std::array< size_t, 2 > > offset_surface_edges () const
 
bool vertex_has_live_offset_edge (size_t vid) const
 Whether ANY live offset-boundary edge is incident to vid; reads only vid's own faces.
 
void refresh_offset_membership (size_t vid)
 Re-derive m_is_on_offset for one vertex from the face labels, exactly: a vertex is on the offset boundary iff some incident edge has the band on one side and a non-complex face on the other. Called from the hooks where an operation can change the answer – the collapse (v2 and the edge's link) and the split (the new vertex). Reads labels, never the flag it is writing. Leaves m_vertex_attribute[vid].m_is_on_surface alone (the base's union over every tracked surface). As in 3D.
 
std::pair< size_t, size_t > offset_membership_mismatches () const
 
void check_offset_membership (const char *when) const
 
std::vector< char > offset_surface_foldover_labels () const
 Per-vertex 0/1: has the offset curve folded back on itself at this vertex? Debug frame diagnostic; see write_vtu(). Costs one pass over the live offset edges, no field evaluation.
 
void log_smooth_trace () const
 
void log_region_edge_mask_health (const std::string &when) const
 Are the tracked region boundaries actually contained by anything?
 
void audit_surface_containment (const std::string &when) const
 Which tracked edges are outside their envelope, and by how much.
 
bool vertex_is_on_region (const size_t vid) const
 Is this vertex on a region boundary – a tag boundary, or the domain wall.
 
uint64_t tag_bits (const CellTag &tags) const
 
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 at all.
 
uint64_t edge_mask (const std::array< size_t, 2 > &vids) const
 
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 symmetric difference init_surfaces_and_boundaries() classified by.
 
std::shared_ptr< Envelope > envelope_for_mask (uint64_t mask) const
 The envelope a simplex with this boundary mask is contained in, or null.
 
std::shared_ptr< Envelope > surface_envelope_for_edge (const std::array< size_t, 2 > &vids, const EdgeAttributes &) const override
 Class-0 segments – every region boundary, the input complex and the domain wall included – carry a containment requirement, and so does the offset boundary in the frozen-front final pass: m_offset_envelope holds it where the loop left it (see containment_for()).
 
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 deletion 3D made to its lower-strata point refusal.
 
std::shared_ptr< Envelope > smoothing_energy_envelope (const size_t vid) const override
 The offset boundary is the one tracked surface with no envelope, in either role.
 
std::shared_ptr< Envelope > smoothing_containment_envelope (const size_t vid) const
 
std::shared_ptr< Envelope > smoothing_containment_envelope (const size_t vid, const std::array< size_t, 2 > &, const EdgeAttributes &) const override
 
bool smooth_front_vertex (const Tuple &t)
 Placement of a front vertex: the shared smoother with the offset's options. See FrontSmooth2d.cpp.
 
bool smooth_repulsion_vertex (const Tuple &t)
 A repulsion vertex in the passes and rounds before the march (repulsion_smoothing()): the shared smoother at the front's options, against the sum of the per-tri energy over its ring – w AMIPS, and O(v) once for every ring face that carries it (repulsion_energy(), repulsion_cell_term()); under offset_front_smooth_veto, the max of the per-tri energy over its ring may not rise. See FrontSmooth2d.cpp.
 
bool smooth_nonfront_vertex (const Tuple &t)
 Every vertex off the front (and off the plastic medium): the shared smoother with TriWild's options (TriOptimizerMesh::smooth_after()), except that it minimises the per-cell energy – smoothing_extra_energy(), w AMIPS over the ring, the smoother adding no AMIPS term of its own – and the veto compares the max of tri_energy() over the ring, under the same key (offset_smooth_veto) and on the same vertices as the engine's AMIPS veto it replaces. See Optimize2d.cpp.
 
std::shared_ptr< polysolve::nonlinear::Problem > amips_energy (size_t vid) const
 
double front_vertex_normal_gradient (size_t vid) const
 
Vector2d front_vertex_move_direction (size_t vid) const
 
double front_move_alignment (size_t vid) const
 
double front_vertex_conv_ratio (size_t vid) const
 
bool front_placed_by_ratio (const double ratio) const
 THE definition of "placed" for a vertex on the offset boundary, applied to its already-measured front_vertex_conv_ratio(): finite and within the one bar. Every caller that asks "is the placement of this front vertex done" – energy_criterion()'s vertex count and the placed split of the chords over the bar, and the adaptive-smoothing stop – goes through here. Unmeasurable (a non-finite ratio) is NOT placed.
 
double edge_offset_term (size_t a, size_t b) const
 THE ONE CHORD FUNCTION: the mean over the chord's stencil of r^2, r = relative_residual(q) / front_conv_frac() – the squared chord measure, in units of the tolerance, 1 at the bar. The 2D twin of TopoOffsetTetMesh::face_offset_term().
 
double edge_offset_term (const OffsetPotential2D &pot, const Vector2d &pa, const Vector2d &pb) const
 
Vector2d front_vertex_normal (size_t vid) const
 The field's outward unit direction at front vertex vid (zero where grad Phi vanishes).
 
std::shared_ptr< polysolve::nonlinear::Problem > front_objective (size_t vid) const
 
bool vertex_carries_offset_term (const size_t vid) const
 
double smoother_amips_weight (const size_t vid) const
 
std::shared_ptr< polysolve::nonlinear::Problem > smoothing_extra_energy (const size_t vid) const override
 
std::shared_ptr< Envelope > released_envelope () const
 
bool edge_borders_released_boundary (const Tuple &e) const
 
bool face_is_deformable (size_t fid) const
 Under deform_others the same set as face_is_plastic(): every face outside the band.
 
bool face_is_plastic (size_t fid) const
 
void stamp_plastic_rests ()
 Stamp rest := current for every plastic face; called before every operation group.
 
bool smooth_plastic_vertex (const Tuple &t)
 
bool face_is_released_band (size_t fid) const
 
void stamp_rest_face (size_t fid)
 
void release_deformable_regions ()
 
std::shared_ptr< polysolve::nonlinear::Problem > rest_energy_for_vertex (size_t vid) const
 
std::shared_ptr< polysolve::nonlinear::Problem > front_energy (size_t vid, const std::shared_ptr< const OffsetPotential2D > &pot) const
 
int stencil_order () const
 Samples per front chord; see for_each_edge_sample().
 
int stencil_points_per_edge () const
 
void check_offset_within_support (const char *when) const
 Stop the run if any reachable band vertex has left the potential's support.
 
DistanceSplit distance_deviation_split () const
 
DistanceSplit residual_split () const
 
std::vector< bool > band_vertex_mask () const
 
double max_band_vertex_distance () const
 
double band_vertex_distance_error (const size_t vid) const
 
double band_vertex_residual (const size_t vid) const
 
template<typename Visit >
void for_each_edge_sample (const Vector2d &p0, const Vector2d &p1, Visit &&visit) const
 The stencil_order stencil a chord is sampled on, handed to visit one point at a time as (point, wa, wb) with the barycentric weights that built it. The 2D twin of TopoOffsetTetMesh::for_each_face_sample(), the same rule one dimension down: order 0 is the two ends alone; order k >= 1 is the vertices of the chord cut into n = 2^(k-1) equal pieces plus the midpoint of each piece, so the counts are 2, 3, 5, 9, 17 for k = 0..4 (stencil_points_per_edge()). The ends are in it on purpose: the quantity sampled is a distance to the level set, which at an end is that vertex's own placement error (see edge_offset_term()).
 
template<typename Visit >
void for_each_offset_edge_sample (const Tuple &e, Visit &&visit) const
 The same lattice over a chord the mesh carries. Visitor signature as above.
 
EdgeSamples offset_edge_samples (const Tuple &e) const
 
GradientSplit gradient_split (bool include_edge_samples=true) const
 
EnergyCriterion energy_criterion ()
 
double front_chord_target (size_t va, size_t vb, double len, double sag, double tube) const
 
size_t refine_front_by_halving (const std::vector< EnergyCriterion::Refinable > &edges)
 
size_t refine_front_by_halving (const std::vector< size_t > &vertices)
 
void grade_sizing (double grade, const std::vector< size_t > &seeds)
 
size_t grade_sizing_by_distance (const std::vector< size_t > &seeds)
 
SmoothingProgress smoothing_progress (const std::vector< Vector2d > &before)
 Measure a pass: before holds every live vertex's position before it, indexed by vid.
 
void smooth_group_to_convergence (const char *group_name)
 
void report_outside_support (const char *when, const DistanceSplit &s) const
 
bool band_vertex_is_reachable (const size_t vid) const
 
size_t refine_sizing_around_worst (double max_metric) override
 TriWild's stall-driven sizing refinement, verbatim.
 
void log_stuck_refine_census (double max_metric, double filter_energy)
 Why the final pass is stuck: a census of the faces stuck-refine is about to chase.
 
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.
 
bool collapse_quality_allowed (size_t v1, size_t, double q, double) const override
 The energy rule's EARLY half, on the engine's lower bound.
 
bool swap_quality_allowed (double after, double, bool) const override
 The energy rule's EARLY half for a swap, on the engine's lower bound.
 
void report_needle (const char *op, size_t fid, double parent_q) const
 Where the first needles come from – a tripwire, not a census.
 
void needle_scan (const char *when) const
 
void collapse_pass_begin () override
 
bool optimization_bare_coarsen_passes () const override
 
double face_criterion_rel (const size_t fid) const
 
void write_smoothing_debug_output (const std::string &path) const override
 Put the optimization's frames on the run's single debug timeline (see write_debug_frame()), labelled "r<turn><tag><pass>_<op>" / "r<turn><tag>_end", tag S in the loop and F in the final pass; led by m_frame_prefix (i during init_optimize). Same scheme as 3D.
 
void optimization_debug_checkpoint () override
 Called by the engine at every smoothing pass boundary (and only there in this component's loop): the per-pass smoothing accounting the 3D engine prints through its log_smoothing_pass_accounting() hook, which the 2D engine does not have, then the debug frame the base default writes. Writes the log and the frame, never the mesh.
 
void log_smoothing_pass_accounting ()
 
void append_frame_label (size_t idx, const std::string &label) const
 One line of <output>_frames.txt; truncates the file on the first frame.
 
void write_debug_frame (const std::string &label)
 One frame of the run's single debug timeline: <output>_NNNNN.vtu with the next sequence number, and one "NNNNN<tab>label" line in <output>_frames.txt. Every debug frame the run writes – the input as loaded, the construction stages, and the optimization's own frames through write_smoothing_debug_output() – goes through this sequence, so the numbers are consecutive and the .txt says what each one is. The only debug files outside it are the ones that are not this mesh: <output>_input_complex.vtu and the phi grid.
 
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
 
bool ambient_assert ()
 ensure ambient tag does not overlap any other tags in mesh.
 
void label_input_complex ()
 label input complex simplices as per boolean expression (or single body mode)
 
bool empty_input_complex ()
 check if the input complex is empty. Only valid after calling init_from_image(...). Checks if any vertices (therefore any simplices) are labelled 1, if not returns true
 
void init_input_complex_bvh ()
 Build the input complex's BVH and its smooth offset potential, from one extraction.
 
void init_offset_potential ()
 Build the smooth offset potential from the extraction init_input_complex_bvh() kept.
 
bool split_edge_before (const Tuple &t) override
 User specified preparations and desideratas for an edge split.
 
bool split_edge_after (const Tuple &t) override
 User specified modifications and desideratas after an edge split.
 
bool split_face_before (const Tuple &t) override
 User specified preparations and desideratas for a face split.
 
bool split_face_after (const Tuple &t) override
 User specified modifications and desideratas after a face split.
 
bool invariants (const std::vector< Tuple > &tris) override
 User specified invariants that can't be violated.
 
void construct_offset (const std::filesystem::path &output_file)
 
void marching_tris ()
 
void repulsion_smoothing ()
 
void relabel_input_complex ()
 
size_t flood_fill ()
 
std::vector< size_t > connected_components_helper (const size_t v_id) const
 All one-ring vertices through input / offset edges (labelled 1 or 2).
 
bool is_simplicially_embedded () const
 check if the input complex (simplices labelled 1) are simplicially embedded w.r.t. the entire mesh
 
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)
 
void simplicial_embedding ()
 make mesh a simplicial embedding of the input complex (simplices labelled 1)
 
void set_offset_tri_tags ()
 update 'tags' data for triangles in the offset region (tris labelled 2) based on the given offset tag values in m_offset_params.offset_tag_value
 
bool offset_is_manifold ()
 verify that the closed offset region (simplices labelled 1 or 2) form a manifold region. This should be true for any offset. This function is for verification
 
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.
 
void write_input_complex (const std::string &path)
 
void write_vtu (const std::string &path, MeshMap *output=nullptr)
 
size_t edge_id_from_simplex (const simplex::Edge &e) const
 get global id of edge from simplex::Edge object
 
Tuple get_tuple_from_edge (const simplex::Edge &e) const
 get Tuple simplex::Edge object
 
- Public Member Functions inherited from wmtk::TriOptimizerMesh
std::vector< std::array< size_t, 2 > > oriented_tracked_edges () const
 
std::vector< std::pair< size_t, int > > tracked_curve_boundary () const
 Vertices where the oriented tracked curves have a boundary (in minus out != 0), each with that sum. Empty for closed input curves; the operations preserve it.
 
 TriOptimizerMesh (OptimizerParameters &params)
 
size_t get_partition_id (const Tuple &loc) const
 
void partition_mesh ()
 
void partition_mesh_morton ()
 
double get_length2 (const Tuple &l) const
 
bool is_inverted (const std::array< size_t, 3 > &vs) const
 Orientation check, exact for the coordinates the vertices actually carry.
 
bool is_inverted (const Tuple &loc) const
 
bool is_inverted (const size_t fid) const
 
bool is_inverted_f (const Tuple &loc) const
 Inversion check using only the double positions.
 
bool is_inverted_f (const size_t fid) const
 
double get_quality (const std::array< size_t, 3 > &vs) const
 
double get_quality (const Tuple &loc) const
 
double get_quality (const size_t fid) const
 
std::tuple< double, double > get_max_avg_energy ()
 
void mesh_improvement (int max_its=80)
 
std::tuple< double, double > local_operations (const std::array< int, 4 > &ops, bool collapse_limit_length=true)
 
bool storage_grows_on_demand () const override
 
bool split_edge_is_due (const Tuple &e) const
 
bool is_force_split_edge (const size_t v1, const size_t v2) const
 
void split_all_edges ()
 
bool split_edge_before (const Tuple &t) override
 User specified preparations and desideratas for an edge split.
 
bool split_edge_after (const Tuple &loc) override
 User specified modifications and desideratas after an edge split.
 
void collapse_all_edges (bool is_limit_length=true)
 
bool collapse_edge_before (const Tuple &t) override
 User specified preparations and desideratas for an edge collapse including the link check as collapse prerequisite.
 
bool collapse_edge_after (const Tuple &t) override
 User specified modifications and desideratas after an edge collapse.
 
size_t coarsen_mesh ()
 Coarsen the mesh without letting the max energy rise.
 
bool coarsen_collapse_edge (const Tuple &e, std::vector< Tuple > &new_tris)
 One collapse under the coarsening rules, outside a coarsening pass.
 
size_t swap_all_edges ()
 Run TriWild's quality-improving interior edge-flip pass.
 
double swap_weight (const Tuple &t) const
 
bool swap_edge_before (const Tuple &t) override
 User specified preparations and desideratas for an edge swap including 1.can't swap on boundary edge. 2. when swap edge between v1, v2, there can't exist edges between the two opposite vertices v3, v4.
 
bool swap_edge_after (const Tuple &t) override
 User specified modifications and desideras after an edge swap.
 
void smooth_all_vertices (size_t n_iters=1)
 Run TriWild's vertex-smoothing pass.
 
bool use_colored_smoothing () const
 Whether this pass smooths by color class (see OptimizerParameters::colored_smoothing).
 
void smooth_vertices_colored (const std::vector< std::pair< std::string, Tuple > > &ops)
 One smoothing sweep over ops (all "vertex_smooth"), one color class at a time.
 
bool smooth_before (const Tuple &t) override
 The smoothing hooks. A derived class overriding them – or invariants(), which a smooth also runs – must read every attribute outside the smoothed vertex's star (other vertices, and the edges and triangles not incident to the vertex) through const access, e.g. std::as_const(m_vertex_attribute)[u].
 
bool smooth_after (const Tuple &t) override
 User specified modifications and desideras after an edge smooth.
 
Vector2d smoothing_position (size_t vid) const
 
void set_smoothing_position (size_t vid, const Vector2d &p)
 
double active_quality_threshold () const
 
virtual std::vector< size_t > active_vertices () const
 
virtual double quality_rel (const size_t fid) const
 A face's quality relative to the quality it is required to reach; <= 1 means it meets it.
 
bool round (const Tuple &v)
 Round a vertex position to floating point, if that inverts no incident face.
 
bool is_edge_on_surface (const Tuple &loc) const
 
bool is_tracked_edge (const Tuple &e) const override
 
std::vector< size_t > tracked_feature_points () const override
 
void record_curve_history (const std::string &metadata, const CurveSources &sources={})
 
std::optional< history::CurveHistory > finish_curve_history (const history::CurveHistory *input=nullptr)
 Check the recorded curves against the tracked edges, stop recording, log what the history holds and return it, for the application to save (io::ComponentIO::save_history). None if not recording.
 
bool is_edge_on_surface (const std::array< size_t, 2 > &vids) const
 
bool is_edge_on_bbox (const Tuple &loc) const
 
bool is_edge_on_bbox (const std::array< size_t, 2 > &vids) const
 
bool vertex_is_on_surface (const size_t vid) const override
 Is a vertex part of the substructure.
 
bool edge_is_on_surface (const std::array< size_t, 2 > &vids) const override
 Is an edge part of the substructure.
 
bool surface_segment_is_outside (const size_t a, const size_t b, const EdgeAttributes &attr) const
 
std::vector< std::array< size_t, 2 > > get_edges_by_condition (std::function< bool(const EdgeAttributes &)> cond) const
 
void gradation_smooth_sizing (double grade, const std::vector< size_t > &seeds)
 Monotone (only-decreasing) gradation smoothing of the sizing field.
 
void throw_if_in_operation_pass (const char *call) const
 
void enable_feature_point_tracking (const bool preserve)
 Turn on feature-point tracking, and the policy when preserve.
 
bool collapse_breaks_feature_point (const size_t v1_id, const size_t v2_id) const
 
bool feature_point_ball_contains (const size_t vid, const Vector2d &p) const
 May vertex vid sit at p as far as its anchor goes?
 
std::pair< size_t, size_t > feature_retention (double *worst_ratio=nullptr) const
 {feature points still represented within m_feature_eps, total feature points}.
 
- Public Member Functions inherited from wmtk::TriMesh
void init (size_t n_vertices, const std::vector< std::array< size_t, 3 > > &tris)
 
void init (const MatrixXi &F)
 Generate the connectivity of the mesh from an IGL-style F matrix.
 
std::vector< Tuple > get_vertices () const
 
std::vector< Tuple > get_edges () const
 
std::vector< Tuple > get_faces () const
 
Tuple tuple_from_edge (size_t vid1, size_t vid2, size_t fid) const
 
Tuple tuple_from_vids (size_t vid0, size_t vid1, size_t vid2) const
 
simplex::Vertex simplex_from_vertex (const Tuple &t) const
 
simplex::Edge simplex_from_edge (const Tuple &t) const
 
simplex::Face simplex_from_face (const Tuple &t) const
 
simplex::Face simplex_from_face (const size_t fid) const
 
Tuple tuple_from_simplex (const simplex::Face &s) const
 
simplex::SimplexCollection simplex_incident_triangles (const simplex::Vertex &v) const
 
simplex::SimplexCollection simplex_incident_triangles (const simplex::Edge &e) const
 
simplex::SimplexCollection simplex_link_vertices (const simplex::Vertex &v) const
 
simplex::SimplexCollection simplex_link_vertices (const simplex::Edge &e) const
 
simplex::SimplexCollection simplex_link_edges (const simplex::Vertex &v) const
 
void set_preallocation_factor (double factor)
 Preallocation factor: init/consolidate reserve capacity = max(floor, ceil(factor * live_count)) so operations can grab fresh slots without resizing the storage. When a pass exhausts the reserved capacity the affected operations fail (retried later after a consolidate). Values < 1 are clamped to 1.
 
double preallocation_factor () const
 
bool slots_exhausted () const
 
void clear_slots_exhausted ()
 
size_t request_tri_slots (size_t n)
 
size_t request_vert_slots (size_t n)
 
size_t tri_capacity () const
 get the current largest global fid
 
size_t vert_capacity () const
 get the current largest global vid
 
void consolidate_mesh ()
 removing the elements that are removed
 
std::shared_ptr< history::Recorder > start_history (std::function< Eigen::Vector3d(size_t)> position)
 Record every operation this mesh commits from now on (see wmtk/history).
 
void stop_history ()
 Stop recording. The recorder keeps the edits it has.
 
std::shared_ptr< history::TrackedCurveRecorder > start_curve_history (std::function< Eigen::Vector3d(size_t)> position)
 Record every change to the tracked curves from now on (see wmtk/history/TrackedRecorder.hpp).
 
void stop_curve_history ()
 Stop recording curves. The recorder keeps what it has.
 
size_t resync_curve_history (const std::vector< std::array< size_t, 2 > > &edges)
 
std::vector< std::array< size_t, 2 > > tracked_edges () const
 Every tracked edge, each with its smaller vertex first.
 
void remove_tris_by_ids (const std::vector< size_t > &fids)
 Mark the given triangles, and any vertex left without an incident triangle, as removed.
 
Tuple switch_vertex (const Tuple &t) const
 a duplicate of Tuple::switch_vertex funciton
 
Tuple switch_edge (const Tuple &t) const
 a duplicate of Tuple::switch_edge funciton
 
std::optional< Tuple > switch_face (const Tuple &t) const
 a duplicate of Tuple::switch_face funciton
 
bool check_link_condition (const Tuple &t) const
 prerequisite for collapse
 
void set_use_link_condition (bool use_it)
 Should collapse_edge_before enforce the link condition?
 
bool use_link_condition () const
 
bool check_mesh_connectivity_validity () const
 verify the connectivity validity of the mesh
 
bool check_edge_manifold () const
 verify the edge manifoldness of the mesh
 
size_t edge_valence (const TriMesh::Tuple &t) const
 Number of triangles incident to the edge the Tuple points at.
 
bool is_boundary_edge (const TriMesh::Tuple &t) const
 Does exactly one triangle share this edge?
 
bool is_manifold_edge (const TriMesh::Tuple &t) const
 Do exactly two triangles share this edge?
 
size_t vertex_component_count (const size_t vid) const
 Number of edge-connected components in the fan of a vertex.
 
size_t vertex_component_count (const TriMesh::Tuple &t) const
 
bool is_manifold_vertex (const size_t vid) const
 
std::optional< Tuple > switch_component (const TriMesh::Tuple &t) const
 Jump to the next edge-connected component of the fan of the Tuple's vertex.
 
bool is_boundary_vertex (const TriMesh::Tuple &t) const
 check if the vertex that's represented by a Tuple is at the boundary of the mesh
 
bool split_edge (const Tuple &t, std::vector< Tuple > &new_t)
 
virtual bool collapse_edge (const Tuple &t, std::vector< Tuple > &new_t)
 
void collapse_edge_conn (const Tuple &loc0, std::vector< Tuple > &new_tris, Tuple &return_t, size_t &new_vid, std::vector< std::pair< size_t, TriangleConnectivity > > &old_tris, std::vector< std::pair< size_t, VertexConnectivity > > &old_vertices, std::vector< std::pair< size_t, size_t > > &same_edge_vid_fid, std::vector< size_t > &n12_intersect_fids)
 
void collapse_edge_rollback (size_t &new_vid, std::vector< std::pair< size_t, TriangleConnectivity > > &old_tris, std::vector< std::pair< size_t, VertexConnectivity > > &old_vertices, std::vector< std::pair< size_t, size_t > > &same_edge_vid_fid, std::vector< size_t > &n12_intersect_fids)
 
bool swap_edge (const Tuple &t, std::vector< Tuple > &new_t)
 
bool smooth_vertex (const Tuple &t)
 
bool split_face (const Tuple &t, std::vector< Tuple > &new_t)
 Split a face in 3 faces.
 
size_t get_valence_for_vertex (const Tuple &t) const
 Count the number of the one ring tris for a vertex.
 
size_t vertex_valence (const size_t vid) const
 Number of triangles incident to a vertex, by id.
 
std::vector< Tuple > get_one_ring_tris_for_vertex (const Tuple &t) const
 Get the one ring tris for a vertex.
 
const std::vector< size_t > & get_one_ring_fids_for_vertex (const Tuple &t) const
 
const std::vector< size_t > & get_one_ring_fids_for_vertex (const size_t vid) const
 
std::vector< size_t > get_one_ring_vids_for_vertex_duplicate (const size_t &t) const
 Get the vids of the incident one ring tris for a vertex.
 
void get_one_ring_vids_for_vertex_duplicate (const size_t &t, std::vector< size_t > &one_ring) const
 
std::vector< size_t > get_incident_fids_for_edge (const Tuple &t) const
 
std::vector< size_t > get_incident_fids_for_edge (const size_t vid0, const size_t vid1) const
 
std::vector< Tuple > get_one_ring_edges_for_vertex (const Tuple &t) const
 Get all edges that are incident to the vertex of Tuple t.
 
std::vector< Tuple > get_one_ring_edges_for_vertex (const size_t vid) const
 
std::array< Tuple, 3 > oriented_tri_vertices (const Tuple &t) const
 Get the incident vertices for a triangle.
 
std::array< size_t, 3 > oriented_tri_vids (const Tuple &t) const
 Get the incident vertices for a triangle.
 
std::array< size_t, 3 > oriented_tri_vids (const size_t i) const
 
std::array< Tuple, 2 > get_edge_vertices (const Tuple &t) const
 
std::array< size_t, 2 > get_edge_vids (const Tuple &t) const
 
Tuple tuple_from_tri (size_t fid) const
 
Tuple tuple_from_vertex (size_t vid) const
 
Tuple tuple_from_edge (size_t fid, size_t local_eid) const
 
std::tuple< Tuple, size_t > tuple_from_edge (const std::array< size_t, 2 > &vids) const
 
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.
 
void start_protect_attributes ()
 Start the phase where the attributes that will be modified can be recorded.
 
void release_protect_attributes ()
 End the modification phase.
 
void rollback_protected_attributes ()
 rollback the attributes that are modified if any condition failed
 
int release_vertex_mutex_in_stack ()
 
int release_vertex_mutex_to (size_t mark)
 Release the mutexes taken since the release stack held mark entries.
 
bool try_set_vertex_mutex_n_ring (const Tuple &v, int threadid, int n)
 Lock every vertex within graph distance n of v, the seed included.
 
bool try_set_vertex_mutex_n_ring (size_t vid, int threadid, int n)
 
bool try_set_edge_mutex_n_ring (const Tuple &e, int threadid, int n)
 try_set_vertex_mutex_n_ring seeded from both ends of an edge.
 
bool try_set_face_mutex_one_ring (const Tuple &f, int threadid)
 try lock the one-ring neighboring triangles' incident vertices.
 
void for_each_face (const std::function< void(const Tuple &)> &)
 perform the given function for each face
 
void for_each_edge (const std::function< void(const Tuple &)> &)
 perform the given function for each edge
 
void for_each_vertex (const std::function< void(const Tuple &)> &)
 perform the given function for each vertex
 
simplex::SimplexCollection get_surface_edges_for_vertex (const size_t vid) const
 Get all edges on the surface that are incident to vid.
 
size_t get_order_of_edge (const std::array< size_t, 2 > &vids) const
 Compute the order of an edge.
 
size_t get_order_of_vertex (const size_t vid) const
 Get the order of a vertex.
 
bool substructure_link_condition (const Tuple &e_tuple) const
 Link condition that also considers substructures.
 
size_t tri_storage_capacity () const
 
size_t vert_storage_capacity () const
 
bool try_set_vertex_mutex_two_ring (const Tuple &v, int threadid)
 Lock v's one-ring and, partially, its two-ring. See the note above.
 
bool try_set_edge_mutex_two_ring (const Tuple &e, int threadid)
 Lock the edge's one-ring and, partially, its two-ring. See the note above.
 
bool try_set_vertex_mutex_one_ring (const Tuple &v, int threadid)
 Lock v and its one-ring. Complete, unlike the two-ring pair.
 
size_t cell_capacity () const
 
size_t cell_storage_capacity () const
 
Tuple tuple_from_cell (size_t cid) const
 
size_t cell_slot_bound (const Tuple &t) const
 
void reserve_free_slots (size_t cells, size_t verts)
 
- Public Member Functions inherited from wmtk::RationalPositions
size_t round_all_vertices ()
 Try to round every un-rounded vertex; returns the number reclaimed.
 
bool round_and_check_all_rounded ()
 Run the sweep, then report whether the mesh is now fully rounded.
 

Public Attributes

std::array< size_t, 3 > m_init_counts = {{0, 0, 0}}
 
size_t m_tags_count
 
int64_t m_curve_tag = -1
 
MatrixXd m_curve_V
 The curve group as loaded, kept because the classification below is redone on demand.
 
MatrixXi m_curve_E
 
std::shared_ptr< SimplicialComplexBVH > m_input_complex_bvh
 The input complex as loaded. Built once, never rebuilt.
 
std::shared_ptr< OffsetPotential2D > m_offset_potential
 The smooth offset potential, and with it the definition of the offset itself.
 
int m_n_regions = 0
 One field per connected piece of the input complex, and which one each band vertex is placed on.
 
std::vector< std::shared_ptr< OffsetPotential2D > > m_region_potentials
 one per piece
 
std::vector< int64_t > m_phi_vert_region
 per m_phi_V row: region index
 
std::vector< int64_t > m_phi_seg_region
 per m_phi_E row: region index, -1 unknown
 
std::vector< int64_t > m_phi_face_region
 per m_phi_F row: region index, -1 unknown
 
std::vector< int64_t > m_phi_point_region
 per m_phi_P entry: region index, -1 unknown
 
std::vector< int > m_face_region
 per face: band's region, -1 none, -2 reached from two
 
std::vector< int > m_vertex_region
 per vertex: region of its band faces, -1 / -2 as above
 
std::map< int64_t, std::shared_ptr< Envelope > > m_tag_envelopes
 One containment envelope per input tag, ambient included. Both phases.
 
std::map< int64_t, int > m_tag_bit
 
std::map< uint64_t, std::shared_ptr< Envelope > > m_isect_cache
 
std::mutex m_isect_mutex
 
std::map< uint64_t, std::shared_ptr< Envelope > > m_offset_isect_cache
 Memoized "region tubes AND the offset envelope", keyed by the region mask.
 
bool m_freeze_front = false
 The final pass: front vertices are not smoothed (see smooth_before()).
 
std::shared_ptr< Envelope > m_offset_envelope
 The tube the offset boundary may not leave during the frozen-front final pass, of half-width offset_envelope, built from the boundary as the loop left it just before that pass (containment_for() holds the final pass's operations to it). Null until then: the loop holds the offset boundary to no envelope. Unlike m_tag_envelopes, which must never be rebuilt. As in 3D.
 
bool m_init_optimize = false
 
std::string m_frame_prefix
 Leads every debug frame label: i during the init_optimize loop, empty otherwise.
 
double m_front_gradient_reference = 0.
 Its value on the band as constructed, measured once before turn 1.
 
EdgeSplitMode m_edge_split_mode = EdgeSplitMode::Midpoint
 
std::map< std::string, int64_t > m_tag_name_to_id
 
std::map< int64_t, std::string > m_tag_id_to_name
 
CellTag m_offset_output_tag_ids
 
bool m_singlebody = false
 
int64_t m_single_tag
 
bool m_has_envelope = false
 
MatrixXd m_V_envelope
 
MatrixXi m_F_envelope
 
Parameters & m_offset_params
 The base holds only wmtk::OptimizerParameters; this is the same object, typed.
 
VertexExtraCol m_vertex_extra
 
EdgeExtraCol m_edge_extra
 
FaceExtraCol m_face_extra
 
size_t m_worst_dist_vid = static_cast<size_t>(-1)
 
std::vector< std::array< double, 8 > > optimization_metrics
 
int m_round = 0
 
size_t m_debug_seq = 0
 Monotonic frame counter for the debug timeline. Mutable because the write hook is const.
 
std::vector< std::string > m_debug_frame_labels
 
std::map< std::string, std::vector< size_t > > m_debug_pvd_series
 
int m_debug_pass = 0
 
int m_debug_last_round = -1
 
char m_debug_last_tag = '?'
 
std::string m_debug_last_prefix = "?"
 
bool m_converged = false
 
bool m_quality_converged = true
 
double m_quality_max_amips = 0.
 
std::atomic< int > iter_cnt_split = 0
 
std::atomic< int > iter_cnt_collapse = 0
 
std::atomic< int > iter_cnt_swap = 0
 
std::atomic< int > iter_cnt_collapse_offset_removed {0}
 
std::atomic< int > iter_cnt_collapse_offset_reject {0}
 Operations refused because they would have left an offset-boundary chord over tolerance.
 
std::atomic< int > iter_cnt_swap_offset_reject {0}
 
std::atomic< int > iter_cnt_collapse_energy_reject {0}
 
std::atomic< int > iter_cnt_swap_energy_reject {0}
 
std::atomic< int > iter_cnt_split_offset_before {0}
 
std::atomic< int > iter_cnt_split_offset {0}
 
wmtk::threading::enumerable_thread_specific< OptSplitCache2d > m_opt_split_cache
 
size_t m_marching_root_splits = 0
 
size_t m_marching_midpoint_splits = 0
 
size_t m_marching_trace_steps = 0
 
size_t m_marching_trace_steps_max = 0
 
double m_construction_distance = 0.
 
SmoothTrace m_smooth_trace
 
optimization::NewtonCounters m_newton
 
optimization::NewtonCounters m_newton_front
 
std::vector< FrontSolveRecord > m_front_solve_log
 
std::mutex m_front_solve_log_mutex
 
std::atomic< size_t > m_front_veto_asked {0}
 
std::atomic< size_t > m_front_veto_fired {0}
 
std::shared_ptr< const OffsetPotential2D > m_repulsion_potential
 
optimization::NewtonCounters m_newton_repulsion
 
std::atomic< size_t > m_repulsion_veto_asked {0}
 
std::atomic< size_t > m_repulsion_veto_fired {0}
 
std::shared_ptr< OffsetEnergy2D > m_repulsion_term
 
std::atomic< size_t > m_repulsion_embed_refused {0}
 
std::array< std::atomic< size_t >, kGradBins > m_front_grad_abs {}
 
std::array< std::atomic< size_t >, kGradBins > m_front_grad_rel {}
 
optimization::NewtonCounters m_newton_plastic
 
std::vector< double > m_cross_ring
 
std::vector< Vector2d > m_cross_pos
 
bool m_cross_valid = false
 
std::atomic< size_t > m_cross_own {0}
 
std::atomic< size_t > m_cross_neighbour {0}
 
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.
 
std::atomic< size_t > m_needle_smooth_offered {0}
 
std::atomic< size_t > m_needle_smooth_reached {0}
 
std::atomic< size_t > m_needle_smooth_fixed {0}
 
std::atomic< size_t > m_needle_smooth_stationary {0}
 
std::atomic< size_t > m_needle_smooth_reports {0}
 Worst-case record: the best (lowest) ring max any needle-adjacent smooth achieved.
 
std::atomic< size_t > m_flat_created_split {0}
 
std::atomic< size_t > m_flat_created_collapse {0}
 
std::atomic< size_t > m_flat_worsened_split {0}
 
std::atomic< size_t > m_flat_genesis_reports {0}
 
wmtk::threading::enumerable_thread_specific< double > m_collapse_parent_flatness
 The flattest face in the collapse's ring before it ran, for record_flatness().
 
wmtk::threading::enumerable_thread_specific< double > m_collapse_survivor_sizing
 
wmtk::threading::enumerable_thread_specific< double > m_collapse_energy_before
 
wmtk::threading::enumerable_thread_specific< CollapseCells > m_collapse_cells
 
wmtk::threading::enumerable_thread_specific< std::vector< size_t > > m_collapse_edge_link
 The link of the collapsed edge, captured in collapse_before_vertex(): the only vertices besides v2 whose offset membership a collapse can change.
 
wmtk::threading::enumerable_thread_specific< double > m_swap_energy_before
 
std::set< int64_t > m_deform_tags
 The released tags. Filled by release_deformable_regions(); empty = feature inactive.
 
std::set< int64_t > m_source_tags
 
std::shared_ptr< Envelope > m_released_envelope
 
std::atomic< bool > m_released_tube_dirty {false}
 
std::mutex m_released_mutex
 
bool m_plastic_active = false
 set in optimize_offset() when deform_others
 
std::optional< EnergyCriterion > m_energy_verdict
 
std::atomic< size_t > m_deg_split_created {0}
 
size_t m_deg_prev_split_created = 0
 
std::atomic< size_t > m_needle_reports {0}
 
std::set< std::pair< long, long > > m_stuck_prev_cells
 
size_t m_stuck_calls = 0
 
MatrixXd m_phi_V
 
MatrixXi m_phi_E
 
MatrixXi m_phi_F
 the complex faces, in the same vertex index space (for per-region BVHs)
 
std::vector< int > m_phi_P
 
- Public Attributes inherited from wmtk::TriOptimizerMesh
VertAttCol m_vertex_attribute
 
EdgeAttCol m_edge_attribute
 
bool m_tracks_orientation = false
 The application set EdgeAttributes::m_orientation at insertion, so the tracked curves carry the input's direction and the operations keep it. See TetOptimizerMesh::m_tracks_orientation; this is the same thing one dimension down.
 
FaceAttCol m_face_attribute
 
AttributeContainerGroup m_vertex_attr_group
 What p_vertex_attrs points at, so a derived class can register more.
 
AttributeContainerGroup m_edge_attr_group
 What p_edge_attrs points at, so a derived class can register more.
 
AttributeContainerGroup m_face_attr_group
 What p_face_attrs points at, so a derived class can register more.
 
OptimizerParameters & m_params
 
std::shared_ptr< Envelope > m_envelope
 
double m_envelope_eps = -1
 
double m_s_amips = -1
 
double m_s_envelope = -1
 
wmtk::threading::enumerable_thread_specific< std::unique_ptr< polysolve::nonlinear::Solver > > m_solver
 
optimization::SmoothRejectCounters m_smooth_rejects
 Why smoothing attempts were refused, reported once per pass.
 
bool m_collapse_limit_length = true
 
int m_debug_print_counter = 0
 
std::string m_debug_pass_name
 
size_t m_tags_count = 0
 
std::map< int64_t, std::string > m_tag_id_to_name
 
std::map< std::string, int64_t > m_tag_name_to_id
 
int m_iterations_used = 0
 Shared TriWild/SimWild outer optimization schedule.
 
std::set< simplex::Edge > m_force_split_edges
 
size_t m_force_split_count = 0
 
std::unique_ptr< std::atomic< int >[]> m_high_valence_claim
 
size_t m_high_valence_claim_size = 0
 
std::atomic< size_t > m_high_valence_rejects = 0
 
size_t m_split_order_waits = 0
 The current split pass's ExecutePass::waits() and wait_defects().
 
size_t m_split_order_wait_defects = 0
 
CoarsenStats m_coarsen_stats
 
FeaturePointAttCol m_feature_point_attribute
 Registered with m_vertex_attr_group only once enable_feature_point_tracking() ran.
 
std::vector< Vector2d > m_feature_points
 
double m_feature_eps = -1
 
bool m_track_feature_points = false
 Whether the shared operations maintain the ids (enable_feature_point_tracking()).
 
bool m_preserve_feature_points = false
 Whether the collapse and smoothing policy is on (its preserve argument).
 
std::atomic< size_t > m_feature_rejects = 0
 
bool m_in_operation_pass = false
 True while a pass of the shared operations runs.
 
- Public Attributes inherited from wmtk::TriMesh
AbstractAttributeContainer * p_vertex_attrs = nullptr
 
AbstractAttributeContainer * p_edge_attrs = nullptr
 
AbstractAttributeContainer * p_face_attrs = nullptr
 
bool m_check_smoothing_stays_in_star = false
 While true, smooth_vertex() checks that the smooth recorded nothing outside the vertex's star – the vertex itself and the edges, faces and triangles incident to it – and throws if it did.
 
wmtk::threading::enumerable_thread_specific< std::vector< size_t > > mutex_release_stack
 
int NUM_THREADS = 0
 

Static Public Attributes

static constexpr int64_t m_wall_tag = -2
 pseudo-tag: the domain wall's tube
 
static constexpr int64_t m_complex_tag = -3
 
static constexpr int INPUT_SURFACE_CLASS = 0
 SurfaceTagAttributes::m_surface_class: which of the two tracked surfaces an edge belongs to. Same scheme as 3D.
 
static constexpr int OFFSET_SURFACE_CLASS = 1
 
static constexpr double kSmoothRelGradNormTol = 1e-6
 
static constexpr int kGradBins = 22
 
static constexpr double kFlatThreshold = 1e-3
 
static constexpr size_t kNeedleReports = 12
 
static constexpr double kNeedleQuality = 1e6
 What counts as a needle for the tripwire – deliberately far below MAX_ENERGY.
 
- Static Public Attributes inherited from wmtk::TriOptimizerMesh
static constexpr double MAX_ENERGY = 1e50
 The sentinel get_quality returns for a face AMIPS2D cannot score.
 
static constexpr size_t NO_FEATURE_POINT = std::numeric_limits<size_t>::max()
 A vertex that stands for no feature point. See FeaturePointAttributes.
 
- Static Public Attributes inherited from wmtk::TriMesh
static constexpr int EDGES_PER_CELL = 3
 

Private Member Functions

void sort_edges_by_length (std::vector< simplex::Edge > &edges)
 sort vector of edge simplices in place by decreasing length
 

Private Attributes

wmtk::threading::enumerable_thread_specific< EdgeSplitCache > edge_split_cache
 
wmtk::threading::enumerable_thread_specific< FaceSplitCache > face_split_cache
 

Additional Inherited Members

- Static Public Member Functions inherited from wmtk::TriMesh
static bool dry_run ()
 Whether operations on this thread are dry runs (see wmtk/OperationDryRun.hpp).
 
static SlotUsage slot_usage_of_this_thread ()
 
- Protected Member Functions inherited from wmtk::TriOptimizerMesh
void on_slot_storage_grown () override
 
virtual std::tuple< double, double > optimization_quality_stats ()
 
virtual double optimization_stop_metric () const
 
virtual bool optimization_stop_at_float () const
 
virtual bool optimization_stalled (double prev, double cur)
 Whether an iteration that moved the metric from prev to cur is stalled, and the sizing refinement should therefore fire.
 
virtual void collapse_pass_end (size_t)
 
std::vector< size_t > all_vertex_ids () const override
 Every live vertex, in the mesh's own iteration order.
 
bool vertex_is_rounded (const size_t vid) const override
 Whether this vertex's double position is currently trusted.
 
bool round_vertex (const size_t vid) override
 
- Protected Member Functions inherited from wmtk::TriMesh
void vertex_fan_components (size_t vid, std::vector< size_t > &component_of, std::vector< size_t > &representatives) const
 
void resize_mutex (size_t v)
 
- Protected Attributes inherited from wmtk::TriOptimizerMesh
uint32_t m_op_epoch = 0
 
wmtk::threading::enumerable_thread_specific< SplitInfoCache > split_cache
 
wmtk::threading::enumerable_thread_specific< CollapseInfoCache > collapse_cache
 
bool m_coarsen_mode = false
 Set for the duration of coarsen_mesh(); read-only while a pass is running.
 
- Protected Attributes inherited from wmtk::RationalPositions
std::atomic< bool > m_all_rounded = false
 True when every vertex is known to be rounded.
 

Detailed Description

The offset's 2D mesh, on the shared 2D optimizer.

Mirrors TopoOffsetTetMesh: the construction phase is entirely its own, and the optimization phase that follows is wmtk::TriOptimizerMesh's.

Two surfaces are tracked. Every tag-region boundary (input complex and domain wall included) keeps the primary class 0 and is held in its tags' envelopes, as triwild holds its input; the offset boundary is OFFSET_SURFACE_CLASS, in 2D exactly the edges across which the incident face labels differ, so label_offset_boundary() derives it rather than storing it. Class-0 edges may move within their tubes; only the offset one is driven toward target_distance.

Member Enumeration Documentation

◆ EnvelopeSetup

Which boundaries the region-class envelopes hold, and how they are built.

PerTag (deform_others false): one exact tube per input tag around that tag's boundary segments, the domain wall in the tags of its wall faces. A vertex carries the bit of every tube it lies on and is contained in their intersection, so every region boundary – the input complex and the wall included – is held.

WallComplex (deform_others true): exactly two tubes, the domain wall and the boundary of the input complex (any dimension, any manifoldness: it is a set of segments), under the pseudo-tags m_wall_tag / m_complex_tag. Every other region boundary carries no bit and is held by nothing; the medium around it is plastic, see face_is_plastic().

Either way build_boundary_envelopes() derives the masks and tubes from the mesh as it stands when called: at load (PerTag, before the complex is labelled), when deform_others switches the setup at construction, and fresh at the start of the final pass. The offset tube is separate and unchanged.

Member Function Documentation

◆ amips_energy()

std::shared_ptr< polysolve::nonlinear::Problem > wmtk::components::topological_offset::TopoOffsetTriMesh::amips_energy ( size_t  vid) const

w AMIPS over vid's one-ring, the per-cell energy's AMIPS part in the smoother's form (optimization::AMIPSEnergy2D, the engine's own quality), w = offset_amips_weight. The 3D twin is amips3_energy(), AMIPS^3 being the 3D engine's quality.

◆ assign_band_regions()

void wmtk::components::topological_offset::TopoOffsetTriMesh::assign_band_regions ( bool  log = true)

Rebuild m_*_region from the mesh. log false suppresses the per-turn "[regions]" line: write_vtu() re-derives the map for its frame diagnostics and puts the old one back.

◆ audit_surface_containment()

void wmtk::components::topological_offset::TopoOffsetTriMesh::audit_surface_containment ( const std::string &  when) const

Which tracked edges are outside their envelope, and by how much.

The shared pass driver's sanity_checks() reports "Edge [a, b] is outside!" but not which envelope refused it, and the answer forks the diagnosis: offset-class (mask 0) means the final pass moved the offset boundary out of the tube holding it; region-class (mask != 0) means a tag-region boundary has drifted off the input partition, refused by that tag's tube or by the intersection of several at a junction.

Reports per-endpoint distance to each real member tube. A multi-bit mask dispatches an IntersectionEnvelope, which must never be asked squared_distance (CompositeEnvelopes.hpp: its BVH is null), so the members are walked individually instead of querying the composite.

Call it at construction as well as inside the loop: an edge already outside before any operation runs is a construction defect, a different bug. Diagnostic only.

< furthest ALONG-SEGMENT distance to a real member tube

< furthest ENDPOINT distance – 0 means both ends are inside

< where along the segment worst_d sits; ~0.5 means a chord bulge

◆ band_vertex_distance_error()

double wmtk::components::topological_offset::TopoOffsetTriMesh::band_vertex_distance_error ( const size_t  vid) const

|dist(vid, input complex) - target_distance|. Diagnostic: the Euclidean offset, which the level set only coincides with away from reentrant features.

◆ band_vertex_is_reachable()

bool wmtk::components::topological_offset::TopoOffsetTriMesh::band_vertex_is_reachable ( const size_t  vid) const
inline

Whether vid is a band vertex the optimizer could still place at target_distance.

Only the domain boundary disqualifies one. m_is_on_input must not: the flag is over-broad – splits propagate it and collapses OR it onto survivors – so a vertex carrying it may sit a full target_distance from the complex, which is exactly where the offset wants it. check_no_vertex_on_both_surfaces() already throws on the genuinely contradictory case, so any vertex reaching here with the flag is placeable. Same rule as 3D.

◆ band_vertex_mask()

std::vector< bool > wmtk::components::topological_offset::TopoOffsetTriMesh::band_vertex_mask ( ) const

Which vertices lie on the band's outer surface – the one that is supposed to sit at target_distance. Shared by every measurement so they all agree on what "the band" is.

◆ band_vertex_residual()

double wmtk::components::topological_offset::TopoOffsetTriMesh::band_vertex_residual ( const size_t  vid) const

How far vid is from the level set Phi = c, as a length. This is what the loop converges on and what the sizing field refines by.

◆ build_boundary_envelopes()

void wmtk::components::topological_offset::TopoOffsetTriMesh::build_boundary_envelopes ( const char *  when,
EnvelopeSetup  setup 
)

Rebuild every region-class tube and every vertex's boundary mask from the current mesh under setup. PerTag at load (the complex is not labelled yet), WallComplex when deform_others switches it at construction, envelope_setup() fresh at the final pass. The tracked-edge flags are left alone: they are the topology the operations maintain. when labels the log line.

◆ check_no_vertex_on_both_surfaces()

void wmtk::components::topological_offset::TopoOffsetTriMesh::check_no_vertex_on_both_surfaces ( const char *  when) const

Hard error if any vertex is on both the input complex and the offset boundary – a state no placement satisfies. Called at construction.

◆ check_offset_membership()

void wmtk::components::topological_offset::TopoOffsetTriMesh::check_offset_membership ( const char *  when) const

Log offset_membership_mismatches() and throw when it is not {0, 0}. perform_sanity_checks only.

◆ check_offset_within_support()

void wmtk::components::topological_offset::TopoOffsetTriMesh::check_offset_within_support ( const char *  when) const

Stop the run if any reachable band vertex has left the potential's support.

Beyond dhat, Phi is identically zero with a zero gradient: the vertex is given no direction back, its residual saturates instead of growing, and the sizing field refines around a vertex nothing can move. There is no recovery from that state and no honest report of it either, so it is a hard error – the answer to it firing is a larger offset_dhat_factor.

Called once per optimization iteration, and once on the band as constructed.

◆ classify_curve_edges()

void wmtk::components::topological_offset::TopoOffsetTriMesh::classify_curve_edges ( )

Mark the mesh edges that lie on the input's curve group (EdgeExtra2d::on_curve).

Geometric, against the curve's own tube (the same eps the tag envelopes use), because triwild writes its curves with their own vertices and there is no index to match on.

Called whenever the complex is labelled, not once at load: the flag is a property of an edge and nothing propagates it through split and collapse, so an operation pass shreds it. Re-deriving it is exact and costs one tube query per edge.

◆ collapse_after_vertex()

void wmtk::components::topological_offset::TopoOffsetTriMesh::collapse_after_vertex ( size_t  v1,
size_t  v2 
)
overridevirtual

Reimplemented from wmtk::TriOptimizerMesh.

◆ collapse_before_vertex()

bool wmtk::components::topological_offset::TopoOffsetTriMesh::collapse_before_vertex ( size_t  v1,
size_t  v2 
)
overridevirtual

Reimplemented from wmtk::TriOptimizerMesh.

◆ collapse_edge_after()

bool wmtk::components::topological_offset::TopoOffsetTriMesh::collapse_edge_after ( const Tuple &  t)
overridevirtual

THE ENERGY RULE for a collapse (see tri_energy()), on the real mesh once the connectivity is committed, then the base's after-hook, the coarsening bar, the sizing restore and the rest re-stamp. A refusal returns false, which the engine rolls back.

The 3D twin applies the rule in collapse_after_connectivity(), a hook the 2D engine does not have; here it runs as the first thing in the after-hook, BEFORE TriOptimizerMesh::collapse_edge_after(), whose last step (collapse_after_vertex()) counts the collapse as done. A collapse moves no vertex, so the energy is read from the mesh as it now is.

Reimplemented from wmtk::TriMesh.

◆ collapse_edge_before()

bool wmtk::components::topological_offset::TopoOffsetTriMesh::collapse_edge_before ( const Tuple &  t)
overridevirtual

Reject any collapse that violates the substructure link condition, and remember the survivor's sizing for sizing_collapse_min = false.

The base applies the link condition only when both endpoints already sit on a tracked surface or the bbox, which is the right rule for tetwild and simwild but not here: the offset region is a thin band, and a collapse with only one endpoint on the boundary can still pinch its two sides together and make the region non-manifold. The offset asks unconditionally. The energy rule's before-half is taken in collapse_before_vertex(), which the base calls before its scoring loop. As in 3D.

Reimplemented from wmtk::TriMesh.

◆ collapse_pass_begin()

void wmtk::components::topological_offset::TopoOffsetTriMesh::collapse_pass_begin ( )
overridevirtual

Diagnostic only: the base offers no per-iteration hook except this one, so the needle population scan rides on it. Calls nothing else – the base default is empty.

Reimplemented from wmtk::TriOptimizerMesh.

◆ collapse_quality_allowed()

bool wmtk::components::topological_offset::TopoOffsetTriMesh::collapse_quality_allowed ( size_t  v1,
size_t  ,
double  q,
double   
) const
inlineoverridevirtual

The energy rule's EARLY half, on the engine's lower bound.

The engine scores each reshaped face before the collapse exists and hands its AMIPS in as q. tri_energy() of that face is weighted_amips(q) plus a non-negative chord term, so q alone above the before-maximum (collapse_before_vertex()) already decides: the rule that collapse_edge_after() applies on the real faces would refuse too. Refusing here costs nothing; refusing there costs the collapse and its rollback. Same rule, same counter, applied as soon as it can be. The engine's own ring_max (AMIPS over v1's ring) is not read; TriWild's exemption for an unrounded v1 is kept. Off with offset_collapse_veto. As in 3D.

Reimplemented from wmtk::TriOptimizerMesh.

◆ compute_distance_deviation()

std::pair< double, double > wmtk::components::topological_offset::TopoOffsetTriMesh::compute_distance_deviation ( ) const

How far the offset boundary is from where it should be: {max, avg} over vertices.

The absolute error |dist(v, input complex) - target_distance| over the offset-boundary vertices only. The max is what the optimization converges against – the offset is only as good as its worst-placed vertex, and an average hides a stretch far off the target. Mirrors TopoOffsetTetMesh::compute_distance_deviation().

◆ construct_offset()

void wmtk::components::topological_offset::TopoOffsetTriMesh::construct_offset ( const std::filesystem::path &  output_file)

Construction, start to finish, on the input mesh as given: the simplicial embedding, repulsion_smoothing() when asked for, marching_tris() and the offset tagging. The optimization is optimize_offset(), which the driver calls afterwards.

◆ containment_for()

std::shared_ptr< Envelope > wmtk::components::topological_offset::TopoOffsetTriMesh::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 intersection of everything that holds it, or null if nothing does.

The single place the two containment families are composed. region_mask dispatches through envelope_for_mask() (itself an intersection when the mask is multi-bit, which is what pins a junction to the junction); on_offset adds m_offset_envelope, in the frozen-front final pass only. Every operation's containment check reaches this through surface_envelope_for_edge(), so in the final pass split, collapse and swap hold the offset boundary to its tube; in the loop they do not. The smoother asks with on_offset false (smoothing_containment_envelope()): placing the front is what moves the offset boundary, and a tube around where it currently sits would cap how far it can travel. As in 3D.

◆ crossing_snapshot()

void wmtk::components::topological_offset::TopoOffsetTriMesh::crossing_snapshot ( const std::string &  pass,
bool  compare,
bool  match_positions 
)

Take the snapshot; with compare, log the crossings against the previous one first. match_positions: an id counts as the same vertex only at the same position (operation passes, which move no vertex; a storage retry inside one renumbers).

◆ edge_borders_released_boundary()

bool wmtk::components::topological_offset::TopoOffsetTriMesh::edge_borders_released_boundary ( const Tuple &  e) const

Whether this edge lies on a released region's boundary, by the incident faces' current tag symmetric difference – the same test the release freed vertices by.

◆ edge_boundary_bits()

uint64_t wmtk::components::topological_offset::TopoOffsetTriMesh::edge_boundary_bits ( const Tuple &  e) const
inline

Diagnostic only: which tag boundaries the incident faces say this edge lies on right now – the same symmetric difference init_surfaces_and_boundaries() classified by.

Nothing dispatches or propagates from this. It is only trustworthy while the face tags are still the input's own: construct_offset() replaces the tags of every face the band grows through, after which this is empty across every region edge the band swallowed, so deriving split masks from it mints uncontained region vertices (log_region_edge_mask_health counts the divergence). New vertices take the endpoints' mask AND behind the parent edge's class gate instead – 3D's rule at both of its split sites – which is what stops a chord, not being a region-class edge, from over-claiming a tube a full target_distance away.

◆ edge_is_complex_boundary()

bool wmtk::components::topological_offset::TopoOffsetTriMesh::edge_is_complex_boundary ( const Tuple &  e) const

Whether this edge lies on the boundary of the input complex: exactly one incident face carries label 1, or the edge itself does while neither face does (a curve or edge piece).

◆ edge_is_offset()

bool wmtk::components::topological_offset::TopoOffsetTriMesh::edge_is_offset ( const size_t  eid) const
inline

Whether edge eid is on the offset boundary / carries input geometry / bounds some other tag region.

◆ edge_is_offset_surface_live()

bool wmtk::components::topological_offset::TopoOffsetTriMesh::edge_is_offset_surface_live ( const Tuple &  e) const

Whether this edge is on the band's outer surface, recomputed from the tags on every call – the live counterpart of edge_is_offset(), for use inside the operation passes.

◆ edge_is_on_surface()

bool wmtk::components::topological_offset::TopoOffsetTriMesh::edge_is_on_surface ( const std::array< size_t, 2 > &  vids) const
overridevirtual

The 2D optimization phase. The operations themselves – split, collapse, the quality-driven edge flip, and the driver that sequences them – are wmtk::TriOptimizerMesh's. What is here is only what the offset knows: where its two tracked surfaces are, and how a vertex on the offset boundary is allowed to move.

Reimplemented from wmtk::TriMesh.

◆ edge_is_region()

bool wmtk::components::topological_offset::TopoOffsetTriMesh::edge_is_region ( const size_t  eid) const
inline

... and whether it bounds a region – any tracked edge that is not the offset boundary. The input complex is included, and deliberately: both are held by the same per-tag envelopes and neither is what the optimization moves. Same shape as 3D's face_is_region().

◆ edge_mask()

uint64_t wmtk::components::topological_offset::TopoOffsetTriMesh::edge_mask ( const std::array< size_t, 2 > &  vids) const
inline

A segment lies on a boundary only if both ends do: the AND of its endpoints' masks. The 2D twin of face_mask(), which ANDs three.

◆ edge_offset_term()

double wmtk::components::topological_offset::TopoOffsetTriMesh::edge_offset_term ( size_t  a,
size_t  b 
) const

THE ONE CHORD FUNCTION: the mean over the chord's stencil of r^2, r = relative_residual(q) / front_conv_frac() – the squared chord measure, in units of the tolerance, 1 at the bar. The 2D twin of TopoOffsetTetMesh::face_offset_term().

Every reader of the chord measure goes through it: the logs print its root, energy_criterion()'s exit, refinement and ring measures, the debug frames' front_err_ratio and front_ring_ratio, and the per-cell energy tri_energy(), which adds it to the band face of every live front chord. -1 when unmeasurable (a sample whose relative_residual() is not finite, which the euclidean field never has), +inf when the bar is not positive.

The (a, b) form reads the field of the chord (potential_for_edge()); the energy passes its band face's field and its ends sorted, so a chord's term does not depend on which face or which operation asks for it.

◆ envelope_for_mask()

std::shared_ptr< Envelope > wmtk::components::topological_offset::TopoOffsetTriMesh::envelope_for_mask ( uint64_t  mask) const

The envelope a simplex with this boundary mask is contained in, or null.

Zero bits: no boundary, no container. One bit: that tag's own envelope. Several bits: a memoized IntersectionEnvelope over the members – inside means inside every tube, which pins junction geometry to the junction. Containment-only for the multi-bit case: the composite implements just the virtual is_outside queries, so it must never be returned from smoothing_energy_envelope(), whose pull calls the non-virtual nearest_point.

◆ face_criterion_rel()

double wmtk::components::topological_offset::TopoOffsetTriMesh::face_criterion_rel ( const size_t  fid) const

Max of the two normalized criteria on this face: AMIPS over stop_energy, and on each live chord it carries the root of edge_offset_term() (the chord's RMS relative error over its stencil in units of the bar; +inf if unmeasurable). > 1 means it fails at least one. The coarsen-mode collapse accept reads it. As in 3D.

◆ face_flatness()

double wmtk::components::topological_offset::TopoOffsetTriMesh::face_flatness ( size_t  fid) const

Scale-invariant flatness: 2*area / longest_edge^2.

~0.433 for an equilateral triangle, -> 0 as the three vertices become collinear, and independent of size. AMIPS saturates at the MAX_ENERGY sentinel while this keeps resolving, which is what the genesis tracking needs: "this face got flatter" is a statement AMIPS cannot make once it is unscoreable.

◆ face_in_region()

bool wmtk::components::topological_offset::TopoOffsetTriMesh::face_in_region ( const size_t  fid) const

Whether face fid belongs to the closed offset region, read from its label.

The region is the offset band (label 2) plus the input complex it wraps (label 1), both set at construction from geometry rather than tags, and every operation carries the label onto the faces it creates, so this is exact. Tags cannot express the distinction: nothing stops the band's output tag already appearing elsewhere in the input mesh, and such a face would read as offset band, so the offset energy would drag an unrelated region to the level set.

◆ face_is_input_complex()

bool wmtk::components::topological_offset::TopoOffsetTriMesh::face_is_input_complex ( const size_t  fid) const

Whether face fid is part of the input complex (as opposed to the offset band). Label 1, assigned by label_input_complex() from the user's selection expression.

◆ face_is_offset_band()

bool wmtk::components::topological_offset::TopoOffsetTriMesh::face_is_offset_band ( const size_t  fid) const

Whether this face carries one of the offset output tags, i.e. is inside the offset band. Read from the tags, which every shared operation maintains, rather than from the face label, which is only refreshed once per optimization iteration.

◆ face_is_released_band()

bool wmtk::components::topological_offset::TopoOffsetTriMesh::face_is_released_band ( size_t  fid) const

A band cell that is a released object's material: every non-output tag released, at least one present. Read by the front placement objective and the rest stamping only – see the definition for why the band's interior smoothing is left equilateral.

◆ flood_fill()

size_t wmtk::components::topological_offset::TopoOffsetTriMesh::flood_fill ( )

Label connected simplicial complex components (simplices labelled 1 or 2) and return their number. The driver compares the count before and after construction. As in 3D.

◆ for_each_edge_sample()

template<typename Visit >
void wmtk::components::topological_offset::TopoOffsetTriMesh::for_each_edge_sample ( const Vector2d &  p0,
const Vector2d &  p1,
Visit &&  visit 
) const
inline

The stencil_order stencil a chord is sampled on, handed to visit one point at a time as (point, wa, wb) with the barycentric weights that built it. The 2D twin of TopoOffsetTetMesh::for_each_face_sample(), the same rule one dimension down: order 0 is the two ends alone; order k >= 1 is the vertices of the chord cut into n = 2^(k-1) equal pieces plus the midpoint of each piece, so the counts are 2, 3, 5, 9, 17 for k = 0..4 (stencil_points_per_edge()). The ends are in it on purpose: the quantity sampled is a distance to the level set, which at an end is that vertex's own placement error (see edge_offset_term()).

The weights are handed out for the front smoother: with the moving vertex x as p0, a sample is wa*x + wb*p1, so it moves by wa per unit of x; StencilEnergy2D keeps the weights fixed while the points slide with x (stencil_edge_at() in FrontSmooth2d.cpp).

Takes positions rather than a Tuple: edge_offset_term() reads a chord's ends in sorted order (see tri_energy()).

◆ front_chord_target()

double wmtk::components::topological_offset::TopoOffsetTriMesh::front_chord_target ( size_t  va,
size_t  vb,
double  len,
double  sag,
double  tube 
) const

The edge length that would bring a front chord's error under the tube: 3/4 L (tube / sag)^(1/p) capped at L/2, with the exponent p measured from how the level set turns across the chord (2 where it is smooth, 1 where the chord straddles a kink). Reached from ONE place: the refinable / at-floor test in energy_criterion(). Same formula as 3D.

◆ front_energy()

std::shared_ptr< polysolve::nonlinear::Problem > wmtk::components::topological_offset::TopoOffsetTriMesh::front_energy ( size_t  vid,
const std::shared_ptr< const OffsetPotential2D > &  pot 
) const

The offset term for a front vertex, at offset_term_weight(): StencilEnergy2D over its incident live front chords, whose value is sum_e O(e), the terms the per-cell energy carries. Defined in FrontSmooth2d.cpp.

◆ front_gradient_linf()

double wmtk::components::topological_offset::TopoOffsetTriMesh::front_gradient_linf ( )

Max over the front vertices of ||grad F . n||, F the vertex's front objective (front_objective()). Logged as the loop's gradient reference.

◆ front_move_alignment()

double wmtk::components::topological_offset::TopoOffsetTriMesh::front_move_alignment ( size_t  vid) const

|cos| between front_vertex_move_direction() and the field normal: 1 means the convergence test's 1-D step is the step toward the level set, 0 means it measures a direction that cannot reduce the distance. Debug-frame diagnostic; see write_vtu().

◆ front_objective()

std::shared_ptr< polysolve::nonlinear::Problem > wmtk::components::topological_offset::TopoOffsetTriMesh::front_objective ( size_t  vid) const

The objective of front vertex vid, as the smoother assembles it for a front vertex it places against the offset term (smoothing_extra_energy()): w AMIPS over the one-ring (amips_energy()), the rest-shape AMIPS of its plastic faces (rest_energy_for_vertex(), null without the plastic medium), and front_energy(). What front_vertex_normal_gradient() differentiates. As in 3D, where the AMIPS part is cubed.

◆ front_vertex_conv_ratio()

double wmtk::components::topological_offset::TopoOffsetTriMesh::front_vertex_conv_ratio ( size_t  vid) const

The vertex measure over the one bar: |relative_residual(x)| / front_conv_frac(), the chord term's order-0 stencil at one end. Infinite when unmeasurable. As in 3D.

◆ front_vertex_move_direction()

Vector2d wmtk::components::topological_offset::TopoOffsetTriMesh::front_vertex_move_direction ( size_t  vid) const

The line a front vertex is placed along: the field normal, or the boundary tangent where an input envelope holds it. See the definition.

◆ front_vertex_normal_gradient()

double wmtk::components::topological_offset::TopoOffsetTriMesh::front_vertex_normal_gradient ( size_t  vid) const

||grad F|| at front vertex vid along its move direction, F the objective smooth_front_vertex() minimises. +inf if unmeasurable.

◆ grade_sizing()

void wmtk::components::topological_offset::TopoOffsetTriMesh::grade_sizing ( double  grade,
const std::vector< size_t > &  seeds 
)

Spread the refinement just made at seeds to the vertices around them, the way sizing_gradation_mode says: "ring" is the base gradation_smooth_sizing(grade, seeds), "distance" is grade_sizing_by_distance(seeds) and ignores grade. Every place the offset lowers the field goes through here.

◆ grade_sizing_by_distance()

size_t wmtk::components::topological_offset::TopoOffsetTriMesh::grade_sizing_by_distance ( const std::vector< size_t > &  seeds)

TetWild's gradation, ported from tetwild::TetWild::adjust_sizing_field: a breadth-first walk out of the seeds over mesh neighbours; every vertex reached within R = 1.8 l of its nearest seed has its scalar multiplied by 0.5 + 0.5 dist / R, the walk stops at vertices farther than R, and the result is floored at the sizing floor. The seeds themselves keep the scalar the caller gave them. Returns the number of vertices lowered.

◆ gradient_split()

TopoOffsetTriMesh::GradientSplit wmtk::components::topological_offset::TopoOffsetTriMesh::gradient_split ( bool  include_edge_samples = true) const
Parameters
include_edge_samplesfalse skips the edge-interior half (the expensive one). Every convergence decision passes true.

◆ init_from_image()

void wmtk::components::topological_offset::TopoOffsetTriMesh::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

Parameters
VV by 2 vertex matrix
F#F by 3 face matrix
F_tags#F by physical groups tag matrix
V_envV_env by 2 EnvelopeSurface vertex matrix
F_envF_env by 2 EnvelopeSurface edge matrix

◆ init_input_complex_bvh()

void wmtk::components::topological_offset::TopoOffsetTriMesh::init_input_complex_bvh ( )

Build the input complex's BVH and its smooth offset potential, from one extraction.

Must be called after init_from_image(...) and label_input_complex(). The potential is built from the boundary of the complex rather than its interior: Phi's 2D primitives are segments and points, so a solid input region enters as its outline. Outside the region – the only place an offset exists – the two descriptions agree exactly.

◆ init_offset_potential()

void wmtk::components::topological_offset::TopoOffsetTriMesh::init_offset_potential ( )

Build the smooth offset potential from the extraction init_input_complex_bvh() kept.

Separate from that call only because it needs target_distance and offset_dhat_factor, which a caller wanting nothing but the distance field has no reason to have set. The geometry is still extracted exactly once, so the potential and the BVH cannot describe different inputs.

◆ init_surfaces_and_boundaries()

void wmtk::components::topological_offset::TopoOffsetTriMesh::init_surfaces_and_boundaries ( )

Classify every region boundary, build the per-tag containment envelopes, and tag the domain wall – once, from the input mesh, before offset construction runs.

The 2D twin of TopoOffsetTetMesh::init_surfaces_and_boundaries(), called from the same place for the same reason: the band's tags replace a face's own rather than joining them, so an envelope built afterwards would be a tube around a curve truncated at the band.

A region boundary is an edge whose two incident faces carry different tag sets; it enters the bucket of every tag on exactly one side (the symmetric difference). An edge with only one incident face is the domain wall and enters its single face's tags' buckets, which is how ambient's envelope comes to hold the box. Requires the face tags to be set, which init_from_image() does just above the call.

◆ invariants()

bool wmtk::components::topological_offset::TopoOffsetTriMesh::invariants ( const std::vector< Tuple > &  )
overridevirtual

User specified invariants that can't be violated.

Parameters
std::vector<Tuple>a vector of Tuples that are concerned in a given operation
Returns
true if the invairnats are not violated

Reimplemented from wmtk::TriMesh.

◆ is_repulsion_vertex()

bool wmtk::components::topological_offset::TopoOffsetTriMesh::is_repulsion_vertex ( const size_t  vid) const
inline

While the repulsion runs: an outer end of a marched edge – off the complex, with a neighbour on it – that no envelope holds. Asked of the mesh every time, so it follows the repulsion rounds' operations. As in 3D.

◆ label_offset_boundary()

void wmtk::components::topological_offset::TopoOffsetTriMesh::label_offset_boundary ( )

Tag the two tracked surfaces for the optimization phase.

The offset boundary has no stored definition in 2D – it is exactly the edges across which the incident face labels differ, so it falls out of the labelling and is recomputed here once. An edge with one incident face is on the domain boundary and is tagged bbox instead, which is what stops the box from collapsing.

◆ log_refine_block_census()

void wmtk::components::topological_offset::TopoOffsetTriMesh::log_refine_block_census ( const std::string &  when,
double  filter_energy 
) const

For every element above filter_energy, why its edges cannot be split.

log_stuck_refine_census() answers "what are the bad elements"; this answers "what is stopping the mesh from fixing them", the question that matters when the final pass refines somewhere else instead. Attributes each of a bad face's three edges to the first gate that refuses it, in the order the code applies them (TriOptimizerMeshSplit.cpp):

short length^2 < splitting_l2 * mean(sizing)^2 – never even offered to the queue. The remedy is the sizing field, not the split. valence a link vertex is over split_high_valence_threshold. Reported as a ceiling: the real gate is one such split per vertex per pass, which a static probe cannot see, so this counts vertices that could be refused, not that were. contain the dispatched envelope refuses one of the two halves, and the column says which envelope. free nothing blocks it – so a face all of whose edges are free is starved by no gate, and the stall is elsewhere.

Two shortcuts, both stated so the output is not over-read:

  • the midpoint cannot invert a healthy parent: each child has exactly half the parent's signed area, so the base's exact inversion check can only fire on an already-inverted parent. The census reports the parent's own inversion instead of probing.
  • the envelope of a child segment is the parent's: surface_envelope_for_edge dispatches on edge_mask(), and the midpoint's mask is itself the AND of the parent's endpoints, so mask(a,m) == mask(m,b) == mask(a,b) and one dispatch serves both halves. Same reasoning split_adjust_position relies on.

Each bad face is also located: centroid, distance to the input complex, and Phi/c there, which separates a collided corridor between two fronts from somewhere in the background.

Diagnostic only: reads the mesh, writes only the log.

< a face's BEST edge – its actual prospect

< which envelope did the refusing

One exemplar per face-level verdict: the worst-quality face that got it.

◆ log_region_edge_mask_health()

void wmtk::components::topological_offset::TopoOffsetTriMesh::log_region_edge_mask_health ( const std::string &  when) const

Are the tracked region boundaries actually contained by anything?

A class-0 edge is dispatched to an envelope by its boundary mask, the symmetric difference of its two faces' tags. An edge whose faces carry the same tags has an empty difference, so envelope_for_mask() gives it nullptr: tracked as a region boundary and held by nothing. Construction cannot produce one, so a non-zero count here is a hole opened afterwards. Called at construction.

◆ log_smoothing_pass_accounting()

void wmtk::components::topological_offset::TopoOffsetTriMesh::log_smoothing_pass_accounting ( )

The per-pass smoothing lines: the Newton counters, the front veto, the gradient bins and, under DEBUG_crossings, the crossings. The 3D twin overrides the engine hook of this name.

◆ log_stuck_refine_census()

void wmtk::components::topological_offset::TopoOffsetTriMesh::log_stuck_refine_census ( double  max_metric,
double  filter_energy 
)

Why the final pass is stuck: a census of the faces stuck-refine is about to chase.

Runs from refine_sizing_around_worst(), which only fires once max energy has stalled. The question is whether refinement is even the right response, so it separates four things the single MAX_ENERGY sentinel fuses:

  • exactly inverted (is_inverted) vs merely float-degenerate (is_inverted_f only). The second is a valid triangle AMIPS2D cannot score because m_posf lost the area – a rounding problem, not a geometry one, and splitting it makes two of them.
  • carrying an unrounded vertex, where m_posf is the wrong number outright.
  • already below the split gate, so the next pass cannot split them at all and lowering the sizing field is pure waste.
  • at the sizing floor, where apply_sizing_refinement has nothing left to give.

Plus where they are: class distribution, connected clusters, and how much the set overlaps the previous call's (quantised on a grid, because fids are recycled and cannot be compared across passes). A high overlap with a low cluster count says the pass is chasing the same few spots forever; a scattered, changing set says something is manufacturing new degeneracies as fast as they are refined.

◆ marching_tris()

void wmtk::components::topological_offset::TopoOffsetTriMesh::marching_tris ( )

Marching triangles: every edge with one endpoint in the input complex (label 1/2) and the other in the background (label 0) is split – where d(x) = target_distance along the edge, or under construction_mode's fallback at half the maximum marchable distance or at the midpoint (see edge_split_sphere_trace()) – and afterwards every background triangle still touching a complex frontier vertex (the split-off halves) becomes the band (label 2).

◆ mark_input_complex_vertices()

void wmtk::components::topological_offset::TopoOffsetTriMesh::mark_input_complex_vertices ( )

Set VertexExtra2d::m_is_on_input from the construction labels, once label_input_complex() has evaluated the selection. Separate from init_surfaces_and_boundaries(), which runs earlier and can only see tag boundaries. The 3D twin is mark_input_complex_vertices().

◆ max_band_vertex_distance()

double wmtk::components::topological_offset::TopoOffsetTriMesh::max_band_vertex_distance ( ) const

The furthest any offset-boundary vertex sits from the input complex, by BVH. 0 when no offset exists yet. Sizes dhat in init_offset_potential(); the 3D twin has the same name.

◆ needle_forensics()

void wmtk::components::topological_offset::TopoOffsetTriMesh::needle_forensics ( ) const

The full post-mortem on why nothing removes the flat faces.

For the worst faces by flatness, reports per edge every gate that decides whether an operation may touch it: length against the collapse gate (4/5 l s-bar) and the split gate (4/3 l s-bar), whether it is force-split queued, is_edge_on_surface (swap_weight returns lowest() for a surface edge, so it is never swapped) and swap_weight itself. Plus a scan for coincident vertices, with whether each pair shares an edge – a pair that does not is geometry no local operation can reach.

◆ needle_scan()

void wmtk::components::topological_offset::TopoOffsetTriMesh::needle_scan ( const char *  when) const

Population scan at a named moment, for the points no operation hook covers – after the after construction, at each collapse pass. Reports the count and the worst few.

◆ offset_edge_samples()

TopoOffsetTriMesh::EdgeSamples wmtk::components::topological_offset::TopoOffsetTriMesh::offset_edge_samples ( const Tuple &  e) const

The Phi residual at the stencil_order stencil's points of front chord e. Returns nothing for a chord with an unreachable end. The 3D twin is offset_face_samples().

◆ offset_membership_mismatches()

std::pair< size_t, size_t > wmtk::components::topological_offset::TopoOffsetTriMesh::offset_membership_mismatches ( ) const

perform_sanity_checks: how many vertices carry m_is_on_offset without a live offset edge, and how many are the other way round. Whole-mesh; zero is the invariant.

◆ offset_surface_edges()

std::vector< std::array< size_t, 2 > > wmtk::components::topological_offset::TopoOffsetTriMesh::offset_surface_edges ( ) const

Every live offset-boundary edge as a sorted vertex pair. The 3D twin is offset_surface_faces().

◆ offset_surface_edges_live_at()

std::vector< TopoOffsetTriMesh::Tuple > wmtk::components::topological_offset::TopoOffsetTriMesh::offset_surface_edges_live_at ( size_t  vid) const

The live offset-boundary edges incident to vid, deduplicated. The 3D twin is offset_surface_faces_live_at().

◆ offset_surface_foldover_labels()

std::vector< char > wmtk::components::topological_offset::TopoOffsetTriMesh::offset_surface_foldover_labels ( ) const

Per-vertex 0/1: has the offset curve folded back on itself at this vertex? Debug frame diagnostic; see write_vtu(). Costs one pass over the live offset edges, no field evaluation.

A vertex on the offset curve carries two live offset edges. Measured through either side, the angle between them is 180 degrees where the curve is straight and 360 where the two edges lie on top of each other with that side pinched to nothing. Over FOLDOVER_OUTER_ANGLE_DEG through EITHER side is the fold, and the vertex gets 1.

Which side is pinched is deliberately not determined; see the 3D twin, where the measured folds pinch the background rather than the band. Since the two sides sum to 360, the test is simply that the unsigned angle is under 360 minus the threshold. Vertices without exactly two live offset edges are left 0, as are degenerate edges: this is a diagnostic, and a number it cannot measure is not a fold.

The 3D twin is TopoOffsetTetMesh::offset_surface_foldover_labels(), which asks the same question of an offset-surface EDGE's two faces and marks that edge's two endpoints.

◆ offset_term_weight()

double wmtk::components::topological_offset::TopoOffsetTriMesh::offset_term_weight ( ) const
inline

1 / front_conv_frac()^2: the factor that turns a squared relative error into a squared error in units of the tolerance. The weight of the front smoother's offset terms, and the scale of edge_offset_term(). As in 3D.

◆ optimization_bare_coarsen_passes()

bool wmtk::components::topological_offset::TopoOffsetTriMesh::optimization_bare_coarsen_passes ( ) const
inlineoverridevirtual

TriWild's bare collapse passes are off for the offset, as TetWild's are in 3D: with no length gate the quality test alone demolishes the band, and the sizing field cannot refuse a collapse.

Reimplemented from wmtk::TriOptimizerMesh.

◆ optimization_debug_checkpoint()

void wmtk::components::topological_offset::TopoOffsetTriMesh::optimization_debug_checkpoint ( )
overridevirtual

Called by the engine at every smoothing pass boundary (and only there in this component's loop): the per-pass smoothing accounting the 3D engine prints through its log_smoothing_pass_accounting() hook, which the 2D engine does not have, then the debug frame the base default writes. Writes the log and the frame, never the mesh.

Reimplemented from wmtk::TriOptimizerMesh.

◆ optimize_offset_loop()

void wmtk::components::topological_offset::TopoOffsetTriMesh::optimize_offset_loop ( bool  final_stage = true,
bool  refine = true,
const std::string &  label = std::string() 
)

TriWild's loop, the front placed inside its smoothing passes. final_stage false is the init_optimize loop: it returns on convergence without the frozen-front final pass and without deciding the verdict. refine false skips the halving. label names the loop in its log. As in 3D.

◆ potential_ptr_for()

std::shared_ptr< const OffsetPotential2D > wmtk::components::topological_offset::TopoOffsetTriMesh::potential_ptr_for ( const size_t  vid) const
inline

The same selection as potential_for(), as the pointer the energies take a share of. Null only when m_offset_potential is, which the front placement paths test for.

◆ rebuild_offset_envelope()

void wmtk::components::topological_offset::TopoOffsetTriMesh::rebuild_offset_envelope ( )

Rebuild m_offset_envelope from the current offset-boundary segments, and drop the intersections memoized against the old one; also refresh_released_envelope().

◆ refine_front_by_halving() [1/2]

size_t wmtk::components::topological_offset::TopoOffsetTriMesh::refine_front_by_halving ( const std::vector< EnergyCriterion::Refinable > &  edges)

THE refinement: halve the sizing scalar at the ends of every refinable chord, once per vertex per call, floored at max(min_sizing_scalar, min_edge_length / l), then graded outward. Returns the number of vertices lowered.

◆ refine_front_by_halving() [2/2]

size_t wmtk::components::topological_offset::TopoOffsetTriMesh::refine_front_by_halving ( const std::vector< size_t > &  vertices)

The same halving at the listed vertices themselves: each lowered once per call, floored, then graded. The edge form above is this on its chords' ends, in the order given; front_measure "vertex_ring" calls it directly with the vertices whose ring measure is over the bar. The 3D twin has the same pair.

◆ refine_sizing_around_worst()

size_t wmtk::components::topological_offset::TopoOffsetTriMesh::refine_sizing_around_worst ( double  max_metric)
overridevirtual

TriWild's stall-driven sizing refinement, verbatim.

Structurally TriWildMesh::refine_sizing_around_worst, down to the shared helpers in wmtk/utils/SizingField.hpp and every stuck_refine_* parameter: rank faces by AMIPS, force-split the worst ones' longest edges, grow the region by rings, lower the per-vertex sizing scalar, grade it outward.

Final pass only: mesh_improvement() is its one caller, and the loop only runs that as the frozen-front final pass.

Implements wmtk::TriOptimizerMesh.

◆ refresh_released_envelope()

void wmtk::components::topological_offset::TopoOffsetTriMesh::refresh_released_envelope ( )

Rebuild deform_others' released-boundary tube now, between passes (see released_envelope(), which never rebuilds mid-operation).

◆ relabel_input_complex()

void wmtk::components::topological_offset::TopoOffsetTriMesh::relabel_input_complex ( )

Every construction label (vertex, edge, face) back to 0, then label_input_complex(): the input complex from the region tags, which the operations carry.

◆ release_deformable_regions()

void wmtk::components::topological_offset::TopoOffsetTriMesh::release_deformable_regions ( )

Drop the released tags' envelopes and stamp every deformable face's rest. Called once from optimize_offset() when deform_others is set; see the FaceExtra2d::rest_valid doc for the tracking contract.

◆ released_envelope()

std::shared_ptr< Envelope > wmtk::components::topological_offset::TopoOffsetTriMesh::released_envelope ( ) const

The current released-boundary tube, rebuilt first if dirty. Null when nothing is released or no released-boundary segment exists.

◆ report_needle()

void wmtk::components::topological_offset::TopoOffsetTriMesh::report_needle ( const char *  op,
size_t  fid,
double  parent_q 
) const

Where the first needles come from – a tripwire, not a census.

The census counts the population once it exists and the attribution counters say which operation touches them; neither says how the first one is born. This logs the first kNeedleReports needle faces any operation hook sees, with what tells a creation from a copy: the operation, the parent quality where there is one, both the float and the exact orientation, full-precision coordinates, and each vertex's flags, birth epoch and rounding.

Deliberately capped – once the force-split loop engages there are thousands per pass, and it is the first few that carry the information.

◆ report_outside_support()

void wmtk::components::topological_offset::TopoOffsetTriMesh::report_outside_support ( const char *  when,
const DistanceSplit &  s 
) const

Turn a residual_split()'s outside-support tally into the hard error. Separate from check_offset_within_support() so the per-round check can reuse a split it already has.

◆ repulsion_cell_term()

double wmtk::components::topological_offset::TopoOffsetTriMesh::repulsion_cell_term ( const std::array< size_t, 3 > &  vids) const
inline

THE REPULSION PART OF THE PER-TRI ENERGY, before the march (tri_energy()): a face carries O(v) of each of its corners v that is an outer end within it – off the complex, held by no envelope, in a face that has a complex vertex, i.e. joined to the complex by a marched edge of this face. These faces are the band the march will make, so this is the counterpart of a band face carrying its front chord's term. Reads the face's own three vertices only. 0 while the repulsion does not run. As in 3D.

◆ repulsion_cells_at()

size_t wmtk::components::topological_offset::TopoOffsetTriMesh::repulsion_cells_at ( const size_t  vid) const
inline

How many faces of vid's ring carry its term (repulsion_cell_term()): the smoother's objective is the ring's sum of the per-tri energy, so it charges O(v) that many times.

◆ repulsion_embedding_kept()

bool wmtk::components::topological_offset::TopoOffsetTriMesh::repulsion_embedding_kept ( const std::vector< size_t > &  fids) const

repulsion_rounds: whether the complex is still simplicially embedded around the faces fids an operation just made – no face off the complex has complex vertices spanning an edge off the complex (tri_is_simp_emb()'s test, decided from the faces' labels, which the operations carry, instead of the edge labels, which they do not). A collapse or a swap that breaks it is refused. Defined in TopoOffsetTriMesh.cpp. As in 3D.

◆ repulsion_smoothing()

void wmtk::components::topological_offset::TopoOffsetTriMesh::repulsion_smoothing ( )

repulsion_smoothing_passes and repulsion_rounds (see the spec): between the simplicial embedding and marching_tris(), smoothing passes and then rounds of the loop's operations that push the outer ends of the marched edges out to 2 x target_distance, stopping once every outer end is beyond target_distance + front_conv. As in 3D.

◆ residual_split()

TopoOffsetTriMesh::DistanceSplit wmtk::components::topological_offset::TopoOffsetTriMesh::residual_split ( ) const

The same split over the quantity the loop converges on: the Phi residual, as a length. Reported beside the Euclidean one so the two offsets can always be compared.

◆ rest_energy_for_vertex()

std::shared_ptr< polysolve::nonlinear::Problem > wmtk::components::topological_offset::TopoOffsetTriMesh::rest_energy_for_vertex ( size_t  vid) const

The rest-shape AMIPS over the deformable faces of vid's one-ring, weighted like the shared smoother weights its AMIPS term at vid (smoother_amips_weight()); null when the ring has none.

◆ set_vertex_position()

void wmtk::components::topological_offset::TopoOffsetTriMesh::set_vertex_position ( const size_t  vid,
const Vector2d &  p 
)
inline

Place a vertex, keeping its exact and rounded coordinates in step.

As in 3D: the offset works in doubles, so every vertex it places is rounded and its exact position is its double one (set_pos_to_posf).

◆ simplex_in_input_complex()

bool wmtk::components::topological_offset::TopoOffsetTriMesh::simplex_in_input_complex ( size_t  a,
size_t  b 
) const

Whether the edge (a, b) is in the input complex, from the faces' labels: an edge of a face of the complex, or, for offset_in, a domain-boundary edge of a body face. Single-body offset_in or offset_out only, which repulsion_smoothing() checks. The 3D twin also takes a face.

◆ smooth_after()

bool wmtk::components::topological_offset::TopoOffsetTriMesh::smooth_after ( const Tuple &  t)
overridevirtual

User specified modifications and desideras after an edge smooth.

Parameters
theedge Tuple to be smoothed
Returns
true if the modifications succeed

Reimplemented from wmtk::TriMesh.

◆ smooth_before()

bool wmtk::components::topological_offset::TopoOffsetTriMesh::smooth_before ( const Tuple &  t)
overridevirtual

User specified preparations and desideratas for an edge smooth.

Parameters
theedge Tuple to be smoothed
Returns
true if the preparation succeed

Reimplemented from wmtk::TriMesh.

◆ smooth_group_to_convergence()

void wmtk::components::topological_offset::TopoOffsetTriMesh::smooth_group_to_convergence ( const char *  group_name)

The interleaved smoothing of one operation group under adaptive_smoothing: one pass at a time through local_operations({0,0,0,1}), each followed by smoothing_progress(), until the front has converged (max ratio <= 1) or stalled (max ratio fell by less than adaptive_smoothing_stall_rel) AND the background has settled (max step <= adaptive_smoothing_step_rel x its target edge), or adaptive_smoothing_max_passes.

◆ smooth_plastic_vertex()

bool wmtk::components::topological_offset::TopoOffsetTriMesh::smooth_plastic_vertex ( const Tuple &  t)

The plastic vertex's smoothing: rest-shape AMIPS over its ring, nothing else – no equilateral term, no quality veto, exact inversion as the only accept test.

◆ smooth_vertex_2d_counted()

bool wmtk::components::topological_offset::TopoOffsetTriMesh::smooth_vertex_2d_counted ( const Tuple &  t,
const optimization::SmoothVertexOptions &  opts,
optimization::NewtonCounters &  newton,
bool *  solved = nullptr 
)

One smooth_vertex_2d() call with its solve recorded in newton: the 3D engine's smooth_vertex_3d() takes a NewtonCounters and the 2D one does not, so the component records the solve itself. A solve runs exactly when the ring is not already inverted in floats (smooth_vertex_2d()'s only refusal before solving; two_stage is off on every component path), so solved reports that, and the solver still holds its state.

◆ smoother_amips_weight()

double wmtk::components::topological_offset::TopoOffsetTriMesh::smoother_amips_weight ( const size_t  vid) const
inline

The AMIPS weight the rest-shape term uses at vid: offset_amips_weight for a vertex placed against the offset term, the engine's w_amips factor otherwise. As in 3D.

◆ smoothing_cell_energy()

double wmtk::components::topological_offset::TopoOffsetTriMesh::smoothing_cell_energy ( const size_t  fid,
const double  quality 
) const
inlineoverridevirtual

The shared smoother's per-face comparison – its projected step for a vertex held on a surface, and its quality veto – is the per-cell energy, so every smoothing test judges a move by the energy the smoother minimises (smoothing_extra_energy()). As in 3D.

Reimplemented from wmtk::TriOptimizerMesh.

◆ smoothing_containment_envelope() [1/2]

std::shared_ptr< Envelope > wmtk::components::topological_offset::TopoOffsetTriMesh::smoothing_containment_envelope ( const size_t  vid) const
inline

... and it is not contained by the offset tube either: placing the front is what moves the offset boundary, so the smoother asks for the region tubes alone. The operations hold the offset boundary to its tube through surface_envelope_for_edge() in the final pass; see containment_for(). As in 3D.

◆ smoothing_containment_envelope() [2/2]

std::shared_ptr< Envelope > wmtk::components::topological_offset::TopoOffsetTriMesh::smoothing_containment_envelope ( const size_t  vid,
const std::array< size_t, 2 > &  ,
const EdgeAttributes &   
) const
inlineoverridevirtual

The smoother holds every tracked segment at the vertex to that one envelope, whatever the segment: the per-vertex answer above, not the base's per-segment one.

Reimplemented from wmtk::TriOptimizerMesh.

◆ smoothing_energy_envelope()

std::shared_ptr< Envelope > wmtk::components::topological_offset::TopoOffsetTriMesh::smoothing_energy_envelope ( const size_t  vid) const
inlineoverridevirtual

The offset boundary is the one tracked surface with no envelope, in either role.

It is the surface the optimization exists to move: a tube around wherever construction left it would cap how far it can ever travel toward the level set. What holds it is the offset term in the objective, not a container.

The pull must be a real envelope, never a composite: this hook's consumers call the non-virtual Envelope queries – nearest_point and the ExactDistanceEnergy2D trio – which on a composite would bind to the base's null BVH. So a junction vertex (several mask bits) is pulled toward its most-violated member tube instead, one real envelope per attempt, while the containment intersection below enforces the full constraint.

Reimplemented from wmtk::TriOptimizerMesh.

◆ smoothing_extra_energy()

std::shared_ptr< polysolve::nonlinear::Problem > wmtk::components::topological_offset::TopoOffsetTriMesh::smoothing_extra_energy ( const size_t  vid) const
inlineoverridevirtual

THE smoothing objective at vid, for every vertex the shared smoother places: the per-cell energy tri_energy() = w AMIPS + O summed over vid's ring, up to the terms that do not move with vid. w AMIPS over the ring (amips_energy()) for every vertex – the smoother adds no AMIPS term of its own, every caller passing opts.w_amips 0 – plus the offset terms of the front chords at vid when it is a front vertex the loop places (front_energy(); null in the final pass and for a front vertex an input envelope also pins), or, in the passes before the march, a repulsion vertex's term once per ring face that carries it (repulsion_energy(), repulsion_cell_term()). Under deform_others the rest-shape AMIPS of the deformable faces in the ring is added. As in 3D.

Reimplemented from wmtk::TriOptimizerMesh.

◆ smoothing_position_is_allowed()

bool wmtk::components::topological_offset::TopoOffsetTriMesh::smoothing_position_is_allowed ( const size_t  ,
const Vector2d &   
) const
inlineoverridevirtual

No per-vertex positional constraint. The per-tag envelopes close that hole structurally – the same deletion 3D made to its lower-strata point refusal.

An isolated point of the complex only ever arises where two or more selected tags meet (the boolean selection can only label an isolated simplex whose face star is tag-heterogeneous), so the edges radiating from it are tag boundaries and its boundary mask carries several bits. smoothing_containment_envelope() therefore hands the smoother an IntersectionEnvelope – within eps of every curve it lies on – which pins it to the junction, and the pull toward the most-violated member drags it back if it strays. Kept as the honest constant: the base's default asks the feature-point anchors, which this mesh never turns on.

Reimplemented from wmtk::TriOptimizerMesh.

◆ smoothing_solver()

polysolve::nonlinear::Solver & wmtk::components::topological_offset::TopoOffsetTriMesh::smoothing_solver ( )

The thread's shared solver, created with the engine's parameters if needed, with kSmoothRelGradNormTol applied. Every smoothing path of this component takes it from here.

◆ split_adjust_position()

bool wmtk::components::topological_offset::TopoOffsetTriMesh::split_adjust_position ( size_t  v_new,
const std::vector< Tuple > &  children 
)
overridevirtual

Carry each parent's region label onto the two children it became.

Bookkeeping, not positioning, and it lives here because of when the base calls the hooks: the containment check inside split_edge_after() runs on both new segments and so reaches surface_envelope_for_edge() and the endpoints' boundary masks. split_after_vertex() runs after that check; this hook is the last one the base offers before it.

Getting it wrong is silent in the dangerous direction: children still holding whatever occupied their recycled fid slots classify as "not a region boundary", which yields a null envelope, and a null envelope makes surface_segment_is_outside() return false – containment skipped rather than failed, so the offset polyline decays unchecked.

Position is left entirely to the base; this always returns its result unchanged.

Reimplemented from wmtk::TriOptimizerMesh.

◆ split_after_vertex()

void wmtk::components::topological_offset::TopoOffsetTriMesh::split_after_vertex ( size_t  v_new)
overridevirtual

Reimplemented from wmtk::TriOptimizerMesh.

◆ split_edge_after()

bool wmtk::components::topological_offset::TopoOffsetTriMesh::split_edge_after ( const Tuple &  t)
overridevirtual

User specified modifications and desideratas after an edge split.

Parameters
theedge Tuple to be split
Returns
true if the modifications succeed

Reimplemented from wmtk::TriMesh.

◆ split_edge_before()

bool wmtk::components::topological_offset::TopoOffsetTriMesh::split_edge_before ( const Tuple &  t)
overridevirtual

User specified preparations and desideratas for an edge split.

Parameters
theedge Tuple to be split
Returns
true if the preparation succeed

Reimplemented from wmtk::TriMesh.

◆ split_face_after()

bool wmtk::components::topological_offset::TopoOffsetTriMesh::split_face_after ( const Tuple &  t)
overridevirtual

User specified modifications and desideratas after a face split.

Parameters
theface Tuple to be split
Returns
true if the modifications succeed

Reimplemented from wmtk::TriMesh.

◆ split_face_before()

bool wmtk::components::topological_offset::TopoOffsetTriMesh::split_face_before ( const Tuple &  t)
overridevirtual

User specified preparations and desideratas for a face split.

Parameters
theface Tuple to be split
Returns
true if the preparation succeed

Reimplemented from wmtk::TriMesh.

◆ stamp_rest_face()

void wmtk::components::topological_offset::TopoOffsetTriMesh::stamp_rest_face ( size_t  fid)

Stamp rest := the face's current corner positions (oriented order). No-op for non-deformable faces.

◆ stencil_points_per_edge()

int wmtk::components::topological_offset::TopoOffsetTriMesh::stencil_points_per_edge ( ) const
inline

How many points for_each_edge_sample() visits at the configured order: 2 at order 0, and 2n + 1 with n = 2^(k-1) above it, i.e. 3, 5, 9, 17, ... Kept in step with for_each_edge_sample() by the unit test stencil-order-point-counts-2d. The 3D twin is stencil_points_per_face().

◆ surface_envelope_for_edge()

std::shared_ptr< Envelope > wmtk::components::topological_offset::TopoOffsetTriMesh::surface_envelope_for_edge ( const std::array< size_t, 2 > &  vids,
const EdgeAttributes &   
) const
inlineoverridevirtual

Class-0 segments – every region boundary, the input complex and the domain wall included – carry a containment requirement, and so does the offset boundary in the frozen-front final pass: m_offset_envelope holds it where the loop left it (see containment_for()).

The envelope holds the other tag regions where they are, and the input complex too. That half is exactly TriWild's input envelope: the complex may be split, collapsed and smoothed, and this is what bounds how far the result may drift from the geometry as loaded. The 2D twin of TopoOffsetTetMesh::surface_envelope_for_face().

Null means "no containment requirement", which the base handles by skipping the check.

Reimplemented from wmtk::TriOptimizerMesh.

◆ swap_edge_after()

bool wmtk::components::topological_offset::TopoOffsetTriMesh::swap_edge_after ( const Tuple &  t)
overridevirtual

THE ENERGY RULE for a swap (see tri_energy()): the max of tri_energy() over the two faces the flip made must be STRICTLY below the max over the two it replaced (m_swap_energy_before). The 3D twin is swap_after_cells().

Reimplemented from wmtk::TriMesh.

◆ swap_edge_before()

bool wmtk::components::topological_offset::TopoOffsetTriMesh::swap_edge_before ( const Tuple &  t)
overridevirtual

Reject a flip whose new edge already exists, and cache the energy rule's before-half.

Flipping (a,b) to (c,d) when c and d are already joined creates a second edge between the same pair. Across a thin offset band that is how the two sides get stitched together and the region stops being manifold. The base refuses tracked-surface edges but not this.

Reimplemented from wmtk::TriMesh.

◆ swap_quality_allowed()

bool wmtk::components::topological_offset::TopoOffsetTriMesh::swap_quality_allowed ( double  after,
double  ,
bool   
) const
inlineoverridevirtual

The energy rule's EARLY half for a swap, on the engine's lower bound.

after is the largest AMIPS over the two new faces. tri_energy() of a face is weighted_amips() of its AMIPS plus a non-negative chord term, so after not strictly below the before-maximum (swap_edge_before()) already decides what swap_edge_after() would decide on the real faces: refused. The engine's before (stored AMIPS of the old faces) is not read. Off with offset_swap_veto. A 2D swap never flips the offset boundary (the engine refuses every tracked edge), so is_surface_flip is always false. As in 3D.

Reimplemented from wmtk::TriOptimizerMesh.

◆ tag_bits()

uint64_t wmtk::components::topological_offset::TopoOffsetTriMesh::tag_bits ( const CellTag &  tags) const
inline

The three helpers of the per-tag envelope dispatch. tag_bits() and edge_mask() are trivial; envelope_for_mask() is out of line (it builds IntersectionEnvelopes lazily).

◆ tri_energy()

double wmtk::components::topological_offset::TopoOffsetTriMesh::tri_energy ( size_t  fid) const

THE per-cell energy, read from the mesh as it is – labels, neighbours and positions, nothing hypothetical. The 2D twin of TopoOffsetTetMesh::tet_energy().

E(t) = w A(t) + [t is band] * sum over the live front chords e of t of O(e)
O(e) = (1/N) sum_i (relative_residual(q_i) / front_conv_frac())^2

A(t) is the base's TriOptimizerMesh::get_quality(), the AMIPS2D the engine stores as the face quality (3D stores AMIPS^3 there, which is why its energy reads AMIPS^3); its MAX_ENERGY (unscoreable) passes through unchanged, and w is offset_amips_weight (weighted_amips()). q_i are the N points of e's stencil_order stencil (for_each_edge_sample()) on the field of e's band face (potential_for_face()), e's ends sorted so that a chord's term does not depend on which face or which operation reads it. O is edge_offset_term(), the squared chord measure in units of the tolerance, 1 at the bar. With no field yet (m_offset_potential null) E = w A.

An unmeasurable chord makes the face MAX_ENERGY, the engine's own "unscoreable". THE BAND SIDE CARRIES THE CHORD: only a band face (face_is_offset_band()) adds terms, and a live front chord has exactly one band side (edge_is_offset_surface_live()), so every front chord is counted once.

WHERE IT IS COMPARED: the collapse rule (collapse_before_vertex() / collapse_edge_after(), early half collapse_quality_allowed()), the swap rule (swap_edge_before() / swap_edge_after(), early half swap_quality_allowed()), the smoother's projected step and veto (smoothing_cell_energy()) and the front and interior vetoes. The engine's stored face quality stays AMIPS2D and every engine diagnostic reads it unchanged.

◆ update_attributes()

void wmtk::components::topological_offset::TopoOffsetTriMesh::update_attributes ( )
overridevirtual

The engine calls this at the start of local_operations() and after each operation pass that ran; DEBUG_crossings hooks the operation passes here. Log-only.

Reimplemented from wmtk::TriOptimizerMesh.

◆ vertex_boundary_mask()

uint64_t wmtk::components::topological_offset::TopoOffsetTriMesh::vertex_boundary_mask ( const size_t  vid) const
inline

The tag boundaries this vertex lies on – the raw mask gated on the vertex still being region geometry at all.

The gate is not redundant, it is what keeps the mask honest. m_boundary_mask propagates by a bare AND of a split's endpoints, which over-claims: an edge whose two ends happen to share a bit hands that bit to its midpoint even when the edge is a chord through the interior, and the offset front is built by splitting precisely such edges. So the mask says which boundaries and vertex_is_on_region() says whether the vertex is on one at all.

◆ vertex_carries_offset_term()

bool wmtk::components::topological_offset::TopoOffsetTriMesh::vertex_carries_offset_term ( const size_t  vid) const
inline

Whether the smoother places vid against the offset term: a front vertex, outside the frozen-front final pass, that no input envelope also pins.

◆ vertex_has_live_offset_edge()

bool wmtk::components::topological_offset::TopoOffsetTriMesh::vertex_has_live_offset_edge ( size_t  vid) const

Whether ANY live offset-boundary edge is incident to vid; reads only vid's own faces.

< the edge's other end

◆ vertex_is_on_domain_boundary()

bool wmtk::components::topological_offset::TopoOffsetTriMesh::vertex_is_on_domain_boundary ( const size_t  vid) const
inline

Identification only – no operation refuses the domain wall through these.

The wall is a tracked region boundary like every other one: init_surfaces_and_boundaries() tags its edges m_is_surface_fs, masks its vertices with ambient's bit and puts its segments in ambient's envelope, so refinement, coarsening, flips and smoothing are governed by the same containment, merge rules and link conditions that govern the input complex. As in 3D, the hooks carry no categorical wall refusal of their own.

What still reads these two:

  • band_vertex_is_reachable(): a wall-clipped offset vertex is booked pinned for the convergence criterion, since gating on it would deadlock the run.
  • the base's own wall rules, which stand apart from the component: the collapse on_bbox_faces subset rule and the smoothing wall freeze – which the component's smooth_before() deliberately bypasses in favour of envelope containment.
  • diagnostics.

◆ vertex_is_on_region()

bool wmtk::components::topological_offset::TopoOffsetTriMesh::vertex_is_on_region ( const size_t  vid) const
inline

Is this vertex on a region boundary – a tag boundary, or the domain wall.

Derived, not stored, exactly as in 3D. Both halves are already maintained: m_is_on_region by the split/collapse hooks, on_bbox_faces by set_intersection of the split endpoints and by the collapse rule that a wall vertex may only merge into one at least as constrained.

◆ vertex_is_on_surface()

bool wmtk::components::topological_offset::TopoOffsetTriMesh::vertex_is_on_surface ( const size_t  vid) const
overridevirtual

The substructure the link condition is evaluated against, derived not cached.

substructure_link_condition() is only as good as these answers. Cached edge tags are refreshed once per iteration, which is too coarse: the split pass creates edges the tagging never classified, so the collapse pass that follows would evaluate against a substructure that no longer describes the mesh – which is why split and collapse tear the region together while each is safe alone. Computing from the face labels on demand cannot go stale.

Reimplemented from wmtk::TriMesh.

◆ warn_if_offset_reaches_domain_boundary()

void wmtk::components::topological_offset::TopoOffsetTriMesh::warn_if_offset_reaches_domain_boundary ( ) const

Warn if the offset band has grown into the domain boundary.

When target_distance exceeds the clearance between the input complex and the bounding box, construction runs out of room and the band's outer boundary becomes the box itself.

Two things then go wrong invisibly: those vertices are on the bbox and cannot be moved, so the target distance is unreachable there, and compute_distance_deviation() cannot even see them – it skips edges with no opposite face, which is what a band edge on the domain boundary is, so the clipped stretch enters neither max_dist_err nor avg_dist_err. The run then looks like a near-miss and is a structural failure, hence a warning, not a debug line.

◆ weighted_amips()

double wmtk::components::topological_offset::TopoOffsetTriMesh::weighted_amips ( const double  amips) const
inline

The AMIPS part of the energy: offset_amips_weight times AMIPS, the MAX_ENERGY sentinel (unscoreable) passed through unscaled so that it stays the largest value anywhere.

◆ write_debug_pvd()

void wmtk::components::topological_offset::TopoOffsetTriMesh::write_debug_pvd ( ) const

DEBUG_output: rewrite <output>{_main,_off,_surf,_edge,_front}.pvd, a ParaView time series over the debug frames. Needed because ParaView only groups a file series when the index is immediately before the extension, which is false for every companion (<output>_NNNNN_off.vtu). Called after every frame, so a killed run still opens.

◆ write_phi_grid()

void wmtk::components::topological_offset::TopoOffsetTriMesh::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.

The offset is a level set of a field defined everywhere and the output mesh only samples that field along one curve, so a result that looks wrong cannot be diagnosed from the mesh alone. This writes the field itself: Phi (clamped, since it diverges on the input complex), the residual as a length, and the exact Euclidean distance beside it, all as vertex fields on a triangulated grid so a viewer can draw the isoline Phi = c directly.

Parameters
nsamples per side; 0 or 1 writes nothing.

◆ write_smoothing_debug_output()

void wmtk::components::topological_offset::TopoOffsetTriMesh::write_smoothing_debug_output ( const std::string &  path) const
inlineoverridevirtual

Put the optimization's frames on the run's single debug timeline (see write_debug_frame()), labelled "r<turn><tag><pass>_<op>" / "r<turn><tag>_end", tag S in the loop and F in the final pass; led by m_frame_prefix (i during init_optimize). Same scheme as 3D.

Implements wmtk::TriOptimizerMesh.

Member Data Documentation

◆ INPUT_SURFACE_CLASS

constexpr int wmtk::components::topological_offset::TopoOffsetTriMesh::INPUT_SURFACE_CLASS = 0
staticconstexpr

SurfaceTagAttributes::m_surface_class: which of the two tracked surfaces an edge belongs to. Same scheme as 3D.

OFFSET is the surface the optimization places at target_distance. Everything else – the input complex, another body's outline, an overlap seam, the domain wall – keeps the primary class 0 and is envelope-checked by the shared operations exactly as in triwild and simwild. The distinction has to exist: filing a region boundary under OFFSET drives placement at vertices nowhere near target_distance and leaves the sizing field refining there forever. Class 0 is not split further – the boundary mask says which tubes hold a simplex, per tag.

◆ iter_cnt_collapse_energy_reject

std::atomic<int> wmtk::components::topological_offset::TopoOffsetTriMesh::iter_cnt_collapse_energy_reject {0}
mutable

The energy rules (see tri_energy()): collapses refused for raising the max energy over the survivor's ring, swaps for not strictly lowering it over the faces they make.

◆ iter_cnt_split_offset_before

std::atomic<int> wmtk::components::topological_offset::TopoOffsetTriMesh::iter_cnt_split_offset_before {0}

Splits of an offset-boundary edge: offered, accepted. Real attempts only: a screened pass's dry runs are not counted.

◆ kGradBins

constexpr int wmtk::components::topological_offset::TopoOffsetTriMesh::kGradBins = 22
staticconstexpr

Diagnostic: where the front solves stop. Per pass, histograms of the final gradient norm of each front solve (log10 bins, -14..+7) and of its ratio to the solve's first gradient norm, read from polysolve's Criteria after the solve. As in 3D.

◆ kNeedleQuality

constexpr double wmtk::components::topological_offset::TopoOffsetTriMesh::kNeedleQuality = 1e6
staticconstexpr

What counts as a needle for the tripwire – deliberately far below MAX_ENERGY.

A healthy triangle is O(2); 1e6 is far outside anything the optimizer should tolerate and far below the sentinel, so the creation event is caught while its parent is still scoreable and can be quoted. A >= MAX_ENERGY test misses parents that are already catastrophically flat, which is where the collinearity actually originates.

◆ kSmoothRelGradNormTol

constexpr double wmtk::components::topological_offset::TopoOffsetTriMesh::kSmoothRelGradNormTol = 1e-6
staticconstexpr

The Newton stopping rule every smoother of this component runs with, set on the thread's shared solver (the engine's m_solver slot) by smoothing_solver() at the start of every visit: relative gradient tolerance 1e-6 beside the engine's absolute 1e-10. See the 3D twin for the measurement behind it.

◆ m_collapse_edge_link

wmtk::threading::enumerable_thread_specific<std::vector<size_t> > wmtk::components::topological_offset::TopoOffsetTriMesh::m_collapse_edge_link
mutable

The link of the collapsed edge, captured in collapse_before_vertex(): the only vertices besides v2 whose offset membership a collapse can change.

The chords that DIE are the ones carrying both endpoints – in 2D only the edge itself, and the faces (v1, v2, w) for w in the link take their two chords (v1, w) and (v2, w) down to one – so only v2 and those w can lose their last front chord; nothing but v2 can gain, since edges only ever move from v1 to v2. Captured before the collapse because the edge is gone by collapse_after_vertex(), which is where the refresh runs. As in 3D.

◆ m_collapse_energy_before

wmtk::threading::enumerable_thread_specific<double> wmtk::components::topological_offset::TopoOffsetTriMesh::m_collapse_energy_before
mutable

The collapse energy rule's before-half: the max of tri_energy() over the one-rings of v1 and v2, cached by collapse_before_vertex() and compared by collapse_edge_after().

◆ m_collapse_survivor_sizing

wmtk::threading::enumerable_thread_specific<double> wmtk::components::topological_offset::TopoOffsetTriMesh::m_collapse_survivor_sizing
mutable

The collapse survivor's own sizing scalar, recorded in collapse_edge_before() and put back in collapse_edge_after() when sizing_collapse_min is false; see that key.

◆ m_complex_tag

constexpr int64_t wmtk::components::topological_offset::TopoOffsetTriMesh::m_complex_tag = -3
staticconstexpr

pseudo-tag: the input complex boundary

◆ m_construction_distance

double wmtk::components::topological_offset::TopoOffsetTriMesh::m_construction_distance = 0.

The distance the marching's sphere trace aims for: target_distance, or half the maximum marchable distance under construction_mode "max_marchable_fallback" (see the marching).

◆ m_converged

bool wmtk::components::topological_offset::TopoOffsetTriMesh::m_converged = false

The run's verdict: the front resolved (EnergyCriterion::converged(): every front chord's measure within the bar, nothing unmeasurable) AND the final quality under stop_energy. Read by the report and by throw_on_nonconvergence.

◆ m_cross_own

std::atomic<size_t> wmtk::components::topological_offset::TopoOffsetTriMesh::m_cross_own {0}

Per smoothing pass: accepted front moves that took the moved vertex itself (own), or one of its front neighbours (neighbour), from a ring measure <= 1 to > 1.

◆ m_cross_ring

std::vector<double> wmtk::components::topological_offset::TopoOffsetTriMesh::m_cross_ring

DEBUG_crossings (log-only). The ring measure of every front vertex as the last pass left it (NaN where a vertex has none), the positions it was taken at, and whether it is current (consolidation renumbers vertices, so the loop clears it). A pass's line counts the front vertices whose ring measure went from <= 1 to > 1 (up), back (down), new vertices already over (a split's, or an old id at a new position after a storage retry), and vertices over the bar that the pass removed. As in 3D.

◆ m_curve_tag

int64_t wmtk::components::topological_offset::TopoOffsetTriMesh::m_curve_tag = -1

Tag id of the input's curve group (the .msh line elements), or -1. An open curve has no face set whose boundary it is, so it is selectable only through this tag: offset_selection naming it makes the curve the complex and the band grows on both of its sides.

◆ m_debug_frame_labels

std::vector<std::string> wmtk::components::topological_offset::TopoOffsetTriMesh::m_debug_frame_labels
mutable

DEBUG_output: the label of each debug frame, indexed by its sequence number, and, per companion suffix, the frame indices that actually produced one. Both exist only to write the ParaView collections – see write_debug_pvd(). Same in 3D.

◆ m_debug_pass

int wmtk::components::topological_offset::TopoOffsetTriMesh::m_debug_pass = 0
mutable

Pass index within the current turn, and the (turn, loop-or-final-pass tag, prefix) it belongs to – when those change the index restarts. All exist only to name frames.

◆ m_energy_verdict

std::optional<EnergyCriterion> wmtk::components::topological_offset::TopoOffsetTriMesh::m_energy_verdict

The energy criterion as measured when the loop converged; the final pass runs after it and the verdict must not be re-measured on that mesh.

◆ m_front_veto_asked

std::atomic<size_t> wmtk::components::topological_offset::TopoOffsetTriMesh::m_front_veto_asked {0}

The front veto (smooth_front_vertex()): moves whose Newton solve succeeded and reached the veto, and how many it refused for raising the ring's max tri_energy. Reported and reset per pass beside the Newton counters.

◆ m_init_optimize

bool wmtk::components::topological_offset::TopoOffsetTriMesh::m_init_optimize = false

init_optimize: optimize_offset() opens with a stencil_order loop without refinement. Set by marching_tris(), only when the target is beyond the maximum marchable distance.

◆ m_input_complex_bvh

std::shared_ptr<SimplicialComplexBVH> wmtk::components::topological_offset::TopoOffsetTriMesh::m_input_complex_bvh

The input complex as loaded. Built once, never rebuilt.

It answers the Euclidean distance to the input, a diagnostic rather than the definition of the offset – see m_offset_potential, which is what the optimization is driven by. init_input_complex_bvh() has one call site, before construct_offset() runs, so this holds the original geometry however the elements representing the complex are later remeshed.

That invariant is load-bearing: rebuilding from the live mesh would redefine the offset distance in terms of a surface the optimizer had just moved, and the convergence criterion would be measuring the mesh against itself.

The only structure over the input complex. For offset_field "euclidean" the potential shares this very object as its query engine, which is why it is a shared_ptr. Containment is not its job – the per-tag region envelopes (m_tag_envelopes) hold the complex in place.

◆ m_isect_cache

std::map<uint64_t, std::shared_ptr<Envelope> > wmtk::components::topological_offset::TopoOffsetTriMesh::m_isect_cache
mutable

Memoized IntersectionEnvelope per multi-bit mask. Lazily built under the mutex because the queries that need them run concurrently under kPartition.

◆ m_marching_root_splits

size_t wmtk::components::topological_offset::TopoOffsetTriMesh::m_marching_root_splits = 0

marching_tris() tallies for the construction log: edges placed on the level set / at the midpoint because the trace left the edge, and the trace steps (total, max). Reset at the start of marching_tris().

◆ m_n_regions

int wmtk::components::topological_offset::TopoOffsetTriMesh::m_n_regions = 0

One field per connected piece of the input complex, and which one each band vertex is placed on.

m_offset_potential above is built over the whole selected complex: the sum of every piece's barrier for the smooth potential, the distance to the nearest piece for the Euclidean field. Neither is the field a front should be placed on where two pieces are close – the sum has no level set at all across a narrow gap, so both fronts are pushed through the background strip until inversion.

A region is a connected piece, not a tag: one tag covering two pieces that never touch would make them share a field and bring that bridging back. Pieces are the connected components of the captured complex under vertex connectivity (two pieces meeting at a point share an offset there, so they share a field), computed once in init_input_complex_bvh() so the numbering is fixed for the whole run. simplicial_embedding() is what makes this correspond to the band: no background triangle can touch two disjoint pieces, so the band's connected components are the disjoint offsets.

A band grown from one piece is placed on that piece's field alone: Phi_A = c for band A, Phi_B = c for band B. Where the two would overlap, each front is pulled outward by its own field and held by the strip's quality bar – a symmetric local minimum with a thin gap of background between the fronts, which is the topological offset.

The map from band to region is assign_band_regions(): a flood fill over the band faces, seeded from every band face with a complex vertex, whose piece is read off the captured complex geometrically. A face reachable from two regions and a vertex on faces of two regions read -2 and fall back to the union field. m_offset_potential is kept for everything that is not per-vertex: the support (dhat), the viewer's grid, the report. connected pieces of the input complex; one field each

◆ m_needle_pre

wmtk::threading::enumerable_thread_specific<std::pair<double, Vector2d> > wmtk::components::topological_offset::TopoOffsetTriMesh::m_needle_pre
mutable

Why smoothing does not lift a sliver's apex off its opposite edge.

Interleaved smoothing is on by default, so every needle-adjacent vertex is visited after every topological pass. These counters say what happens when it is:

  • offered : smooth_before() entered with a needle already in the one-ring
  • reached : the solve produced a candidate and smooth_after() saw it
  • fixed : that candidate actually dropped the ring's worst below kNeedleQuality
  • stationary: the candidate moved the vertex less than 1e-12 – the solve found nothing

offered minus reached is the search failing outright; reached minus fixed is a move being made that does not repair the sliver. The two have different causes and the fix for one is not the fix for the other, which is why they are counted separately.

◆ m_newton

optimization::NewtonCounters wmtk::components::topological_offset::TopoOffsetTriMesh::m_newton

How the solves this class makes itself ended, per smoothing pass. Background: every smooth_nonfront_vertex() solve – the 3D engine counts these itself (its m_newton), the 2D engine does not, so the component keeps the counter. Front: every front placement. Plastic: the rest-shape solve of smooth_plastic_vertex(). Logged and reset by log_smoothing_pass_accounting().

One difference from 3D, read before comparing the two logs: the 2D engine's smoother (optimization::smooth_vertex_2d) catches a solver throw internally and reports nothing, so the threw count is always 0 here; a solve that threw shows only through its stop status (LineSearchFailed, the throw polysolve raises).

◆ m_newton_repulsion

optimization::NewtonCounters wmtk::components::topological_offset::TopoOffsetTriMesh::m_newton_repulsion

Per repulsion pass or round, as m_newton_front and the front veto counters are per loop pass.

◆ m_offset_isect_cache

std::map<uint64_t, std::shared_ptr<Envelope> > wmtk::components::topological_offset::TopoOffsetTriMesh::m_offset_isect_cache
mutable

Memoized "region tubes AND the offset envelope", keyed by the region mask.

A simplex can be on both, so this is always their intersection, never an either/or.

Separate from m_isect_cache because the members differ in lifetime: the tag envelopes live for the whole run, m_offset_envelope is built for the final pass. rebuild_offset_envelope() clears this and must keep doing so. Guarded by m_isect_mutex.

◆ m_offset_potential

std::shared_ptr<OffsetPotential2D> wmtk::components::topological_offset::TopoOffsetTriMesh::m_offset_potential

The smooth offset potential, and with it the definition of the offset itself.

The offset boundary is the level set Phi = c. Built from the same extraction as m_input_complex_bvh, in the same call, so the two describe the same geometry and the same never-rebuilt rule applies. See OffsetPotential for what Phi is.

shared_ptr because OffsetEnergy2D holds one per smoothing call.

◆ m_phi_V

MatrixXd wmtk::components::topological_offset::TopoOffsetTriMesh::m_phi_V

The complex as the potential sees it: vertices, its boundary segments, and its isolated points. Filled by init_input_complex_bvh(), consumed by init_offset_potential().

◆ m_plastic_active

bool wmtk::components::topological_offset::TopoOffsetTriMesh::m_plastic_active = false

set in optimize_offset() when deform_others

Plastic medium: under deform_others every background face – ambient and the other objects alike – is plastic, its rest shape re-stamped before every operation group, so smoothing resists only the increment since the group started and the medium flows instead of behaving as an elastic solid glued to the walls. The band (label 2) and the complex (label 1) are not plastic; element quality in the medium is the operation passes' job.

◆ m_quality_converged

bool wmtk::components::topological_offset::TopoOffsetTriMesh::m_quality_converged = true

The finishing-pass half of the verdict: max AMIPS < stop_energy once the front is resolved, after the final pass when one ran. True when no pass was needed; false when the pass ended still over. m_quality_max_amips is the value it was judged on.

◆ m_released_envelope

std::shared_ptr<Envelope> wmtk::components::topological_offset::TopoOffsetTriMesh::m_released_envelope
mutable

The released boundaries' ops-only tube: an Envelope around the current deformed boundaries, consulted only by surface_envelope_for_edge() – the dispatch every operation containment check goes through and no smoothing path does – so operations preserve the current shape through remeshing while smoothing stays free to carry the object. Rebuilt lazily by released_envelope() when m_released_tube_dirty says a smoothing accept may have moved the boundary.

◆ m_repulsion_potential

std::shared_ptr<const OffsetPotential2D> wmtk::components::topological_offset::TopoOffsetTriMesh::m_repulsion_potential

The repulsion passes and rounds (repulsion_smoothing()), set only while they run: the Euclidean field at level 2 x target_distance. Null otherwise, so every other path is untouched.

◆ m_repulsion_term

std::shared_ptr<OffsetEnergy2D> wmtk::components::topological_offset::TopoOffsetTriMesh::m_repulsion_term

O(v) = (max(0, 2 delta - d(v)) / front_conv)^2: the Euclidean residual against level 2 delta is (d - 2 delta) / (2 delta), so the weight (2 delta / front_conv)^2 = 4 offset_term_weight() puts it in units of the tolerance, as the front's terms are. One object for the whole repulsion (value() changes nothing in it, so threads share it); set with m_repulsion_potential.

◆ m_round

int wmtk::components::topological_offset::TopoOffsetTriMesh::m_round = 0

The turn the run is in, 1-based; 0 before the loop starts. Read only by write_smoothing_debug_output(), to tag each frame with the turn it belongs to.

◆ m_source_tags

std::set<int64_t> wmtk::components::topological_offset::TopoOffsetTriMesh::m_source_tags

The source tags (offset_selection's tags_involved), stored at release so the ops-only tube's edge classification applies the same never-freed rule the release did.

◆ m_stuck_prev_cells

std::set<std::pair<long, long> > wmtk::components::topological_offset::TopoOffsetTriMesh::m_stuck_prev_cells

Quantised centroids of the MAX_ENERGY faces at the previous stuck-refine, for the overlap line above. Diagnostic only; nothing reads it but log_stuck_refine_census().

◆ m_swap_energy_before

wmtk::threading::enumerable_thread_specific<double> wmtk::components::topological_offset::TopoOffsetTriMesh::m_swap_energy_before
mutable

The swap energy rule's before-half: the max of tri_energy() over the two faces the flip replaces, cached by swap_edge_before() and compared by swap_edge_after() and, early, by swap_quality_allowed(). The 3D twin is SwapEnergyBefore, which also carries the band cells beyond a 3-2 flip's faces; a 2D flip replaces two faces with two over the same quad, every quad edge keeping its outer face, so there is no "outside" to add.

◆ m_tag_bit

std::map<int64_t, int> wmtk::components::topological_offset::TopoOffsetTriMesh::m_tag_bit

Input tag id -> bit position in VertexExtra2d::m_boundary_mask. Assigned in init_from_image() once the tag maps are complete; at most 64 input tags.

◆ m_tag_envelopes

std::map<int64_t, std::shared_ptr<Envelope> > wmtk::components::topological_offset::TopoOffsetTriMesh::m_tag_envelopes

One containment envelope per input tag, ambient included. Both phases.

E_t is a tube of half-width m_envelope_eps around region t's boundary segments as the input mesh carried them, built in init_surfaces_and_boundaries() before offset construction: the band's tags replace a face's own, so an envelope built later would be a tube around a curve truncated at the band. A simplex on several boundaries is held by the intersection of its tags' tubes (envelope_for_mask()), which pins junction points to the junction itself.

m_envelope (the base's pointer) survives as a UnionEnvelope over these members, purely so the shared engine's direct uses of it – the collapse_edge_before point check and the "segment does not exist yet" fallback here – keep union semantics.

Interior edges of a region are not held by these: identical tag sets on both sides land in no bucket, so a filled complex's interior is free to optimise.

◆ m_worst_dist_vid

size_t wmtk::components::topological_offset::TopoOffsetTriMesh::m_worst_dist_vid = static_cast<size_t>(-1)
mutable

The vertex compute_distance_deviation() last found the max at, and a dump of everything that could be stopping it from moving. Diagnostic only.

◆ optimization_metrics

std::vector<std::array<double, 8> > wmtk::components::topological_offset::TopoOffsetTriMesh::optimization_metrics

{max_dist_err, avg_dist_err, max_phi_residual, avg_phi_residual, max_grad, avg_grad, max_grad_at_vertex, max_grad_in_edge}. max_grad is the convergence criterion – the full placement-gradient norm at band vertices – so max_grad_at_vertex repeats it and max_grad_in_edge is the chord diagnostic; the rest are diagnostics. One entry for the whole run, as in 3D.


The documentation for this class was generated from the following files: