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  DistanceSplit
 The band's distance error, split by whether the optimizer can do anything about it. More...
 
struct  EdgeSamples
 The residual sampled at points along a band edge – see offset_edge_samples(). More...
 
struct  EdgeSnapshot2d
 An edge's / face's shared attributes together with the offset's own label. More...
 
struct  EdgeSplitCache
 
struct  EnergyCriterion
 The "energy_gradient" criterion: the front is at a critical point of Phase B's energy, and every edge resolves the pull that drives it there. More...
 
struct  FaceSnapshot2d
 
struct  FaceSplitCache
 
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  SmoothTrace
 What smoothing did with each class of vertex, per pass. More...
 
struct  TagPolyline2d
 The per-tag boundary polyline, with the adjacency an arclength walk needs. More...
 

Public Types

enum class  EdgeSplitMode { Midpoint = 0 , Optimization = 2 }
 
enum class  OptPhase { A , B , Single }
 Which half of the alternating optimization is running. More...
 
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 >
 
- 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 ()
 
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 OffsetPotential2Dpotential_for_region (const int region) const
 
const OffsetPotential2Dpotential_for (const size_t vid) const
 
std::shared_ptr< const OffsetPotential2Dpotential_ptr_for (const size_t vid) const
 
const OffsetPotential2Dpotential_for_edge (const size_t va, const size_t vb) const
 
const OffsetPotential2Dpotential_for_face (const size_t fid) const
 
std::shared_ptr< SampleEnvelopecontainment_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.
 
bool project_into_containment (size_t vid, Vector2d &x) const
 Move x back inside every region tube this vertex lies on. True if it ended up inside all of them.
 
int64_t tangent_curve_tag (size_t vid, const Vector2d &x) const
 Which tag's boundary curve a vertex slides along, or -1.
 
bool walk_along_curve (int64_t tag, const Vector2d &x, double s, Vector2d &out) const
 March s of arclength along tag tag's boundary polyline from x's foot on it.
 
bool curve_tangent (int64_t tag, const Vector2d &x, const Vector2d &prefer, Vector2d &tau) const
 The unit tangent of tag tag's curve at x's foot, or false if there is none.
 
bool phase_places_front () const
 
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 check_no_vertex_on_both_surfaces (const char *when) const
 
void optimize_offset_single_phase ()
 TriWild's loop, the front placed inside its smoothing passes.
 
double phase_b_front_gradient_linf ()
 
 TopoOffsetTriMesh (Parameters &_m_offset_params, int _num_threads=0)
 
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
 Is an edge part of the substructure.
 
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 init_offset_sizing_field ()
 
bool marching_split_edge_before (const Tuple &t)
 
bool marching_split_edge_after (const Tuple &t)
 
bool collapse_edge_before (const Tuple &t) override
 Reject any collapse that violates the substructure link condition.
 
bool swap_edge_before (const Tuple &t) override
 Reject a flip whose new edge already exists.
 
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.
 
bool edge_is_on_domain_boundary (const size_t eid) const
 
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.
 
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}.
 
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< SampleEnvelopeenvelope_for_mask (uint64_t mask) const
 The envelope a simplex with this boundary mask is contained in, or null.
 
std::shared_ptr< SampleEnvelopesurface_envelope_for_edge (const std::array< size_t, 2 > &vids) const override
 Class-0 segments – every region boundary, the input complex and the domain wall included – carry a containment requirement; the offset boundary does not.
 
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< SampleEnvelopesmoothing_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< SampleEnvelopesmoothing_containment_envelope (const size_t vid) const override
 ... and it is not contained by one either, except in Phase A.
 
bool smooth_front_vertex_phase_b (const Tuple &t)
 Phase B placement of a front vertex: the shared smoother with the offset's options.
 
double front_vertex_normal_gradient (size_t vid) const
 
Vector2d front_vertex_move_direction (size_t vid) const
 
bool front_vertex_alignment_traps_1d_solve (size_t vid) const
 
double front_vertex_conv_ratio (size_t vid) const
 
double edge_conv_ratio (const Tuple &e) const
 The edge test divided by its bar (1 = bar), per front_conv_criterion; -1 unmeasurable.
 
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 > phase_b_front_objective (size_t vid, const Vector2d &x) const
 
std::shared_ptr< polysolve::nonlinear::Problem > smoothing_extra_energy (const size_t vid) const override
 
std::shared_ptr< SampleEnvelopereleased_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 > phase_b_front_energy (size_t vid, const std::shared_ptr< const OffsetPotential2D > &pot) const
 
