From a751f37afe68aad7dd3e82faedbe859a04cf06bd Mon Sep 17 00:00:00 2001 From: Ryan Capouellez Date: Wed, 15 Oct 2025 16:44:41 -0400 Subject: [PATCH 1/5] Added face mapping to refinement --- include/feature/feature/interface.h | 3 ++- include/feature/feature/surgery/refinement.h | 9 +++++++-- include/util/util/common.h | 6 ++++++ src/feature/interface.cpp | 8 +++++--- src/feature/surgery/refinement.cpp | 16 ++++++++++++---- 5 files changed, 32 insertions(+), 10 deletions(-) diff --git a/include/feature/feature/interface.h b/include/feature/feature/interface.h index 1240651..0cea3eb 100644 --- a/include/feature/feature/interface.h +++ b/include/feature/feature/interface.h @@ -21,8 +21,9 @@ namespace Feature { * @return refined face indices * @return refined feature edges * @return refined feature forest edges (spanning or minimal) + * @return map from refined faces to parent faces */ -std::tuple, std::vector> generate_refined_feature_mesh( +std::tuple, std::vector, std::vector> generate_refined_feature_mesh( const Eigen::MatrixXd& V, const Eigen::MatrixXi& F, bool use_minimal_forest=false); diff --git a/include/feature/feature/surgery/refinement.h b/include/feature/feature/surgery/refinement.h index fb8fba6..6552346 100644 --- a/include/feature/feature/surgery/refinement.h +++ b/include/feature/feature/surgery/refinement.h @@ -77,6 +77,8 @@ class IntrinsicRefinementMesh Mesh& get_mesh() { return m_mesh; } const Mesh& get_mesh() const { return m_mesh; } + const std::vector& get_face_parent() const { return face_parent; } + protected: int get_new_face(); int get_new_vertex(); @@ -86,6 +88,7 @@ class IntrinsicRefinementMesh Mesh m_mesh; std::map> m_endpoints; + std::vector face_parent; private: virtual int refine_single_face(int face_index); @@ -99,8 +102,9 @@ class IntrinsicRefinementMesh * @return refined vertex positions * @return refined face indices * @return refined feature edges + * @return map from refined faces to parent faces */ -std::tuple> refine_corner_feature_faces( +std::tuple, std::vector> refine_corner_feature_faces( const FeatureFinder& feature_finder); /** @@ -113,8 +117,9 @@ std::tuple> refine_cor * @return refined face indices * @return refined feature edges * @return refined feature forest edges (spanning or minimal) + * @return map from refined faces to parent faces */ -std::tuple, std::vector> +std::tuple, std::vector, std::vector> refine_feature_components(const FeatureFinder& feature_finder, bool use_minimal_forest = false); } // namespace Feature diff --git a/include/util/util/common.h b/include/util/util/common.h index 3855744..f8f9c2a 100644 --- a/include/util/util/common.h +++ b/include/util/util/common.h @@ -83,6 +83,12 @@ inline void arange(size_t n, std::vector& vec) vec.resize(n); std::iota(vec.begin(), vec.end(), 0); } +inline std::vector arange(size_t n) +{ + std::vector vec; + arange(n, vec); + return vec; +} template Mesh change_mesh_type(const Mesh& m) diff --git a/src/feature/interface.cpp b/src/feature/interface.cpp index 05d86b2..6722717 100644 --- a/src/feature/interface.cpp +++ b/src/feature/interface.cpp @@ -191,7 +191,7 @@ std::tuple>, std::vector> generate_com } -std::tuple, std::vector> generate_refined_feature_mesh( +std::tuple, std::vector, std::vector> generate_refined_feature_mesh( const Eigen::MatrixXd& V, const Eigen::MatrixXi& F, bool use_minimal_forest) @@ -203,12 +203,14 @@ std::tuple, std::vecto feature_finder.prune_small_features(5); // refine faces to avoid faces with two boundary edges - auto [V_ref_f, F_ref_f, feature_edges_f] = refine_corner_feature_faces(feature_finder); + auto [V_ref_f, F_ref_f, feature_edges_f, fn_to_f_f] = refine_corner_feature_faces(feature_finder); // refine edges to avoid feature components without a forest edge feature_finder = FeatureFinder(V_ref_f, F_ref_f); feature_finder.mark_features(feature_edges_f); - return refine_feature_components(feature_finder, use_minimal_forest); + auto [V_ref_e, F_ref_e, feature_edges_e, spanning_edges_e, fn_to_f_e] = refine_feature_components(feature_finder, use_minimal_forest); + auto fn_to_f = vector_compose(fn_to_f_e, fn_to_f_f); + return std::make_tuple(V_ref_e, F_ref_e, feature_edges_e, spanning_edges_e, fn_to_f); } AlignedMetricGenerator::AlignedMetricGenerator( diff --git a/src/feature/surgery/refinement.cpp b/src/feature/surgery/refinement.cpp index 7f10fcf..0c4a819 100644 --- a/src/feature/surgery/refinement.cpp +++ b/src/feature/surgery/refinement.cpp @@ -11,6 +11,7 @@ namespace Feature { IntrinsicRefinementMesh::IntrinsicRefinementMesh(const Mesh& m) : m_mesh(m) , m_endpoints({}) + , face_parent(arange(m.n_faces())) {} // Refine a single face, disregarding symmetry structure and independent vertices @@ -37,6 +38,9 @@ int IntrinsicRefinementMesh::refine_single_face(int face_index) int fijl = face_index; int fjkl = get_new_face(); int fkil = get_new_face(); + face_parent[fijl] = fijl; + face_parent[fjkl] = fijl; + face_parent[fkil] = fijl; auto [hil, hli] = get_new_edge(); auto [hjl, hlj] = get_new_edge(); auto [hkl, hlk] = get_new_edge(); @@ -135,8 +139,12 @@ int IntrinsicRefinementMesh::refine_single_halfedge(int halfedge_index) int vm = get_new_vertex(); int fmjk = fijk; int fmki = get_new_face(); + face_parent[fmjk] = fijk; + face_parent[fmki] = fijk; int fmil = fjil; int fmlj = get_new_face(); + face_parent[fmil] = fjil; + face_parent[fmlj] = fjil; int him = hij; int hmi = hji; auto [hmj, hjm] = get_new_edge(); @@ -449,7 +457,7 @@ int get_refined_index(int vertex_index, const std::vector& vtx_reindex) } } -std::tuple> refine_corner_feature_faces(const FeatureFinder& feature_finder) +std::tuple, std::vector> refine_corner_feature_faces(const FeatureFinder& feature_finder) { const Mesh& m = feature_finder.get_mesh(); int num_faces = m.n_faces(); @@ -505,7 +513,7 @@ std::tuple> refine_cor const auto& V = feature_finder.get_vertex_positions(); auto [V_ref, F_ref] = refinement_mesh.generate_mesh(V, vtx_reindex); - return std::make_tuple(V_ref, F_ref, feature_edges); + return std::make_tuple(V_ref, F_ref, feature_edges, refinement_mesh.get_face_parent()); } @@ -544,7 +552,7 @@ void make_minimal_forest(const Mesh& m, std::vector& is_spanning_h } } -std::tuple, std::vector> refine_feature_components(const FeatureFinder& feature_finder, bool use_minimal_forest) +std::tuple, std::vector, std::vector> refine_feature_components(const FeatureFinder& feature_finder, bool use_minimal_forest) { // initialize mesh for refinement const Mesh& m = feature_finder.get_mesh(); @@ -635,7 +643,7 @@ std::tuple, std::vecto const auto& V = feature_finder.get_vertex_positions(); auto [V_ref, F_ref] = refinement_mesh.generate_mesh(V, vtx_reindex); - return std::make_tuple(V_ref, F_ref, feature_edges, spanning_edges); + return std::make_tuple(V_ref, F_ref, feature_edges, spanning_edges, refinement_mesh.get_face_parent()); } From 256edc044cc8e0e6762f5cd7aa9db9dfed0f71c4 Mon Sep 17 00:00:00 2001 From: Ryan Capouellez Date: Wed, 15 Oct 2025 16:47:56 -0400 Subject: [PATCH 2/5] Added face mapping to refinement --- src/feature/interface.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/feature/interface.cpp b/src/feature/interface.cpp index 6722717..ca78c75 100644 --- a/src/feature/interface.cpp +++ b/src/feature/interface.cpp @@ -209,7 +209,7 @@ std::tuple, std::vecto feature_finder = FeatureFinder(V_ref_f, F_ref_f); feature_finder.mark_features(feature_edges_f); auto [V_ref_e, F_ref_e, feature_edges_e, spanning_edges_e, fn_to_f_e] = refine_feature_components(feature_finder, use_minimal_forest); - auto fn_to_f = vector_compose(fn_to_f_e, fn_to_f_f); + auto fn_to_f = vector_compose(fn_to_f_f, fn_to_f_e); return std::make_tuple(V_ref_e, F_ref_e, feature_edges_e, spanning_edges_e, fn_to_f); } From 9de5276870f6e673bbcbf919868fc90582bc1b10 Mon Sep 17 00:00:00 2001 From: Ryan Capouellez Date: Wed, 15 Oct 2025 16:59:50 -0400 Subject: [PATCH 3/5] Added face mapping to refinement --- include/feature/feature/surgery/refinement.h | 4 ++-- src/feature/surgery/refinement.cpp | 20 ++++++++++++-------- 2 files changed, 14 insertions(+), 10 deletions(-) diff --git a/include/feature/feature/surgery/refinement.h b/include/feature/feature/surgery/refinement.h index 6552346..3bcb854 100644 --- a/include/feature/feature/surgery/refinement.h +++ b/include/feature/feature/surgery/refinement.h @@ -77,7 +77,7 @@ class IntrinsicRefinementMesh Mesh& get_mesh() { return m_mesh; } const Mesh& get_mesh() const { return m_mesh; } - const std::vector& get_face_parent() const { return face_parent; } + const std::vector& get_face_parent() const { return m_face_parent; } protected: int get_new_face(); @@ -88,7 +88,7 @@ class IntrinsicRefinementMesh Mesh m_mesh; std::map> m_endpoints; - std::vector face_parent; + std::vector m_face_parent; private: virtual int refine_single_face(int face_index); diff --git a/src/feature/surgery/refinement.cpp b/src/feature/surgery/refinement.cpp index 0c4a819..9707325 100644 --- a/src/feature/surgery/refinement.cpp +++ b/src/feature/surgery/refinement.cpp @@ -11,7 +11,7 @@ namespace Feature { IntrinsicRefinementMesh::IntrinsicRefinementMesh(const Mesh& m) : m_mesh(m) , m_endpoints({}) - , face_parent(arange(m.n_faces())) + , m_face_parent(arange(m.n_faces())) {} // Refine a single face, disregarding symmetry structure and independent vertices @@ -36,11 +36,12 @@ int IntrinsicRefinementMesh::refine_single_face(int face_index) // get new vertex, face and hafledge indices int vl = get_new_vertex(); int fijl = face_index; + int face_parent = m_face_parent[face_index]; int fjkl = get_new_face(); int fkil = get_new_face(); - face_parent[fijl] = fijl; - face_parent[fjkl] = fijl; - face_parent[fkil] = fijl; + m_face_parent[fijl] = face_parent; + m_face_parent[fjkl] = face_parent; + m_face_parent[fkil] = face_parent; auto [hil, hli] = get_new_edge(); auto [hjl, hlj] = get_new_edge(); auto [hkl, hlk] = get_new_edge(); @@ -134,17 +135,19 @@ int IntrinsicRefinementMesh::refine_single_halfedge(int halfedge_index) int vj = m.to[hij]; int vk = m.to[hjk]; int vl = m.to[hil]; + int fijk_parent = m_face_parent[fijk]; + int fjil_parent = m_face_parent[fjil]; // get new vertex, face and hafledge indices int vm = get_new_vertex(); int fmjk = fijk; int fmki = get_new_face(); - face_parent[fmjk] = fijk; - face_parent[fmki] = fijk; + m_face_parent[fmjk] = fijk_parent; + m_face_parent[fmki] = fijk_parent; int fmil = fjil; int fmlj = get_new_face(); - face_parent[fmil] = fjil; - face_parent[fmlj] = fjil; + m_face_parent[fmil] = fjil_parent; + m_face_parent[fmlj] = fjil_parent; int him = hij; int hmi = hji; auto [hmj, hjm] = get_new_edge(); @@ -359,6 +362,7 @@ int IntrinsicRefinementMesh::get_new_face() // add null entries for face attributes m.h.push_back(-1); + m_face_parent.push_back(-1); return face_index; } From 2c34512a56cfa6fbf7da24d60b37a06dda68f0d2 Mon Sep 17 00:00:00 2001 From: Ryan Capouellez Date: Tue, 6 Jan 2026 00:41:30 -0500 Subject: [PATCH 4/5] Fixed bugs --- src/holonomy/field/cross_field.cpp | 5 +++-- src/optimization/parameterization/layout.cpp | 4 ++-- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/src/holonomy/field/cross_field.cpp b/src/holonomy/field/cross_field.cpp index 01357e1..ceb0079 100644 --- a/src/holonomy/field/cross_field.cpp +++ b/src/holonomy/field/cross_field.cpp @@ -1,5 +1,5 @@ #include "holonomy/field/cross_field.h" - +/* #include #include #include @@ -243,4 +243,5 @@ std::array reduce_curl( } } // namespace Holonomy -} // namespace Penner \ No newline at end of file +} // namespace Penner +*/ diff --git a/src/optimization/parameterization/layout.cpp b/src/optimization/parameterization/layout.cpp index 4d97bfc..330ee22 100644 --- a/src/optimization/parameterization/layout.cpp +++ b/src/optimization/parameterization/layout.cpp @@ -1038,7 +1038,7 @@ get_consistent_layout( int current_seg = m_o.first_segment[hij]; while (m_o.vertex_type[m_o.to[current_seg]] != ORIGINAL_VERTEX) { - std::vector tmp = m_o.seg_bcs[current_seg]; + std::array tmp = m_o.seg_bcs[current_seg]; lambdas.push_back(tmp[1]); current_seg = m_o.next_segment(current_seg); } @@ -1048,7 +1048,7 @@ get_consistent_layout( int cnt = lambdas.size() - 1; while (m_o.vertex_type[m_o.to[current_seg]] != ORIGINAL_VERTEX) { - std::vector tmp = m_o.seg_bcs[current_seg]; + std::array tmp = m_o.seg_bcs[current_seg]; if (abs(lambdas[cnt] - tmp[0]) > 1e-10) { spdlog::error("alignment problem: {}, {}", lambdas[cnt], tmp[0]); From 7946443c295ca1ec0baff5aa42976fdfd070edfa Mon Sep 17 00:00:00 2001 From: Ryan Capouellez Date: Wed, 14 Jan 2026 03:06:09 -0500 Subject: [PATCH 5/5] Fixed cone move and collapse bugs --- include/feature/feature/interface.h | 4 +++- src/feature/interface.cpp | 5 +++-- src/holonomy/field/intrinsic_field.cpp | 30 +++++++++++++++++++++----- 3 files changed, 31 insertions(+), 8 deletions(-) diff --git a/include/feature/feature/interface.h b/include/feature/feature/interface.h index 0cea3eb..805c9d8 100644 --- a/include/feature/feature/interface.h +++ b/include/feature/feature/interface.h @@ -17,6 +17,7 @@ namespace Feature { * @param V: mesh vertices * @param F: mesh faces * @param use_minimal_forest: (optional) if true, use minimal feature forest instead of spanning tree + * @param target_angle: (optional) dihedral angle for features * @return refined vertex positions * @return refined face indices * @return refined feature edges @@ -26,7 +27,8 @@ namespace Feature { std::tuple, std::vector, std::vector> generate_refined_feature_mesh( const Eigen::MatrixXd& V, const Eigen::MatrixXi& F, - bool use_minimal_forest=false); + bool use_minimal_forest=false, + Scalar target_angle=60.); /** * @brief Generate a parameterization aligned to features and the given cross field. diff --git a/src/feature/interface.cpp b/src/feature/interface.cpp index ca78c75..3acc28f 100644 --- a/src/feature/interface.cpp +++ b/src/feature/interface.cpp @@ -194,11 +194,12 @@ std::tuple>, std::vector> generate_com std::tuple, std::vector, std::vector> generate_refined_feature_mesh( const Eigen::MatrixXd& V, const Eigen::MatrixXi& F, - bool use_minimal_forest) + bool use_minimal_forest, + Scalar target_angle) { // generate default features FeatureFinder feature_finder(V, F); - feature_finder.mark_dihedral_angle_features(60.); + feature_finder.mark_dihedral_angle_features(target_angle); feature_finder.prune_small_components(4); feature_finder.prune_small_features(5); diff --git a/src/holonomy/field/intrinsic_field.cpp b/src/holonomy/field/intrinsic_field.cpp index 6824f45..4ccb0bb 100644 --- a/src/holonomy/field/intrinsic_field.cpp +++ b/src/holonomy/field/intrinsic_field.cpp @@ -1800,8 +1800,12 @@ void IntrinsicNRosyField::collapse_adjacent_cones(const Mesh& m) if ((!is_boundary[wj]) && (cones[vj] == 4)) continue; // modify cones - spdlog::info("collapsing cones {} and {}", cones[vi], cones[vj]); Scalar defect = 4 - cones[vi]; + + if ((cones[vi] + defect) < 1) continue; + if ((cones[vj] - defect) < 1) continue; + + spdlog::info("collapsing adjacent cones {} and {}", cones[vi], cones[vj]); set_period_jump(m, hij, period_jump[hij] - defect); cones[vi] += defect; cones[vj] -= defect; @@ -1826,8 +1830,8 @@ void IntrinsicNRosyField::move_nearby_cones(const Mesh& m) // generate list of boundary vertices int num_vertices = cones.size(); std::vector is_boundary = compute_boundary_vertices(m); - std::vector is_cone(cones.size(), false); - std::vector is_one_away(cones.size(), false); + std::vector is_cone(m.n_ind_vertices(), false); + std::vector is_one_away(m.n_ind_vertices(), false); std::vector is_two_away(m.n_ind_vertices(), false); auto adjust_adacent = [&](int wi) @@ -1851,7 +1855,7 @@ void IntrinsicNRosyField::move_nearby_cones(const Mesh& m) int hjk = hji; do { - int vk = m.v_rep[m.to[hij]]; + int vk = m.v_rep[m.to[hjk]]; if ((vk != vi) && (!is_one_away[vk])) { is_two_away[vk] = true; @@ -1872,6 +1876,7 @@ void IntrinsicNRosyField::move_nearby_cones(const Mesh& m) // check for interior cone if ((cones[vi] == 4) || (is_boundary[wi])) continue; cones_to_place.push_back({wi, 1}); + is_cone[vi] = true; } // place cones, ensuring sufficient distance throughout @@ -1881,10 +1886,12 @@ void IntrinsicNRosyField::move_nearby_cones(const Mesh& m) auto [wi, cone_index] = cones_to_place[i]; int vi = m.v_rep[wi]; int defect = (4 - cones[vi]); + is_cone[vi] = false; // if adjacent, try to move two away if (is_one_away[vi]) { + spdlog::info("moving adjacent cone {}", vi); int hij = m.out[wi]; do { @@ -1895,16 +1902,20 @@ void IntrinsicNRosyField::move_nearby_cones(const Mesh& m) if ((!is_cone[vj]) && (!is_one_away[vj])) { move_cone(m, vi, vj, defect); + spdlog::info("moved cone to {}", vj); wi = wj; vi = vj; break; } + + hij = m.n[m.opp[hij]]; } while (hij != m.out[wi]); } // if two away, try to move to free spot if (is_two_away[vi]) { + spdlog::info("moving two away cone {}", vi); int hij = m.out[wi]; do { @@ -1915,14 +1926,18 @@ void IntrinsicNRosyField::move_nearby_cones(const Mesh& m) if ((!is_cone[vj]) && (!is_one_away[vj]) && (!is_two_away[vj])) { move_cone(m, vi, vj, defect); + spdlog::info("moved cone to {}", vj); wi = wj; vi = vj; break; } + + hij = m.n[m.opp[hij]]; } while (hij != m.out[wi]); } // mark region around cone + spdlog::info("placed cone at {}", vi); adjust_adacent(wi); } } @@ -2004,10 +2019,11 @@ void IntrinsicNRosyField::collapse_nearby_cones(const Mesh& m) int vj = m.v_rep[m.to[hij]]; // cannot collapse cone to below pi/2 + if ((cones[vk] + defect) < 1) continue; if ((cones[vj] - defect) < 1) continue; // collapse cone - spdlog::info("collapsing cones {} and {}", cones[vj], cones[vk]); + spdlog::info("collapsing nearby cones {} and {}", cones[vj], cones[vk]); set_period_jump(m, hij, period_jump[hij] - defect); set_period_jump(m, hik, period_jump[hik] + defect); cones[vk] += defect; @@ -2024,6 +2040,10 @@ void IntrinsicNRosyField::collapse_nearby_cones(const Mesh& m) std::vector final_cones = generate_cones(m); for (int vi = 0; vi < num_vertices; ++vi) { + if (cones[vi] == 0) + { + spdlog::error("zero cone at {}", vi); + } if (cones[vi] != final_cones[vi]) { spdlog::error("inconsistent cones {} and {}", cones[vi], final_cones[vi]);