Wildmeshing Toolkit
Loading...
Searching...
No Matches
Parameters.h
1#pragma once
2#include <wmtk/OptimizerParameters.h>
3#include <nlohmann/json.hpp>
4#include <wmtk/Types.hpp>
5#include <wmtk/components/simwild/expression_parser/Expression.hpp>
6
7using ExpressionPtr = wmtk::components::simwild::expression_parser::ExpressionPtr;
8
9namespace wmtk::components::topological_offset {
10
25{
26 ExpressionPtr offset_selection;
27 std::set<std::string> offset_output_tag;
28 std::set<std::string> protected_tags;
29 bool respect_all_topologies;
30 bool offset_in;
31 bool offset_out;
32 double target_distance;
33 double target_distance_rel;
34 double convergence_target; // absolute; if < 0, computed from convergence_target_rel in init()
35 double convergence_target_rel; // relative to target_distance, not the bbox diagonal
36 // Max normal deviation, in DEGREES, that the offset boundary must reach before the
37 // optimization may terminate early. Absolute by nature -- an angle has no natural relative
38 // form the way a distance does -- so there is no _rel counterpart. Convergence requires both
39 // this and convergence_target; <= 0 disables the criterion, leaving distance alone deciding.
40 double convergence_normal_deviation;
41 // Turn a non-converged run into a hard error instead of a warning. Off by default -- a run
42 // that misses the target is still a usable offset, and the warnings already name the criterion
43 // that failed. Integration tests set it true so a regression in convergence fails the test
44 // rather than passing with a warning nobody reads.
45 bool throw_on_nonconvergence;
46 // Half-width of the envelope that contains every tag-region boundary during optimization.
47 // Absolute; if < 0, computed from envelope_size_rel (relative to the bbox diagonal).
48 double envelope_size;
49 double envelope_size_rel;
50 // Capture that envelope from the input mesh, before the offset truncates the region
51 // boundaries it grows through. See TopoOffsetTriMesh::init_region_boundary_envelope_from_input.
52 // 2D only; the 3D path never builds this envelope.
53 bool region_envelope_from_input;
54 double relative_ball_threshold;
55 // Termination length for the distance-field root finds in EdgeSplittingTet.cpp
56 // (edge_split_binary_search, edge_split_log_root_find, edge_split_sphere_tracing).
57 // 3D ONLY. The 2D path has no consumer: its sphere-tracing split was removed along with the
58 // distance-field marching pass that selected it, because placing the offset boundary is the
59 // optimization phase's job, not the insertion's. Delete this field and its spec entry when
60 // 3D drops those modes too -- see the note in .claude/CLAUDE.md.
61 double edge_search_term_len;
62 bool sorted_marching;
63 std::string output_path; // no extension
64 bool optimize; // whether to run optimization on the offset
65 bool save_vtu;
66
67 int num_threads; // number of threads for parallel execution (smoothing, collapse). 0 = serial
68 int optimization_iterations; // number of split/collapse/swap/smooth passes in optimize_offset()
69
70 // max angle (degrees, 0-90) allowed between an offset-surface face's own normal and the
71 // input-complex normal it is supposed to approximate, before collapse/swap reject a move
72 // that would push it further out of alignment.
73 double max_normal_deviation_deg;
74 // sigma_min from the paper (Sec. 5.3, "controls when the offset curvature is considered
75 // planar"): a stretch of offset whose normal deviation is below this is flat enough that
76 // the sizing field may coarsen it. Only the 2D sizing field reads it.
77 double min_normal_deviation_deg;
78 // l_min from the paper, = 2 * delta * sin(sigma_max): the shortest edge the sizing field
79 // may ask for, in absolute units. Tied to the offset distance rather than the bounding box
80 // because that is the scale the offset actually has. Derived in init() when < 0. This is a
81 // floor on refinement, so raising it makes the result COARSER (paper Fig. 18).
82 double min_edge_length;
83
84 // ---- offset-surface smoothing blend, see TopoOffsetTetMesh::smooth_after_offset_surface()
85 // ---- each offset-surface vertex moves to a weighted blend of its previous position,
86 // the quadrics-optimal target vertex, and the Laplacian of its offset-surface
87 // neighbors; the remaining weight (1 - w - u) stays with the previous position.
88 double smooth_quadrics_weight; // w: blend toward the quadrics-optimal target vertex
89 double smooth_laplacian_weight; // u: blend toward the offset-surface Laplacian
90 // SVD threshold used by Quadrics::solve() when solving for the quadrics-optimal target
91 // vertex. Controls sensitivity to feature edges: lower means more sensitive.
92 double quadrics_svd_threshold;
93
94 // ---- sizing field, see TopoOffsetTetMesh::update_sizing_field() ----
95 // bounds for VertexAttributes::m_sizing_scalar
96 double min_sizing_scalar;
97 double max_sizing_scalar;
98 // mean ratio metric strictly below this is "bad" (refine); strictly above is "good"
99 // (coarsen), matching the reference's compute_target_edge_length()
100 double sizing_mrm_threshold;
101 // gradation cap: neighboring vertices' sizing scalars may differ by at most this factor,
102 // enforced by propagating the refinement outward (monotone, only ever lowers a
103 // neighbor's scalar). <= 1 disables gradation entirely.
104 double sizing_gradation;
105
106 VectorXd box_min;
107 VectorXd box_max;
108
109 Parameters() = default;
110
111 Parameters(const nlohmann::json& json_params)
112 {
113 for (const std::string& tag : json_params["offset_output_tags"]) {
114 if (tag == "ambient") {
115 logger().warn(
116 "'ambient' tag cannot be given explicitly to offset_output_tags, ignoring. To "
117 "set offset to 'ambient', pass offset_output_tags=[].");
118 continue;
119 }
120 offset_output_tag.insert(tag);
121 }
122 for (const std::string& tag : json_params["protected_tags"]) {
123 if (tag == "ambient") {
124 logger().warn("'ambient' tag cannot be protected, ignoring.");
125 continue;
126 }
127 protected_tags.insert(tag);
128 }
129 respect_all_topologies = json_params["respect_all_topologies"];
130 offset_in = json_params["offset_in"];
131 offset_out = json_params["offset_out"];
132 target_distance = json_params["target_distance"];
133 target_distance_rel = json_params["target_distance_rel"];
134 convergence_target = json_params["convergence_target"];
135 convergence_target_rel = json_params["convergence_target_rel"];
136 convergence_normal_deviation = json_params["convergence_normal_deviation"];
137 throw_on_nonconvergence = json_params["throw_on_nonconvergence"];
138 envelope_size = json_params["envelope_size"];
139 envelope_size_rel = json_params["envelope_size_rel"];
140 region_envelope_from_input = json_params["region_envelope_from_input"];
141 relative_ball_threshold = json_params["relative_ball_threshold"];
142 if (relative_ball_threshold < 0.0 || relative_ball_threshold > 1.0) {
143 log_and_throw_error(
144 "Invalid relative_ball_threshold [{}], must be between 0 and 1.",
145 relative_ball_threshold);
146 }
147
148 edge_search_term_len = json_params["edge_search_termination_len"];
149 sorted_marching = json_params["sorted_marching"];
150 output_path = json_params["output"];
151 optimize = json_params["optimize"];
152 save_vtu = json_params["save_vtu"];
153
154 num_threads = json_params["num_threads"];
155 optimization_iterations = json_params["optimization_iterations"];
156
157 max_normal_deviation_deg = json_params["max_normal_deviation_deg"];
158 min_normal_deviation_deg = json_params["min_normal_deviation_deg"];
159 min_edge_length = json_params["min_edge_length"];
160
161 smooth_quadrics_weight = json_params["smooth_quadrics_weight"];
162 smooth_laplacian_weight = json_params["smooth_laplacian_weight"];
163 quadrics_svd_threshold = json_params["quadrics_svd_threshold"];
164
165 min_sizing_scalar = json_params["min_sizing_scalar"];
166 max_sizing_scalar = json_params["max_sizing_scalar"];
167 sizing_mrm_threshold = json_params["sizing_mrm_threshold"];
168 sizing_gradation = json_params["sizing_gradation"];
169
170 // ---- inherited from wmtk::OptimizerParameters ----
171 debug_output = json_params["DEBUG_output"];
172 lr = json_params["length_rel"];
173 l = json_params["length"];
174 stop_energy = json_params["stop_energy"];
175 num_smoothing_passes = json_params["smoothing_iterations"];
176 split_high_valence_threshold = json_params["split_high_valence_threshold"];
177 skip_good_regions = json_params["skip_good_regions"];
178 w_amips = json_params["w_amips"];
179 smoothing_mode = json_params["smoothing_mode"];
180 project_line_search_steps = json_params["project_line_search_steps"];
181 project_line_search_nested_steps = json_params["project_line_search_nested_steps"];
182 w_envelope = 1. - w_amips;
183 perform_sanity_checks = json_params["perform_sanity_checks"];
184 }
185
186 void init(const VectorXd& min_, const VectorXd& max_)
187 {
188 box_min = min_;
189 box_max = max_;
190
191 // Not a user knob. A TOPOLOGICAL offset is defined by preserving the topology of the
192 // region it wraps, so the shared collapse must always apply the substructure link
193 // condition -- without it a collapse across a thin offset band pinches the two sides
194 // together and the region stops being manifold. The offset's own collapse applied this
195 // unconditionally before it moved onto the shared engine, where it is gated on this
196 // flag; tetwild and simwild leave the flag off, which is why it is set here and not
197 // changed in wmtk.
198 preserve_topology = true;
199
200 // Fills diag_l, l/lr and splitting_l2 / collapsing_l2 -- the same 16/9 and 16/25
201 // factors this used to spell out itself. It also derives eps from epsr, which the
202 // offset never reads: its envelope tolerance is m_envelope_eps, set on the mesh.
203 init_lengths_from_diagonal((max_ - min_).norm());
204
205 if (target_distance > 0) {
206 target_distance_rel = target_distance / diag_l;
207 } else {
208 target_distance = target_distance_rel * diag_l;
209 }
210
211 // The convergence threshold is an error in the SAME quantity target_distance measures --
212 // how far an offset vertex sits from where it should be -- so it is relative to
213 // target_distance, not to the bounding box diagonal like every other relative length.
214 if (convergence_target > 0) {
215 convergence_target_rel = convergence_target / target_distance;
216 } else {
217 convergence_target = convergence_target_rel * target_distance;
218 }
219
220 // An ordinary relative length, unlike convergence_target: it bounds how far a region
221 // boundary may drift in space, so the bounding box is the right reference.
222 if (envelope_size > 0) {
223 envelope_size_rel = envelope_size / diag_l;
224 } else {
225 envelope_size = envelope_size_rel * diag_l;
226 }
227
228 // l_min = 2 * delta * sin(sigma_max), from the paper's parameter list (Sec. 5.3). The
229 // reasoning is geometric: sigma_max is how far the offset surface is allowed to turn
230 // across one element, and an element subtending that angle on a circle of radius delta
231 // -- which is the shape the offset takes around a convex feature -- has chord length
232 // 2*delta*sin(sigma_max). Scaling to the offset distance rather than the bounding box
233 // is the point: it is the offset that has to be resolved, and its scale is delta.
234 if (min_edge_length < 0) {
235 const double sigma = max_normal_deviation_deg * M_PI / 180.;
236 min_edge_length = 2. * target_distance * std::sin(std::min(sigma, M_PI / 2.));
237 }
238 }
239};
240} // namespace wmtk::components::topological_offset
The parameters tetwild, triwild and simwild all share.
Definition OptimizerParameters.h:28
int project_line_search_nested_steps
Partial-projection bisections tried after it gives up; 0 disables that pass.
Definition OptimizerParameters.h:160
double w_amips
Definition OptimizerParameters.h:154
std::string smoothing_mode
"projected" or "exact"; see SmoothVertexOptions::SmoothingMode.
Definition OptimizerParameters.h:156
void init_lengths_from_diagonal(const double diag)
Derive the edge-length and envelope quantities from the bounding-box diagonal.
Definition OptimizerParameters.h:309
int split_high_valence_threshold
Definition OptimizerParameters.h:70
int project_line_search_steps
Bisections tried before the projected search gives up. See SmoothVertexOptions.
Definition OptimizerParameters.h:158
int num_smoothing_passes
Number and placement of smoothing passes in the shared Wild optimization driver.
Definition OptimizerParameters.h:164
What the offset needs on top of the parameters every wmtk optimizer shares.
Definition Parameters.h:25