std::tuple< double, double > optimization_quality_stats () override
 The loop's convergence metric, normalized so that 1.0 means "done".
 
double optimization_stop_metric () const override
 1.0 in Phase B, where the metric is normalized; the base's stop_energy in Phase A.
 
int offset_residual_samples () const
 
double offset_residual_tolerance () const
 
double offset_gradient_tolerance () const
 The convergence tolerance: the bound on |grad (Phi - c)^2| at a band vertex.
 
double gradient_reference () 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
 
EdgeSamples offset_edge_samples (const Tuple &e) const
 The Phi residual at offset_residual_samples interior points of band edge e.
 
template<typename Visit >
void for_each_offset_edge_sample (const Tuple &e, Visit &&visit) const
 Visit the same interior sample points offset_edge_samples() measures on.
 
GradientSplit gradient_split (bool include_edge_samples=true) const
 
EnergyCriterion energy_criterion ()
 
bool front_vertex_touches_other (size_t vid) const
 
double front_chord_target (size_t va, size_t vb, double len, double sag, double tube) const
 
size_t refine_front_from_sag (const std::vector< EnergyCriterion::Refinable > &edges)
 
double edge_interpolation_residual (const Tuple &e) const
 The interpolation residual of front edge e, see EnergyCriterion. -1 when unmeasurable.
 
Vector2d offset_vertex_normal (const size_t vid) const
 The normal at an offset vertex. Every caller that needs one goes through here, so switching the definition is a one-line edit rather than a hunt through the call sites.
 
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 Phase A 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 v2, double q, double ring_max) const override
 Instrumentation only: which operation manufactures the MAX_ENERGY needles.
 
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
 
bool collapse_edge_after (const Tuple &t) override
 A collapse is accepted by the same criterion the smoothing minimises.
 
double face_criterion_rel (const size_t fid) const
 
void write_smoothing_debug_output (const std::string &path) const override
 Put the frames beside the run's own output, and rename them into one timeline.
 
void append_frame_label (size_t idx, const std::string &label) const
 
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 pre_optimize_input_mesh ()
 TriWild over the input mesh, before any of the offset exists.
 
void execute_offset (const std::filesystem::path &output_file)
 
void marching_tris ()
 execute simplistic marching tris. All edges with one vertex labelled 0 and the other 1/2 are split, always at the midpoint.
 
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)
 
void write_msh_groups (const std::string &file)
 
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
 
std::vector< Tupleget_edge_adjacent_faces (const Tuple &f) const
 get faces (as Tuples) that are edge-adjacent to the given face (as Tuple)
 
- Public Member Functions inherited from wmtk::TriOptimizerMesh
 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 ()
 
virtual void update_attributes ()
 Update the attributes of the mesh after an iteration of operations.
 
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 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 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.
 
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_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
 
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.
 
- 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< Tupleget_vertices () const
 
std::vector< Tupleget_edges () const
 
std::vector< Tupleget_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
 
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< Tupleswitch_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< Tupleswitch_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< Tupleget_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< Tupleget_one_ring_edges_for_vertex (const Tuple &t) const
 Get all edges that are incident to the vertex of Tuple t.
 
std::vector< Tupleget_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
 
Tuple tuple_from_cell (size_t cid) const
 
- 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

int m_vtu_counter = 0
 
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< SimplicialComplexBVHm_input_complex_bvh
 The input complex as loaded. Built once, never rebuilt.
 
std::shared_ptr< OffsetPotential2Dm_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< SampleEnvelope > > m_tag_envelopes
 One containment envelope per input tag, ambient included. Both phases.
 
std::vector< Eigen::Vector2d > m_env_polyline_V
 
std::map< int64_t, TagPolyline2dm_tag_polyline
 
std::map< int64_t, int > m_tag_bit
 
std::map< uint64_t, std::shared_ptr< SampleEnvelope > > m_isect_cache
 
std::mutex m_isect_mutex
 
std::map< uint64_t, std::shared_ptr< SampleEnvelope > > m_offset_isect_cache
 Memoized "region tubes AND the offset envelope", keyed by the region mask.
 
OptPhase m_phase = OptPhase::A
 Which phase is running. Read by every hook that differs between them; see OptPhase.
 
bool m_freeze_front = false
 The final Phase A: front vertices are not smoothed (see smooth_before()).
 
