22#include < Mesh/Mesh.h>
33#include < Mesh/MeshUtils.h>
44#include < Mesh/MeshWarper.h>
5+ #include < Profiling.h>
56#include < igl/biharmonic_coordinates.h>
67#include < igl/boundary_loop.h>
8+ #include < igl/cotmatrix.h>
79#include < igl/edges.h>
810#include < igl/facet_components.h>
11+ #include < igl/min_quad_with_fixed.h>
912#include < igl/point_mesh_squared_distance.h>
1013#include < igl/remove_unreferenced.h>
1114#include < vtkCellLocator.h>
1922#include < vtkTriangleFilter.h>
2023
2124#include < set>
25+ #include < unordered_map>
2226
2327namespace shapeworks {
2428
@@ -27,6 +31,7 @@ static std::mutex mutex;
2731
2832// ---------------------------------------------------------------------------
2933vtkSmartPointer<vtkPolyData> MeshWarper::build_mesh (const Eigen::MatrixXd& particles) {
34+ TIME_SCOPE (" MeshWarper::build_mesh" );
3035 if (!warp_available_) {
3136 return nullptr ;
3237 }
@@ -44,7 +49,8 @@ vtkSmartPointer<vtkPolyData> MeshWarper::build_mesh(const Eigen::MatrixXd& parti
4449
4550 auto points = remove_bad_particles (particles);
4651
47- vtkSmartPointer<vtkPolyData> poly_data = MeshWarper::warp_mesh (points);
52+ vtkSmartPointer<vtkPolyData> poly_data =
53+ laplacian_ready_ ? MeshWarper::warp_mesh_laplacian (points) : MeshWarper::warp_mesh (points);
4854
4955 for (int i = 0 ; i < poly_data->GetNumberOfPoints (); i++) {
5056 double * p = poly_data->GetPoint (i);
@@ -81,6 +87,7 @@ void MeshWarper::set_reference_mesh(vtkSmartPointer<vtkPolyData> reference_mesh,
8187
8288 // mark that the warp needs to be generated
8389 needs_warp_ = true ;
90+ laplacian_ready_ = false ;
8491
8592 warp_available_ = true ;
8693
@@ -372,6 +379,7 @@ void MeshWarper::split_cell_on_edge(int cell_id, int new_vertex, int v0, int v1,
372379
373380// ---------------------------------------------------------------------------
374381bool MeshWarper::generate_warp () {
382+ TIME_SCOPE (" MeshWarper::generate_warp" );
375383 if (is_contour_) {
376384 update_progress (1.0 );
377385 needs_warp_ = false ;
@@ -414,6 +422,15 @@ bool MeshWarper::generate_warp() {
414422 faces_ = reference_mesh.faces ();
415423
416424 // Perform warp
425+ if (warp_method_ == WarpMethod::Laplacian) {
426+ if (generate_laplacian_warp (vertices, faces_, vertices_)) {
427+ update_progress (1.0 );
428+ needs_warp_ = false ;
429+ return true ;
430+ }
431+ SW_DEBUG (" Laplacian warp failed, falling back to biharmonic" );
432+ }
433+
417434 if (MeshWarper::generate_warp_matrix (vertices, faces_, vertices_, warp_)) {
418435 // Success!
419436 update_progress (1.0 );
@@ -422,7 +439,7 @@ bool MeshWarper::generate_warp() {
422439 }
423440 }
424441
425- SW_ERROR (" Mesh Warp Error: igl:biharmonic_coordinates failed" );
442+ SW_ERROR (" Mesh Warp Error: warp generation failed" );
426443
427444 // All attempts failed
428445 update_progress (1.0 );
@@ -433,6 +450,7 @@ bool MeshWarper::generate_warp() {
433450// ---------------------------------------------------------------------------
434451bool MeshWarper::generate_warp_matrix (Eigen::MatrixXd target_vertices, Eigen::MatrixXi target_faces,
435452 const Eigen::MatrixXd& references_vertices, Eigen::MatrixXd& warp) {
453+ TIME_SCOPE (" MeshWarper::generate_warp_matrix (biharmonic)" );
436454 Eigen::VectorXi closest_vertex_indices;
437455
438456 Eigen::VectorXi vertex_indices = Eigen::VectorXi::LinSpaced (target_vertices.rows (), 0 , target_vertices.rows () - 1 );
@@ -480,6 +498,7 @@ bool MeshWarper::find_landmarks_vertices_on_ref_mesh() {
480498
481499// ---------------------------------------------------------------------------
482500vtkSmartPointer<vtkPolyData> MeshWarper::warp_mesh (const Eigen::MatrixXd& points) {
501+ TIME_SCOPE (" MeshWarper::warp_mesh (biharmonic)" );
483502 int num_vertices = warp_.rows ();
484503 int num_faces = faces_.rows ();
485504 Eigen::MatrixXd v_out = warp_ * (points.rowwise () + Eigen::RowVector3d (0 , 0 , 0 ));
@@ -502,6 +521,135 @@ vtkSmartPointer<vtkPolyData> MeshWarper::warp_mesh(const Eigen::MatrixXd& points
502521 return poly_data;
503522}
504523
524+ // ---------------------------------------------------------------------------
525+ int MeshWarper::get_num_warp_vertices () const {
526+ if (laplacian_ready_) {
527+ return laplacian_vertices_.rows ();
528+ }
529+ return warp_.rows ();
530+ }
531+
532+ // ---------------------------------------------------------------------------
533+ bool MeshWarper::generate_laplacian_warp (const Eigen::MatrixXd& V, const Eigen::MatrixXi& F,
534+ const Eigen::MatrixXd& control_points) {
535+ TIME_SCOPE (" MeshWarper::generate_laplacian_warp" );
536+ // Map control points to closest mesh vertices
537+ Eigen::VectorXi vertex_indices = Eigen::VectorXi::LinSpaced (V.rows (), 0 , V.rows () - 1 );
538+ Eigen::VectorXd squared_distances;
539+ Eigen::MatrixXd unused_closest_points;
540+ Eigen::VectorXi closest_vertex_indices;
541+ igl::point_mesh_squared_distance (control_points, V, vertex_indices, squared_distances, closest_vertex_indices,
542+ unused_closest_points);
543+
544+ // Deduplicate handle indices
545+ std::set<int > handle_set (closest_vertex_indices.data (),
546+ closest_vertex_indices.data () + closest_vertex_indices.size ());
547+ laplacian_handles_.resize (handle_set.size ());
548+ int idx = 0 ;
549+ for (int h : handle_set) {
550+ laplacian_handles_ (idx++) = h;
551+ }
552+
553+ // Build cotangent Laplacian
554+ Eigen::SparseMatrix<double > L;
555+ igl::cotmatrix (V, F, L);
556+
557+ // Q = L^T * L (bilaplacian energy)
558+ Eigen::SparseMatrix<double > Q = L.transpose () * L;
559+
560+ // Precompute factorization with fixed handle constraints
561+ Eigen::SparseMatrix<double > Aeq; // empty equality constraints
562+ if (!igl::min_quad_with_fixed_precompute (Q, laplacian_handles_, Aeq, true , mqwf_data_)) {
563+ SW_DEBUG (" Laplacian precompute failed" );
564+ return false ;
565+ }
566+
567+ // Store constant RHS: B = -Q * V_ref (the linear term for the unconstrained part)
568+ laplacian_rhs_ = -Q * V;
569+
570+ // Store reference vertices
571+ laplacian_vertices_ = V;
572+
573+ // Precompute handle_map_: a sparse matrix (num_handles x num_controls) that maps
574+ // control point positions to handle Y rows, averaging when multiple controls map to the same handle.
575+ // Also precompute handle_default_: reference positions for handles with no control point.
576+ int num_handles = laplacian_handles_.size ();
577+ int num_controls = closest_vertex_indices.size ();
578+
579+ // Build a map from vertex id to handle index
580+ std::unordered_map<int , int > vertex_to_handle;
581+ for (int i = 0 ; i < num_handles; i++) {
582+ vertex_to_handle[laplacian_handles_ (i)] = i;
583+ }
584+
585+ // Count how many controls map to each handle
586+ Eigen::VectorXi handle_counts = Eigen::VectorXi::Zero (num_handles);
587+ for (int i = 0 ; i < num_controls; i++) {
588+ auto it = vertex_to_handle.find (closest_vertex_indices (i));
589+ if (it != vertex_to_handle.end ()) {
590+ handle_counts (it->second )++;
591+ }
592+ }
593+
594+ // Build sparse matrix with weights = 1/count for averaging
595+ std::vector<Eigen::Triplet<double >> triplets;
596+ triplets.reserve (num_controls);
597+ for (int i = 0 ; i < num_controls; i++) {
598+ auto it = vertex_to_handle.find (closest_vertex_indices (i));
599+ if (it != vertex_to_handle.end ()) {
600+ int h = it->second ;
601+ triplets.emplace_back (h, i, 1.0 / handle_counts (h));
602+ }
603+ }
604+ handle_map_.resize (num_handles, num_controls);
605+ handle_map_.setFromTriplets (triplets.begin (), triplets.end ());
606+
607+ // Default Y: reference positions for handles with no control point
608+ handle_default_ = Eigen::MatrixXd::Zero (num_handles, 3 );
609+ for (int i = 0 ; i < num_handles; i++) {
610+ if (handle_counts (i) == 0 ) {
611+ handle_default_.row (i) = V.row (laplacian_handles_ (i));
612+ }
613+ }
614+
615+ laplacian_ready_ = true ;
616+ return true ;
617+ }
618+
619+ // ---------------------------------------------------------------------------
620+ vtkSmartPointer<vtkPolyData> MeshWarper::warp_mesh_laplacian (const Eigen::MatrixXd& points) {
621+ TIME_SCOPE (" MeshWarper::warp_mesh_laplacian" );
622+ // Build Y from precomputed sparse map: Y = handle_map_ * points + handle_default_
623+ Eigen::MatrixXd Y = handle_map_ * points + handle_default_;
624+
625+ Eigen::MatrixXd Beq; // empty
626+ Eigen::MatrixXd Z;
627+ if (!igl::min_quad_with_fixed_solve (mqwf_data_, laplacian_rhs_, Y, Beq, Z)) {
628+ SW_ERROR (" Laplacian solve failed" );
629+ return warp_mesh (points); // fallback to biharmonic
630+ }
631+
632+ // Build VTK polydata from solved vertices
633+ int num_vertices = Z.rows ();
634+ int num_faces = faces_.rows ();
635+ vtkSmartPointer<vtkPolyData> poly_data = vtkSmartPointer<vtkPolyData>::New ();
636+ vtkSmartPointer<vtkPoints> out_points = vtkSmartPointer<vtkPoints>::New ();
637+ out_points->SetNumberOfPoints (num_vertices);
638+ for (vtkIdType i = 0 ; i < num_vertices; i++) {
639+ out_points->SetPoint (i, Z (i, 0 ), Z (i, 1 ), Z (i, 2 ));
640+ }
641+ vtkSmartPointer<vtkCellArray> polys = vtkSmartPointer<vtkCellArray>::New ();
642+ for (vtkIdType i = 0 ; i < num_faces; i++) {
643+ polys->InsertNextCell (3 );
644+ polys->InsertCellPoint (faces_ (i, 0 ));
645+ polys->InsertCellPoint (faces_ (i, 1 ));
646+ polys->InsertCellPoint (faces_ (i, 2 ));
647+ }
648+ poly_data->SetPoints (out_points);
649+ poly_data->SetPolys (polys);
650+ return poly_data;
651+ }
652+
505653// ---------------------------------------------------------------------------
506654// ---------------------------------------------------------------------------
507655void MeshWarper::diagnose_biharmonic_failure (const Eigen::MatrixXd& TV, const Eigen::MatrixXi& TF,
0 commit comments