diff --git a/Data/Testing/HornSelection.csv b/Data/Testing/HornSelection.csv new file mode 100644 index 0000000000000000000000000000000000000000..38a68b1d99db7f08d31e3049c7b147c31a3c6d0b --- /dev/null +++ b/Data/Testing/HornSelection.csv @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:c8b99f5fc3c51805d913b472170ca71574ca2de9171171572ad281f5ba205e65 +size 5537 diff --git a/vtkCGAL/PolygonMeshProcessing/CMakeLists.txt b/vtkCGAL/PolygonMeshProcessing/CMakeLists.txt index 51222c27a4a84bf07aeecf6ec452b1239ea806bf..7d5e743743b777b2a4eef325519c1a1ab1f0a610 100644 --- a/vtkCGAL/PolygonMeshProcessing/CMakeLists.txt +++ b/vtkCGAL/PolygonMeshProcessing/CMakeLists.txt @@ -3,6 +3,7 @@ set(vtkcgalpmp_files vtkCGALBooleanOperation vtkCGALIsotropicRemesher + vtkCGALMeshDeformation vtkCGALPatchFilling vtkCGALRegionFairing ) diff --git a/vtkCGAL/PolygonMeshProcessing/Testing/CMakeLists.txt b/vtkCGAL/PolygonMeshProcessing/Testing/CMakeLists.txt index 8a2dea90963bbd333c391859fc931a8e4f1305a0..890e383d7394d60fdec9978af7eb44dd30a8eb08 100644 --- a/vtkCGAL/PolygonMeshProcessing/Testing/CMakeLists.txt +++ b/vtkCGAL/PolygonMeshProcessing/Testing/CMakeLists.txt @@ -36,6 +36,7 @@ vtk_add_test_cxx(vtkCGALCxxTests no_data_tests NO_DATA NO_VALID NO_OUTPUT TestPMPInstance.cxx TestPMPBooleanExecution.cxx + TestPMPDeformExecution.cxx TestPMPFairExecution.cxx TestPMPFillExecution.cxx TestPMPIsotropicExecution.cxx diff --git a/vtkCGAL/PolygonMeshProcessing/Testing/TestPMPBooleanExecution.cxx b/vtkCGAL/PolygonMeshProcessing/Testing/TestPMPBooleanExecution.cxx index 8cd24b8848ea3145ef046c4a1543127d2cbb1589..79cc8557f0ad207db5bf70c69d857206ff4d8181 100644 --- a/vtkCGAL/PolygonMeshProcessing/Testing/TestPMPBooleanExecution.cxx +++ b/vtkCGAL/PolygonMeshProcessing/Testing/TestPMPBooleanExecution.cxx @@ -28,19 +28,16 @@ int TestPMPBooleanExecution(int, char* argv[]) vtkNew writer; writer->SetInputConnection(boolOp->GetOutputPort()); writer->SetFileName("boolean_operation_difference.vtp"); - writer->Update(); writer->Write(); // Compute intersection boolOp->SetOperationType(vtkCGALBooleanOperation::INTERSECTION); writer->SetFileName("boolean_operation_intersection.vtp"); - writer->Update(); writer->Write(); // Compute union boolOp->SetOperationType(vtkCGALBooleanOperation::UNION); writer->SetFileName("boolean_operation_union.vtp"); - writer->Update(); writer->Write(); return 0; diff --git a/vtkCGAL/PolygonMeshProcessing/Testing/TestPMPDeformExecution.cxx b/vtkCGAL/PolygonMeshProcessing/Testing/TestPMPDeformExecution.cxx new file mode 100644 index 0000000000000000000000000000000000000000..7d34a9548d282ee5d639e4eda4bc97b2e032692b --- /dev/null +++ b/vtkCGAL/PolygonMeshProcessing/Testing/TestPMPDeformExecution.cxx @@ -0,0 +1,85 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "vtkCGALMeshDeformation.h" + +int TestPMPDeformExecution(int, char* argv[]) +{ + // Open data + vtkNew reader; + std::string cfname(argv[1]); + cfname += "/dragon.vtp"; + reader->SetFileName(cfname.c_str()); + + // Create point set for target positions of the control points + vtkNew targets; + + // Define global point IDs of the control points + vtkNew ctrlPointIds; + ctrlPointIds->SetName("vtkOriginalPointIds"); + ctrlPointIds->SetNumberOfTuples(2); + ctrlPointIds->SetValue(0, 42868); + ctrlPointIds->SetValue(1, 48903); + targets->GetPointData()->AddArray(ctrlPointIds); + + // Move the X coordinates by -0.2 + vtkNew targetPoints; + targetPoints->SetNumberOfPoints(2); + targetPoints->SetPoint(0, 1.47875, 2.01455, -0.040048); + targetPoints->SetPoint(1, 1.48846, 2.01667, -0.0417898); + targets->SetPoints(targetPoints); + + // Create point selection for the ROI + vtkNew sel; + vtkNew node; + sel->AddNode(node); + node->GetProperties()->Set(vtkSelectionNode::CONTENT_TYPE(), vtkSelectionNode::INDICES); + node->GetProperties()->Set(vtkSelectionNode::FIELD_TYPE(), vtkSelectionNode::POINT); + + // Read ROI points IDs from CSV file + vtkNew roiReader; + std::string roiFilename(argv[1]); + roiFilename += "/HornSelection.csv"; + roiReader->SetFileName(roiFilename.c_str()); + roiReader->SetHaveHeaders(true); + roiReader->SetDetectNumericColumns(true); + roiReader->Update(); + + vtkIntArray* roiArr = + vtkIntArray::SafeDownCast(roiReader->GetOutput()->GetColumnByName("vtkOriginalPointIds")); + + vtkNew arr; + arr->SetNumberOfTuples(roiArr->GetNumberOfTuples()); + + for (std::size_t idx = 0; idx < roiArr->GetNumberOfTuples(); ++idx) + { + arr->SetValue(idx, roiArr->GetValue(idx)); + } + + node->SetSelectionList(arr); + + // Deform selected ROI using target positions + vtkNew deformer; + deformer->SetInputConnection(0, reader->GetOutputPort()); + deformer->SetInputData(1, targets); + deformer->SetInputData(2, sel); + deformer->SetGlobalIdArray("vtkOriginalPointIds"); + + // Save result + vtkNew writer; + writer->SetInputConnection(deformer->GetOutputPort()); + writer->SetFileName("deform_dragon.vtp"); + writer->Write(); + + return 0; +} diff --git a/vtkCGAL/PolygonMeshProcessing/Testing/TestPMPFairExecution.cxx b/vtkCGAL/PolygonMeshProcessing/Testing/TestPMPFairExecution.cxx index 60cb3ddea8e9a2b5e3ff168a728549a6945932dd..bf1c619d5f392a756e00e9d41abbb04974c2ee35 100644 --- a/vtkCGAL/PolygonMeshProcessing/Testing/TestPMPFairExecution.cxx +++ b/vtkCGAL/PolygonMeshProcessing/Testing/TestPMPFairExecution.cxx @@ -61,7 +61,6 @@ int TestPMPFairExecution(int, char* argv[]) vtkNew writer; writer->SetInputConnection(fr->GetOutputPort()); writer->SetFileName("fair_points.vtp"); - writer->Update(); writer->Write(); return 0; diff --git a/vtkCGAL/PolygonMeshProcessing/Testing/TestPMPFillExecution.cxx b/vtkCGAL/PolygonMeshProcessing/Testing/TestPMPFillExecution.cxx index 4de9af0a1017e46d9241e1d1984c1a162cab519b..37ffce8418ec4d789e6218b8e668bc77509940e5 100644 --- a/vtkCGAL/PolygonMeshProcessing/Testing/TestPMPFillExecution.cxx +++ b/vtkCGAL/PolygonMeshProcessing/Testing/TestPMPFillExecution.cxx @@ -61,7 +61,6 @@ int TestPMPFillExecution(int, char* argv[]) vtkNew writer; writer->SetInputConnection(pf->GetOutputPort()); writer->SetFileName("fill_tunnels.vtp"); - writer->Update(); writer->Write(); return 0; diff --git a/vtkCGAL/PolygonMeshProcessing/Testing/TestPMPIsotropicExecution.cxx b/vtkCGAL/PolygonMeshProcessing/Testing/TestPMPIsotropicExecution.cxx index 4cc4abb2023237ee1bd050e5d3e4e7ebe5532749..7b3f8a60d1dc05a160b30563579e08be87a93a5e 100644 --- a/vtkCGAL/PolygonMeshProcessing/Testing/TestPMPIsotropicExecution.cxx +++ b/vtkCGAL/PolygonMeshProcessing/Testing/TestPMPIsotropicExecution.cxx @@ -27,7 +27,6 @@ int TestPMPIsotropicExecution(int, char* argv[]) vtkNew writer; writer->SetInputConnection(rm->GetOutputPort()); writer->SetFileName("isotropic_remesh.vtp"); - writer->Update(); writer->Write(); return 0; diff --git a/vtkCGAL/PolygonMeshProcessing/vtk.module b/vtkCGAL/PolygonMeshProcessing/vtk.module index 2b0e2351bbf84b1a4136d432f0077d80871991ce..9cc476dc36c81784de815d0d8b499e25b7406961 100644 --- a/vtkCGAL/PolygonMeshProcessing/vtk.module +++ b/vtkCGAL/PolygonMeshProcessing/vtk.module @@ -19,5 +19,6 @@ OPTIONAL_DEPENDS TEST_DEPENDS VTK::CommonSystem VTK::FiltersSources + VTK::IOInfovis VTK::IOXML VTK::TestingCore diff --git a/vtkCGAL/PolygonMeshProcessing/vtkCGALMeshDeformation.cxx b/vtkCGAL/PolygonMeshProcessing/vtkCGALMeshDeformation.cxx new file mode 100644 index 0000000000000000000000000000000000000000..dd0b82ed2f3186b76a49f3120f1ed9d65f7cd440 --- /dev/null +++ b/vtkCGAL/PolygonMeshProcessing/vtkCGALMeshDeformation.cxx @@ -0,0 +1,201 @@ +#include "vtkCGALMeshDeformation.h" + +// VTK related includes +#include "vtkExtractSelection.h" +#include "vtkInformation.h" +#include "vtkInformationVector.h" +#include "vtkObjectFactory.h" +#include "vtkPointData.h" +#include "vtkPointSet.h" +#include "vtkSelection.h" + +// CGAL related includes +#include + +vtkStandardNewMacro(vtkCGALMeshDeformation); + +using Mesh_Deformation = CGAL::Surface_mesh_deformation; + +//------------------------------------------------------------------------------ +vtkCGALMeshDeformation::vtkCGALMeshDeformation() +{ + this->SetNumberOfInputPorts(3); +} + +//------------------------------------------------------------------------------ +void vtkCGALMeshDeformation::PrintSelf(ostream& os, vtkIndent indent) +{ + os << indent << "Iterations :" << this->Iterations << std::endl; + os << indent << "Tolerance :" << this->Tolerance << std::endl; + this->Superclass::PrintSelf(os, indent); +} + +//------------------------------------------------------------------------------ +int vtkCGALMeshDeformation::FillInputPortInformation(int port, vtkInformation* info) +{ + if (port == 0) + { + info->Set(vtkAlgorithm::INPUT_REQUIRED_DATA_TYPE(), "vtkPolyData"); + } + else if (port == 1) + { + info->Set(vtkAlgorithm::INPUT_REQUIRED_DATA_TYPE(), "vtkPointSet"); + } + else + { + info->Set(vtkAlgorithm::INPUT_REQUIRED_DATA_TYPE(), "vtkSelection"); + info->Set(vtkAlgorithm::INPUT_IS_OPTIONAL(), 1); + } + + return 1; +} + +//------------------------------------------------------------------------------ +int vtkCGALMeshDeformation::RequestData( + vtkInformation*, vtkInformationVector** inputVector, vtkInformationVector* outputVector) +{ + // Get the input and output data objects. + vtkPolyData* input = vtkPolyData::GetData(inputVector[0]); + vtkPointSet* targets = vtkPointSet::GetData(inputVector[1]); + vtkPolyData* output = vtkPolyData::GetData(outputVector); + + if (!input || !targets || !output) + { + vtkErrorMacro(<< "Missing input or output!"); + return 0; + } + + // Get the optional selection input + vtkInformation* selInfo = inputVector[2]->GetInformationObject(0); + vtkNew roi; + + // If no selection is given, use control points as ROI + // A better approach could use all points within a radius + // of the control points to avoid self intersections + if (selInfo) + { + vtkSelection* roiSel = vtkSelection::SafeDownCast(selInfo->Get(vtkDataObject::DATA_OBJECT())); + vtkNew extractSelection; + extractSelection->SetInputData(0, input); + extractSelection->SetInputData(1, roiSel); + extractSelection->Update(); + roi->ShallowCopy(extractSelection->GetOutputDataObject(0)); + } + else + { + roi->ShallowCopy(targets); + } + + // Create the triangle mesh for CGAL + // -------------------------------- + + std::unique_ptr cgalMesh = this->toCGAL(input); + + // Create the deformation object + // --------------------------------- + + Mesh_Deformation deformer(cgalMesh->surface); + + // Find global ID array name + // --------------------------------- + + if (this->GlobalIdArray.empty()) + { + if (!input->GetPointData()->GetGlobalIds()) + { + vtkErrorMacro( + << "Could not find a default array for global IDs. Please specify an array name " + "using SetGlobalIdArray()."); + return 0; + } + else + { + this->GlobalIdArray = input->GetPointData()->GetGlobalIds()->GetName(); + } + } + + // Define the ROI + // --------------------------------- + + if (!roi->GetPointData()->GetArray(this->GlobalIdArray.c_str())) + { + vtkErrorMacro(<< "No array named " << this->GlobalIdArray << " for the ROI!"); + return 0; + } + + auto gids = + vtk::DataArrayValueRange<1>(roi->GetPointData()->GetArray(this->GlobalIdArray.c_str())); + std::vector roiVerts(gids.cbegin(), gids.cend()); + try + { + deformer.insert_roi_vertices(roiVerts.begin(), roiVerts.end()); + } + catch (std::exception& e) + { + vtkErrorMacro("CGAL Exception: " << e.what()); + return 0; + } + + // Define the control points + // --------------------------------- + + if (!targets->GetPointData()->GetArray(this->GlobalIdArray.c_str())) + { + vtkErrorMacro(<< "No array named " << this->GlobalIdArray << " for the control points!"); + return 0; + } + + gids = + vtk::DataArrayValueRange<1>(targets->GetPointData()->GetArray(this->GlobalIdArray.c_str())); + std::vector ctrlPoints(gids.cbegin(), gids.cend()); + try + { + deformer.insert_control_vertices(ctrlPoints.begin(), ctrlPoints.end()); + } + catch (std::exception& e) + { + vtkErrorMacro("CGAL Exception: " << e.what()); + return 0; + } + + // Define the target positions for the control points + // --------------------------------- + + for (vtkIdType ptIdx = 0; ptIdx < targets->GetNumberOfPoints(); ++ptIdx) + { + double coords[3] = { 0.0, 0.0, 0.0 }; + targets->GetPoint(ptIdx, coords); + Mesh_Deformation::Point targetPos(coords[0], coords[1], coords[2]); + try + { + deformer.set_target_position(ctrlPoints[ptIdx], targetPos); + } + catch (std::exception& e) + { + vtkErrorMacro("CGAL Exception: " << e.what()); + return 0; + } + } + + // CGAL Processing + // --------------- + + try + { + // Deform mesh given ROI and control point targets + deformer.deform(this->Iterations, this->Tolerance); + } + catch (std::exception& e) + { + vtkErrorMacro("CGAL Exception: " << e.what()); + return 0; + } + + // VTK Output + // ---------- + + output->ShallowCopy(this->toVTK(cgalMesh.get())); + this->copyAttributes(input, output); + + return 1; +} diff --git a/vtkCGAL/PolygonMeshProcessing/vtkCGALMeshDeformation.h b/vtkCGAL/PolygonMeshProcessing/vtkCGALMeshDeformation.h new file mode 100644 index 0000000000000000000000000000000000000000..7393c9f62a9d44b5ebdd799d93ae086ee323453a --- /dev/null +++ b/vtkCGAL/PolygonMeshProcessing/vtkCGALMeshDeformation.h @@ -0,0 +1,76 @@ +/** + * @class vtkCGALMeshDeformation + * @brief Deforms a surface mesh + * + * vtkCGALMeshDeformation is a filter that deforms a surface mesh by moving + * control points to target positions. Neighboring points contained in a + * Region of Interest (ROI) may be moved to obtain a smooth deformation. + * + * The filter can take three inputs (on ports 0, 1, 2, respectively): + * - the vtkPolyData mesh to deform with unique IDs for the points + * - a vtkPointSet with the target positions of the control points, identified by their IDs + * - a vtkSelection corresponding to the ROI (optional) + * + * If a ROI is not specified, it is defined with the control points. + * In this case, the control points will simply be moved to their destinations without + * modifying the rest of the mesh. + */ + +#ifndef vtkCGALMeshDeformation_h +#define vtkCGALMeshDeformation_h + +#include "vtkCGALPolyDataAlgorithm.h" + +#include "vtkCGALPMPModule.h" // For export macro + +class VTKCGALPMP_EXPORT vtkCGALMeshDeformation : public vtkCGALPolyDataAlgorithm +{ +public: + static vtkCGALMeshDeformation* New(); + vtkTypeMacro(vtkCGALMeshDeformation, vtkCGALPolyDataAlgorithm); + void PrintSelf(ostream& os, vtkIndent indent) override; + + ///@{ + /** + * Get/set the number of iterations used in the deformation process. + * Default is 5. + **/ + vtkGetMacro(Iterations, double); + vtkSetMacro(Iterations, double); + ///@} + + ///@{ + /** + * Get/set the tolerance of the energy convergence used in the deformation process. + * Default is 1e-4. + **/ + vtkGetMacro(Tolerance, double); + vtkSetMacro(Tolerance, double); + ///@} + + ///@{ + /** + * Get/set the name of the array containing the IDs to use when defining ROI and control points. + * Default is the array returned by GetGlobalIds for the points. + **/ + vtkGetMacro(GlobalIdArray, std::string); + vtkSetMacro(GlobalIdArray, std::string); + ///@} + +protected: + vtkCGALMeshDeformation(); + ~vtkCGALMeshDeformation() override = default; + + int RequestData(vtkInformation*, vtkInformationVector**, vtkInformationVector*) override; + int FillInputPortInformation(int port, vtkInformation* info) override; + + unsigned int Iterations = 5; + double Tolerance = 1e-4; + std::string GlobalIdArray = ""; + +private: + vtkCGALMeshDeformation(const vtkCGALMeshDeformation&) = delete; + void operator=(const vtkCGALMeshDeformation&) = delete; +}; + +#endif diff --git a/vtkCGAL/PolygonMeshProcessing/vtkCGALPolyDataAlgorithm.cxx b/vtkCGAL/PolygonMeshProcessing/vtkCGALPolyDataAlgorithm.cxx index 2929d31bbe54b07491be137b5ec70baff076bd02..c115936cba54c3bff9c5e8cb343ef3fe00892121 100644 --- a/vtkCGAL/PolygonMeshProcessing/vtkCGALPolyDataAlgorithm.cxx +++ b/vtkCGAL/PolygonMeshProcessing/vtkCGALPolyDataAlgorithm.cxx @@ -1,8 +1,10 @@ #include "vtkCGALPolyDataAlgorithm.h" // VTK related includes +#include "vtkCellData.h" #include "vtkCellIterator.h" #include "vtkObjectFactory.h" +#include "vtkPointData.h" #include "vtkProbeFilter.h" vtkStandardNewMacro(vtkCGALPolyDataAlgorithm); @@ -114,5 +116,17 @@ bool vtkCGALPolyDataAlgorithm::interpolateAttributes(vtkPolyData* input, vtkPoly vtkMesh->ShallowCopy(probe->GetOutput()); } - return 1; + return true; +} + +//------------------------------------------------------------------------------ +bool vtkCGALPolyDataAlgorithm::copyAttributes(vtkPolyData* input, vtkPolyData* vtkMesh) +{ + if (this->UpdateAttributes) + { + vtkMesh->GetPointData()->ShallowCopy(input->GetPointData()); + vtkMesh->GetCellData()->ShallowCopy(input->GetCellData()); + } + + return true; } diff --git a/vtkCGAL/PolygonMeshProcessing/vtkCGALPolyDataAlgorithm.h b/vtkCGAL/PolygonMeshProcessing/vtkCGALPolyDataAlgorithm.h index 1390bc5184619fa82f8bc17dece1f0a51785747d..e30b1801d150508bc4e754f2cbe6be3d97d5c88d 100644 --- a/vtkCGAL/PolygonMeshProcessing/vtkCGALPolyDataAlgorithm.h +++ b/vtkCGAL/PolygonMeshProcessing/vtkCGALPolyDataAlgorithm.h @@ -78,6 +78,12 @@ protected: */ bool interpolateAttributes(vtkPolyData* input, vtkPolyData* vtkMesh); + /** + * Copy the attributes of input onto vtkMesh + * if UpdateAttributes is true. + */ + bool copyAttributes(vtkPolyData* input, vtkPolyData* vtkMesh); + // Fields bool UpdateAttributes = true;