std::shared_ptr< SampleEnvelopem_offset_envelope
 The tube the offset boundary may not leave during Phase A, of half-width offset_envelope_rel x target_distance. Rebuilt at the end of every Phase B from the boundary as that phase left it, which is what lets the boundary travel across rounds. Non-null once the offset exists; whether it constrains is containment_for()'s phase test, not the pointer. Unlike m_tag_envelopes, which must never be rebuilt.
 
double m_front_gradient_reference = 0.
 
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
 
Parametersm_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
 
std::vector< std::array< int, 3 > > churn_counts
 
std::vector< std::array< int, 3 > > op_counts
 
int m_ab_round = 0
 
size_t m_debug_seq = 0
 Monotonic frame counter for the debug timeline. Mutable because the write hook is const.
 
int m_debug_pass = 0
 
int m_debug_last_round = -1
 
char m_debug_last_phase = '?'
 
double m_gradient_reference = 0.
 See offset_gradient_tolerance(). Nothing sets it on the single-phase path; it stays 0.
 
bool m_converged = false
 
bool m_quality_converged = true
 
double m_quality_max_amips = 0.
 
std::atomic< int > iter_cnt_split_born {0}
 
std::atomic< int > iter_cnt_recollapsed {0}
 
std::atomic< int > iter_cnt_recollapsed_same_pass {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 face over tolerance.
 
std::atomic< int > iter_cnt_swap_offset_reject {0}
 
std::atomic< int > iter_cnt_split_offset_before {0}
 Splits of an offset-boundary edge: offered, accepted.
 
std::atomic< int > iter_cnt_split_offset {0}
 
wmtk::threading::enumerable_thread_specific< OptSplitCache2dm_opt_split_cache
 
SmoothTrace m_smooth_trace
 
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
 
std::atomic< int > m_placement_env_entry_outside {0}
 
std::atomic< int > m_placement_projected {0}
 
std::atomic< int > m_placement_tangential {0}
 
size_t m_front_gradient_worst_vid
 
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< SampleEnvelopem_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< EnergyCriterionm_energy_verdict
 
std::vector< char > m_placement_pressed
 
std::atomic< size_t > m_deg_split_created {0}
 
std::atomic< size_t > m_deg_collapse_offered {0}
 
std::atomic< size_t > m_deg_collapse_allowed {0}
 
std::atomic< size_t > m_deg_collapse_by_ringmax {0}
 
std::atomic< size_t > m_deg_collapse_by_stop {0}
 
std::atomic< size_t > m_deg_collapse_by_unrounded {0}
 
std::array< size_t, 6 > m_deg_prev_counts {{0, 0, 0, 0, 0, 0}}
 Values at the previous census, so each census can report deltas rather than totals.
 
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
 
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.
 
OptimizerParametersm_params
 
std::shared_ptr< SampleEnvelopem_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::Edgem_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
 
CoarsenStats m_coarsen_stats
 
- Public Attributes inherited from wmtk::TriMesh
AbstractAttributeContainerp_vertex_attrs = nullptr
 
AbstractAttributeContainerp_edge_attrs = nullptr
 
AbstractAttributeContainerp_face_attrs = nullptr
 
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 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 Public Attributes inherited from wmtk::TriMesh
static constexpr int EDGES_PER_CELL = 3
 

Private Member Functions

bool any_tag_present (const CellTag &tag1, const CellTag &tag2) const
 determine if any tag from tag1 is also present in tag2.
 
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< EdgeSplitCacheedge_split_cache
 
wmtk::threading::enumerable_thread_specific< FaceSplitCacheface_split_cache
 

Additional Inherited Members

- Protected Member Functions inherited from wmtk::TriOptimizerMesh
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 optimization_debug_checkpoint ()
 Called at every pass boundary, whether or not debug output is on.
 
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< SplitInfoCachesplit_cache
 
wmtk::threading::enumerable_thread_specific< CollapseInfoCachecollapse_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.

◆ OptPhase

Which half of the alternating optimization is running.

The two criteria are optimized in turn, not jointly. The 2D counterpart of TopoOffsetTetMesh::OptPhase.

Phase A is TriWild and nothing else: same operations, gates, sizing field and stall-driven refinement, with no offset energy term, acceptance criterion or stop metric. Its one addition is m_offset_envelope.

Phase B moves the offset boundary and nothing else: smoothing passes against the offset energy, run to a fixed point, with no envelope on the offset (it is what has to travel) and no topological operations at all.

The sizing field is shared and both phases write it: Phase A through TriWild's stall refinement on element quality, Phase B through the Phi residual of the faces smoothing could not place. Single is the mode the 2D run uses: TriWild's own loop with the front placed by Phase B's objective inside the smoothing passes. It follows B wherever the smoother is concerned (which objective a front vertex gets, no offset tube while it moves) and A everywhere the loop is concerned (quality stats and the stop metric are TriWild's).

