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;
+};