// SPDX-FileCopyrightText: Copyright (c) Ken Martin, Will Schroeder, Bill Lorensen // SPDX-License-Identifier: BSD-3-Clause #include "vtkGeometryFilter.h" #include "vtkArrayDispatch.h" #include "vtkArrayDispatchDataSetArrayList.h" #include "vtkArrayListTemplate.h" // For processing attribute data #include "vtkCellArray.h" #include "vtkCellData.h" #include "vtkCellTypeUtilities.h" #include "vtkDataArrayRange.h" #include "vtkDataSetSurfaceFilter.h" #include "vtkGenericCell.h" #include "vtkHexagonalPrism.h" #include "vtkHexahedron.h" #include "vtkIncrementalPointLocator.h" #include "vtkInformation.h" #include "vtkInformationVector.h" #include "vtkLogger.h" #include "vtkNew.h" #include "vtkObjectFactory.h" #include "vtkPentagonalPrism.h" #include "vtkPointData.h" #include "vtkPolyData.h" #include "vtkPyramid.h" #include "vtkRectilinearGrid.h" #include "vtkSMPTools.h" #include "vtkStaticCellLinksTemplate.h" #include "vtkStaticFaceHashLinksTemplate.h" #include "vtkStreamingDemandDrivenPipeline.h" #include "vtkStructuredData.h" #include "vtkStructuredGrid.h" #include "vtkTetra.h" #include "vtkUniformGrid.h" #include "vtkUnsignedCharArray.h" #include "vtkUnstructuredGrid.h" #include "vtkVoxel.h" #include "vtkWedge.h" #include #include #include VTK_ABI_NAMESPACE_BEGIN vtkStandardNewMacro(vtkGeometryFilter); static constexpr unsigned char MASKED_CELL_VALUE = vtkDataSetAttributes::HIDDENCELL | vtkDataSetAttributes::DUPLICATECELL | vtkDataSetAttributes::REFINEDCELL; static constexpr unsigned char MASKED_CELL_VALUE_NOT_VISIBLE = vtkDataSetAttributes::HIDDENCELL | vtkDataSetAttributes::REFINEDCELL; static constexpr unsigned char MASKED_POINT_VALUE = vtkDataSetAttributes::HIDDENPOINT; //------------------------------------------------------------------------------ // Construct with all types of clipping turned off. vtkGeometryFilter::vtkGeometryFilter() { this->PointMinimum = 0; this->PointMaximum = VTK_ID_MAX; this->CellMinimum = 0; this->CellMaximum = VTK_ID_MAX; this->Extent[0] = -VTK_DOUBLE_MAX; this->Extent[1] = VTK_DOUBLE_MAX; this->Extent[2] = -VTK_DOUBLE_MAX; this->Extent[3] = VTK_DOUBLE_MAX; this->Extent[4] = -VTK_DOUBLE_MAX; this->Extent[5] = VTK_DOUBLE_MAX; this->PointClipping = false; this->CellClipping = false; this->ExtentClipping = false; this->Merging = true; this->Locator = nullptr; this->OutputPointsPrecision = DEFAULT_PRECISION; this->FastMode = false; this->RemoveGhostInterfaces = true; this->PieceInvariant = 0; this->PassThroughCellIds = 0; this->PassThroughPointIds = 0; this->OriginalCellIdsName = nullptr; this->OriginalPointIdsName = nullptr; // optional 2nd input this->SetNumberOfInputPorts(2); // Compatibility with vtkDataSetSurfaceFilter this->NonlinearSubdivisionLevel = 1; this->MatchBoundariesIgnoringCellOrder = 0; // Enable delegation to an internal vtkDataSetSurfaceFilter. this->Delegation = true; } //------------------------------------------------------------------------------ vtkGeometryFilter::~vtkGeometryFilter() { this->SetOriginalCellIdsName(nullptr); this->SetOriginalPointIdsName(nullptr); } //------------------------------------------------------------------------------ // Specify a (xmin,xmax, ymin,ymax, zmin,zmax) bounding box to clip data. void vtkGeometryFilter::SetExtent( double xMin, double xMax, double yMin, double yMax, double zMin, double zMax) { double extent[6]; extent[0] = xMin; extent[1] = xMax; extent[2] = yMin; extent[3] = yMax; extent[4] = zMin; extent[5] = zMax; this->SetExtent(extent); } //------------------------------------------------------------------------------ // Specify a (xmin,xmax, ymin,ymax, zmin,zmax) bounding box to clip data. void vtkGeometryFilter::SetExtent(double extent[6]) { int i; if (extent[0] != this->Extent[0] || extent[1] != this->Extent[1] || extent[2] != this->Extent[2] || extent[3] != this->Extent[3] || extent[4] != this->Extent[4] || extent[5] != this->Extent[5]) { this->Modified(); for (i = 0; i < 3; i++) { extent[2 * i + 1] = std::max(extent[2 * i + 1], extent[2 * i]); this->Extent[2 * i] = extent[2 * i]; this->Extent[2 * i + 1] = extent[2 * i + 1]; } } } //------------------------------------------------------------------------------ void vtkGeometryFilter::SetOutputPointsPrecision(int precision) { if (this->OutputPointsPrecision != precision) { this->OutputPointsPrecision = precision; this->Modified(); } } //------------------------------------------------------------------------------ int vtkGeometryFilter::GetOutputPointsPrecision() const { return this->OutputPointsPrecision; } //------------------------------------------------------------------------------ // Excluded faces are defined here. template struct vtkExcludedFaces { vtkStaticCellLinksTemplate* Links; vtkPolyData* ExcludedFaces; vtkExcludedFaces() : Links(nullptr) , ExcludedFaces(nullptr) { } ~vtkExcludedFaces() { delete this->Links; } }; //---------------------------------------------------------------------------- int vtkGeometryFilter::RequestData(vtkInformation* vtkNotUsed(request), vtkInformationVector** inputVector, vtkInformationVector* outputVector) { // get the info objects vtkInformation* inInfo = inputVector[0]->GetInformationObject(0); vtkInformation* excInfo = inputVector[1]->GetInformationObject(0); vtkInformation* outInfo = outputVector->GetInformationObject(0); // get the input and output vtkDataSet* input = vtkDataSet::SafeDownCast(inInfo->Get(vtkDataObject::DATA_OBJECT())); vtkPolyData* output = vtkPolyData::SafeDownCast(outInfo->Get(vtkDataObject::DATA_OBJECT())); vtkIdType numPts = input->GetNumberOfPoints(); vtkIdType numCells = input->GetNumberOfCells(); if (numPts == 0 || numCells == 0) { return 1; } // Check to see if excluded faces have been provided, and is so prepare the data // for use. vtkPolyData* excFaces = nullptr; if (excInfo) { excFaces = vtkPolyData::SafeDownCast(excInfo->Get(vtkDataObject::DATA_OBJECT())); } int wholeExtent[6] = { 0, -1, 0, -1, 0, -1 }; if (input->GetExtentType() == VTK_3D_EXTENT) { const int* wholeExt32; wholeExt32 = inInfo->Get(vtkStreamingDemandDrivenPipeline::WHOLE_EXTENT()); std::copy(wholeExt32, wholeExt32 + 6, wholeExtent); } // Prepare to delegate based on dataset type and characteristics. if (vtkPolyData::SafeDownCast(input)) { return this->PolyDataExecute(input, output, excFaces); } else if (vtkUnstructuredGridBase::SafeDownCast(input)) { return this->UnstructuredGridExecute(input, output, nullptr, excFaces); } else if (vtkImageData::SafeDownCast(input) || vtkRectilinearGrid::SafeDownCast(input) || vtkStructuredGrid::SafeDownCast(input)) { return this->StructuredExecute(input, output, wholeExtent, excFaces); } else { // Use the general case return this->DataSetExecute(input, output, excFaces); } } //------------------------------------------------------------------------------ void vtkGeometryFilter::SetExcludedFacesData(vtkPolyData* input) { this->Superclass::SetInputData(1, input); } //------------------------------------------------------------------------------ // Specify the input data or filter. void vtkGeometryFilter::SetExcludedFacesConnection(vtkAlgorithmOutput* algOutput) { this->Superclass::SetInputConnection(1, algOutput); } //------------------------------------------------------------------------------ // Return the input data or filter. vtkPolyData* vtkGeometryFilter::GetExcludedFaces() { if (this->GetNumberOfInputConnections(1) < 1) { return nullptr; } return vtkPolyData::SafeDownCast(this->GetExecutive()->GetInputData(1, 0)); } //------------------------------------------------------------------------------ int vtkGeometryFilter::FillInputPortInformation(int port, vtkInformation* info) { if (port == 0) { info->Set(vtkAlgorithm::INPUT_REQUIRED_DATA_TYPE(), "vtkDataSet"); } else if (port == 1) { info->Set(vtkAlgorithm::INPUT_REQUIRED_DATA_TYPE(), "vtkPolyData"); info->Set(vtkAlgorithm::INPUT_IS_OPTIONAL(), 1); } return 1; } //------------------------------------------------------------------------------ void vtkGeometryFilter::PrintSelf(ostream& os, vtkIndent indent) { this->Superclass::PrintSelf(os, indent); os << indent << "Precision of the output points: " << this->OutputPointsPrecision << "\n"; os << indent << "Point Minimum : " << this->PointMinimum << "\n"; os << indent << "Point Maximum : " << this->PointMaximum << "\n"; os << indent << "Cell Minimum : " << this->CellMinimum << "\n"; os << indent << "Cell Maximum : " << this->CellMaximum << "\n"; os << indent << "Extent: \n"; os << indent << " Xmin,Xmax: (" << this->Extent[0] << ", " << this->Extent[1] << ")\n"; os << indent << " Ymin,Ymax: (" << this->Extent[2] << ", " << this->Extent[3] << ")\n"; os << indent << " Zmin,Zmax: (" << this->Extent[4] << ", " << this->Extent[5] << ")\n"; os << indent << "PointClipping: " << (this->PointClipping ? "On\n" : "Off\n"); os << indent << "CellClipping: " << (this->CellClipping ? "On\n" : "Off\n"); os << indent << "ExtentClipping: " << (this->ExtentClipping ? "On\n" : "Off\n"); os << indent << "Merging: " << (this->Merging ? "On\n" : "Off\n"); os << indent << "Fast Mode: " << (this->FastMode ? "On\n" : "Off\n"); os << indent << "Remove Ghost Interfaces: " << (this->RemoveGhostInterfaces ? "On\n" : "Off\n") << "\n"; os << indent << "PieceInvariant: " << this->GetPieceInvariant() << endl; os << indent << "PassThroughCellIds: " << (this->GetPassThroughCellIds() ? "On\n" : "Off\n"); os << indent << "PassThroughPointIds: " << (this->GetPassThroughPointIds() ? "On\n" : "Off\n"); os << indent << "OriginalCellIdsName: " << this->GetOriginalCellIdsName() << endl; os << indent << "OriginalPointIdsName: " << this->GetOriginalPointIdsName() << endl; os << indent << "NonlinearSubdivisionLevel: " << this->GetNonlinearSubdivisionLevel() << endl; os << indent << "MatchBoundariesIgnoringCellOrder: " << this->GetMatchBoundariesIgnoringCellOrder() << endl; } //------------------------------------------------------------------------------ // Acceleration methods and classes for unstructured grid geometry extraction. namespace // anonymous { /** * A face class for defining faces */ template class Face { public: Face* Next = nullptr; int NumberOfPoints; bool IsGhost; TInputIdType OriginalCellId; TInputIdType* PointIds; Face() = default; Face(const vtkIdType& originalCellId, const vtkIdType& numberOfPoints, const bool& isGhost) : NumberOfPoints(static_cast(numberOfPoints)) , IsGhost(isGhost) , OriginalCellId(static_cast(originalCellId)) { } bool operator==(const Face& other) const { if (this->NumberOfPoints != other.NumberOfPoints) { return false; } switch (this->NumberOfPoints) { case 3: { return (this->PointIds[1] == other.PointIds[2] && this->PointIds[2] == other.PointIds[1]) || (this->PointIds[1] == other.PointIds[1] && this->PointIds[2] == other.PointIds[2]); } case 4: { return this->PointIds[2] == other.PointIds[2] && ((this->PointIds[1] == other.PointIds[3] && this->PointIds[3] == other.PointIds[1]) || (this->PointIds[1] == other.PointIds[1] && this->PointIds[3] == other.PointIds[3])); } default: { // if the first two points match loop through forwards // checking all points if (this->NumberOfPoints > 1 && this->PointIds[1] == other.PointIds[1]) { for (int i = 2; i < this->NumberOfPoints; ++i) { if (this->PointIds[i] != other.PointIds[i]) { return false; } } } else { // check if the points go in the opposite direction for (int i = 1; i < this->NumberOfPoints; ++i) { if (this->PointIds[this->NumberOfPoints - i] != other.PointIds[i]) { return false; } } } return true; } } } bool operator!=(const Face& other) const { return !(*this == other); } }; /** * A subclass of face to define Faces with a static number of points */ template class StaticFace : public Face { private: TInputIdType PointIdsContainer[TSize]; public: StaticFace(const vtkIdType& originalCellId, const vtkIdType* pointIds, const bool& isGhost) : Face(originalCellId, TSize, isGhost) { this->PointIds = this->PointIdsContainer; this->Initialize(pointIds); } static constexpr int GetSize() { return TSize; } template typename std::enable_if<(Size == 3), void>::type Initialize(const vtkIdType* pointIds) { // Reorder to get smallest id in first. if (pointIds[1] <= pointIds[0] && pointIds[1] <= pointIds[2]) { this->PointIds[0] = static_cast(pointIds[1]); this->PointIds[1] = static_cast(pointIds[2]); this->PointIds[2] = static_cast(pointIds[0]); } else if (pointIds[2] <= pointIds[0] && pointIds[2] <= pointIds[1]) { this->PointIds[0] = static_cast(pointIds[2]); this->PointIds[1] = static_cast(pointIds[0]); this->PointIds[2] = static_cast(pointIds[1]); } else { this->PointIds[0] = static_cast(pointIds[0]); this->PointIds[1] = static_cast(pointIds[1]); this->PointIds[2] = static_cast(pointIds[2]); } } template typename std::enable_if<(Size == 4), void>::type Initialize(const vtkIdType* pointIds) { // Reorder to get smallest id in first. if (pointIds[1] <= pointIds[0] && pointIds[1] <= pointIds[2] && pointIds[1] <= pointIds[3]) { this->PointIds[0] = static_cast(pointIds[1]); this->PointIds[1] = static_cast(pointIds[2]); this->PointIds[2] = static_cast(pointIds[3]); this->PointIds[3] = static_cast(pointIds[0]); } else if (pointIds[2] <= pointIds[0] && pointIds[2] <= pointIds[1] && pointIds[2] <= pointIds[3]) { this->PointIds[0] = static_cast(pointIds[2]); this->PointIds[1] = static_cast(pointIds[3]); this->PointIds[2] = static_cast(pointIds[0]); this->PointIds[3] = static_cast(pointIds[1]); } else if (pointIds[3] <= pointIds[0] && pointIds[3] <= pointIds[1] && pointIds[3] <= pointIds[2]) { this->PointIds[0] = static_cast(pointIds[3]); this->PointIds[1] = static_cast(pointIds[0]); this->PointIds[2] = static_cast(pointIds[1]); this->PointIds[3] = static_cast(pointIds[2]); } else { this->PointIds[0] = static_cast(pointIds[0]); this->PointIds[1] = static_cast(pointIds[1]); this->PointIds[2] = static_cast(pointIds[2]); this->PointIds[3] = static_cast(pointIds[3]); } } template typename std::enable_if<(Size != 3 && Size != 4), void>::type Initialize( const vtkIdType* pointIds) { // find the index to the smallest id int offset = 0; int index; for (index = 1; index < TSize; ++index) { if (pointIds[index] < pointIds[offset]) { offset = index; } } // copy ids into ordered array with the smallest id first for (index = 0; index < TSize; ++index) { this->PointIds[index] = static_cast(pointIds[(offset + index) % TSize]); } } }; /** * A subclass of face to define Faces with a dynamic number of points */ template class DynamicFace : public Face { private: std::vector PointIdsContainer; public: DynamicFace(const vtkIdType& originalCellId, const vtkIdType& numberOfPoints, const vtkIdType* pointIds, const bool& isGhost) : Face(originalCellId, numberOfPoints, isGhost) { assert(this->NumberOfPoints != 0); this->PointIdsContainer.resize(static_cast(this->NumberOfPoints)); this->PointIds = this->PointIdsContainer.data(); this->Initialize(pointIds); } int GetSize() const { return this->NumberOfPoints; } void Initialize(const vtkIdType* pointIds) { // find the index to the smallest id int offset = 0; int index; for (index = 1; index < this->NumberOfPoints; ++index) { if (pointIds[index] < pointIds[offset]) { offset = index; } } // copy ids into ordered array with the smallest id first for (index = 0; index < this->NumberOfPoints; ++index) { this->PointIds[index] = static_cast(pointIds[(offset + index) % this->NumberOfPoints]); } } }; /** * Memory pool for faces. * Code was aggregated from vtkDataSetSurfaceFilter */ template class FaceMemoryPool { private: using TFace = Face; static constexpr int FSize = sizeof(TFace); static constexpr int SizeId = sizeof(TInputIdType); static constexpr int PointerSize = sizeof(void*); static constexpr bool Is64BitsSystem = PointerSize == 8; static constexpr bool IsId64Bits = SizeId == 8; // The following assert holds for both 32 and 64 bit Ids for both 32 and 64 bit systems. static_assert(FSize % SizeId == 0, "Face size must be a multiple of Id size"); static constexpr bool EasyToComputeSize = !Is64BitsSystem || IsId64Bits; static constexpr int FSizeDivSizeId = FSize / SizeId; static constexpr int SizeOfFace(const int& numberOfPoints) { return FaceMemoryPool::FSize + (FaceMemoryPool::EasyToComputeSize ? numberOfPoints * FaceMemoryPool::SizeId : (numberOfPoints + (numberOfPoints & 1 /*fast %2*/)) * FaceMemoryPool::SizeId); } static constexpr size_t ChunkSize = static_cast(5000 * FaceMemoryPool::SizeOfFace(4)); size_t NextChunkIndex; size_t NextFaceIndex; std::vector> Chunks; public: FaceMemoryPool() : NextChunkIndex(0) , NextFaceIndex(0) { } // Copy ctor. FaceMemoryPool(const FaceMemoryPool&) = default; // Copy assignment operator. FaceMemoryPool& operator=(const FaceMemoryPool&) = default; ~FaceMemoryPool() = default; void Initialize() { this->Reset(); // size the chunks based on the size of a quadrilateral static constexpr int numberOfInitialChunks = 100; this->NextChunkIndex = 0; this->NextFaceIndex = 0; this->Chunks.resize(numberOfInitialChunks, nullptr); // Initialize the first chunk // NOLINTNEXTLINE(modernize-make-shared) this->Chunks[0] = std::shared_ptr( new unsigned char[FaceMemoryPool::ChunkSize], std::default_delete()); } void ResetIndices() { this->NextChunkIndex = 0; this->NextFaceIndex = 0; } void Reset() { this->ResetIndices(); this->Chunks.clear(); } TFace* Allocate(const int& numberOfPoints) { // see if there's room for this one const int polySize = FaceMemoryPool::SizeOfFace(numberOfPoints); if (this->NextFaceIndex + polySize > FaceMemoryPool::ChunkSize) { ++this->NextChunkIndex; this->NextFaceIndex = 0; // Although this should not happen often, check first. if (this->NextChunkIndex >= this->Chunks.size()) { this->Chunks.resize(this->Chunks.size() * 2); } // Next: allocate a new array if necessary. if (this->Chunks[this->NextChunkIndex] == nullptr) { // NOLINTNEXTLINE(modernize-make-shared) this->Chunks[this->NextChunkIndex] = std::shared_ptr( new unsigned char[FaceMemoryPool::ChunkSize], std::default_delete()); } } TFace* face = reinterpret_cast(this->Chunks[this->NextChunkIndex].get() + this->NextFaceIndex); face->NumberOfPoints = numberOfPoints; face->PointIds = (TInputIdType*)face + FaceMemoryPool::FSizeDivSizeId; this->NextFaceIndex += polySize; return face; } }; // This class accumulates cell array-related information. Also marks points // as used if a point map is provided. template class CellArrayType { private: TInputIdType* PointMap; vtkStaticCellLinksTemplate* ExcFaces; const unsigned char* PointGhost; public: // Make things a little more expressive using IdListType = std::vector; IdListType Cells; IdListType OrigCellIds; CellArrayType() : PointMap(nullptr) , ExcFaces(nullptr) , PointGhost(nullptr) { } void SetPointsGhost(const unsigned char* pointGhost) { this->PointGhost = pointGhost; } void SetPointMap(TInputIdType* ptMap) { this->PointMap = ptMap; } void SetExcludedFaces(vtkStaticCellLinksTemplate* exc) { this->ExcFaces = exc; } vtkIdType GetNumberOfCells() { return static_cast(this->OrigCellIds.size()); } vtkIdType GetNumberOfConnEntries() { return static_cast(this->Cells.size()); } template void InsertNextCell(T1 npts, const TConnectivityIter pts, TCellIdType cellId) { // Only insert the face cell if it's not excluded if (this->ExcFaces && this->ExcFaces->MatchesCell(npts, pts)) { return; } else if (this->PointGhost) { for (T1 i = 0; i < npts; ++i) { if (this->PointGhost[pts[i]] & MASKED_POINT_VALUE) { return; } } } // Okay insert the boundary face cell this->Cells.emplace_back(npts); if (!this->PointMap) { for (T1 i = 0; i < npts; ++i) { this->Cells.emplace_back(static_cast(pts[i])); } } else { for (T1 i = 0; i < npts; ++i) { this->Cells.emplace_back(static_cast(pts[i])); this->PointMap[pts[i]] = 1; } } this->OrigCellIds.emplace_back(static_cast(cellId)); } }; /** * Forward list for faces */ template class FaceForwardList { private: using TFace = Face; using TCellArrayType = CellArrayType; using TFaceMemoryPool = FaceMemoryPool; TFaceMemoryPool FacePool; TFace* Head = nullptr; public: FaceForwardList() = default; /** * Initialize the list */ void Initialize() { this->FacePool.Initialize(); this->Reset(); } /** * Reset the list */ void Reset() { // reset list this->Head = nullptr; // reset face memory pool this->FacePool.ResetIndices(); } /** * Try to insert a face into the list, if it's already there, delete it. */ template void Insert(const FaceType& face) { TFace** current = &this->Head; while (*current != nullptr) { if (**current == face) { // delete the duplicate *current = (*current)->Next; return; } current = &(*current)->Next; } // not found, add it TFace* newFace = this->FacePool.Allocate(face.GetSize()); newFace->Next = nullptr; newFace->OriginalCellId = face.OriginalCellId; std::memcpy(newFace->PointIds, face.PointIds, face.GetSize() * sizeof(TInputIdType)); newFace->IsGhost = face.IsGhost; *current = newFace; } /** * Populate the cell array */ void PopulateCellArray(TCellArrayType* polys) { for (auto currentFace = this->Head; currentFace != nullptr; currentFace = currentFace->Next) { if (!currentFace->IsGhost) { polys->template InsertNextCell( currentFace->NumberOfPoints, currentFace->PointIds, currentFace->OriginalCellId); } } } }; //-------------------------------------------------------------------------- // Functor/worklet interfaces VTK -> SMPTools threading. This class enables // compositing the output threads into a final VTK output. The actual work // is performed by by subclasses of ExtractCellBoundaries which implement // their own operator() method (i.e., the subclasses specialize // to a particular dataset type). template struct LocalDataType { using TCellArrayType = CellArrayType; using TFaceForwardList = FaceForwardList; // Later on (in Reduce()), a thread id is assigned to the thread. int ThreadId; // If point merging is specified, then a non-null point map is provided. TInputIdType* PointMap; // These collect the boundary entities from geometry extraction. Note also // that these implicitly keep track of the number of cells inserted. TCellArrayType Verts; TCellArrayType Lines; TCellArrayType Polys; TCellArrayType Strips; // Later (in the Reduce() method) build an offset structure to support // threaded compositing of output geometric entities. vtkIdType VertsConnOffset; // this thread's offset into the output vert connectivity vtkIdType VertsOffset; // this thread's offset into the output offsets vtkIdType LinesConnOffset; // this thread's offset into the output line connectivity vtkIdType LinesOffset; // offset into the output line cells vtkIdType PolysConnOffset; // this thread's offset into the output poly connectivity vtkIdType PolysOffset; // offset into the output poly cells vtkIdType StripsConnOffset; // this thread's offset into the output strip connectivity vtkIdType StripsOffset; // offset into the output triangle strip cells // These are scratch arrays to avoid repeated allocations vtkSmartPointer Cell; vtkSmartPointer CellIds; vtkSmartPointer IPts; TFaceForwardList FaceList; LocalDataType() { this->PointMap = nullptr; this->Cell.TakeReference(vtkGenericCell::New()); this->CellIds.TakeReference(vtkIdList::New()); this->IPts.TakeReference(vtkIdList::New()); } LocalDataType(const LocalDataType& other) { this->ThreadId = other.ThreadId; this->Verts = other.Verts; this->Lines = other.Lines; this->Polys = other.Polys; this->Strips = other.Strips; this->VertsConnOffset = other.VertsConnOffset; this->VertsOffset = other.VertsOffset; this->LinesConnOffset = other.LinesConnOffset; this->LinesOffset = other.LinesOffset; this->PolysConnOffset = other.PolysConnOffset; this->PolysOffset = other.PolysOffset; this->StripsConnOffset = other.StripsConnOffset; this->StripsOffset = other.StripsOffset; this->PointMap = other.PointMap; // These are here to have a different allocation for each threads this->Cell.TakeReference(vtkGenericCell::New()); this->CellIds.TakeReference(vtkIdList::New()); this->IPts.TakeReference(vtkIdList::New()); this->FaceList = other.FaceList; } LocalDataType& operator=(const LocalDataType& other) { if (this != &other) { LocalDataType tmp = LocalDataType(other); this->Swap(tmp); } return *this; } void Swap(LocalDataType& other) { using std::swap; // the compiler will use custom swap for members if it exists swap(this->Verts, other.Verts); swap(this->Lines, other.Lines); swap(this->Polys, other.Polys); swap(this->Strips, other.Strips); swap(this->VertsConnOffset, other.VertsConnOffset); swap(this->VertsOffset, other.VertsOffset); swap(this->LinesConnOffset, other.LinesConnOffset); swap(this->LinesOffset, other.LinesOffset); swap(this->PolysConnOffset, other.PolysConnOffset); swap(this->PolysOffset, other.PolysOffset); swap(this->StripsConnOffset, other.StripsConnOffset); swap(this->StripsOffset, other.StripsOffset); swap(this->PointMap, other.PointMap); swap(this->Cell, other.Cell); swap(this->CellIds, other.CellIds); swap(this->IPts, other.IPts); swap(this->FaceList, other.FaceList); } void SetPointMap(TInputIdType* ptMap) { this->PointMap = ptMap; this->Verts.SetPointMap(ptMap); this->Lines.SetPointMap(ptMap); this->Polys.SetPointMap(ptMap); this->Strips.SetPointMap(ptMap); } void SetExcludedFaces(vtkStaticCellLinksTemplate* exc) { this->Verts.SetExcludedFaces(exc); this->Lines.SetExcludedFaces(exc); this->Polys.SetExcludedFaces(exc); this->Strips.SetExcludedFaces(exc); } }; template using ThreadIterType = typename vtkSMPThreadLocal>::iterator; template using ThreadOutputType = std::vector>; //-------------------------------------------------------------------------- // Given a cell and a bunch of supporting objects (to support computing and // minimize allocation/deallocation), extract boundary features from the cell. // This method works with arbitrary datasets. template void ExtractDSCellGeometry(vtkDataSet* input, vtkIdType cellId, const char* cellVis, const unsigned char* cellGhosts, bool removeGhostInterfaces, LocalDataType* localData) { using TCellArrayType = CellArrayType; static constexpr int pixelConvert[4] = { 0, 1, 3, 2 }; vtkGenericCell* cell = localData->Cell; input->GetCell(cellId, cell); int cellType = cell->GetCellType(); if (cellType != VTK_EMPTY_CELL) { TCellArrayType& verts = localData->Verts; TCellArrayType& lines = localData->Lines; TCellArrayType& polys = localData->Polys; TCellArrayType& strips = localData->Strips; vtkIdList* cellIds = localData->CellIds.Get(); vtkIdList* ptIds = localData->IPts.Get(); ptIds->SetNumberOfIds(4); int cellDim = cell->GetCellDimension(); vtkIdType npts = cell->PointIds->GetNumberOfIds(); vtkIdType* pts = cell->PointIds->GetPointer(0); switch (cellDim) { // create new points and then cell case 0: verts.InsertNextCell(npts, pts, cellId); break; case 1: lines.InsertNextCell(npts, pts, cellId); break; case 2: if (cellType == VTK_TRIANGLE_STRIP) { strips.InsertNextCell(npts, pts, cellId); } else if (cellType == VTK_PIXEL) { ptIds->SetId(0, pts[pixelConvert[0]]); ptIds->SetId(1, pts[pixelConvert[1]]); ptIds->SetId(2, pts[pixelConvert[2]]); ptIds->SetId(3, pts[pixelConvert[3]]); polys.InsertNextCell(npts, ptIds->GetPointer(0), cellId); } else { polys.InsertNextCell(npts, pts, cellId); } break; case 3: int numFaces = cell->GetNumberOfFaces(); for (int j = 0; j < numFaces; j++) { vtkCell* face = cell->GetFace(j); input->GetCellNeighbors(cellId, face->PointIds, cellIds); bool hasVisibleNeighbor = false; if (cellIds->GetNumberOfIds() > 0) { const vtkIdType neighborId = cellIds->GetId(0); const unsigned char neighborGhost = cellGhosts ? cellGhosts[neighborId] : 0; // Neighbor is "visible" (prevents a face) if it's not hidden, // and it's either not a duplicate or we are removing ghost interfaces. hasVisibleNeighbor = !(neighborGhost & MASKED_CELL_VALUE_NOT_VISIBLE) && (!cellVis || cellVis[neighborId]) && (removeGhostInterfaces || !(neighborGhost & vtkDataSetAttributes::DUPLICATECELL)); } // Draw the face only if there is no visible neighbor to "cover" it. if (!hasVisibleNeighbor) { vtkIdType numFacePts = face->GetNumberOfPoints(); polys.InsertNextCell(numFacePts, face->PointIds->GetPointer(0), cellId); } } break; } // switch } // non-empty cell } // extract dataset geometry //-------------------------------------------------------------------------- template inline void InsertAFace(FaceForwardList& faceList, const vtkIdType& cellId, const vtkIdType numFacePoints, const vtkIdType* facePointIds, const bool& isGhost) { #define INSERT_STATIC_FACE(N) \ case N: \ faceList.Insert(StaticFace(cellId, facePointIds, isGhost)); \ break; switch (numFacePoints) { INSERT_STATIC_FACE(3) INSERT_STATIC_FACE(4) INSERT_STATIC_FACE(5) INSERT_STATIC_FACE(6) INSERT_STATIC_FACE(7) INSERT_STATIC_FACE(8) INSERT_STATIC_FACE(9) INSERT_STATIC_FACE(10) INSERT_STATIC_FACE(11) INSERT_STATIC_FACE(12) INSERT_STATIC_FACE(13) INSERT_STATIC_FACE(14) INSERT_STATIC_FACE(15) INSERT_STATIC_FACE(16) INSERT_STATIC_FACE(17) INSERT_STATIC_FACE(18) INSERT_STATIC_FACE(19) INSERT_STATIC_FACE(20) default: faceList.Insert(DynamicFace(cellId, numFacePoints, facePointIds, isGhost)); break; } #undef INSERT_STATIC_FACE // Clean up macro } //-------------------------------------------------------------------------- // Given a cell and a bunch of supporting objects (to support computing and // minimize allocation/deallocation), extract boundary features from the cell. // This method works with unstructured grids. template void ExtractCellGeometry(vtkUnstructuredGrid* input, vtkIdType cellId, int cellType, TOffsetsValueType npts, const TConnectivityIter pts, int faceId, LocalDataType* localData, const bool& isGhost) { using Triangle = StaticFace<3, TInputIdType>; using Quad = StaticFace<4, TInputIdType>; using Pentagon = StaticFace<5, TInputIdType>; using Hexagon = StaticFace<6, TInputIdType>; static constexpr int MAX_FACE_POINTS = 32; vtkIdType ptIds[MAX_FACE_POINTS]; // cell face point ids const vtkIdType* faceVerts; static constexpr int pixelConvert[4] = { 0, 1, 3, 2 }; switch (cellType) { case VTK_EMPTY_CELL: break; case VTK_VERTEX: case VTK_POLY_VERTEX: localData->Verts.InsertNextCell(npts, pts, cellId); break; case VTK_LINE: case VTK_POLY_LINE: localData->Lines.InsertNextCell(npts, pts, cellId); break; case VTK_TRIANGLE: case VTK_QUAD: case VTK_POLYGON: localData->Polys.InsertNextCell(npts, pts, cellId); break; case VTK_TRIANGLE_STRIP: localData->Strips.InsertNextCell(npts, pts, cellId); break; case VTK_PIXEL: // pixelConvert (in the following loop) is an int[4]. GCC 5.1.1 // warns about pixelConvert[4] being uninitialized due to loop // unrolling -- forcibly restricting npts <= 4 prevents this warning. ptIds[0] = pts[pixelConvert[0]]; ptIds[1] = pts[pixelConvert[1]]; ptIds[2] = pts[pixelConvert[2]]; ptIds[3] = pts[pixelConvert[3]]; localData->Polys.InsertNextCell(static_cast(npts), ptIds, cellId); break; case VTK_TETRA: faceVerts = vtkTetra::GetFaceArray(faceId); ptIds[0] = pts[faceVerts[0]]; ptIds[1] = pts[faceVerts[1]]; ptIds[2] = pts[faceVerts[2]]; localData->FaceList.Insert(Triangle(cellId, ptIds, isGhost)); break; case VTK_VOXEL: faceVerts = vtkVoxel::GetFaceArray(faceId); ptIds[0] = pts[faceVerts[pixelConvert[0]]]; ptIds[1] = pts[faceVerts[pixelConvert[1]]]; ptIds[2] = pts[faceVerts[pixelConvert[2]]]; ptIds[3] = pts[faceVerts[pixelConvert[3]]]; localData->FaceList.Insert(Quad(cellId, ptIds, isGhost)); break; case VTK_HEXAHEDRON: faceVerts = vtkHexahedron::GetFaceArray(faceId); ptIds[0] = pts[faceVerts[0]]; ptIds[1] = pts[faceVerts[1]]; ptIds[2] = pts[faceVerts[2]]; ptIds[3] = pts[faceVerts[3]]; localData->FaceList.Insert(Quad(cellId, ptIds, isGhost)); break; case VTK_WEDGE: faceVerts = vtkWedge::GetFaceArray(faceId); ptIds[0] = pts[faceVerts[0]]; ptIds[1] = pts[faceVerts[1]]; ptIds[2] = pts[faceVerts[2]]; if (faceVerts[3] < 0) { localData->FaceList.Insert(Triangle(cellId, ptIds, isGhost)); } else { ptIds[3] = pts[faceVerts[3]]; localData->FaceList.Insert(Quad(cellId, ptIds, isGhost)); } break; case VTK_PYRAMID: faceVerts = vtkPyramid::GetFaceArray(faceId); ptIds[0] = pts[faceVerts[0]]; ptIds[1] = pts[faceVerts[1]]; ptIds[2] = pts[faceVerts[2]]; if (faceVerts[3] < 0) { localData->FaceList.Insert(Triangle(cellId, ptIds, isGhost)); } else { ptIds[3] = pts[faceVerts[3]]; localData->FaceList.Insert(Quad(cellId, ptIds, isGhost)); } break; case VTK_HEXAGONAL_PRISM: faceVerts = vtkHexagonalPrism::GetFaceArray(faceId); ptIds[0] = pts[faceVerts[0]]; ptIds[1] = pts[faceVerts[1]]; ptIds[2] = pts[faceVerts[2]]; ptIds[3] = pts[faceVerts[3]]; if (faceVerts[4] < 0) { localData->FaceList.Insert(Quad(cellId, ptIds, isGhost)); } else { ptIds[4] = pts[faceVerts[4]]; ptIds[5] = pts[faceVerts[5]]; localData->FaceList.Insert(Hexagon(cellId, ptIds, isGhost)); } break; case VTK_PENTAGONAL_PRISM: faceVerts = vtkPentagonalPrism::GetFaceArray(faceId); ptIds[0] = pts[faceVerts[0]]; ptIds[1] = pts[faceVerts[1]]; ptIds[2] = pts[faceVerts[2]]; ptIds[3] = pts[faceVerts[3]]; if (faceVerts[4] < 0) { localData->FaceList.Insert(Quad(cellId, ptIds, isGhost)); } else { ptIds[4] = pts[faceVerts[4]]; localData->FaceList.Insert(Pentagon(cellId, ptIds, isGhost)); } break; case VTK_POLYHEDRON: { auto globalFaceId = input->GetPolyhedronFaceLocations()->GetCellPointAtId(cellId, faceId); vtkIdType numFacePoints; const vtkIdType* facePointIds = nullptr; input->GetPolyhedronFaces()->GetCellAtId( globalFaceId, numFacePoints, facePointIds, localData->IPts); InsertAFace(localData->FaceList, cellId, numFacePoints, facePointIds, isGhost); break; } default: // Other types of 3D linear cells handled by vtkGeometryFilter. Exactly what // is a linear cell is defined by vtkCellTypeUtilities::IsLinear(). auto cell = localData->Cell; input->GetCell(cellId, cell); if (cell->GetCellDimension() == 3 && cell->GetNumberOfFaces() > 0) { vtkCell* face = cell->GetFace(faceId); InsertAFace(localData->FaceList, cellId, face->PointIds->GetNumberOfIds(), face->PointIds->GetPointer(0), isGhost); } // if 3D else { vtkLog(ERROR, "Unknown cell type."); } } // switch } // ExtractCellGeometry() // Base class to extract boundary entities. Derived by all dataset extraction // types -- the operator() method needs to be implemented by subclasses. template struct ExtractCellBoundaries { vtkGeometryFilter* Self; // If point merging is specified, then a point map is created. TInputIdType* PointMap; // Cell visibility and cell ghost levels const char* CellVis; const unsigned char* CellGhosts; const unsigned char* PointGhost; // Thread-related output data vtkSMPThreadLocal> LocalData; vtkIdType VertsNumPts, VertsNumCells; vtkIdType LinesNumPts, LinesNumCells; vtkIdType PolysNumPts, PolysNumCells; vtkIdType StripsNumPts, StripsNumCells; vtkIdType VertsCellIdOffset; vtkIdType LinesCellIdOffset; vtkIdType PolysCellIdOffset; vtkIdType StripsCellIdOffset; vtkIdType NumPts; vtkIdType NumCells; ExtractCellBoundaries* Extract; vtkStaticCellLinksTemplate* ExcFaces; using TThreadOutputType = ThreadOutputType; TThreadOutputType* Threads; ExtractCellBoundaries(vtkGeometryFilter* self, const char* cellVis, const unsigned char* cellGhosts, const unsigned char* pointGhost, vtkExcludedFaces* exc, TThreadOutputType* threads) : Self(self) , PointMap(nullptr) , CellVis(cellVis) , CellGhosts(cellGhosts) , PointGhost(pointGhost) , Threads(threads) { this->ExcFaces = (exc == nullptr ? nullptr : exc->Links); } virtual ~ExtractCellBoundaries() { delete[] this->PointMap; } // If point merging is needed, create the point map (map from old points // to new points). void CreatePointMap(vtkIdType numPts) { this->PointMap = new TInputIdType[numPts]; vtkSMPTools::Fill(this->PointMap, this->PointMap + numPts, -1); } // Initialize thread data virtual void Initialize() { // Make sure cells have been built auto& localData = this->LocalData.Local(); localData.SetPointMap(this->PointMap); localData.SetExcludedFaces(this->ExcFaces); localData.Verts.SetPointsGhost(this->PointGhost); localData.Lines.SetPointsGhost(this->PointGhost); localData.Polys.SetPointsGhost(this->PointGhost); localData.Strips.SetPointsGhost(this->PointGhost); } // operator() implemented by dataset-specific subclasses // Composite local thread data; i.e., rather than linearly appending data from each // thread into the filter's output, this performs a parallel append. virtual void Reduce() { // Determine offsets to partition work and perform memory allocations. vtkIdType numCells, numConnEntries; this->VertsNumPts = this->VertsNumCells = 0; this->LinesNumPts = this->LinesNumCells = 0; this->PolysNumPts = this->PolysNumCells = 0; this->StripsNumPts = this->StripsNumCells = 0; int threadId = 0; // Loop over the local data generated by each thread. Setup the // offsets and such to insert into the output cell arrays. auto tItr = this->LocalData.begin(); auto tEnd = this->LocalData.end(); for (; tItr != tEnd; ++tItr) { tItr->ThreadId = threadId++; this->Threads->emplace_back(tItr); // need pointers to local thread data tItr->VertsConnOffset = this->VertsNumPts; tItr->VertsOffset = this->VertsNumCells; numCells = tItr->Verts.GetNumberOfCells(); numConnEntries = tItr->Verts.GetNumberOfConnEntries() - numCells; this->VertsNumCells += numCells; this->VertsNumPts += numConnEntries; tItr->LinesConnOffset = this->LinesNumPts; tItr->LinesOffset = this->LinesNumCells; numCells = tItr->Lines.GetNumberOfCells(); numConnEntries = tItr->Lines.GetNumberOfConnEntries() - numCells; this->LinesNumCells += numCells; this->LinesNumPts += numConnEntries; tItr->PolysConnOffset = this->PolysNumPts; tItr->PolysOffset = this->PolysNumCells; numCells = tItr->Polys.GetNumberOfCells(); numConnEntries = tItr->Polys.GetNumberOfConnEntries() - numCells; this->PolysNumCells += numCells; this->PolysNumPts += numConnEntries; tItr->StripsConnOffset = this->StripsNumPts; tItr->StripsOffset = this->StripsNumCells; numCells = tItr->Strips.GetNumberOfCells(); numConnEntries = tItr->Strips.GetNumberOfConnEntries() - numCells; this->StripsNumCells += numCells; this->StripsNumPts += numConnEntries; } this->VertsCellIdOffset = 0; this->LinesCellIdOffset = this->VertsNumCells; this->PolysCellIdOffset = this->VertsNumCells + this->LinesNumCells; this->StripsCellIdOffset = this->VertsNumCells + this->LinesNumCells + this->PolysNumCells; this->NumCells = this->VertsNumCells + this->LinesNumCells + this->PolysNumCells + this->StripsNumCells; this->NumPts = this->VertsNumPts + this->LinesNumPts + this->PolysNumPts + this->StripsNumPts; } }; // Extract unstructured grid boundary by visiting each cell and examining // cell features. template struct ExtractUG : public ExtractCellBoundaries { // The unstructured grid to process vtkUnstructuredGrid* Grid; using TFaceHashLinks = vtkStaticFaceHashLinksTemplate; const TFaceHashLinks& FaceHashLinks; bool RemoveGhostInterfaces; vtkIdType NumberOfCells; const unsigned char MASKED_CELL; ExtractUG(vtkGeometryFilter* self, vtkUnstructuredGrid* grid, TFaceHashLinks& faceHashLinks, const char* cellVis, const unsigned char* cellGhost, const unsigned char* pointGhost, vtkExcludedFaces* exc, ThreadOutputType* t) : ExtractCellBoundaries(self, cellVis, cellGhost, pointGhost, exc, t) , Grid(grid) , FaceHashLinks(faceHashLinks) , RemoveGhostInterfaces(self->GetRemoveGhostInterfaces()) , NumberOfCells(grid->GetNumberOfCells()) , MASKED_CELL( self->GetRemoveGhostInterfaces() ? MASKED_CELL_VALUE : MASKED_CELL_VALUE_NOT_VISIBLE) { if (self->GetMerging()) { this->CreatePointMap(grid->GetNumberOfPoints()); } } // Initialize thread data void Initialize() override { this->ExtractCellBoundaries::Initialize(); this->LocalData.Local().FaceList.Initialize(); } struct FaceOperator : public vtkCellArray::DispatchUtilities { template void operator()(OffsetsT* offsets, ConnectivityT* conn, CellTypesT* cellTypes, ExtractUG* This, vtkIdType beginHash, vtkIdType endHash) { auto& localData = This->LocalData.Local(); auto& faceHashLinks = This->FaceHashLinks; auto& faceList = localData.FaceList; auto& polys = localData.Polys; using ValueType = GetAPIType; const auto connectivityPtr = GetRange(conn).begin(); const auto offsetsRange = GetRange(offsets); const auto cellTypesRange = vtk::DataArrayValueRange<1, unsigned char>(cellTypes); vtkIdType localFaceId; bool isGhost, isDuplicate; const bool isFirst = vtkSMPTools::GetSingleThread(); const auto checkAbortInterval = std::min((endHash - beginHash) / 10 + 1, (vtkIdType)1000); for (vtkIdType hash = beginHash; hash < endHash; ++hash) { if (hash % checkAbortInterval == 0) { if (isFirst) { This->Self->CheckAbort(); } if (This->Self->GetAbortOutput()) { break; } } const auto numberOfFaces = faceHashLinks.GetNumberOfFacesInHash(hash); if (numberOfFaces == 0) { continue; } const auto cellsOfFacesInHash = faceHashLinks.GetCellIdOfFacesInHash(hash); const auto facesOfFacesInHash = faceHashLinks.GetFaceIdOfFacesInHash(hash); for (localFaceId = 0; localFaceId < numberOfFaces; ++localFaceId) { auto& cellId = cellsOfFacesInHash[localFaceId]; auto& faceId = facesOfFacesInHash[localFaceId]; // ----------------------------------Ghost explanation------------------------------------ // Note: This->MASKED_CELL is dynamically set based on RemoveGhostInterfaces. // // 1) For 0D-1D-2D cells: // - When RemoveGhostInterfaces is ON: All ghost types are skipped (dim < 3). // - When RemoveGhostInterfaces is OFF: We skip only hidden/refined cells; // duplicates are kept as they are considered part of the visible geometry. // // 2) For 3D cells: // - HIDDEN/REFINED Cells: These are ALWAYS skipped. By skipping them, their // faces never enter the FaceList to cancel neighbors, which forces the // adjacent "real" cells to generate a capping surface. // - DUPLICATE Cells: // - When RemoveGhostInterfaces is ON: We do NOT skip 3D duplicates. We // process them so their faces enter the FaceList and cancel out the // faces of local "real" cells, making the partition interface invisible. // - When RemoveGhostInterfaces is OFF: We skip 3D duplicates (via isGhost) // so they do not cancel neighbor faces, leaving the partition boundary // visible as a "cap." // --------------------------------------------------------------------------------------- const unsigned char& type = cellTypesRange[cellId]; unsigned char cellGhost = This->CellGhosts ? This->CellGhosts[cellId] : 0; isGhost = cellGhost & This->MASKED_CELL; isDuplicate = cellGhost & vtkDataSetAttributes::DUPLICATECELL; if (isGhost && (vtkCellTypeUtilities::GetDimension(type) < 3 || !isDuplicate)) { continue; } // If the cell is visible process it if (!This->CellVis || This->CellVis[cellId]) { // get cell points by just accessing the connectivity/offsets array const ValueType npts = offsetsRange[cellId + 1] - offsetsRange[cellId]; const auto pts = connectivityPtr + offsetsRange[cellId]; ExtractCellGeometry(This->Grid, cellId, type, npts, pts, faceId, &localData, isGhost); } // if cell visible } // for all cells in this hash // add external faces from the face list to polys (if any) faceList.PopulateCellArray(&polys); // and reset the list. faceList.Reset(); } // for all populated hashes if (isFirst) { This->Self->UpdateProgress( static_cast(0.4 * endHash / faceHashLinks.GetNumberOfHashes() + 0.4)); } } }; void operator()(vtkIdType beginHash, vtkIdType endHash) { using Dispatcher = vtkArrayDispatch::Dispatch3ByArray; auto cells = this->Grid->GetCells(); if (!Dispatcher::Execute(cells->GetOffsetsArray(), cells->GetConnectivityArray(), this->Grid->GetCellTypes(), FaceOperator{}, this, beginHash, endHash)) { FaceOperator{}(cells->GetOffsetsArray(), cells->GetConnectivityArray(), this->Grid->GetCellTypes(), this, beginHash, endHash); } } // Composite local thread data void Reduce() override { // free up memory from the face list for (auto& localData : this->LocalData) { localData.FaceList.Initialize(); } this->ExtractCellBoundaries::Reduce(); } }; // Extract structured 3D grid boundary with and without visible cells template struct ExtractStructured : public ExtractCellBoundaries { TGrid* Input; // Input data bool FastMode; // Whether to use fast mode or not bool* ExtractFaces; // Whether to extract faces or not int* Extent; // Data extent int* WholeExtent; // Whole extent int Dims[3]; // Grid dimensions bool ForceSimpleVisibilityCheck; // Whether to force simple visibility check bool AllCellsVisible; // Whether all cells are visible int CurrentAxis; // Current Axis int IAxis, JAxis; // Axis indices int MinFace; // Whether to extract max face or min face vtkIdType NumberOfFaces; ExtractStructured(vtkGeometryFilter* self, TGrid* ds, int* wholeExtent, bool* extractFaces, bool merging, unsigned char* cellGhosts, vtkExcludedFaces* exc, ThreadOutputType* t) : ExtractCellBoundaries(self, nullptr, cellGhosts, nullptr, exc, t) , Input(ds) , FastMode(self->GetFastMode()) , ExtractFaces(extractFaces) , Extent(ds->GetExtent()) , WholeExtent(wholeExtent) { this->Dims[0] = this->Extent[1] - this->Extent[0] + 1; this->Dims[1] = this->Extent[3] - this->Extent[2] + 1; this->Dims[2] = this->Extent[5] - this->Extent[4] + 1; if (merging) { this->CreatePointMap(ds->GetNumberOfPoints()); } this->ForceSimpleVisibilityCheck = extractFaces != nullptr; this->AllCellsVisible = !this->Input->GetCellData()->HasAnyGhostBitSet( self->GetRemoveGhostInterfaces() ? MASKED_CELL_VALUE : MASKED_CELL_VALUE_NOT_VISIBLE); } // Initialize thread data void Initialize() override { this->ExtractCellBoundaries::Initialize(); } /** * This check is needed to understand of a side of the structure data needs to be extracted * when running in distributed mode. */ bool CheckIfFaceShouldBeProcessed() { auto& aAxis = this->CurrentAxis; auto& bAxis = this->IAxis; auto& cAxis = this->JAxis; int aA2 = aAxis * 2; int bA2 = bAxis * 2; int cA2 = cAxis * 2; // We might as well put the test for this face here. if (this->Extent[bA2] == this->Extent[bA2 + 1] || this->Extent[cA2] == this->Extent[cA2 + 1]) { return false; } if (!this->MinFace) { if (this->Extent[aA2 + 1] < this->WholeExtent[aA2 + 1]) { return false; } } else { // min faces have a slightly different condition to avoid coincident faces. if (this->Extent[aA2] == this->Extent[aA2 + 1] || this->Extent[aA2] > this->WholeExtent[aA2]) { return false; } } return true; } enum FaceMode { WHOLE_FACE = 0, // Used by the FaceOperator SHRINKING_FACES = 1 // Used by ShrinkingFacesOperator }; std::array GetFace(const int ijk[3], bool minFace, FaceMode faceMode) { const int& axis = this->CurrentAxis; const int& iAxis = this->IAxis; const int& jAxis = this->JAxis; int ptIjk[3] = { ijk[0], ijk[1], ijk[2] }; if (!minFace) { ++ptIjk[axis]; } std::array face; face[0] = static_cast(vtkStructuredData::ComputePointIdForExtent(this->Extent, ptIjk)); ++ptIjk[iAxis]; face[1] = static_cast(vtkStructuredData::ComputePointIdForExtent(this->Extent, ptIjk)); ++ptIjk[jAxis]; face[2] = static_cast(vtkStructuredData::ComputePointIdForExtent(this->Extent, ptIjk)); --ptIjk[iAxis]; face[3] = static_cast(vtkStructuredData::ComputePointIdForExtent(this->Extent, ptIjk)); if (minFace || faceMode == FaceMode::WHOLE_FACE) { // invert face order to get an outside pointing normal. std::swap(face[1], face[3]); } return face; } void FaceOperator(vtkIdType faceBeginCellId, vtkIdType faceEndCellId) { auto& polys = this->LocalData.Local().Polys; const auto& extent = this->Extent; const auto& dims = this->Dims; const int& axis = this->CurrentAxis; const int& iAxis = this->IAxis; const int& jAxis = this->JAxis; const int axis2 = this->CurrentAxis * 2; const int iAxis2 = this->IAxis * 2; const int jAxis2 = this->JAxis * 2; TInputIdType cellId; int ijk[3]; ijk[axis] = this->MinFace ? extent[axis2] : extent[axis2 + 1] - 1; const int faceWidth_1 = dims[iAxis] - 1; const bool isFirst = vtkSMPTools::GetSingleThread(); const auto checkAbortInterval = std::min((faceEndCellId - faceBeginCellId) / 10 + 1, (vtkIdType)1000); for (vtkIdType faceCellId = faceBeginCellId; faceCellId < faceEndCellId; ++faceCellId) { if (faceCellId % checkAbortInterval == 0) { if (isFirst) { this->Self->CheckAbort(); } if (this->Self->GetAbortOutput()) { break; } } ijk[iAxis] = extent[iAxis2] + static_cast(faceCellId % faceWidth_1); ijk[jAxis] = extent[jAxis2] + static_cast(faceCellId / faceWidth_1); cellId = static_cast(vtkStructuredData::ComputeCellIdForExtent(extent, ijk)); if (!this->ForceSimpleVisibilityCheck || this->Input->IsCellVisible(cellId)) { const auto face = this->GetFace(ijk, this->MinFace, FaceMode::WHOLE_FACE); polys.template InsertNextCell(4, face.data(), cellId); } } } /** * Implementation to compute the external polydata for a structured grid with * blanking. The algorithm, which we call "Shrinking Faces", * takes the min and max face along each axis and then for each cell on the * face, keep on advancing the cell in the direction of the axis till a visible * cell is found and then extracts the face long the chosen axis. For min face, * this advancing is done in the positive direction of the axis while it's in * reverse for the max face. This works well for generating an outer shell and * is quite fast too. However we miss internal faces. So in non-fast mode, we * don't reverse the direction instead continue along the axis while * flip-flopping between detecting visible or invisible cells and then picking * the appropriate face to extract. * * This implementation only supports 3D grids. For 2D/1D grids, the standard * algorithm for extracting surface is adequate. * * This function returns false if data is not appropriate in which case the * caller should simply fall back to the default case without blanking. */ void ShrinkingFacesOperator(vtkIdType faceBeginCellId, vtkIdType faceEndCellId) { auto& polys = this->LocalData.Local().Polys; const auto& extent = this->Extent; const auto& dims = this->Dims; const int& axis = this->CurrentAxis; const int& iAxis = this->IAxis; const int& jAxis = this->JAxis; const int axis2 = this->CurrentAxis * 2; const int iAxis2 = this->IAxis * 2; const int jAxis2 = this->JAxis * 2; TInputIdType cellId, reverseCellId; bool minFace; int ijk[3]; const int faceWidth_1 = dims[iAxis] - 1; const bool isFirst = vtkSMPTools::GetSingleThread(); const auto checkAbortInterval = std::min((faceEndCellId - faceBeginCellId) / 10 + 1, (vtkIdType)1000); for (vtkIdType faceCellId = faceBeginCellId; faceCellId < faceEndCellId; ++faceCellId) { if (faceCellId % checkAbortInterval == 0) { if (isFirst) { this->Self->CheckAbort(); } if (this->Self->GetAbortOutput()) { break; } } ijk[iAxis] = extent[iAxis2] + static_cast(faceCellId % faceWidth_1); ijk[jAxis] = extent[jAxis2] + static_cast(faceCellId / faceWidth_1); minFace = true; for (int k = extent[axis2]; k < extent[axis2 + 1]; ++k) { ijk[axis] = k; cellId = static_cast(vtkStructuredData::ComputeCellIdForExtent(extent, ijk)); // ---------------------------- Ghost Interface Logic ---------------------------- // We determine the "topological visibility" of a cell to decide where to draw surfaces. // // 1) isHidden: Represents true blanking (HIDDENCELL or HIDDENPOINT). These cells // are always treated as "The Void," forcing a capping surface on adjacent real cells. // // 2) isDuplicate: Used when RemoveGhostInterfaces is ON. We treat these ghosts // as "Visible" (Transparent). By pretending they are part of the volume, the // transition logic (minFace vs cellVisible) never triggers at the partition // interface, making the seam invisible. // // 3) Boundary Detection: // - If RemoveGhostInterfaces is ON: cellVisible is false only for truly hidden cells. // - If RemoveGhostInterfaces is OFF: cellVisible is false for both hidden and // duplicate cells, causing the interface to be capped (visible). // // The 'if (!isDuplicate)' guard ensures that even if a boundary is triggered // (e.g., at the start of a block), we never actually draw the ghost's geometry. // ------------------------------------------------------------------------------- const bool isHidden = !this->Input->IsCellVisible(cellId); unsigned char cellGhost = this->CellGhosts ? this->CellGhosts[cellId] : 0; const bool isDuplicate = this->Self->GetRemoveGhostInterfaces() ? cellGhost & vtkDataSetAttributes::DUPLICATECELL : false; bool cellVisible = !isHidden && !isDuplicate; if ((minFace && cellVisible) || (!minFace && !cellVisible)) { if (!isDuplicate) { // this ensures correct cell-data is picked for the face ijk[axis] = minFace ? k : (k - 1); const auto face = this->GetFace(ijk, minFace, FaceMode::SHRINKING_FACES); cellId = static_cast(vtkStructuredData::ComputeCellIdForExtent(extent, ijk)); polys.template InsertNextCell( 4, face.data(), cellId); } if (this->FastMode) { // in fast mode, we immediately start iterating from the other // side instead to find the capping surface. we can ignore // interior surfaces for speed. // find max-face (reverse order) for (int reverseK = extent[axis2 + 1] - 1; reverseK >= k; --reverseK) { ijk[axis] = reverseK; reverseCellId = static_cast(vtkStructuredData::ComputeCellIdForExtent(extent, ijk)); if (this->Input->IsCellVisible(reverseCellId)) { const auto face2 = this->GetFace(ijk, /*minFace=*/false, FaceMode::SHRINKING_FACES); polys.template InsertNextCell( 4, face2.data(), reverseCellId); break; } } break; } minFace = !minFace; } } // If not in fast mode, and we've stepped out of the volume without a // capping-surface, add the capping surface. if (!minFace && !this->FastMode) { cellId = static_cast(vtkStructuredData::ComputeCellIdForExtent(extent, ijk)); ijk[axis] = extent[axis2 + 1] - 1; const auto face = this->GetFace(ijk, /*minFace=*/false, FaceMode::SHRINKING_FACES); polys.template InsertNextCell(4, face.data(), cellId); } } } void operator()(vtkIdType faceBeginCellId, vtkIdType faceEndCellId) { const bool isFirst = vtkSMPTools::GetSingleThread(); if (this->AllCellsVisible || this->ForceSimpleVisibilityCheck) { this->FaceOperator(faceBeginCellId, faceEndCellId); if (isFirst) { this->Self->UpdateProgress(0.05 * (this->CurrentAxis + !this->MinFace) + (0.05 * faceEndCellId / this->NumberOfFaces)); } } else { this->ShrinkingFacesOperator(faceBeginCellId, faceEndCellId); if (isFirst) { this->Self->UpdateProgress( 0.1 * this->CurrentAxis + (0.1 * faceEndCellId / this->NumberOfFaces)); } } } // operator() // Composite local thread data void Reduce() override {} static ExtractCellBoundaries* Execute(vtkGeometryFilter* self, TGrid* ds, int* wholeExtent, bool* extractFaces, bool merging, unsigned char* cellGhosts, vtkExcludedFaces* exc, ThreadOutputType* t) { auto extract = new ExtractStructured( self, ds, wholeExtent, extractFaces, merging, cellGhosts, exc, t); if (extract->AllCellsVisible || extract->ForceSimpleVisibilityCheck) { const auto& extent = extract->Extent; auto& axis = extract->CurrentAxis; auto& iAxis = extract->IAxis; auto& jAxis = extract->JAxis; for (axis = 0; axis < 3; ++axis) { // axisMin-face iAxis = (axis + 1) % 3; jAxis = (axis + 2) % 3; extract->MinFace = true; bool processFace = !extract->ForceSimpleVisibilityCheck || extract->ExtractFaces[2 * axis]; if (processFace && extract->CheckIfFaceShouldBeProcessed()) { extract->NumberOfFaces = std::abs(extent[2 * iAxis + 1] - extent[2 * iAxis]) * std::abs(extent[2 * jAxis] - extent[2 * jAxis + 1]); vtkSMPTools::For(0, extract->NumberOfFaces, *extract); } // axisMax-face std::swap(iAxis, jAxis); extract->MinFace = false; processFace = !extract->ForceSimpleVisibilityCheck || extract->ExtractFaces[2 * axis + 1]; if (processFace && extract->CheckIfFaceShouldBeProcessed()) { extract->NumberOfFaces = std::abs(extent[2 * iAxis + 1] - extent[2 * iAxis]) * std::abs(extent[2 * jAxis] - extent[2 * jAxis + 1]); vtkSMPTools::For(0, extract->NumberOfFaces, *extract); } } } else { auto& extent = extract->Extent; auto& axis = extract->CurrentAxis; auto& iAxis = extract->IAxis; auto& jAxis = extract->JAxis; for (axis = 0; axis < 3; ++axis) { iAxis = (axis + 1) % 3; jAxis = (axis + 2) % 3; extract->NumberOfFaces = std::abs(extent[2 * iAxis + 1] - extent[2 * iAxis]) * std::abs(extent[2 * jAxis] - extent[2 * jAxis + 1]); vtkSMPTools::For(0, extract->NumberOfFaces, *extract); } } extract->ExtractCellBoundaries::Reduce(); return extract; } }; // Extract the boundaries of a general vtkDataSet by visiting each cell and // examining cell features. This is slower than specialized types and should be // avoided if possible. template struct ExtractDS : public ExtractCellBoundaries { // The unstructured grid to process vtkDataSet* DataSet; vtkIdType NumberOfCells; const unsigned char MASKED_CELL; ExtractDS(vtkGeometryFilter* self, vtkDataSet* ds, const char* cellVis, const unsigned char* cellGhost, const unsigned char* pointGhost, vtkExcludedFaces* exc, ThreadOutputType* t) : ExtractCellBoundaries(self, cellVis, cellGhost, pointGhost, exc, t) , DataSet(ds) , NumberOfCells(ds->GetNumberOfCells()) , MASKED_CELL( self->GetRemoveGhostInterfaces() ? MASKED_CELL_VALUE : MASKED_CELL_VALUE_NOT_VISIBLE) { // Point merging is always required since points are not explicitly // represented and cannot be passed through to the output. this->CreatePointMap(ds->GetNumberOfPoints()); // Make sure any internal initialization methods which may not be thread // safe are built. this->DataSet->GetCell(0); } // Initialize thread data void Initialize() override { this->ExtractCellBoundaries::Initialize(); } void operator()(vtkIdType beginCellId, vtkIdType endCellId) { auto& localData = this->LocalData.Local(); const bool isFirst = vtkSMPTools::GetSingleThread(); const auto checkAbortInterval = std::min((endCellId - beginCellId) / 10 + 1, (vtkIdType)1000); for (vtkIdType cellId = beginCellId; cellId < endCellId; ++cellId) { if (cellId % checkAbortInterval == 0) { if (isFirst) { this->Self->CheckAbort(); } if (this->Self->GetAbortOutput()) { break; } } // Handle ghost cells here. Another option was used cellVis array. if (this->CellGhosts && this->CellGhosts[cellId] & this->MASKED_CELL) { // Do not create surfaces in outer ghost cells. continue; } // If the cell is visible process it if (!this->CellVis || this->CellVis[cellId]) { ExtractDSCellGeometry(this->DataSet, cellId, this->CellVis, this->CellGhosts, this->Self->GetRemoveGhostInterfaces(), &localData); } // if cell visible } // for all cells in this batch if (isFirst) { this->Self->UpdateProgress(static_cast(0.8 * endCellId / this->NumberOfCells)); } } // operator() // Composite local thread data void Reduce() override { this->ExtractCellBoundaries::Reduce(); } }; // Helper class to record original point and cell ids. This is for copying // cell data, and also to produce output arrays indicating where output // cells originated from (typically used in picking). struct IdRecorder { vtkSmartPointer Ids; IdRecorder( vtkTypeBool passThru, const char* name, vtkDataSetAttributes* attrD, vtkIdType allocSize) { if (passThru) { this->Ids.TakeReference(vtkIdTypeArray::New()); this->Ids->SetName(name); this->Ids->SetNumberOfComponents(1); this->Ids->Allocate(allocSize); attrD->AddArray(this->Ids.Get()); } } IdRecorder(vtkTypeBool passThru, const char* name, vtkDataSetAttributes* attrD) { if (passThru) { this->Ids.TakeReference(vtkIdTypeArray::New()); this->Ids->SetName(name); this->Ids->SetNumberOfComponents(1); attrD->AddArray(this->Ids.Get()); } else { this->Ids = nullptr; } } void Insert(vtkIdType destId, vtkIdType origId) { if (this->Ids.Get() != nullptr) { this->Ids->InsertValue(destId, origId); } } vtkIdType* GetPointer() { return this->Ids->GetPointer(0); } vtkTypeBool PassThru() { return this->Ids.Get() != nullptr; } void Allocate(vtkIdType num) { if (this->Ids.Get() != nullptr) { this->Ids->Allocate(num); } } void SetNumberOfValues(vtkIdType num) { if (this->Ids.Get() != nullptr) { this->Ids->SetNumberOfValues(num); } } }; // id recorder // Generate point map for explicit point representations. template struct GenerateExpPoints { TIP* InPts; TOP* OutPts; TInputIdType* PointMap; ArrayList* PtArrays; vtkGeometryFilter* Filter; GenerateExpPoints( TIP* inPts, TOP* outPts, TInputIdType* ptMap, ArrayList* ptArrays, vtkGeometryFilter* filter) : InPts(inPts) , OutPts(outPts) , PointMap(ptMap) , PtArrays(ptArrays) , Filter(filter) { } void operator()(vtkIdType beginPtId, vtkIdType endPtId) { const auto inPts = vtk::DataArrayTupleRange<3>(this->InPts); auto outPts = vtk::DataArrayTupleRange<3>(this->OutPts); vtkIdType mapId; const bool isFirst = vtkSMPTools::GetSingleThread(); const auto checkAbortInterval = std::min((endPtId - beginPtId) / 10 + 1, (vtkIdType)1000); for (vtkIdType ptId = beginPtId; ptId < endPtId; ++ptId) { if (ptId % checkAbortInterval == 0) { if (isFirst) { this->Filter->CheckAbort(); } if (this->Filter->GetAbortOutput()) { break; } } mapId = this->PointMap[ptId]; if (mapId >= 0) { auto xIn = inPts[ptId]; auto xOut = outPts[mapId]; xOut[0] = xIn[0]; xOut[1] = xIn[1]; xOut[2] = xIn[2]; this->PtArrays->Copy(ptId, mapId); } } } }; // Generate point map for implicit point representations. template struct GenerateImpPoints { vtkDataSet* InPts; TOP* OutPts; TInputIdType* PointMap; ArrayList* PtArrays; vtkGeometryFilter* Filter; GenerateImpPoints(vtkDataSet* inPts, TOP* outPts, TInputIdType* ptMap, ArrayList* ptArrays, vtkGeometryFilter* filter) : InPts(inPts) , OutPts(outPts) , PointMap(ptMap) , PtArrays(ptArrays) , Filter(filter) { } void operator()(vtkIdType beginPtId, vtkIdType endPtId) { auto outPts = vtk::DataArrayTupleRange<3>(this->OutPts); double xIn[3]; vtkIdType mapId; const bool isFirst = vtkSMPTools::GetSingleThread(); const auto checkAbortInterval = std::min((endPtId - beginPtId) / 10 + 1, (vtkIdType)1000); for (vtkIdType ptId = beginPtId; ptId < endPtId; ++ptId) { if (ptId % checkAbortInterval == 0) { if (isFirst) { this->Filter->CheckAbort(); } if (this->Filter->GetAbortOutput()) { break; } } mapId = this->PointMap[ptId]; if (mapId >= 0) { this->InPts->GetPoint(ptId, xIn); auto xOut = outPts[mapId]; xOut[0] = xIn[0]; xOut[1] = xIn[1]; xOut[2] = xIn[2]; this->PtArrays->Copy(ptId, mapId); } } } }; // Base class for point generation workers. template struct GeneratePtsWorker { vtkIdType NumOutputPoints; vtkGeometryFilter* Filter; GeneratePtsWorker(vtkGeometryFilter* filter) : NumOutputPoints(0) , Filter(filter) { } // Create the final point map. This could be threaded (prefix_sum) but // performance gains are minimal. TInputIdType* GeneratePointMap( vtkIdType numInputPts, ExtractCellBoundaries* extract) { // The PointMap has been marked as to which points are being used. // This needs to be updated to indicate the output point ids. TInputIdType* ptMap = extract->PointMap; for (vtkIdType ptId = 0; ptId < numInputPts; ++ptId) { if (ptMap[ptId] == 1) { ptMap[ptId] = this->NumOutputPoints++; } } return ptMap; } }; // Dispatch to explicit, templated point types template struct ExpPtsWorker : public GeneratePtsWorker { ExpPtsWorker(vtkGeometryFilter* filter) : GeneratePtsWorker(filter) { } template void operator()(TIP* inPts, TOP* outPts, vtkIdType numInputPts, vtkPointData* inPD, vtkPointData* outPD, ExtractCellBoundaries* extract) { // Finalize the point map TInputIdType* ptMap = this->GeneratePointMap(numInputPts, extract); // Now generate all of the points and point attribute data ArrayList ptArrays; outPD->CopyAllocate(inPD, this->NumOutputPoints); ptArrays.AddArrays(this->NumOutputPoints, inPD, outPD, 0.0, false); outPts->SetNumberOfTuples(this->NumOutputPoints); GenerateExpPoints genPts(inPts, outPts, ptMap, &ptArrays, this->Filter); vtkSMPTools::For(0, numInputPts, genPts); } }; // Dispatch to implicit input points, explicit output points template struct ImpPtsWorker : public GeneratePtsWorker { ImpPtsWorker(vtkGeometryFilter* filter) : GeneratePtsWorker(filter) { } template void operator()(TOP* outPts, vtkDataSet* inPts, vtkIdType numInputPts, vtkPointData* inPD, vtkPointData* outPD, ExtractCellBoundaries* extract) { // Finalize the point map TInputIdType* ptMap = this->GeneratePointMap(numInputPts, extract); // Now generate all of the points and point attribute data ArrayList ptArrays; outPD->CopyAllocate(inPD, this->NumOutputPoints); ptArrays.AddArrays(this->NumOutputPoints, inPD, outPD, 0.0, false); outPts->SetNumberOfTuples(this->NumOutputPoints); GenerateImpPoints genPts(inPts, outPts, ptMap, &ptArrays, this->Filter); vtkSMPTools::For(0, numInputPts, genPts); } }; // Composite threads to produce output cell topology template struct CompositeCells { const TInputIdType* PointMap; ArrayList* CellArrays; ExtractCellBoundaries* Extractor; ThreadOutputType* Threads; // These are the final composited output cell arrays vtkCellArray* Verts; // output verts TOutputIdType* VertsConnPtr; // output connectivity array TOutputIdType* VertsOffsetPtr; // output offsets array vtkCellArray* Lines; // output lines TOutputIdType* LinesConnPtr; TOutputIdType* LinesOffsetPtr; vtkCellArray* Polys; // output polys TOutputIdType* PolysConnPtr; TOutputIdType* PolysOffsetPtr; vtkCellArray* Strips; // output triangle strips TOutputIdType* StripsConnPtr; TOutputIdType* StripsOffsetPtr; vtkGeometryFilter* Filter; CompositeCells(TInputIdType* ptMap, ArrayList* cellArrays, ExtractCellBoundaries* extract, ThreadOutputType* threads, vtkCellArray* verts, vtkCellArray* lines, vtkCellArray* polys, vtkCellArray* strips, vtkGeometryFilter* filter) : PointMap(ptMap) , CellArrays(cellArrays) , Extractor(extract) , Threads(threads) , Verts(verts) , VertsConnPtr(nullptr) , VertsOffsetPtr(nullptr) , Lines(lines) , LinesConnPtr(nullptr) , LinesOffsetPtr(nullptr) , Polys(polys) , PolysConnPtr(nullptr) , PolysOffsetPtr(nullptr) , Strips(strips) , StripsConnPtr(nullptr) , StripsOffsetPtr(nullptr) , Filter(filter) { // Allocate data for the output cell arrays: connectivity and // offsets are required to construct a cell array. if (extract->VertsNumPts > 0) { this->AllocateCellArray(extract->VertsNumPts, extract->VertsNumCells, this->Verts, this->VertsConnPtr, this->VertsOffsetPtr); } if (extract->LinesNumPts > 0) { this->AllocateCellArray(extract->LinesNumPts, extract->LinesNumCells, this->Lines, this->LinesConnPtr, this->LinesOffsetPtr); } if (extract->PolysNumPts > 0) { this->AllocateCellArray(extract->PolysNumPts, extract->PolysNumCells, this->Polys, this->PolysConnPtr, this->PolysOffsetPtr); } if (extract->StripsNumPts > 0) { this->AllocateCellArray(extract->StripsNumPts, extract->StripsNumCells, this->Strips, this->StripsConnPtr, this->StripsOffsetPtr); } } // Helper function to allocate and construct output cell arrays. // Also keep local information to facilitate compositing. void AllocateCellArray(vtkIdType connSize, vtkIdType numCells, vtkCellArray* ca, TOutputIdType*& connPtr, TOutputIdType*& offsetPtr) { vtkNew> outConn; outConn->SetNumberOfValues(connSize); connPtr = outConn->GetPointer(0); vtkNew> outOffsets; outOffsets->SetNumberOfValues(numCells + 1); offsetPtr = outOffsets->GetPointer(0); offsetPtr[numCells] = connSize; ca->SetData(outOffsets, outConn); } void CompositeCellArray(CellArrayType* cat, vtkIdType connOffset, vtkIdType offset, vtkIdType cellIdOffset, TOutputIdType* connPtr, TOutputIdType* offsetPtr) { TInputIdType* cells = cat->Cells.data(); vtkIdType numCells = cat->GetNumberOfCells(); connPtr += connOffset; offsetPtr += offset; vtkIdType offsetVal = connOffset; vtkIdType globalCellId = cellIdOffset + offset; // If not merging points, we reuse input points and so do not need to // produce new points nor point data. if (!this->PointMap) { for (vtkIdType cellId = 0; cellId < numCells; ++cellId) { *offsetPtr++ = static_cast(offsetVal); TInputIdType npts = *cells++; for (TInputIdType i = 0; i < npts; ++i) { *connPtr++ = static_cast(*cells++); } offsetVal += npts; this->CellArrays->Copy(cat->OrigCellIds[cellId], globalCellId++); } } else // Merging - i.e., using a point map { for (vtkIdType cellId = 0; cellId < numCells; ++cellId) { *offsetPtr++ = static_cast(offsetVal); TInputIdType npts = *cells++; for (TInputIdType i = 0; i < npts; ++i) { *connPtr++ = static_cast(this->PointMap[*cells++]); } offsetVal += npts; this->CellArrays->Copy(cat->OrigCellIds[cellId], globalCellId++); } } } void operator()(vtkIdType threadBegin, vtkIdType threadEnd) { auto* extract = this->Extractor; const auto checkAbortInterval = std::min((threadEnd - threadBegin) / 10 + 1, (vtkIdType)1000); const bool isFirst = vtkSMPTools::GetSingleThread(); for (vtkIdType thread = threadBegin; thread < threadEnd; ++thread) { if (thread % checkAbortInterval == 0) { if (isFirst) { this->Filter->CheckAbort(); } if (this->Filter->GetAbortOutput()) { break; } } auto tItr = (*this->Threads)[thread]; if (this->VertsConnPtr) { this->CompositeCellArray(&tItr->Verts, tItr->VertsConnOffset, tItr->VertsOffset, extract->VertsCellIdOffset, this->VertsConnPtr, this->VertsOffsetPtr); } if (this->LinesConnPtr) { this->CompositeCellArray(&tItr->Lines, tItr->LinesConnOffset, tItr->LinesOffset, extract->LinesCellIdOffset, this->LinesConnPtr, this->LinesOffsetPtr); } if (this->PolysConnPtr) { this->CompositeCellArray(&tItr->Polys, tItr->PolysConnOffset, tItr->PolysOffset, extract->PolysCellIdOffset, this->PolysConnPtr, this->PolysOffsetPtr); } if (this->StripsConnPtr) { this->CompositeCellArray(&tItr->Strips, tItr->StripsConnOffset, tItr->StripsOffset, extract->StripsCellIdOffset, this->StripsConnPtr, this->StripsOffsetPtr); } } } }; // CompositeCells // Composite threads to produce originating cell ids template struct CompositeCellIds { ExtractCellBoundaries* Extractor; ::CompositeCells* CompositeCells; ThreadOutputType* Threads; vtkIdType* OrigIds; vtkGeometryFilter* Filter; CompositeCellIds(ExtractCellBoundaries* extract, ::CompositeCells* compositeCells, ThreadOutputType* threads, vtkIdType* origIds, vtkGeometryFilter* filter) : Extractor(extract) , CompositeCells(compositeCells) , Threads(threads) , OrigIds(origIds) , Filter(filter) { } void CompositeIds(CellArrayType* cat, vtkIdType offset, vtkIdType cellIdOffset) { vtkIdType numCells = cat->GetNumberOfCells(); vtkIdType globalCellId = cellIdOffset + offset; for (vtkIdType cellId = 0; cellId < numCells; ++cellId) { this->OrigIds[globalCellId++] = cat->OrigCellIds[cellId]; } } void operator()(vtkIdType threadBegin, vtkIdType threadEnd) { auto* extract = this->Extractor; auto* compositeCells = this->CompositeCells; const auto checkAbortInterval = std::min((threadEnd - threadBegin) / 10 + 1, (vtkIdType)1000); const bool isFirst = vtkSMPTools::GetSingleThread(); for (vtkIdType thread = threadBegin; thread < threadEnd; ++thread) { if (thread % checkAbortInterval == 0) { if (isFirst) { this->Filter->CheckAbort(); } if (this->Filter->GetAbortOutput()) { break; } } auto tItr = (*this->Threads)[thread]; if (compositeCells->VertsConnPtr) { this->CompositeIds(&tItr->Verts, tItr->VertsOffset, extract->VertsCellIdOffset); } if (compositeCells->LinesConnPtr) { this->CompositeIds(&tItr->Lines, tItr->LinesOffset, extract->LinesCellIdOffset); } if (compositeCells->PolysConnPtr) { this->CompositeIds(&tItr->Polys, tItr->PolysOffset, extract->PolysCellIdOffset); } if (compositeCells->StripsConnPtr) { this->CompositeIds(&tItr->Strips, tItr->StripsOffset, extract->StripsCellIdOffset); } } } }; // CompositeCellIds } // anonymous namespace //------------------------------------------------------------------------------ // This is currently not threaded. Usually polydata extraction is only used to // setup originating cell or point ids - this part is threaded. namespace { template int ExecutePolyData(vtkGeometryFilter* self, vtkDataSet* dataSetInput, vtkPolyData* output, vtkExcludedFaces* exc) { vtkPolyData* input = static_cast(dataSetInput); vtkIdType cellId; int i; int allVisible; vtkIdType npts; const vtkIdType* pts; vtkPoints* p = input->GetPoints(); vtkIdType numCells = input->GetNumberOfCells(); vtkIdType numPts = input->GetNumberOfPoints(); vtkPointData* pd = input->GetPointData(); vtkCellData* cd = input->GetCellData(); vtkPointData* outputPD = output->GetPointData(); vtkCellData* outputCD = output->GetCellData(); vtkIdType newCellId, ptId; int visible, type; double x[3]; unsigned char* cellGhosts = nullptr; vtkStaticCellLinksTemplate* links = (exc == nullptr ? nullptr : exc->Links); vtkDebugWithObjectMacro(self, << "Executing geometry filter for poly data input"); if (cd) { if (auto ghosts = cd->GetGhostArray()) { cellGhosts = ghosts->GetPointer(0); } } auto cellClipping = self->GetCellClipping(); auto cellMinimum = self->GetCellMinimum(); auto cellMaximum = self->GetCellMaximum(); auto pointClipping = self->GetPointClipping(); auto pointMinimum = self->GetPointMinimum(); auto pointMaximum = self->GetPointMaximum(); auto extentClipping = self->GetExtentClipping(); auto extent = self->GetExtent(); if ((!cellClipping) && (!pointClipping) && (!extentClipping)) { allVisible = 1; } else { allVisible = 0; } IdRecorder origCellIds( self->GetPassThroughCellIds(), self->GetOriginalCellIdsName(), output->GetCellData()); IdRecorder origPointIds( self->GetPassThroughPointIds(), self->GetOriginalPointIdsName(), output->GetPointData()); // vtkPolyData points are not culled if (origPointIds.PassThru()) { origPointIds.SetNumberOfValues(numPts); vtkIdType* origPointIdsPtr = origPointIds.GetPointer(); vtkSMPTools::For(0, numPts, [&origPointIdsPtr](vtkIdType pId, vtkIdType endPId) { for (; pId < endPId; ++pId) { origPointIdsPtr[pId] = pId; } }); } // Special case when data is just passed through if (allVisible && links == nullptr) { output->CopyStructure(input); outputPD->PassData(pd); outputCD->PassData(cd); if (origCellIds.PassThru()) { origCellIds.SetNumberOfValues(numCells); vtkIdType* origCellIdsPtr = origCellIds.GetPointer(); vtkSMPTools::For(0, numCells, [&origCellIdsPtr](vtkIdType cId, vtkIdType endCId) { for (; cId < endCId; ++cId) { origCellIdsPtr[cId] = cId; } }); } return 1; } // Okay slower path, clipping by cells and/or point ids, or excluding // faces. Cells may be culled. Always pass point data (points are not // culled). output->SetPoints(p); outputPD->PassData(pd); // Allocate // origCellIds.Allocate(numCells); origPointIds.Allocate(numPts); output->AllocateEstimate(numCells, 1); outputCD->CopyAllocate(cd, numCells, numCells / 2); input->BuildCells(); // needed for GetCellPoints() const unsigned char MASKED_CELL = self->GetRemoveGhostInterfaces() ? MASKED_CELL_VALUE : MASKED_CELL_VALUE_NOT_VISIBLE; vtkIdType progressInterval = numCells / 20 + 1; bool abort = false; for (cellId = 0; cellId < numCells && !abort; cellId++) { // Progress and abort method support if (!(cellId % progressInterval)) { vtkDebugWithObjectMacro(self, << "Process cell #" << cellId); self->UpdateProgress(static_cast(cellId) / numCells); abort = self->CheckAbort(); } // Handle ghost cells here. Another option was used cellVis array. if (cellGhosts && cellGhosts[cellId] & MASKED_CELL) { // Do not create surfaces in outer ghost cells. continue; } input->GetCellPoints(cellId, npts, pts); visible = 1; if (!allVisible) { if (cellClipping && (cellId < cellMinimum || cellId > cellMaximum)) { visible = 0; } else { for (i = 0; i < npts; i++) { ptId = pts[i]; input->GetPoint(ptId, x); if ((pointClipping && (pts[i] < pointMinimum || pts[i] > pointMaximum)) || (extentClipping && (x[0] < extent[0] || x[0] > extent[1] || x[1] < extent[2] || x[1] > extent[3] || x[2] < extent[4] || x[2] > extent[5]))) { visible = 0; break; } } } } // now if visible extract geometry - i.e., cells may be culled if ((allVisible || visible) && (!links || !links->MatchesCell(npts, pts))) { type = input->GetCellType(cellId); newCellId = output->InsertNextCell(type, npts, pts); outputCD->CopyData(cd, cellId, newCellId); origCellIds.Insert(cellId, newCellId); } // if visible } // for all cells // Update ourselves and release memory // output->Squeeze(); vtkDebugWithObjectMacro(self, << "Extracted " << output->GetNumberOfPoints() << " points," << output->GetNumberOfCells() << " cells."); return 1; } } //------------------------------------------------------------------------------ int vtkGeometryFilter::PolyDataExecute( vtkDataSet* dataSetInput, vtkPolyData* output, vtkPolyData* excludedFaces) { #ifdef VTK_USE_64BIT_IDS bool use64BitsIds = (dataSetInput->GetNumberOfPoints() > VTK_TYPE_INT32_MAX || dataSetInput->GetNumberOfCells() > VTK_TYPE_INT32_MAX); if (use64BitsIds) { using TInputIdType = vtkTypeInt64; vtkExcludedFaces exc; if (excludedFaces) { exc.ExcludedFaces = excludedFaces; vtkCellArray* excPolys = excludedFaces->GetPolys(); if (excPolys->GetNumberOfCells() > 0) { exc.Links = new vtkStaticCellLinksTemplate; exc.Links->BuildLinks( dataSetInput->GetNumberOfPoints(), excPolys->GetNumberOfCells(), excPolys); } } return ExecutePolyData(this, dataSetInput, output, &exc); } else #endif { using TInputIdType = vtkTypeInt32; vtkExcludedFaces exc; if (excludedFaces) { exc.ExcludedFaces = excludedFaces; vtkCellArray* excPolys = excludedFaces->GetPolys(); if (excPolys->GetNumberOfCells() > 0) { exc.Links = new vtkStaticCellLinksTemplate; exc.Links->BuildLinks( dataSetInput->GetNumberOfPoints(), excPolys->GetNumberOfCells(), excPolys); } } return ExecutePolyData(this, dataSetInput, output, &exc); } } //------------------------------------------------------------------------------ int vtkGeometryFilter::PolyDataExecute(vtkDataSet* dataSetInput, vtkPolyData* output) { return this->PolyDataExecute(dataSetInput, output, nullptr); } namespace { //------------------------------------------------------------------------------ struct CharacterizeGrid { vtkUnstructuredGridBase* Grid; using CellTypesInformation = vtkGeometryFilterHelper::CellTypesInformation; using CellType = vtkGeometryFilterHelper::CellType; vtkSMPThreadLocal TLCellTypesInfo; CellTypesInformation CellTypesInfo; unsigned char IsLinear; CharacterizeGrid(vtkUnstructuredGridBase* grid) : Grid(grid) { } void Initialize() { CellTypesInformation& cellTypesInfo = this->TLCellTypesInfo.Local(); std::fill(cellTypesInfo.begin(), cellTypesInfo.end(), false); } static void AssignCellTypeInfo(const unsigned char& cellType, CellTypesInformation& cellTypesInfo) { switch (cellType) { case VTK_VERTEX: case VTK_POLY_VERTEX: if (!cellTypesInfo[CellType::VERTS]) { cellTypesInfo[CellType::VERTS] = true; } break; case VTK_LINE: case VTK_POLY_LINE: if (!cellTypesInfo[CellType::LINES]) { cellTypesInfo[CellType::LINES] = true; } break; case VTK_TRIANGLE: case VTK_QUAD: case VTK_POLYGON: if (!cellTypesInfo[CellType::POLYS]) { cellTypesInfo[CellType::POLYS] = true; } break; case VTK_TRIANGLE_STRIP: if (!cellTypesInfo[CellType::STRIPS]) { cellTypesInfo[CellType::STRIPS] = true; } break; case VTK_EMPTY_CELL: case VTK_PIXEL: case VTK_TETRA: case VTK_VOXEL: case VTK_HEXAHEDRON: case VTK_WEDGE: case VTK_PYRAMID: case VTK_PENTAGONAL_PRISM: case VTK_HEXAGONAL_PRISM: case VTK_CONVEX_POINT_SET: case VTK_POLYHEDRON: if (!cellTypesInfo[CellType::OTHER_LINEAR_CELLS]) { cellTypesInfo[CellType::OTHER_LINEAR_CELLS] = true; } break; default: if (!cellTypesInfo[CellType::NON_LINEAR_CELLS]) { cellTypesInfo[CellType::NON_LINEAR_CELLS] = true; } } } void operator()(vtkIdType beginCellId, vtkIdType endCellId) { CellTypesInformation& cellTypesInfo = this->TLCellTypesInfo.Local(); for (vtkIdType cellId = beginCellId; cellId < endCellId; ++cellId) { CharacterizeGrid::AssignCellTypeInfo( static_cast(this->Grid->GetCellType(cellId)), cellTypesInfo); } } void Reduce() { // initialize std::fill(this->CellTypesInfo.begin(), this->CellTypesInfo.end(), false); for (const auto& cellTypesInfo : this->TLCellTypesInfo) { for (int i = 0; i < CellType::NUM_CELL_TYPES; ++i) { if (cellTypesInfo[i] && !this->CellTypesInfo[i]) { this->CellTypesInfo[i] = true; } } } this->IsLinear = static_cast(!this->CellTypesInfo[CellType::NON_LINEAR_CELLS]); } }; //------------------------------------------------------------------------------ // Threaded creation to generate array of originating point ids. template void PassPointIds(const char* name, vtkIdType numInputPts, vtkIdType numOutputPts, TInputIdType* ptMap, vtkPointData* outPD) { vtkNew origPtIds; origPtIds->SetName(name); origPtIds->SetNumberOfComponents(1); origPtIds->SetNumberOfTuples(numOutputPts); outPD->AddArray(origPtIds); vtkIdType* origIds = origPtIds->GetPointer(0); // Now threaded populate the array vtkSMPTools::For(0, numInputPts, [&origIds, &ptMap](vtkIdType ptId, vtkIdType endPtId) { for (; ptId < endPtId; ++ptId) { if (ptMap[ptId] >= 0) { origIds[ptMap[ptId]] = ptId; } } }); } //------------------------------------------------------------------------------ // Threaded compositing of originating cell ids. template void PassCellIds(const char* name, ExtractCellBoundaries* extract, CompositeCells* compositeCells, ThreadOutputType* threads, vtkCellData* outCD, vtkGeometryFilter* filter) { vtkIdType numOutputCells = extract->NumCells; vtkNew origCellIds; origCellIds->SetName(name); origCellIds->SetNumberOfComponents(1); origCellIds->SetNumberOfTuples(numOutputCells); outCD->AddArray(origCellIds); vtkIdType* origIds = origCellIds->GetPointer(0); // Now populate the original cell ids CompositeCellIds compIds( extract, compositeCells, threads, origIds, filter); vtkSMPTools::For(0, static_cast(threads->size()), compIds); } } // anonymous //---------------------------------------------------------------------------- vtkGeometryFilterHelper* vtkGeometryFilterHelper::CharacterizeUnstructuredGrid( vtkUnstructuredGridBase* input) { vtkGeometryFilterHelper* info = new vtkGeometryFilterHelper; // Check to see if the data actually has nonlinear cells. Handling // nonlinear cells requires delegation to the appropriate filter. CharacterizeGrid characterize(input); vtkSMPTools::For(0, input->GetNumberOfCells(), characterize); info->CellTypesInfo = characterize.CellTypesInfo; info->IsLinear = characterize.IsLinear; return info; } //---------------------------------------------------------------------------- void vtkGeometryFilterHelper::CopyFilterParams(vtkGeometryFilter* gf, vtkDataSetSurfaceFilter* dssf) { // Helper method to copy key parameters from this filter to an instance of // vtkDataSetSurfaceFilter. This is for delegation. dssf->SetPieceInvariant(gf->GetPieceInvariant()); dssf->SetPassThroughCellIds(gf->GetPassThroughCellIds()); dssf->SetPassThroughPointIds(gf->GetPassThroughPointIds()); dssf->SetOriginalCellIdsName(gf->GetOriginalCellIdsName()); dssf->SetOriginalPointIdsName(gf->GetOriginalPointIdsName()); dssf->SetNonlinearSubdivisionLevel(gf->GetNonlinearSubdivisionLevel()); dssf->SetMatchBoundariesIgnoringCellOrder(gf->GetMatchBoundariesIgnoringCellOrder()); dssf->SetFastMode(gf->GetFastMode()); } //---------------------------------------------------------------------------- void vtkGeometryFilterHelper::CopyFilterParams(vtkDataSetSurfaceFilter* dssf, vtkGeometryFilter* gf) { // Helper method to copy key parameters from an instance of // vtkDataSetSurfaceFilter to vtkGeometryFilter. This is for delegation. gf->SetPieceInvariant(dssf->GetPieceInvariant()); gf->SetPassThroughCellIds(dssf->GetPassThroughCellIds()); gf->SetPassThroughPointIds(dssf->GetPassThroughPointIds()); gf->SetOriginalCellIdsName(dssf->GetOriginalCellIdsName()); gf->SetOriginalPointIdsName(dssf->GetOriginalPointIdsName()); gf->SetNonlinearSubdivisionLevel(dssf->GetNonlinearSubdivisionLevel()); gf->SetMatchBoundariesIgnoringCellOrder(dssf->GetMatchBoundariesIgnoringCellOrder()); gf->SetFastMode(dssf->GetFastMode()); } namespace { //---------------------------------------------------------------------------- template int ExecuteUnstructuredGrid(vtkGeometryFilter* self, vtkDataSet* dataSetInput, vtkPolyData* output, vtkGeometryFilterHelper* info, vtkExcludedFaces* exc) { vtkUnstructuredGridBase* uGridBase = vtkUnstructuredGridBase::SafeDownCast(dataSetInput); if (uGridBase->GetNumberOfCells() == 0) { vtkDebugWithObjectMacro(self, << "Nothing to extract"); return 0; } vtkUnstructuredGrid* uGrid = vtkUnstructuredGrid::SafeDownCast(uGridBase); // If no info, then compute information about the unstructured grid. // Depending on the outcome, we may process the data ourselves, or send over // to the faster vtkGeometryFilter. bool mayDelegate = (info == nullptr && self->GetDelegation()); bool info_owned = false; if (info == nullptr) { info = vtkGeometryFilterHelper::CharacterizeUnstructuredGrid(uGridBase); info_owned = true; } // Nonlinear cells are handled by vtkDataSetSurfaceFilter // non-linear cells using sub-division. if (!info->IsLinear && mayDelegate) { vtkNew dssf; vtkGeometryFilterHelper::CopyFilterParams(self, dssf.Get()); dssf->UnstructuredGridExecute(dataSetInput, output, info); delete info; return 1; } // if it's an unstructured grid base and not an unstructured grid if (!uGrid && uGridBase) { if (info_owned) { delete info; } return self->DataSetExecute(uGridBase, output, exc->ExcludedFaces); } // fast conversion when input is actually polydata with one cell array if (uGrid && (info->HasOnlyVerts() || info->HasOnlyLines() || info->HasOnlyPolys() || info->HasOnlyStrips())) { vtkNew polyDataInput; polyDataInput->SetPoints(uGrid->GetPoints()); polyDataInput->GetPointData()->ShallowCopy(uGrid->GetPointData()); if (self->GetPassThroughPointIds() && polyDataInput->GetPointData()->HasArray(self->GetOriginalPointIdsName())) { polyDataInput->GetPointData()->RemoveArray(self->GetOriginalPointIdsName()); } if (info->HasOnlyVerts()) { polyDataInput->SetVerts(uGrid->GetCells()); } else if (info->HasOnlyLines()) { polyDataInput->SetLines(uGrid->GetCells()); } else if (info->HasOnlyPolys()) { polyDataInput->SetPolys(uGrid->GetCells()); } else if (info->HasOnlyStrips()) { polyDataInput->SetStrips(uGrid->GetCells()); } polyDataInput->GetCellData()->ShallowCopy(uGrid->GetCellData()); if (self->GetPassThroughCellIds() && polyDataInput->GetCellData()->HasArray(self->GetOriginalCellIdsName())) { polyDataInput->GetCellData()->RemoveArray(self->GetOriginalCellIdsName()); } if (info_owned) { delete info; } return ExecutePolyData(self, polyDataInput, output, exc); } if (info_owned) { delete info; } vtkIdType cellId; vtkIdType npts = 0; vtkNew pointIdList; const vtkIdType* pts = nullptr; vtkPoints* inPts = uGrid->GetPoints(); vtkIdType numInputPts = uGrid->GetNumberOfPoints(), numOutputPts; vtkIdType numCells = uGrid->GetNumberOfCells(); vtkPointData* inPD = uGrid->GetPointData(); vtkCellData* inCD = uGrid->GetCellData(); vtkPointData* outPD = output->GetPointData(); vtkCellData* outCD = output->GetCellData(); vtkNew> cellVisArray; char* cellVis; unsigned char* cellGhosts = nullptr; unsigned char* pointGhosts = nullptr; vtkDebugWithObjectMacro(self, << "Executing geometry filter for unstructured grid input"); if (inCD) { if (auto ghosts = inCD->GetGhostArray()) { cellGhosts = ghosts->GetPointer(0); } } if (inPD) { if (auto ghosts = inPD->GetGhostArray()) { pointGhosts = ghosts->GetPointer(0); } } auto cellClipping = self->GetCellClipping(); auto cellMinimum = self->GetCellMinimum(); auto cellMaximum = self->GetCellMaximum(); auto pointClipping = self->GetPointClipping(); auto pointMinimum = self->GetPointMinimum(); auto pointMaximum = self->GetPointMaximum(); auto extentClipping = self->GetExtentClipping(); auto extent = self->GetExtent(); // Determine nature of what we have to do if ((!cellClipping) && (!pointClipping) && (!extentClipping)) { cellVis = nullptr; } else { cellVisArray->SetNumberOfValues(numCells); cellVis = cellVisArray->GetPointer(0); } outPD->CopyGlobalIdsOn(); outCD->CopyGlobalIdsOn(); // Loop over the cells determining what's visible. This could be threaded // if necessary - for now it's not used very often so serial. if (cellVis) { double x[3]; for (cellId = 0; cellId < numCells; ++cellId) { uGrid->GetCellPoints(cellId, npts, pts, pointIdList); cellVis[cellId] = 1; if (cellClipping && (cellId < cellMinimum || cellId > cellMaximum)) { cellVis[cellId] = 0; } else { for (int i = 0; i < npts; i++) { inPts->GetPoint(pts[i], x); if ((pointClipping && (pts[i] < pointMinimum || pts[i] > pointMaximum)) || (extentClipping && (x[0] < extent[0] || x[0] > extent[1] || x[1] < extent[2] || x[1] > extent[3] || x[2] < extent[4] || x[2] > extent[5]))) { cellVis[cellId] = 0; break; } // point/extent clipping } // for each point } // if point clipping needs checking } // for all cells } // if not all visible // Prepare to generate the output. The cell arrays are of course the output vertex, // line, polygon, and triangle strip output. The four IdListType's capture the // generating cell ids (used later to copy cell attributes). vtkNew outPts; if (self->GetOutputPointsPrecision() == vtkAlgorithm::DEFAULT_PRECISION) { outPts->SetDataType(inPts->GetDataType()); } else if (self->GetOutputPointsPrecision() == vtkAlgorithm::SINGLE_PRECISION) { outPts->SetDataType(VTK_FLOAT); } else if (self->GetOutputPointsPrecision() == vtkAlgorithm::DOUBLE_PRECISION) { outPts->SetDataType(VTK_DOUBLE); } if (!self->GetMerging()) // no merging, just use input points { output->SetPoints(inPts); outPD->PassData(inPD); } else { output->SetPoints(outPts); } vtkNew verts; vtkNew lines; vtkNew polys; vtkNew strips; output->SetVerts(verts); output->SetLines(lines); output->SetPolys(polys); output->SetStrips(strips); // Build a face hash links to quickly determine the faces that have the same hash. vtkStaticFaceHashLinksTemplate faceHashLinks; faceHashLinks.BuildHashLinks(uGrid); self->UpdateProgress(0.4); // Threaded visit of each cell to extract boundary features. Each thread gathers // output which is then composited into the final vtkPolyData. // Keep track of each thread's output, we'll need this later for compositing. ThreadOutputType threads; // Perform the threaded boundary cell extraction. This performs some // initial reduction and allocation of the output. It also computes offsets // and sizes for allocation and writing of data. auto* extract = new ExtractUG( self, uGrid, faceHashLinks, cellVis, cellGhosts, pointGhosts, exc, &threads); vtkSMPTools::For(0, faceHashLinks.GetNumberOfHashes(), *extract); numCells = extract->NumCells; self->UpdateProgress(0.8); // free up the hash links faceHashLinks.Reset(); // If merging points, then it's necessary to allocate the points array, // configure the point map, and generate the new points. Here we are using // an explicit point dispatch (i.e., the point representation is explicitly // represented by a data array as we are processing an unstructured grid). TInputIdType* ptMap = extract->PointMap; if (self->GetMerging()) { using ExpPtsDispatch = vtkArrayDispatch::Dispatch2ByArray; ExpPtsWorker compWorker(self); if (!ExpPtsDispatch::Execute( inPts->GetData(), outPts->GetData(), compWorker, numInputPts, inPD, outPD, extract)) { // Fallback to slowpath for other point types compWorker(inPts->GetData(), outPts->GetData(), numInputPts, inPD, outPD, extract); } numOutputPts = compWorker.NumOutputPoints; // Generate originating point ids if requested and merging is // on. (Generating these originating point ids only makes sense if the // points are merged.) if (self->GetPassThroughPointIds()) { PassPointIds(self->GetOriginalPointIdsName(), numInputPts, numOutputPts, ptMap, outPD); } } self->UpdateProgress(0.9); // Finally we can composite the output topology. ArrayList cellArrays; outCD->CopyAllocate(inCD, numCells); cellArrays.AddArrays(numCells, inCD, outCD, 0.0, false); #ifdef VTK_USE_64BIT_IDS vtkIdType connectivitySize = extract->VertsNumPts + extract->LinesNumPts + extract->PolysNumPts + extract->StripsNumPts; bool use64BitsIds = connectivitySize > VTK_TYPE_INT32_MAX; if (use64BitsIds) { using TOutputIdType = vtkTypeInt64; CompositeCells compCells( ptMap, &cellArrays, extract, &threads, verts, lines, polys, strips, self); vtkSMPTools::For(0, static_cast(threads.size()), compCells); // Generate originating cell ids if requested. if (self->GetPassThroughCellIds()) { PassCellIds( self->GetOriginalCellIdsName(), extract, &compCells, &threads, outCD, self); } } else #endif { using TOutputIdType = vtkTypeInt32; CompositeCells compCells( ptMap, &cellArrays, extract, &threads, verts, lines, polys, strips, self); vtkSMPTools::For(0, static_cast(threads.size()), compCells); // Generate originating cell ids if requested. if (self->GetPassThroughCellIds()) { PassCellIds( self->GetOriginalCellIdsName(), extract, &compCells, &threads, outCD, self); } } self->UpdateProgress(1.0); vtkDebugWithObjectMacro(self, << "Extracted " << output->GetNumberOfPoints() << " points," << output->GetNumberOfCells() << " cells."); // Clean up and get out delete extract; return 1; } } //---------------------------------------------------------------------------- int vtkGeometryFilter::UnstructuredGridExecute(vtkDataSet* dataSetInput, vtkPolyData* output, vtkGeometryFilterHelper* info, vtkPolyData* excludedFaces) { const auto uGrid = vtkUnstructuredGrid::SafeDownCast(dataSetInput); #ifdef VTK_USE_64BIT_IDS bool use64BitsIds = (dataSetInput->GetNumberOfPoints() > VTK_TYPE_INT32_MAX || dataSetInput->GetNumberOfCells() > VTK_TYPE_INT32_MAX); if (use64BitsIds) { using TInputIdType = vtkTypeInt64; vtkExcludedFaces exc; if (excludedFaces) { exc.ExcludedFaces = excludedFaces; vtkCellArray* excPolys = excludedFaces->GetPolys(); if (excPolys->GetNumberOfCells() > 0) { exc.Links = new vtkStaticCellLinksTemplate; exc.Links->BuildLinks( dataSetInput->GetNumberOfPoints(), excPolys->GetNumberOfCells(), excPolys); } } if (uGrid && !uGrid->GetPolyhedronFaces()) { using TFaceIdType = vtkTypeInt8; return ExecuteUnstructuredGrid( this, dataSetInput, output, info, &exc); } else { using TFaceIdType = vtkTypeInt32; return ExecuteUnstructuredGrid( this, dataSetInput, output, info, &exc); } } else #endif { using TInputIdType = vtkTypeInt32; vtkExcludedFaces exc; if (excludedFaces) { exc.ExcludedFaces = excludedFaces; vtkCellArray* excPolys = excludedFaces->GetPolys(); if (excPolys->GetNumberOfCells() > 0) { exc.Links = new vtkStaticCellLinksTemplate; exc.Links->BuildLinks( dataSetInput->GetNumberOfPoints(), excPolys->GetNumberOfCells(), excPolys); } } if (uGrid && !uGrid->GetPolyhedronFaces()) { using TFaceIdType = vtkTypeInt8; return ExecuteUnstructuredGrid( this, dataSetInput, output, info, &exc); } else { using TFaceIdType = vtkTypeInt32; return ExecuteUnstructuredGrid( this, dataSetInput, output, info, &exc); } } } //---------------------------------------------------------------------------- int vtkGeometryFilter::UnstructuredGridExecute(vtkDataSet* dataSetInput, vtkPolyData* output) { return this->UnstructuredGridExecute(dataSetInput, output, nullptr, nullptr); } namespace { //------------------------------------------------------------------------------ template int ExecuteStructured(vtkGeometryFilter* self, vtkDataSet* input, vtkPolyData* output, int* wholeExtent, vtkExcludedFaces* exc, bool* extractFace) { vtkIdType numCells = input->GetNumberOfCells(); vtkPointData* inPD = input->GetPointData(); vtkCellData* inCD = input->GetCellData(); vtkPointData* outPD = output->GetPointData(); vtkCellData* outCD = output->GetCellData(); // Setup processing bool mergePts = true; // implicit point representations require merging vtkPoints* inPts = nullptr; if (auto sgrid = vtkStructuredGrid::SafeDownCast(input)) { inPts = sgrid->GetPoints(); mergePts = self->GetMerging(); // may not be required for explicit } unsigned char* cellGhosts = nullptr; if (inCD) { if (auto ghosts = inCD->GetGhostArray()) { cellGhosts = ghosts->GetPointer(0); } } // We can now extract the boundary topology. This works for all structured // types. Here we are only dealing with 3D structured datasets. The 2D cells // are handled as a general dataset. vtkNew polys; output->SetPolys(polys); ThreadOutputType threads; outPD->CopyGlobalIdsOn(); outCD->CopyGlobalIdsOn(); ExtractCellBoundaries* extStr; // if extractFace are defined, it's an AMR block if (auto image = vtkImageData::SafeDownCast(input)) { extStr = ExtractStructured::Execute( self, image, wholeExtent, extractFace, mergePts, cellGhosts, exc, &threads); } else if (auto sGrid = vtkStructuredGrid::SafeDownCast(input)) { extStr = ExtractStructured::Execute( self, sGrid, wholeExtent, extractFace, mergePts, cellGhosts, exc, &threads); } else if (auto rGrid = vtkRectilinearGrid::SafeDownCast(input)) { extStr = ExtractStructured::Execute( self, rGrid, wholeExtent, extractFace, mergePts, cellGhosts, exc, &threads); } else { vtkErrorWithObjectMacro(self, "Data type " << input->GetClassName() << "is not supported."); return 0; } numCells = extStr->NumCells; self->UpdateProgress(0.3); // Generate the output points vtkIdType numInputPts = input->GetNumberOfPoints(), numOutputPts; vtkNew outPts; if (self->GetOutputPointsPrecision() == vtkAlgorithm::DEFAULT_PRECISION && inPts != nullptr) { outPts->SetDataType(inPts->GetDataType()); } else if (self->GetOutputPointsPrecision() == vtkAlgorithm::SINGLE_PRECISION) { outPts->SetDataType(VTK_FLOAT); } else if (self->GetOutputPointsPrecision() == vtkAlgorithm::DOUBLE_PRECISION) { outPts->SetDataType(VTK_DOUBLE); } if (!mergePts && inPts != nullptr) // no merging, just use input points { output->SetPoints(inPts); outPD->PassData(inPD); } else { output->SetPoints(outPts); } if (mergePts && inPts != nullptr) // are these explicit points with merging on? { using ExpPtsDispatch = vtkArrayDispatch::Dispatch2ByArray; ExpPtsWorker compWorker(self); if (!ExpPtsDispatch::Execute( inPts->GetData(), outPts->GetData(), compWorker, numInputPts, inPD, outPD, extStr)) { // Fallback to slowpath for other point types compWorker(inPts->GetData(), outPts->GetData(), numInputPts, inPD, outPD, extStr); } numOutputPts = compWorker.NumOutputPoints; } else // implicit point representation { // Some of these datasets have explicit point representations, we'll generate // the geometry (i.e., points) now. using ImpPtsDispatch = vtkArrayDispatch::DispatchByArray; ImpPtsWorker compWorker(self); if (!ImpPtsDispatch::Execute( outPts->GetData(), compWorker, input, numInputPts, inPD, outPD, extStr)) { // Fallback to slowpath for other point types compWorker(outPts->GetData(), input, numInputPts, inPD, outPD, extStr); } numOutputPts = compWorker.NumOutputPoints; } // Generate originating point ids if requested and merging is // on. (Generating these originating point ids only makes sense if the // points are merged.) TInputIdType* ptMap = extStr->PointMap; if (self->GetPassThroughPointIds() && (inPts == nullptr || mergePts)) { PassPointIds(self->GetOriginalPointIdsName(), numInputPts, numOutputPts, ptMap, outPD); } self->UpdateProgress(0.75); // Finally we can composite the output topology. ArrayList cellArrays; outCD->CopyAllocate(inCD, numCells); cellArrays.AddArrays(numCells, inCD, outCD, 0.0, false); #ifdef VTK_USE_64BIT_IDS vtkIdType connectivitySize = extStr->VertsNumPts + extStr->LinesNumPts + extStr->PolysNumPts + extStr->StripsNumPts; bool use64BitsIds = connectivitySize > VTK_TYPE_INT32_MAX; if (use64BitsIds) { using TOutputIdType = vtkTypeInt64; CompositeCells compCells( ptMap, &cellArrays, extStr, &threads, nullptr, nullptr, polys, nullptr, self); vtkSMPTools::For(0, static_cast(threads.size()), compCells); // Generate originating cell ids if requested. if (self->GetPassThroughCellIds()) { PassCellIds( self->GetOriginalCellIdsName(), extStr, &compCells, &threads, outCD, self); } } else #endif { using TOutputIdType = vtkTypeInt32; CompositeCells compCells( ptMap, &cellArrays, extStr, &threads, nullptr, nullptr, polys, nullptr, self); vtkSMPTools::For(0, static_cast(threads.size()), compCells); // Generate originating cell ids if requested. if (self->GetPassThroughCellIds()) { PassCellIds( self->GetOriginalCellIdsName(), extStr, &compCells, &threads, outCD, self); } } self->UpdateProgress(1.0); vtkDebugWithObjectMacro(self, << "Extracted " << output->GetNumberOfPoints() << " points," << output->GetNumberOfCells() << " cells."); delete extStr; return 1; } } //------------------------------------------------------------------------------ int vtkGeometryFilter::StructuredExecute(vtkDataSet* input, vtkPolyData* output, int* wholeExtent, vtkPolyData* excludedFaces, bool* extractFace) { int dataDim = -1; if (auto imageData = vtkImageData::SafeDownCast(input)) { dataDim = imageData->GetDataDimension(); } else if (auto structured = vtkStructuredGrid::SafeDownCast(input)) { dataDim = structured->GetDataDimension(); } else if (auto rectilinear = vtkRectilinearGrid::SafeDownCast(input)) { dataDim = rectilinear->GetDataDimension(); } assert(dataDim != -1); // Delegate to the generic dataset processing if structuredGrid is not 3d or cell/point/extent // clipping is requested. Otherwise. use the fast structured algorithms. This is done for // simplification purposes. if (dataDim != 3 || this->GetCellClipping() || this->GetPointClipping() || this->GetExtentClipping()) { return this->DataSetExecute(input, output, excludedFaces); } #ifdef VTK_USE_64BIT_IDS bool use64BitsIds = (input->GetNumberOfPoints() > VTK_TYPE_INT32_MAX || input->GetNumberOfCells() > VTK_TYPE_INT32_MAX); if (use64BitsIds) { using TInputIdType = vtkTypeInt64; vtkExcludedFaces exc; if (excludedFaces) { exc.ExcludedFaces = excludedFaces; vtkCellArray* excPolys = excludedFaces->GetPolys(); if (excPolys->GetNumberOfCells() > 0) { exc.Links = new vtkStaticCellLinksTemplate; exc.Links->BuildLinks(input->GetNumberOfPoints(), excPolys->GetNumberOfCells(), excPolys); } } return ExecuteStructured(this, input, output, wholeExtent, &exc, extractFace); } else #endif { using TInputIdType = vtkTypeInt32; vtkExcludedFaces exc; if (excludedFaces) { exc.ExcludedFaces = excludedFaces; vtkCellArray* excPolys = excludedFaces->GetPolys(); if (excPolys->GetNumberOfCells() > 0) { exc.Links = new vtkStaticCellLinksTemplate; exc.Links->BuildLinks(input->GetNumberOfPoints(), excPolys->GetNumberOfCells(), excPolys); } } return ExecuteStructured(this, input, output, wholeExtent, &exc, extractFace); } } //------------------------------------------------------------------------------ // Process various types of structured datasets. int vtkGeometryFilter::StructuredExecute( vtkDataSet* input, vtkPolyData* output, int* wholeExtent, bool* extractFace) { return this->StructuredExecute(input, output, wholeExtent, nullptr, extractFace); } //------------------------------------------------------------------------------ namespace { template int ExecuteDataSet(vtkGeometryFilter* self, vtkDataSet* input, vtkPolyData* output, vtkExcludedFaces* exc) { auto explicitSG = vtkExplicitStructuredGrid::SafeDownCast(input); if (explicitSG) { explicitSG->BuildLinks(); } vtkIdType cellId; int i; vtkIdType numCells = input->GetNumberOfCells(); double x[3]; vtkIdType ptId; vtkPointData* inPD = input->GetPointData(); vtkCellData* inCD = input->GetCellData(); vtkPointData* outPD = output->GetPointData(); vtkCellData* outCD = output->GetCellData(); vtkNew> cellVisArray; char* cellVis; unsigned char* cellGhosts = nullptr; unsigned char* pointGhosts = nullptr; vtkDebugWithObjectMacro(self, << "Executing geometry filter"); if (inCD) { if (auto ghosts = inCD->GetGhostArray()) { cellGhosts = ghosts->GetPointer(0); } } if (inPD) { if (auto ghosts = inPD->GetGhostArray()) { pointGhosts = ghosts->GetPointer(0); } } auto cellClipping = self->GetCellClipping(); auto cellMinimum = self->GetCellMinimum(); auto cellMaximum = self->GetCellMaximum(); auto pointClipping = self->GetPointClipping(); auto pointMinimum = self->GetPointMinimum(); auto pointMaximum = self->GetPointMaximum(); auto extentClipping = self->GetExtentClipping(); auto extent = self->GetExtent(); // Determine nature of what we have to do if ((!cellClipping) && (!pointClipping) && (!extentClipping)) { cellVis = nullptr; } else { cellVisArray->SetNumberOfValues(numCells); cellVis = cellVisArray->GetPointer(0); } // Mark cells as being visible or not // if (cellVis) { vtkNew cell; vtkIdList* ptIds; for (cellId = 0; cellId < numCells; cellId++) { if (cellClipping && (cellId < cellMinimum || cellId > cellMaximum)) { cellVis[cellId] = 0; } else { input->GetCell(cellId, cell); ptIds = cell->GetPointIds(); vtkIdType ncells = ptIds->GetNumberOfIds(); for (i = 0; i < ncells; i++) { ptId = ptIds->GetId(i); input->GetPoint(ptId, x); if ((pointClipping && (ptId < pointMinimum || ptId > pointMaximum)) || (extentClipping && (x[0] < extent[0] || x[0] > extent[1] || x[1] < extent[2] || x[1] > extent[3] || x[2] < extent[4] || x[2] > extent[5]))) { cellVis[cellId] = 0; break; } } if (i >= ncells) { cellVis[cellId] = 1; } } } } // Create new output points. In a dataset, points are assumed to be // implicitly represented, so merging must occur, vtkNew outPts; if (self->GetOutputPointsPrecision() == vtkAlgorithm::SINGLE_PRECISION || self->GetOutputPointsPrecision() == vtkAlgorithm::DEFAULT_PRECISION) { outPts->SetDataType(VTK_FLOAT); } else if (self->GetOutputPointsPrecision() == vtkAlgorithm::DOUBLE_PRECISION) { outPts->SetDataType(VTK_DOUBLE); } output->SetPoints(outPts); vtkNew verts; vtkNew lines; vtkNew polys; vtkNew strips; output->SetVerts(verts); output->SetLines(lines); output->SetPolys(polys); output->SetStrips(strips); outPD->CopyGlobalIdsOn(); outCD->CopyGlobalIdsOn(); // The extraction process for vtkDataSet ThreadOutputType threads; ExtractDS extract(self, input, cellVis, cellGhosts, pointGhosts, exc, &threads); vtkSMPTools::For(0, numCells, extract); numCells = extract.NumCells; self->UpdateProgress(0.8); // If merging points, then it's necessary to allocate the points // array. This will be populated later when the final compositing // occurs. vtkIdType numInputPts = input->GetNumberOfPoints(), numOutputPts; // Generate the new points using ImpPtsDispatch = vtkArrayDispatch::DispatchByArray; ImpPtsWorker compWorker(self); if (!ImpPtsDispatch::Execute( outPts->GetData(), compWorker, input, numInputPts, inPD, outPD, &extract)) { // Fallback to slowpath for other point types compWorker(outPts->GetData(), input, numInputPts, inPD, outPD, &extract); } numOutputPts = compWorker.NumOutputPoints; // Generate originating point ids if requested and merging is // on. (Generating these originating point ids only makes sense if the // points are merged.) TInputIdType* ptMap = extract.PointMap; if (self->GetPassThroughPointIds()) { PassPointIds(self->GetOriginalPointIdsName(), numInputPts, numOutputPts, ptMap, outPD); } self->UpdateProgress(0.9); // Finally we can composite the output topology. ArrayList cellArrays; outCD->CopyAllocate(inCD, numCells); cellArrays.AddArrays(numCells, inCD, outCD, 0.0, false); #ifdef VTK_USE_64BIT_IDS vtkIdType connectivitySize = extract.VertsNumPts + extract.LinesNumPts + extract.PolysNumPts + extract.StripsNumPts; bool use64BitsIds = connectivitySize > VTK_TYPE_INT32_MAX; if (use64BitsIds) { using TOutputIdType = vtkTypeInt64; CompositeCells compCells( ptMap, &cellArrays, &extract, &threads, verts, lines, polys, strips, self); vtkSMPTools::For(0, static_cast(threads.size()), compCells); // Generate originating cell ids if requested. if (self->GetPassThroughCellIds()) { PassCellIds( self->GetOriginalCellIdsName(), &extract, &compCells, &threads, outCD, self); } } else #endif { using TOutputIdType = vtkTypeInt32; CompositeCells compCells( ptMap, &cellArrays, &extract, &threads, verts, lines, polys, strips, self); vtkSMPTools::For(0, static_cast(threads.size()), compCells); // Generate originating cell ids if requested. if (self->GetPassThroughCellIds()) { PassCellIds( self->GetOriginalCellIdsName(), &extract, &compCells, &threads, outCD, self); } } self->UpdateProgress(1.0); vtkDebugWithObjectMacro(self, << "Extracted " << output->GetNumberOfPoints() << " points," << output->GetNumberOfCells() << " cells."); return 1; } } //------------------------------------------------------------------------------ int vtkGeometryFilter::DataSetExecute( vtkDataSet* input, vtkPolyData* output, vtkPolyData* excludedFaces) { bool use64BitsIds = (input->GetNumberOfPoints() > VTK_INT_MAX || input->GetNumberOfCells() > VTK_INT_MAX); if (use64BitsIds) { using TInputIdType = vtkIdType; vtkExcludedFaces exc; if (excludedFaces) { exc.ExcludedFaces = excludedFaces; vtkCellArray* excPolys = excludedFaces->GetPolys(); if (excPolys->GetNumberOfCells() > 0) { exc.Links = new vtkStaticCellLinksTemplate; exc.Links->BuildLinks(input->GetNumberOfPoints(), excPolys->GetNumberOfCells(), excPolys); } } return ExecuteDataSet(this, input, output, &exc); } else { using TInputIdType = int; vtkExcludedFaces exc; if (excludedFaces) { exc.ExcludedFaces = excludedFaces; vtkCellArray* excPolys = excludedFaces->GetPolys(); if (excPolys->GetNumberOfCells() > 0) { exc.Links = new vtkStaticCellLinksTemplate; exc.Links->BuildLinks(input->GetNumberOfPoints(), excPolys->GetNumberOfCells(), excPolys); } } return ExecuteDataSet(this, input, output, &exc); } } //------------------------------------------------------------------------------ int vtkGeometryFilter::DataSetExecute(vtkDataSet* input, vtkPolyData* output) { return this->DataSetExecute(input, output, nullptr); } //------------------------------------------------------------------------------ int vtkGeometryFilter::RequestUpdateExtent(vtkInformation* vtkNotUsed(request), vtkInformationVector** inputVector, vtkInformationVector* outputVector) { // get the info objects vtkInformation* inInfo = inputVector[0]->GetInformationObject(0); vtkInformation* outInfo = outputVector->GetInformationObject(0); int piece, numPieces, ghostLevels; piece = outInfo->Get(vtkStreamingDemandDrivenPipeline::UPDATE_PIECE_NUMBER()); numPieces = outInfo->Get(vtkStreamingDemandDrivenPipeline::UPDATE_NUMBER_OF_PIECES()); ghostLevels = outInfo->Get(vtkStreamingDemandDrivenPipeline::UPDATE_NUMBER_OF_GHOST_LEVELS()); if (numPieces > 1 && this->PieceInvariant) { // The special execute for structured data handle boundaries internally. // PolyData does not need any ghost levels. vtkDataObject* dobj = inInfo->Get(vtkDataObject::DATA_OBJECT()); if (dobj && !strcmp(dobj->GetClassName(), "vtkUnstructuredGrid")) { // Processing does nothing for ghost levels yet so ... // Be careful to set output ghost level value one less than default // when they are implemented. I had trouble with multiple executes. ++ghostLevels; } } inInfo->Set(vtkStreamingDemandDrivenPipeline::UPDATE_PIECE_NUMBER(), piece); inInfo->Set(vtkStreamingDemandDrivenPipeline::UPDATE_NUMBER_OF_PIECES(), numPieces); inInfo->Set(vtkStreamingDemandDrivenPipeline::UPDATE_NUMBER_OF_GHOST_LEVELS(), ghostLevels); inInfo->Set(vtkStreamingDemandDrivenPipeline::EXACT_EXTENT(), 1); return 1; } VTK_ABI_NAMESPACE_END