Member Function Documentation

◆ append_frame_label()

void wmtk::components::topological_offset::TopoOffsetTriMesh::append_frame_label ( size_t  idx,
const std::string &  label 
) const

One line of <output>_frames.txt; truncates the file on the first frame. See write_smoothing_debug_output().

◆ 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 Phase A moved the offset boundary out of the tube holding it where Phase B left 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 (TagEnvelopes.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, and the pre-optimize pass holds every tag boundary as it always did), 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 and after every phase.

◆ 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

A collapse is accepted by the same criterion the smoothing minimises.

The smoother places an offset vertex by minimising w (Phi - c)^2, so every other operation must answer to that same measure or it undoes in one collapse what the smoother spent an iteration achieving. A length gate cannot express it: it asks whether an edge is short against a sizing target, a statement about the mesh, not whether the boundary is still the offset, a statement about the geometry – and only the second is what the run is for.

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.

The base applies it 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.

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  v2,
double  q,
double  ring_max 
) const
overridevirtual

Instrumentation only: which operation manufactures the MAX_ENERGY needles.

The base leaves two doors open and this counts what goes through each.

  • a split is never refused on quality (it checks orientation, rounding and containment only), so needles counted at a freshly split midpoint were created by that split.
  • a collapse is admitted by collapse_quality_allowed() when q <= ring_max, and once a single needle sits in the ring ring_max is MAX_ENERGY – so the clause admits a collapse that produces another needle. The counters below say which clause did the admitting.

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().

◆ containment_for()

std::shared_ptr< SampleEnvelope > 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, but only in Phase A – Phase B is the pass whose job is to move the offset boundary, so there the result is the region tubes alone.

◆ curve_tangent()

bool wmtk::components::topological_offset::TopoOffsetTriMesh::curve_tangent ( int64_t  tag,
const Vector2d &  x,
const Vector2d &  prefer,
Vector2d &  tau 
) const

The unit tangent of tag tag's curve at x's foot, or false if there is none.

From nearest_point_feature()'s seg_normal, rotated a quarter turn. At a polyline vertex the tangent is two-valued, so this takes the incident segment best aligned with the descent the caller is about to attempt – the standard reading of a one-sided derivative at a kink.

◆ 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: execute_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

Is an edge part of the substructure.

Parameters
vidsThe vertex IDs of the edge

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.

◆ envelope_for_mask()

std::shared_ptr< SampleEnvelope > 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 (AMIPS over stop, residual over tolerance) on this face; >= 1 means it fails at least one. The coarsen-mode collapse accept reads it, and it is the per-face form of optimization_quality_stats()'s Phase B max.

◆ 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.

◆ for_each_offset_edge_sample()

template<typename Visit >
void wmtk::components::topological_offset::TopoOffsetTriMesh::for_each_offset_edge_sample ( const Tuple e,
Visit &&  visit 
) const
inline

Visit the same interior sample points offset_edge_samples() measures on.

Factored out so the residual and the convergence gradient are measured on one lattice – a criterion sampled on a different set of points from the quantity the sizing field refines by is two measurements pretending to be one. The 3D twin is for_each_offset_face_sample().

◆ 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 sag 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). See the definition; energy_criterion() and refine_front_from_sag() both use it.

◆ front_vertex_alignment_traps_1d_solve()

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

Whether the 1-D placement at vid is trapped by the alignment term: a live front edge at or past perpendicular to the field AND the alignment term's 1-D gradient opposing the placement term's along the move direction. See the definition.

◆ front_vertex_conv_ratio()

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

The vertex's convergence measure divided by its bar, per front_conv_criterion: 1 is the bar. See the spec entry for the three measures. Infinite when unmeasurable.

◆ 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, F the objective smooth_front_vertex_phase_b() minimises. +inf if unmeasurable. The pass stop and the loop's vertex test.

◆ front_vertex_touches_other()

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

Whether a front vertex touches another front, the input or a region boundary through a background triangle – the topological fact behind the pressed state.

◆ gradient_reference()

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

max |2 (Phi - c) grad Phi . n| over the initial offset-surface vertices; the scale offset_gradient_tolerance() is a fraction of. Always 0 on the single-phase path (never measured); kept for the report.

◆ 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
V#V 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_offset_sizing_field()

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

Seed the sizing field from the offset's current edge lengths (paper Sec. 5.3.3, Step 1), once, before the first operation pass. Without it the field starts at the background target length and the first collapse pass decimates the offset.

