How Vertex Constraint Handling Works in the AutoRemesher Isotropic Remesher
The AutoRemesher isotropic remesher accepts per-vertex target edge lengths via setVertexTargetEdgeLengths(), copies them into half-edge mesh vertices, and uses local averages to dynamically adjust edge split and collapse thresholds during remeshing.
Vertex constraint handling in the AutoRemesher isotropic remesher allows users to prescribe spatially varying mesh densities by assigning a specific target edge length to each vertex of the input mesh. This mechanism drives adaptive refinement, enabling finer tessellation in detailed regions and coarser elements in flat or simple areas. The implementation stores these constraints in the IsotropicHalfedgeMesh::Vertex structure and applies them during the split and collapse phases of the remeshing algorithm.
Setting Per-Vertex Target Edge Lengths
The public API exposes vertex constraints through the setVertexTargetEdgeLengths method declared in thirdparty/isotropicremesher/isotropicremesher.h. Users supply a std::vector<double> where each index corresponds to a vertex index in the input mesh.
remesher.setVertexTargetEdgeLengths(const std::vector<double> *targetLengths);
The implementation stores this pointer in IsotropicRemesher::m_vertexTargetEdgeLengths according to the source in thirdparty/isotropicremesher/isotropicremesher.cpp【source L61-L64】. If the pointer is non-null, the remesher assumes adaptive density control is active.
Propagating Constraints to the Half-Edge Mesh
Before the remeshing iterations begin, the system copies the per-vertex values into the half-edge data structure. The remesher iterates through all vertices in m_halfedgeMesh and assigns the targetEdgeLength field of each IsotropicHalfedgeMesh::Vertex object【source L20-L27】:
if (m_vertexTargetEdgeLengths != nullptr) {
IsotropicHalfedgeMesh::Vertex *vertex = m_halfedgeMesh->moveToNextVertex(nullptr);
for (size_t vi = 0; vertex != nullptr && vi < m_vertexTargetEdgeLengths->size();
vertex = m_halfedgeMesh->moveToNextVertex(vertex), ++vi) {
vertex->targetEdgeLength = (*m_vertexTargetEdgeLengths)[vi];
}
}
This propagation ensures that every vertex in the internal IsotropicHalfedgeMesh carries its local constraint value for the duration of the remeshing process.
Local Edge Length Thresholds
During remeshing, the algorithm evaluates each edge against locally computed thresholds rather than global constants. The splitLongEdges and collapseShortEdges functions calculate an edge-specific target by averaging the targetEdgeLength values of the two endpoint vertices.
Splitting Long Edges
The maximum allowed edge length is normally computed as 4/3 · targetLength. When per-vertex constraints are present, the implementation recomputes the limit using the average of the two vertex targets【source L73-L80】:
double edgeTarget = (t0 + t1) * 0.5;
edgeMaxLenSq = std::pow(4.0 / 3.0 * edgeTarget, 2);
An edge is split when its squared length exceeds edgeMaxLenSq. This means vertices with smaller target values trigger earlier splitting, producing denser local triangulation.
Collapsing Short Edges
Similarly, the minimum allowed length uses a 4/5 factor applied to the averaged target. The collapseShortEdges logic sets both minimum and maximum bounds based on the local average【source L13-L18】:
double edgeTarget = (t0 + t1) * 0.5;
edgeMinLenSq = std::pow(4.0 / 5.0 * edgeTarget, 2);
edgeMaxLenSq = std::pow(4.0 / 3.0 * edgeTarget, 2);
An edge is collapsed only when its squared length falls below edgeMinLenSq and the operation does not violate the maximum length constraint.
Practical Example: Adaptive Mesh Density
The following example demonstrates how to use vertex constraint handling to create finer mesh density near the X-axis while keeping coarser resolution elsewhere:
// Load input mesh
std::vector<Vector3> vertices = loadMeshVertices();
std::vector<std::vector<size_t>> triangles = loadMeshTriangles();
// Create per-vertex target lengths: 0.02 near X-axis, 0.05 elsewhere
std::vector<double> perVertexTarget(vertices.size(), 0.0);
for (size_t i = 0; i < vertices.size(); ++i) {
perVertexTarget[i] = (std::abs(vertices[i].x) < 0.5) ? 0.02 : 0.05;
}
// Initialize and configure remesher
IsotropicRemesher remesher(&vertices, &triangles);
remesher.setVertexTargetEdgeLengths(&perVertexTarget);
remesher.setTargetEdgeLength(0.04); // Fallback global target
remesher.remesh(5); // Run 5 iterations
After remeshing, you can inspect the effective target lengths stored in the half-edge mesh:
auto *mesh = remesher.remeshedHalfedgeMesh();
for (auto *v = mesh->moveToNextVertex(nullptr); v != nullptr;
v = mesh->moveToNextVertex(v)) {
std::cout << "Vertex " << v->debugIndex
<< " target length = " << v->targetEdgeLength << "\n";
}
Key Implementation Files
thirdparty/isotropicremesher/isotropicremesher.h– Declares the public API includingsetVertexTargetEdgeLengths.thirdparty/isotropicremesher/isotropicremesher.cpp– Implements constraint storage, propagation to vertices, and the split/collapse logic using local averages.thirdparty/isotropicremesher/isotropichalfedgemesh.cpp– DefinesIsotropicHalfedgeMesh::Vertexwith thetargetEdgeLengthfield.src/AutoRemesher/autoremesher.cpp– Higher-level wrapper that forwards user constraints from the application layer to the isotropic remesher instance.
Summary
- Vertex constraints are supplied as a
std::vector<double>viasetVertexTargetEdgeLengths()and stored inm_vertexTargetEdgeLengths. - The remesher copies these values into
IsotropicHalfedgeMesh::Vertex::targetEdgeLengthbefore processing begins. - Edge thresholds are computed locally by averaging the
targetEdgeLengthvalues of both endpoints. - The split threshold uses a factor of
4/3applied to the local average, while the collapse threshold uses4/5. - This system enables adaptive remeshing where local density is controlled independently per vertex while maintaining the isotropic quality of the output mesh.
Frequently Asked Questions
How do I set different target edge lengths for specific vertices?
Pass a std::vector<double> pointer to setVertexTargetEdgeLengths() where the index of each element corresponds to the vertex index in your input mesh. The remesher reads this vector during initialization and assigns each value to the respective vertex in the internal half-edge structure.
What happens if I only set a global target edge length?
If you do not call setVertexTargetEdgeLengths() (or pass nullptr), the remesher uses the global value set via setTargetEdgeLength() for all edges. The vertex constraint system is optional and only activates when m_vertexTargetEdgeLengths is non-null.
How does the remesher handle edges between vertices with different target lengths?
The algorithm computes an edge-specific target by averaging the targetEdgeLength values of the two endpoint vertices. This averaged value then feeds into the standard 4/3 split factor and 4/5 collapse factor, producing a smooth transition in mesh density rather than abrupt changes at vertex boundaries.
Can I inspect the target edge lengths after remeshing?
Yes. After calling remesh(), access the remeshed half-edge mesh via remeshedHalfedgeMesh() and iterate through vertices using moveToNextVertex(). Each vertex object exposes its effective targetEdgeLength field, allowing you to verify how constraints propagated through the remeshing operations.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →