From 9033e3f05352d9605246d3335b1aa27652e7788e Mon Sep 17 00:00:00 2001 From: "chandan.satija" Date: Fri, 2 Dec 2022 08:03:42 -0500 Subject: [PATCH] stkCGALRegionFairingOperator --- src/CMakeLists.txt | 2 + src/Resources/stkCGALRegionFairing.xml | 237 ++++++++++++++++++ src/Testing/CMakeLists.txt | 1 + src/Testing/TestCGALRegionFairingOperator.cxx | 175 +++++++++++++ src/Testing/TestCGALSurfaceMeshTopology.cxx | 2 +- src/stkCGALRegionFairingOperator.cxx | 170 +++++++++++++ src/stkCGALRegionFairingOperator.h | 37 +++ 7 files changed, 623 insertions(+), 1 deletion(-) create mode 100644 src/Resources/stkCGALRegionFairing.xml create mode 100644 src/Testing/TestCGALRegionFairingOperator.cxx create mode 100644 src/stkCGALRegionFairingOperator.cxx create mode 100644 src/stkCGALRegionFairingOperator.h diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 512c3645cb..61aeb458fc 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -30,6 +30,7 @@ set(classes stkCGALPolygonOffset stkCGAL3DConvexHull stkCGALFillHoles + stkCGALRegionFairingOperator ) set(private_headers @@ -62,6 +63,7 @@ set(xml_files Resources/stkCGALRegionGrowing.xml Resources/stkCGALSelfIntersectionMeasurer.xml Resources/stkCGALSurfaceMeshTopology.xml + Resources/stkCGALRegionFairing.xml ) paraview_add_server_manager_xmls( diff --git a/src/Resources/stkCGALRegionFairing.xml b/src/Resources/stkCGALRegionFairing.xml new file mode 100644 index 0000000000..5ddce51c57 --- /dev/null +++ b/src/Resources/stkCGALRegionFairing.xml @@ -0,0 +1,237 @@ + + + + + + + The points of the selected vertices are relocated to yield an as-smooth-as-possible + surface patch. The parameter fairing_continuity gives the ability to control the + tangential continuity Cn of the output mesh. + The region described by vertices might contain multiple disconnected components. + Note that the mesh connectivity is not altered in any way, only vertex locations get + updated. + Fairing might fail if fixed vertices, which are used as boundary conditions, do not + suffice to solve constructed linear system. + Note that if the vertex range to which fairing is applied contains all the vertices + of the triangle mesh, fairing does not fail, but the mesh gets shrinked to origin. + + + + + + + + + + Set the tangential continuity of the output surface for fairing. + The possible values are 0, 1 and 2, refering to the C0, C1 and C2 continuity. + + + + + + + If true, mask array indicating faired points and cells will be generated. + + + + + + + Set the Name of the Point Mask Array for faired cells. + + + + + + + + + Set the Name of the Point Mask Array for faired cells. + + + + + + + + + + If true, all the Arrays of Input will passed to the Output of the Filter without any change. + + + + + + + If true, the Input Mask Array will not be passed to the Output of the Filter. + + + + + + + + + + + + + + + + + + + + + + + + Point Mask Array Used for defining the Region to be Faired. Positive value means + region will be faired. + + + + + + + + + + + + + + + + + + + + + + + + + + + + Set the Input Surface Mesh + + + + + + + + + + Point Mask Array Used for defining the Region to be Faired. Positive + value means region will be faired. + + + + + + + + + + \ No newline at end of file diff --git a/src/Testing/CMakeLists.txt b/src/Testing/CMakeLists.txt index 1203e024ea..923bfe8040 100644 --- a/src/Testing/CMakeLists.txt +++ b/src/Testing/CMakeLists.txt @@ -31,6 +31,7 @@ vtk_add_test_cxx(stkCGALModuleTests tests TestCGALFillHoles.cxx TestCGALPolygonOffset.cxx TestCGAL3DConvexHull.cxx + TestCGALRegionFairingOperator.cxx # Alternative syntax to specify the options per-file: # TestUtils.cxx,NO_DATA,NO_VALID,NO_OUTPUT diff --git a/src/Testing/TestCGALRegionFairingOperator.cxx b/src/Testing/TestCGALRegionFairingOperator.cxx new file mode 100644 index 0000000000..9a11500727 --- /dev/null +++ b/src/Testing/TestCGALRegionFairingOperator.cxx @@ -0,0 +1,175 @@ +#include "doctest.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "stkCGALRegionFairingOperator.h" + +#include + +// Use the following command in Windows Powershell from SpecifX's Build folder to run this test: +// .\bin\stkCGALModuleTests.exe TestCGALRegionFairingOperator +// CMake variable PARAVIEW_BUILD_TESTING must be set to ON to be able to run this test. + +int TestCGALRegionFairingOperator(int argc, char** const argv) +{ + doctest::Context context; + + // https://github.com/onqtam/doctest/blob/4d8716f1efc1d14aa736ef52ee727bd4204f4c40/doc/markdown/commandline.md + context.setOption("force-colors", true); + context.setOption("duration", true); + context.setOption("test-suite", "TestCGALRegionFairingOperator*"); + + // Command line can be used to override above parameters. + context.applyCommandLine(argc, argv); + + return context.run(); +} + +namespace TestCGALRegionFairingOperatorNS +{ + +TEST_SUITE("TestCGALRegionFairingOperator") +{ + int resolution = 8; + int subdivisionLevel = 2; + int dummyCellValue = 5; + std::string dummyCellArrayName = "DummyCellData"; + int dummyPointValue = 18; + std::string dummyPointArrayName = "DummyPointData"; + std::string vertaxMaskArrayName = "RegionMask"; + std::string fairedCellArrayName = "FairedCells"; + std::string fairedPointArrayName = "FairedPoints"; + + TEST_CASE("Feature Loops on Cylinder Test") + { + auto cylinderSource = vtkSmartPointer::New(); + cylinderSource->SetRadius(5.0); + cylinderSource->SetHeight(2.0); + cylinderSource->CappingOn(); + cylinderSource->SetResolution(resolution); + cylinderSource->SetCenter(0.0, 0.0, 0.0); + cylinderSource->Update(); + + auto cleanFilter = vtkSmartPointer::New(); + cleanFilter->SetInputData(cylinderSource->GetOutput()); + cleanFilter->PointMergingOn(); + cleanFilter->ConvertPolysToLinesOff(); + cleanFilter->ConvertStripsToPolysOff(); + cleanFilter->ConvertLinesToPointsOff(); + cleanFilter->Update(); + + auto triangualteFilter = vtkSmartPointer::New(); + triangualteFilter->SetInputData(cleanFilter->GetOutput()); + triangualteFilter->Update(); + + auto subdivideFilter = vtkSmartPointer::New(); + subdivideFilter->SetInputData(triangualteFilter->GetOutput()); + subdivideFilter->SetNumberOfSubdivisions(subdivisionLevel); + subdivideFilter->Update(); + + auto testInput = vtkSmartPointer::New(); + testInput->ShallowCopy(subdivideFilter->GetOutput()); + + REQUIRE(testInput != nullptr); + REQUIRE(testInput->GetNumberOfPoints() != 0); + REQUIRE(testInput->GetNumberOfCells() != 0); + + auto featuresEdgesFilter = vtkSmartPointer::New(); + featuresEdgesFilter->BoundaryEdgesOff(); + featuresEdgesFilter->NonManifoldEdgesOff(); + featuresEdgesFilter->ManifoldEdgesOff(); + featuresEdgesFilter->ColoringOff(); + featuresEdgesFilter->FeatureEdgesOn(); + featuresEdgesFilter->SetFeatureAngle(80.0); + + featuresEdgesFilter->SetInputData(testInput); + featuresEdgesFilter->Update(); + + // Top and Both Loop near the caps should be present in Input + CHECK_MESSAGE(featuresEdgesFilter->GetOutput()->GetNumberOfCells() == + resolution * std::pow(subdivisionLevel, 2) * 2, + "Input Cylinder does not contain expected feature edges"); + + // Adding some dummy data to test input + auto dummyCellArray = vtkSmartPointer::New(); + dummyCellArray->SetNumberOfComponents(1); + dummyCellArray->SetNumberOfTuples(testInput->GetNumberOfPoints()); + dummyCellArray->SetName(dummyCellArrayName.c_str()); + dummyCellArray->Fill(dummyCellValue); + + testInput->GetCellData()->AddArray(dummyCellArray); + + auto dummyPointArray = vtkSmartPointer::New(); + dummyPointArray->SetNumberOfComponents(1); + dummyPointArray->SetNumberOfTuples(testInput->GetNumberOfPoints()); + dummyPointArray->SetName(dummyPointArrayName.c_str()); + dummyPointArray->Fill(dummyPointValue); + + testInput->GetPointData()->AddArray(dummyPointArray); + + // Mask Array + auto vertexMaskArray = vtkSmartPointer::New(); + vertexMaskArray->SetNumberOfComponents(1); + vertexMaskArray->SetNumberOfTuples(testInput->GetNumberOfPoints()); + vertexMaskArray->SetName(vertaxMaskArrayName.c_str()); + vertexMaskArray->Fill(0); + + for (int id = 0; id < testInput->GetNumberOfPoints(); id++) + { + if (testInput->GetPoints()->GetPoint(id)[1] > 0.0) + { + vertexMaskArray->SetTuple1(id, 1.0); + } + } + + testInput->GetPointData()->AddArray(vertexMaskArray); + + auto regionFairingOperatorFilter = vtkSmartPointer::New(); + regionFairingOperatorFilter->SetInputData(testInput); + regionFairingOperatorFilter->SetRegionArrayName(vertaxMaskArrayName.c_str()); + regionFairingOperatorFilter->SetFairingContinuity(stkCGALRegionFairingOperator::C1); + regionFairingOperatorFilter->GenerateFairedMaskArraysOn(); + regionFairingOperatorFilter->SetFairedPointMaskArrayName(fairedPointArrayName.c_str()); + regionFairingOperatorFilter->SetFairedCellMaskArrayName(fairedCellArrayName.c_str()); + regionFairingOperatorFilter->PassAllArraysOn(); + regionFairingOperatorFilter->ConsumeInputMaskArrayOn(); + regionFairingOperatorFilter->Update(); + + auto fairedOutput = regionFairingOperatorFilter->GetOutput(); + + featuresEdgesFilter->SetInputData(fairedOutput); + featuresEdgesFilter->Update(); + + // Only Loop near bottom cap should be present in Faired Output + CHECK_MESSAGE(featuresEdgesFilter->GetOutput()->GetNumberOfCells() == + resolution * std::pow(subdivisionLevel, 2), + "Faired Cylinder does not contain expected feature edges"); + + // Checks for existance of array in the output. Data value are not being checked in this unit + // test. + CHECK_MESSAGE(fairedOutput->GetPointData()->HasArray(fairedPointArrayName.c_str()) == 1, + "Output does not contain Faired Point Mask"); + + CHECK_MESSAGE(fairedOutput->GetCellData()->HasArray(fairedCellArrayName.c_str()) == 1, + "Output does not contain Faired Cell Mask"); + + CHECK_MESSAGE(fairedOutput->GetPointData()->HasArray(vertaxMaskArrayName.c_str()) == 0, + "Output does not consume Input Point Mask"); + + CHECK_MESSAGE(fairedOutput->GetPointData()->HasArray(dummyPointArrayName.c_str()) == 1, + "Output does not contain Dummy Point Data"); + + CHECK_MESSAGE(fairedOutput->GetCellData()->HasArray(dummyCellArrayName.c_str()) == 1, + "Output does not contained Dummy Cell Data"); + } +} +} \ No newline at end of file diff --git a/src/Testing/TestCGALSurfaceMeshTopology.cxx b/src/Testing/TestCGALSurfaceMeshTopology.cxx index bba2822956..fafe3b209d 100644 --- a/src/Testing/TestCGALSurfaceMeshTopology.cxx +++ b/src/Testing/TestCGALSurfaceMeshTopology.cxx @@ -61,7 +61,7 @@ TEST_SUITE("TestCGALSurfaceMeshTopology") auto testInput = vtkSmartPointer::New(); testInput->ShallowCopy(subdivideFilter->GetOutput()); - auto vertexMaskArray = vtkDoubleArray::New(); + auto vertexMaskArray = vtkSmartPointer::New(); vertexMaskArray->SetNumberOfComponents(1); vertexMaskArray->SetNumberOfTuples(testInput->GetNumberOfPoints()); vertexMaskArray->SetName("Base Point Mask Array"); diff --git a/src/stkCGALRegionFairingOperator.cxx b/src/stkCGALRegionFairingOperator.cxx new file mode 100644 index 0000000000..568dc5e0c1 --- /dev/null +++ b/src/stkCGALRegionFairingOperator.cxx @@ -0,0 +1,170 @@ +#include + +#include + +#include +#include +#include +#include +#include +#include + +#include +#include + +#include + +typedef CGAL::Exact_predicates_inexact_constructions_kernel K; +typedef CGAL::Surface_mesh Surface_Mesh; +typedef boost::graph_traits::vertex_descriptor Graph_Verts; + +namespace PMP = CGAL::Polygon_mesh_processing; + +//---------------------------------------------------------------------------- +vtkStandardNewMacro(stkCGALRegionFairingOperator); + +//---------------------------------------------------------------------------- +int stkCGALRegionFairingOperator::RequestData(vtkInformation* vtkNotUsed(request), + vtkInformationVector** inputVector, vtkInformationVector* outputVector) +{ + vtkPolyData* inputMesh = vtkPolyData::GetData(inputVector[0]); + vtkPolyData* outputMesh = vtkPolyData::GetData(outputVector, 0); + + if (inputMesh == nullptr) + { + vtkErrorMacro("Input Mesh is NULL"); + return 0; + } + + if (inputMesh->GetNumberOfPoints() < 3) + { + vtkErrorMacro("Input Mesh contains less than 3 Points."); + return 0; + } + + if (inputMesh->GetNumberOfPolys() == 0) + { + vtkErrorMacro("Input Mesh does not contain any Polygonal Cells."); + return 0; + } + + auto maskArray = inputMesh->GetPointData()->GetArray(this->RegionArrayName.c_str()); + + if (maskArray == nullptr) + { + vtkErrorMacro("Could not find Array from the provided RegionArrayName"); + return 0; + } + + if (maskArray->GetNumberOfComponents() != 1) + { + vtkErrorMacro("Expected Mask Array to contain only 1 component"); + return 0; + } + + VTK_ASSUME(maskArray->GetNumberOfComponents() == 1); + + Surface_Mesh cgalMesh; + stkCGALUtilities::vtkPolyDataToPolygonMesh(inputMesh, cgalMesh); + + if (!CGAL::is_triangle_mesh(cgalMesh)) + { + vtkErrorMacro("Input Mesh is not triangular."); + return 0; + } + + std::vector roi; // region of interest + + // Get the Vertices of the Patch to be faired from the Mask Array + for (vtkIdType id = 0; id < maskArray->GetNumberOfTuples(); id++) + { + if (maskArray->GetTuple1(id) > 0.0) + { + roi.emplace_back(Graph_Verts(id)); + } + } + + try + { + PMP::fair(cgalMesh, roi, PMP::parameters::fairing_continuity(this->FairingContinuity)); + } + catch (std::exception& e) + { + vtkErrorMacro("CGAL Exception: " << e.what()); + return 0; + } + + stkCGALUtilities::SurfaceMeshToPolyData(cgalMesh, outputMesh); + + // Mask Arrays to indicide faired point and cells + if (this->GenerateFairedMaskArrays) + { + auto pointDataToCellData = vtkSmartPointer::New(); + pointDataToCellData->SetInputData(inputMesh); + pointDataToCellData->PassPointDataOff(); + pointDataToCellData->ProcessAllArraysOff(); + pointDataToCellData->CategoricalDataOff(); + pointDataToCellData->AddPointDataArray(maskArray->GetName()); + pointDataToCellData->Update(); + + auto fairedCellMaskArray = vtkSmartPointer::New(); + fairedCellMaskArray->SetName(this->FairedCellMaskArrayName.c_str()); + fairedCellMaskArray->SetNumberOfComponents(1); + fairedCellMaskArray->SetNumberOfTuples(inputMesh->GetNumberOfCells()); + fairedCellMaskArray->Fill(0.0); + + auto tempCellDataArray = pointDataToCellData->GetPolyDataOutput()->GetCellData()->GetArray( + this->RegionArrayName.c_str()); + + if (tempCellDataArray) + { + unsigned char fillValue = 1; + for (vtkIdType id = 0; id < tempCellDataArray->GetNumberOfTuples(); id++) + { + if (tempCellDataArray->GetTuple1(id) > 0.0) + { + fairedCellMaskArray->SetValue(id, fillValue); + } + } + } + + outputMesh->GetCellData()->AddArray(fairedCellMaskArray); + + auto fairedPointMaskArray = vtkSmartPointer::New(); + fairedPointMaskArray->DeepCopy(maskArray); + fairedPointMaskArray->SetName(this->FairedPointMaskArrayName.c_str()); + + outputMesh->GetPointData()->AddArray(fairedPointMaskArray); + } + + if (this->PassAllArrays) + { + // Transfer All Point Ids Arrays + for (int pointArrayID = 0; pointArrayID < inputMesh->GetPointData()->GetNumberOfArrays(); + pointArrayID++) + { + auto sourcePointArray = inputMesh->GetPointData()->GetAbstractArray(pointArrayID); + + if (this->ConsumeInputMaskArray && + (std::string(sourcePointArray->GetName()) == this->RegionArrayName)) + { + continue; + } + + outputMesh->GetPointData()->AddArray(sourcePointArray); + } + + // Transfer All Cells Ids Arrays + for (int cellArrayID = 0; cellArrayID < inputMesh->GetCellData()->GetNumberOfArrays(); + cellArrayID++) + { + auto sourceCellArray = inputMesh->GetCellData()->GetAbstractArray(cellArrayID); + outputMesh->GetCellData()->AddArray(sourceCellArray); + } + + // Transfer Field Data + outputMesh->GetFieldData()->PassData(inputMesh->GetFieldData()); + } + + return 1; +} diff --git a/src/stkCGALRegionFairingOperator.h b/src/stkCGALRegionFairingOperator.h new file mode 100644 index 0000000000..355de6a32d --- /dev/null +++ b/src/stkCGALRegionFairingOperator.h @@ -0,0 +1,37 @@ +/** + * @class stkCGALRegionFairingOperator + * @brief Fairs a region on a triangle mesh. + * + * The points of the selected vertices are relocated to yield an as-smooth-as-possible surface patch. The parameter fairing_continuity gives the ability to control the tangential continuity Cn of the output mesh. + * The region described by vertices might contain multiple disconnected components. Note that the mesh connectivity is not altered in any way, only vertex locations get updated. + * Fairing might fail if fixed vertices, which are used as boundary conditions, do not suffice to solve constructed linear system. + * Note that if the vertex range to which fairing is applied contains all the vertices of the triangle mesh, fairing does not fail, but the mesh gets shrinked to origin. + * + * @sa + * stkCGALRegionFairingOperatorInterface + */ +#pragma once + +#include +#include + +/** + * @ingroup stkCGAL + * + */ +class STKCGAL_EXPORT stkCGALRegionFairingOperator : public stkCGALRegionFairingOperatorInterface +{ +public: + static stkCGALRegionFairingOperator* New(); + vtkTypeMacro(stkCGALRegionFairingOperator, stkCGALRegionFairingOperatorInterface); + +protected: + stkCGALRegionFairingOperator() = default; + ~stkCGALRegionFairingOperator() = default; + + int RequestData(vtkInformation*, vtkInformationVector**, vtkInformationVector*) override; + +private: + stkCGALRegionFairingOperator(const stkCGALRegionFairingOperator&) = delete; + void operator=(const stkCGALRegionFairingOperator&) = delete; +};