◆ 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.

◆ 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 Phase A 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, in either phase. Construction cannot produce one, so a non-zero count here is a hole opened afterwards. Called at construction and at each Phase B entry so the two can be compared.

◆ log_stuck_refine_census()

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

Why Phase A is stuck: a census of the faces stuck-refine is about to chase.

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 ( )

execute simplistic marching tris. All edges with one vertex labelled 0 and the other 1/2 are split, always at the midpoint.

No target_distance enters construction at all: the paper places inserted vertices at the midpoint (Sec. 5.2) and leaves the distance to Step 3, so carrying the boundary out to the level set is entirely the optimization phase's job. Same as 3D.

◆ 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 pre-pass, 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 offset_residual_samples interior points of band edge e.

The criterion cannot be a vertex criterion: a boundary can have every vertex exactly on the level set while zig-zagging or cutting corners between them, which reads as converged and is not the offset. That is the gap the paper's normal-deviation criterion (Sec. 5.3.3) covered.

Sampling the edges is what the potential makes possible and a distance field did not: Phi is defined everywhere, so the offset can be measured anywhere along the band rather than only where the mesh happens to have put a vertex. The same samples feed face_criterion_rel(), so the sizing field refines a band too coarse to represent the offset instead of letting it decimate.

Samples are uniform interior points, i/(k+1) for i = 1..k, so k = 1 is the midpoint. Returns nothing for an edge with an unreachable endpoint: a segment running onto the input complex is legitimately closer than target_distance along its length.

◆ offset_gradient_tolerance()

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

The convergence tolerance: the bound on |grad (Phi - c)^2| at a band vertex.

A fraction of target_distance, which is the right unit: grad E = 2 (Phi - c) grad Phi, and grad Phi is dimensionless for a field whose value is a length, so grad E is a length.

The gradient needs no conversion of Phi's value into a length – it is the stationarity condition of the objective Phase B minimises, so it is the same test for the exact Euclidean field and for the smooth potential alike, which is what lets a reentrant input be judged by the same number as a convex one. See TopoOffsetTetMesh::offset_gradient_tolerance() for the full derivation of the slope normalization.

◆ offset_residual_samples()

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

Samples per band edge; see offset_edge_samples(). 0 falls back to a vertex-only criterion, which is measurably blind to a band too coarse to be the offset.

◆ offset_residual_tolerance()

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

The residual scale, derived from the criterion rather than configured beside it.

grad E = 2 (Phi - c) grad Phi, so on a field with unit slope at the level set the gradient bound |grad E| <= g is exactly |Phi - c| <= g/2: half the gradient tolerance, in length units. It feeds the Phase A offset envelope (offset_envelope_rel x this) and the derived min_edge_length floor, so loosening the criterion loosens the tube with it.

◆ offset_vertex_normal()

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

The normal at an offset vertex. Every caller that needs one goes through here, so switching the definition is a one-line edit rather than a hunt through the call sites.

n is the unit vector from the nearest point on the input complex to the vertex – the direction the offset grew along. A property of the input geometry alone, so it does not move as the offset mesh is re-triangulated and it is defined for every band vertex whether or not it has live offset edges. Known weakness: it flips discontinuously across the medial axis, exactly where two offset fronts approach each other.

Returns
unit vector, or the zero vector where the definition cannot produce one.

◆ 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: the opening one (length gate off), the closing one, and coarsen_mesh(). The opening pass is meant to run once on an inserted input, but the A/B loop calls mesh_improvement() every round, so with no length gate the quality test alone demolishes the band interior and background each round. The sizing field cannot refuse a collapse – only the length gate can, and that pass switches it off – and the offset envelope holds the boundary, not the interior. Phase A is otherwise TriWild's mesh_improvement(), untouched.

Reimplemented from wmtk::TriOptimizerMesh.

◆ optimization_quality_stats()

std::tuple< double, double > wmtk::components::topological_offset::TopoOffsetTriMesh::optimization_quality_stats ( )
overridevirtual

The loop's convergence metric, normalized so that 1.0 means "done".

The max of the two criteria this optimization has to meet, each divided by its own target, so mesh_improvement() stops exactly when both are met:

  • max face AMIPS over stop_energy – TriWild's, via quality_rel()
  • max Phi residual over (front_conv_rel / 2) * target_distance, over the reachable band

The average returned alongside it is the same expression over the two averages, so both numbers live on the same 1.0 scale. Nothing reads the average; it is logged.

Reimplemented from wmtk::TriOptimizerMesh.

◆ optimization_stop_metric()

double wmtk::components::topological_offset::TopoOffsetTriMesh::optimization_stop_metric ( ) const
inlineoverridevirtual

1.0 in Phase B, where the metric is normalized; the base's stop_energy in Phase A.

The units are part of "identical to TriWild", and getting this wrong is silent. The pair (optimization_quality_stats, optimization_stop_metric) has to be in one set of units, because refine_sizing_around_worst() derives its filter from the first and then compares that filter against a per-face score. Phase A ranks by m_face_attribute[].m_quality, which is absolute AMIPS, so its metric and its bar must be absolute too – otherwise select_worst_cells returns nothing, no sizing is refined, and the stall detector fires every iteration and does nothing.

Reimplemented from wmtk::TriOptimizerMesh.

◆ phase_b_front_energy()

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

The two offset terms for a front vertex, see smooth_front_vertex_phase_b(): the zeroth-order OffsetEnergy2D and the first-order AlignEnergy2D (one residual per incident live front edge). Defined in FrontSmooth2d.cpp, next to the criterion measuring the same quantities.

◆ phase_b_front_gradient_linf()

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

Max over the front vertices Phase B places of ||grad F||, F the vertex's full Phase B objective (AMIPS + the offset terms, as the shared smoother assembles it). The pass stop.

◆ phase_b_front_objective()

std::shared_ptr< polysolve::nonlinear::Problem > wmtk::components::topological_offset::TopoOffsetTriMesh::phase_b_front_objective ( size_t  vid,
const Vector2d &  x 
) const

The Phase B objective of front vertex vid with the vertex at x: AMIPS of its one-ring + phase_b_front_energy(). What the measure above differentiates.

◆ phase_places_front()

bool wmtk::components::topological_offset::TopoOffsetTriMesh::phase_places_front ( ) const
inline

Whether the smoother places front vertices against the offset objective: Phase B, and the single-phase mode that does the same thing inside TriWild's passes.

◆ 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.

◆ pre_optimize_input_mesh()

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

TriWild over the input mesh, before any of the offset exists.

Runs the shared mesh_improvement() with Phase A's own parameters and units, at a point where the only tracked surfaces are the tag-region boundaries (input complex and domain wall among them) and the only containment is their per-tag envelopes. There is no offset yet, so no offset envelope and no Phi term: this is TriWild, exactly.

Worth a pass because marching_tris() puts the offset boundary on the background triangulation's own cell boundaries: how far the constructed offset lands from the complex is a property of the input mesh, and init_offset_potential() sizes dhat from that reach. One coarse cell touching the complex inflates dhat several-fold, and a large dhat merges the level sets of features that are close together, which no later stage can undo.

The sizing field is seeded first, to target_distance on the input-complex boundary and graded outward: a band built on cells of delta scale lands near delta from the complex, which is what keeps dhat small. Unseeded, the base field is m_params.l everywhere, far coarser than the mesh around the complex, so the pass would coarsen the input instead.

◆ project_into_containment()

bool wmtk::components::topological_offset::TopoOffsetTriMesh::project_into_containment ( size_t  vid,
Vector2d &  x 
) const

Move x back inside every region tube this vertex lies on. True if it ended up inside all of them.

The projection half of the projected-gradient placement: an offset vertex a region envelope also holds takes the same unconstrained step on the same objective as any other, and this restores its validity. Refusal cannot express "go as far as you may" – a vertex whose every trial step leaves the tube never moves at all – while projecting keeps the component of the step the tube allows, which for a vertex on a boundary curve is motion along that curve.

Never asks a composite: nearest_point() and squared_distance() are non-virtual on SampleEnvelope and would bind to an IntersectionEnvelope's base subobject, whose BVH was never built (TagEnvelopes.hpp). So this walks the mask's real members from m_tag_envelopes and composes them by alternating projection onto the worst-violated one; each nearest_point() lands x on that member's curve, so the rounds converge a junction onto the intersection of its curves.

Returns false if the alternation did not converge, which is the caller's signal to keep the entry position rather than commit an invalid one.

◆ 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. Called when the offset is created and at the end of every Phase B.

◆ refine_front_from_sag()

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

The resolution rule: sets the target length at each refinable edge's ends from front_chord_target(), graded outward. Returns the vertices changed.

◆ 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.

Phase A only: mesh_improvement() is its one caller, and the driver only runs that as Phase A. Phase B's refinement question belongs to update_band_sizing_from_tolerance().

Implements wmtk::TriOptimizerMesh.

◆ 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< SampleEnvelope > 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.

◆ 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; 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, but m_pos must still be filled because the shared split's exact-midpoint fallback reads it.

◆ 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_front_vertex_phase_b()

bool wmtk::components::topological_offset::TopoOffsetTriMesh::smooth_front_vertex_phase_b ( const Tuple t)

Phase B placement of a front vertex: the shared smoother with the offset's options.

The same 2-D Newton solve, line search and accept tests as any TriWild vertex; the objective carries the offset terms through smoothing_extra_energy(). No quality veto, because a front vertex has to be able to worsen its ring on its way to the level set – element shape is Phase A's job.

◆ 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.

◆ smoothing_containment_envelope()

std::shared_ptr< SampleEnvelope > wmtk::components::topological_offset::TopoOffsetTriMesh::smoothing_containment_envelope ( const size_t  vid) const
inlineoverridevirtual

... and it is not contained by one either, except in Phase A.

Phase A is TriWild and its smoothing minimises AMIPS alone; without a container nothing would stop it relocating the offset boundary for the sake of element shape, which Phase B would then have to undo. Phase B keeps null – that is the pass whose job is to move it.

Boundary geometry is contained in the intersection of its tags' tubes, in both phases: the caller only asks is_outside(segment), which composites answer, so unlike the pull this side may hand out an IntersectionEnvelope.

Reimplemented from wmtk::TriOptimizerMesh.

◆ smoothing_energy_envelope()

std::shared_ptr< SampleEnvelope > 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 SampleEnvelope 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

Phase B's offset terms, handed to the shared smoother for a front vertex it is placing (null in Phase A and for a front vertex an input envelope also pins) – plus, under deform_others, the rest-shape AMIPS of the deformable faces in the vertex's ring, in every phase. Whichever apply are summed; null when neither does.

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. The base's hook is pure virtual, so this stays as the honest constant rather than being deleted outright.

Implements wmtk::TriOptimizerMesh.

◆ 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.

◆ surface_envelope_for_edge()

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

Class-0 segments – every region boundary, the input complex and the domain wall included – carry a containment requirement; the offset boundary does not.

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 offset boundary is exempt in Phase B, where it is the surface the optimization exists to move and a tube around its initial position would cap how far it can travel. In Phase A it is held by m_offset_envelope instead – a tube of one Phi tolerance around wherever Phase B last left it, rebuilt each round – which turns "do not degrade the offset" from a per-operation criterion into a geometric constraint every shared operation already honours.

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

Only to count an accepted flip. The base decides whether it is accepted; there is nothing the offset needs to do to a flip that survives swap_edge_before().

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.

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.

◆ 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).

◆ tangent_curve_tag()

int64_t wmtk::components::topological_offset::TopoOffsetTriMesh::tangent_curve_tag ( size_t  vid,
const Vector2d &  x 
) const

Which tag's boundary curve a vertex slides along, or -1.

The bit of its mask whose curve passes closest to it. For the common multi-bit case that choice is immaterial: a mask carries a bit per tag on either side of the boundary, so an interface between two regions gives both bits and both curves contain it. Where the curves genuinely differ the line search still tests containment against every member tube, so picking the nearest chooses the parameterization, never the constraint.

◆ 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_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.

◆ walk_along_curve()

bool wmtk::components::topological_offset::TopoOffsetTriMesh::walk_along_curve ( int64_t  tag,
const Vector2d &  x,
double  s,
Vector2d &  out 
) const

March s of arclength along tag tag's boundary polyline from x's foot on it.

The reduced coordinate of the tangential placement: the constraint is eliminated rather than enforced, so every point this returns is on the curve and needs no containment test of its own. Returns false where the walk cannot continue – an open end, or a polyline vertex where three or more segments meet and the continuation is ambiguous – with out holding the furthest point reached, the end of the feasible interval.

Corners are not special-cased, deliberately. The walk crosses any corner; what stops a vertex sliding past a sharp one is the incident chord leaving its tube, which the caller's backtracking finds. That reproduces the true bound (roughly eps/sin(theta) for a turn of theta, unbounded along a straight run) with no angle threshold anywhere.

◆ 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.

◆ 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 frames beside the run's own output, and rename them into one timeline.

Every debug frame in 2D goes through here – the shared driver's per-pass frames, Phase B's, and the A/B driver's per-phase ones – so this is the one place that can give them all a common order. A bare debug_N or phase_<r><A|B> comes out as

<output>_step_<NNNNN>_r<round><A|B><pass>.vtu      a pass inside a phase
<output>_step_<NNNNN>_r<round><A|B>_end.vtu         the frame the phase handed on

NNNNN is a single monotonic counter across the whole run, so sorting on it is run order. <pass> counts passes within the current phase and restarts whenever the round or the phase changes, so r1A3 reads as "round 1, phase A, third pass"; the restart is detected here, so no call site has to remember to reset anything. Round 0 is construction.

Renaming here rather than at the call sites is what keeps this out of the shared driver: wmtk::TriOptimizerMesh is also triwild's and simwild's, so its naming is not ours to change.

Implements wmtk::TriOptimizerMesh.

Member Data Documentation

◆ churn_counts

std::vector<std::array<int, 3> > wmtk::components::topological_offset::TopoOffsetTriMesh::churn_counts

{split-born vertices, recollapsed, recollapsed in the immediately following collapse pass} per A/B round, in step with op_counts. See VertexExtra2d::m_born_epoch.

◆ 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_split_born

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

Churn: split-born vertices that a collapse later removed, and the subset removed in the same pass-pair that created them.

◆ 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.

◆ m_ab_round

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

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

◆ 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_converged

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

The run's verdict: the front placed AND the final quality under stop_energy. Read by the report and by throw_on_nonconvergence.

◆ 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_pass

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

Pass index within the current phase, and the (round, phase) it belongs to – when those change the index restarts. All three 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 A/B loop converged; the final Phase A runs after it and the verdict must not be re-measured on that mesh.

◆ m_env_polyline_V

std::vector<Eigen::Vector2d> wmtk::components::topological_offset::TopoOffsetTriMesh::m_env_polyline_V

Shared vertex array for every TagPolyline2d: the positions as init_surfaces_and_boundaries() saw them, indexed by the mesh vid at construction. Never renumbered – the envelopes hold the same snapshot, and both describe the input, not the live mesh.

◆ m_front_gradient_reference

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

Its value on the band as constructed, measured once before round 1: the reference the Phase B pass stop is a fraction of.

◆ m_front_gradient_worst_vid

size_t wmtk::components::topological_offset::TopoOffsetTriMesh::m_front_gradient_worst_vid
mutable
Initial value:
=
static_cast<size_t>(-1)

argmax of phase_b_front_gradient_linf()

◆ 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 execute_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<SampleEnvelope> > 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_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_offset_isect_cache

std::map<uint64_t, std::shared_ptr<SampleEnvelope> > 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 rebuilt after every Phase B. rebuild_offset_envelope() clears this and must keep doing so – a stale entry holds the previous round's offset tube and would pin the boundary to where it was two rounds ago. 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_placement_env_entry_outside

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

How many Phase B offset placements found the vertex already outside its own envelope on entry. The post-step projection pulls it back in, but the invariant is 0: a nonzero count means construction or Phase A leaves offset vertices outside their region tube. A run total.

◆ m_placement_pressed

std::vector<char> wmtk::components::topological_offset::TopoOffsetTriMesh::m_placement_pressed

Set by the placement when a vertex's last visit stopped on QualityBound, cleared when it moved. distance_criterion() counts such a vertex as placed – its level set is unreachable by construction – and drops edges touching one from the resolution and orientation halves; update_band_sizing_from_tolerance() does not refine such edges.

◆ m_placement_projected

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

How many Phase B offset placements had their accepted step projected back into the vertex's region tubes – expected wherever the offset coincides with a region boundary, and not a problem: it counts constrained motion along a boundary curve. Read it against the EnvelopeBlocked count, where a projection that could not be committed lands. A run total.

◆ m_placement_tangential

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

How many Phase B offset placements were solved tangentially – reduced to arclength along the vertex's own tag boundary curve rather than stepped freely in 2D, expected for every offset vertex a region envelope also holds. Read it against ChordBlocked, which counts the visits whose slide an incident chord leaving its tube cut off. A run total.

◆ 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 placed, 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<SampleEnvelope> wmtk::components::topological_offset::TopoOffsetTriMesh::m_released_envelope
mutable

The released boundaries' ops-only tube: a SampleEnvelope 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_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_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<SampleEnvelope> > 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.

◆ op_counts

std::vector<std::array<int, 3> > wmtk::components::topological_offset::TopoOffsetTriMesh::op_counts

{splits, collapses, swaps} per A/B round – one entry per round the driver runs, including the round that converges, as deltas rather than running totals. Phase B does no topological work, so a round's entry is exactly what its Phase A did. This does not mirror optimization_metrics, which is a single whole-run summary.

◆ 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: