// SPDX-FileCopyrightText: Copyright (c) Ken Martin, Will Schroeder, Bill Lorensen // SPDX-License-Identifier: BSD-3-Clause #include "vtkDataSetSurfaceFilter.h" #include "vtkBezierCurve.h" #include "vtkBezierQuadrilateral.h" #include "vtkBezierTriangle.h" #include "vtkCell.h" #include "vtkCellArray.h" #include "vtkCellData.h" #include "vtkCellTypeUtilities.h" #include "vtkDoubleArray.h" #include "vtkGenericCell.h" #include "vtkHexahedron.h" #include "vtkIdList.h" #include "vtkIdTypeArray.h" #include "vtkInformation.h" #include "vtkInformationVector.h" #include "vtkLagrangeQuadrilateral.h" #include "vtkLagrangeTriangle.h" #include "vtkLogger.h" #include "vtkMergePoints.h" #include "vtkNew.h" #include "vtkObjectFactory.h" #include "vtkPointData.h" #include "vtkPolyData.h" #include "vtkPyramid.h" #include "vtkRectilinearGrid.h" #include "vtkRectilinearGridGeometryFilter.h" #include "vtkSmartPointer.h" #include "vtkStreamingDemandDrivenPipeline.h" #include "vtkStructuredData.h" #include "vtkStructuredGrid.h" #include "vtkStructuredGridGeometryFilter.h" #include "vtkStructuredPoints.h" #include "vtkTetra.h" #include "vtkUniformGrid.h" #include "vtkUnsignedCharArray.h" #include "vtkUnstructuredGrid.h" #include "vtkUnstructuredGridBase.h" #include "vtkUnstructuredGridGeometryFilter.h" #include "vtkVector.h" #include "vtkVoxel.h" #include "vtkWedge.h" #include #include #include #include namespace { constexpr int FSize = sizeof(vtkFastGeomQuad); constexpr int SizeId = sizeof(vtkIdType); constexpr int PointerSize = sizeof(void*); constexpr bool Is64BitsSystem = PointerSize == 8; constexpr bool IsId64Bits = SizeId == 8; constexpr bool EasyToComputeSize = !Is64BitsSystem || IsId64Bits; constexpr int FSizeDivSizeId = FSize / SizeId; inline int sizeofFastQuad(int numPts) { return FSize + (EasyToComputeSize ? numPts * SizeId : (numPts + (numPts & 1 /*fast %2*/)) * SizeId); } /** * 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. */ template bool StructuredExecuteWithBlanking( DataSetT* input, vtkPolyData* output, vtkDataSetSurfaceFilter* self) { if (input == nullptr) { return false; } int inExtent[6]; input->GetExtent(inExtent); if (vtkStructuredData::GetDataDimension(inExtent) != 3 || !input->HasAnyBlankCells()) { // no need to use this logic for non 3D cells or if no blanking is provided. return false; } vtkLogScopeF(TRACE, "StructuredExecuteWithBlanking (fastMode=%d)", (int)self->GetFastMode()); vtkNew points; points->Allocate(input->GetNumberOfPoints() / 2); output->AllocateEstimate(input->GetNumberOfCells(), 4); output->SetPoints(points); // Extracts a either the min (or max) face along the `axis` for the cell // identified by `cellId` in the input dataset. auto getFace = [&inExtent](const int ijk[3], const int axis, bool minFace) { const int iAxis = (axis + 1) % 3; const int jAxis = (axis + 2) % 3; int ptIjk[3] = { ijk[0], ijk[1], ijk[2] }; if (!minFace) { ++ptIjk[axis]; } std::array face; face[0] = vtkStructuredData::ComputePointIdForExtent(inExtent, ptIjk); ++ptIjk[iAxis]; face[1] = vtkStructuredData::ComputePointIdForExtent(inExtent, ptIjk); ++ptIjk[jAxis]; face[2] = vtkStructuredData::ComputePointIdForExtent(inExtent, ptIjk); --ptIjk[iAxis]; face[3] = vtkStructuredData::ComputePointIdForExtent(inExtent, ptIjk); if (minFace) { // invert face order to get an outside pointing normal. return std::array({ face[0], face[3], face[2], face[1] }); } return face; }; // Passes data arrays. Also adds `originalIds` the output if `arrayName` // non-null. auto passData = [](vtkIdTypeArray* originalIds, vtkDataSetAttributes* inputDSA, vtkDataSetAttributes* outputDSA, const char* arrayName) { const auto numValues = originalIds->GetNumberOfTuples(); outputDSA->CopyGlobalIdsOn(); outputDSA->CopyFieldOff(vtkDataSetAttributes::GhostArrayName()); outputDSA->CopyAllocate(inputDSA, numValues); vtkNew fromIds; fromIds->SetArray(originalIds->GetPointer(0), numValues); // don't forget to call `Release` vtkNew toIds; toIds->SetNumberOfIds(numValues); std::iota(toIds->begin(), toIds->end(), 0); outputDSA->CopyData(inputDSA, fromIds, toIds); fromIds->Release(); // necessary to avoid double delete. // unmark global ids, if any since we don't really preserve input global // ids. outputDSA->SetActiveAttribute(-1, vtkDataSetAttributes::GLOBALIDS); if (arrayName) { originalIds->SetName(arrayName); outputDSA->AddArray(originalIds); } outputDSA->Squeeze(); }; // This map is used to avoid inserting same point multiple times in the // output. Since points are looked up using their ids, we simply use that to // uniquify points and don't need any locator. // key: input point id, value: output point id. std::unordered_map pointMap; vtkNew originalPtIds; originalPtIds->Allocate(input->GetNumberOfPoints()); vtkNew originalCellIds; originalCellIds->Allocate(input->GetNumberOfCells()); auto addFaceToOutput = [&](const std::array& ptIds, vtkIdType inCellId) { vtkIdType outPtIds[5]; for (int cc = 0; cc < 4; ++cc) { auto iter = pointMap.find(ptIds[cc]); if (iter != pointMap.end()) { outPtIds[cc] = iter->second; } else { double pt[3]; input->GetPoint(ptIds[cc], pt); outPtIds[cc] = points->InsertNextPoint(pt); pointMap.insert(std::make_pair(ptIds[cc], outPtIds[cc])); originalPtIds->InsertNextValue(ptIds[cc]); } } outPtIds[4] = outPtIds[0]; output->InsertNextCell(VTK_POLYGON, 5, outPtIds); originalCellIds->InsertNextValue(inCellId); }; for (int axis = 0; axis < 3; ++axis) { const int iAxis = (axis + 1) % 3; const int jAxis = (axis + 2) % 3; const int extent[6] = { inExtent[2 * iAxis], inExtent[2 * iAxis + 1], inExtent[2 * jAxis], inExtent[2 * jAxis + 1], inExtent[2 * axis], inExtent[2 * axis + 1] }; // iterate over cells for (int i = extent[0]; i < extent[1]; ++i) { int ijk[3]; ijk[iAxis] = i; for (int j = extent[2]; j < extent[3]; ++j) { ijk[jAxis] = j; bool minFace = true; for (int k = extent[4]; k < extent[5]; ++k) { ijk[axis] = k; const auto cellId = vtkStructuredData::ComputeCellIdForExtent(inExtent, ijk); const bool cellVisible = input->IsCellVisible(cellId); if ((minFace && cellVisible) || (!minFace && !cellVisible)) { ijk[axis] = minFace ? k : (k - 1); // this ensure correct cell-data is picked for the face. addFaceToOutput(getFace(ijk, axis, /*minFace=*/minFace), vtkStructuredData::ComputeCellIdForExtent(inExtent, ijk)); if (self->GetFastMode()) { // 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[5] - 1; reverseK >= k; --reverseK) { ijk[axis] = reverseK; const auto reverseCellId = vtkStructuredData::ComputeCellIdForExtent(inExtent, ijk); if (input->IsCellVisible(reverseCellId)) { addFaceToOutput(getFace(ijk, axis, /*minFace=*/false), 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 && !self->GetFastMode()) { const auto cellId = vtkStructuredData::ComputeCellIdForExtent(inExtent, ijk); ijk[axis] = extent[5] - 1; addFaceToOutput(getFace(ijk, axis, false), cellId); } } } } // Now copy cell and point data. We want to copy global ids, however we don't // want them to be flagged as global ids. So we do this. passData(originalPtIds, input->GetPointData(), output->GetPointData(), self->GetPassThroughPointIds() ? self->GetOriginalPointIdsName() : nullptr); passData(originalCellIds, input->GetCellData(), output->GetCellData(), self->GetPassThroughCellIds() ? self->GetOriginalCellIdsName() : nullptr); output->Squeeze(); return true; } } VTK_ABI_NAMESPACE_BEGIN class vtkDataSetSurfaceFilter::vtkEdgeInterpolationMap { public: void AddEdge(vtkIdType endpoint1, vtkIdType endpoint2, vtkIdType midpoint) { if (endpoint1 > endpoint2) std::swap(endpoint1, endpoint2); Map.insert(std::make_pair(std::make_pair(endpoint1, endpoint2), midpoint)); } vtkIdType FindEdge(vtkIdType endpoint1, vtkIdType endpoint2) { if (endpoint1 == endpoint2) { return endpoint1; } if (endpoint1 > endpoint2) std::swap(endpoint1, endpoint2); MapType::iterator iter = Map.find(std::make_pair(endpoint1, endpoint2)); if (iter != Map.end()) { return iter->second; } else { return -1; } } void clear() { Map.clear(); } protected: struct HashFunction { public: size_t operator()(std::pair edge) const { return static_cast(edge.first + edge.second); } }; typedef std::unordered_map, vtkIdType, HashFunction> MapType; MapType Map; }; vtkObjectFactoryNewMacro(vtkDataSetSurfaceFilter); //------------------------------------------------------------------------------ vtkDataSetSurfaceFilter::vtkDataSetSurfaceFilter() { this->QuadHash = nullptr; this->PointMap = nullptr; this->EdgeMap = nullptr; this->QuadHashLength = 0; this->NumberOfNewCells = 0; // Quad allocation stuff. this->FastGeomQuadArrayLength = 0; this->NumberOfFastGeomQuadArrays = 0; this->FastGeomQuadArrays = nullptr; this->NextArrayIndex = 0; this->NextQuadIndex = 0; this->FastMode = false; this->PieceInvariant = 0; this->PassThroughCellIds = 0; this->PassThroughPointIds = 0; this->OriginalCellIds = nullptr; this->OriginalPointIds = nullptr; this->OriginalCellIdsName = nullptr; this->OriginalPointIdsName = nullptr; this->NonlinearSubdivisionLevel = 1; this->MatchBoundariesIgnoringCellOrder = 0; this->AllowInterpolation = true; this->Delegation = false; } //------------------------------------------------------------------------------ vtkDataSetSurfaceFilter::~vtkDataSetSurfaceFilter() { this->SetOriginalCellIdsName(nullptr); this->SetOriginalPointIdsName(nullptr); if (this->OriginalPointIds) { this->OriginalPointIds->Delete(); this->OriginalPointIds = nullptr; } if (this->OriginalCellIds) { this->OriginalCellIds->Delete(); this->OriginalCellIds = nullptr; } } //------------------------------------------------------------------------------ int vtkDataSetSurfaceFilter::RequestData(vtkInformation* vtkNotUsed(request), vtkInformationVector** inputVector, vtkInformationVector* outputVector) { // get the info objects vtkInformation* inInfo = inputVector[0]->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 numCells = input->GetNumberOfCells(); int wholeExt[6] = { 0, -1, 0, -1, 0, -1 }; if (input->CheckAttributes()) { return 1; } if (numCells == 0) { vtkDebugMacro(<< "Number of cells is zero, no data to process."); return 1; } if (input->GetExtentType() == VTK_3D_EXTENT) { const int* wholeExt32; wholeExt32 = inInfo->Get(vtkStreamingDemandDrivenPipeline::WHOLE_EXTENT()); std::copy(wholeExt32, wholeExt32 + 6, wholeExt); } switch (input->GetDataObjectType()) { case VTK_UNSTRUCTURED_GRID: case VTK_UNSTRUCTURED_GRID_BASE: { this->UnstructuredGridExecute(input, output); output->CheckAttributes(); return 1; } case VTK_RECTILINEAR_GRID: { auto rg = vtkRectilinearGrid::SafeDownCast(input); return this->StructuredExecute(input, output, rg->GetExtent(), wholeExt); } case VTK_STRUCTURED_GRID: { auto sg = vtkStructuredGrid::SafeDownCast(input); return this->StructuredExecute(input, output, sg->GetExtent(), wholeExt); } case VTK_UNIFORM_GRID: case VTK_STRUCTURED_POINTS: case VTK_IMAGE_DATA: { auto img = vtkImageData::SafeDownCast(input); return this->StructuredExecute(input, output, img->GetExtent(), wholeExt); } case VTK_POLY_DATA: { vtkPolyData* inPd = vtkPolyData::SafeDownCast(input); output->ShallowCopy(inPd); if (this->PassThroughCellIds) { // make a 1:1 mapping this->OriginalCellIds = vtkIdTypeArray::New(); this->OriginalCellIds->SetName(this->GetOriginalCellIdsName()); this->OriginalCellIds->SetNumberOfComponents(1); vtkCellData* outputCD = output->GetCellData(); outputCD->AddArray(this->OriginalCellIds); vtkIdType numTup = output->GetNumberOfCells(); this->OriginalCellIds->SetNumberOfValues(numTup); for (vtkIdType cId = 0; cId < numTup; cId++) { if (this->CheckAbort()) { break; } this->OriginalCellIds->SetValue(cId, cId); } this->OriginalCellIds->Delete(); this->OriginalCellIds = nullptr; } if (this->PassThroughPointIds) { // make a 1:1 mapping this->OriginalPointIds = vtkIdTypeArray::New(); this->OriginalPointIds->SetName(this->GetOriginalPointIdsName()); this->OriginalPointIds->SetNumberOfComponents(1); vtkPointData* outputPD = output->GetPointData(); outputPD->AddArray(this->OriginalPointIds); vtkIdType numTup = output->GetNumberOfPoints(); this->OriginalPointIds->SetNumberOfValues(numTup); for (vtkIdType cId = 0; cId < numTup; cId++) { if (this->CheckAbort()) { break; } this->OriginalPointIds->SetValue(cId, cId); } this->OriginalPointIds->Delete(); this->OriginalPointIds = nullptr; } return 1; } default: return this->DataSetExecute(input, output); } } //------------------------------------------------------------------------------ void vtkDataSetSurfaceFilter::EstimateStructuredDataArraySizes( vtkIdType* ext, vtkIdType* wholeExt, vtkIdType& numPoints, vtkIdType& numCells) { // Sanity Checks assert(ext != nullptr); assert(wholeExt != nullptr); numPoints = numCells = 0; // xMin face if (ext[0] == wholeExt[0] && ext[2] != ext[3] && ext[4] != ext[5] && ext[0] != ext[1]) { numCells += (ext[3] - ext[2]) * (ext[5] - ext[4]); numPoints += (ext[3] - ext[2] + 1) * (ext[5] - ext[4] + 1); } // xMax face if (ext[1] == wholeExt[1] && ext[2] != ext[3] && ext[4] != ext[5]) { numCells += (ext[3] - ext[2]) * (ext[5] - ext[4]); numPoints += (ext[3] - ext[2] + 1) * (ext[5] - ext[4] + 1); } // yMin face if (ext[2] == wholeExt[2] && ext[0] != ext[1] && ext[4] != ext[5] && ext[2] != ext[3]) { numCells += (ext[1] - ext[0]) * (ext[5] - ext[4]); numPoints += (ext[1] - ext[0] + 1) * (ext[5] - ext[4] + 1); } // yMax face if (ext[3] == wholeExt[3] && ext[0] != ext[1] && ext[4] != ext[5]) { numCells += (ext[1] - ext[0]) * (ext[5] - ext[4]); numPoints += (ext[1] - ext[0] + 1) * (ext[5] - ext[4] + 1); } // zMin face if (ext[4] == wholeExt[4] && ext[0] != ext[1] && ext[2] != ext[3] && ext[4] != ext[5]) { numCells += (ext[1] - ext[0]) * (ext[3] - ext[2]); numPoints += (ext[1] - ext[0] + 1) * (ext[3] - ext[2] + 1); } // zMax face if (ext[5] == wholeExt[5] && ext[0] != ext[1] && ext[2] != ext[3]) { numCells += (ext[1] - ext[0]) * (ext[3] - ext[2]); numPoints += (ext[1] - ext[0] + 1) * (ext[3] - ext[2] + 1); } } //------------------------------------------------------------------------------ int vtkDataSetSurfaceFilter::UniformGridExecute( vtkDataSet* input, vtkPolyData* output, vtkIdType* ext, vtkIdType* wholeExt, bool extractface[6]) { vtkIdType numPoints, numCells; vtkPoints* gridPnts = vtkPoints::New(); vtkCellArray* gridCells = vtkCellArray::New(); int originalPassThroughCellIds = this->PassThroughCellIds; // Lets figure out the max number of cells and points we are going to have numPoints = numCells = 0; this->EstimateStructuredDataArraySizes(ext, wholeExt, numPoints, numCells); gridPnts->Allocate(numPoints); gridCells->AllocateEstimate(numCells, 1); output->SetPoints(gridPnts); gridPnts->Delete(); output->SetPolys(gridCells); gridCells->Delete(); // Allocate attributes for copying. output->GetPointData()->CopyGlobalIdsOn(); output->GetPointData()->CopyAllocate(input->GetPointData(), numPoints); output->GetCellData()->CopyGlobalIdsOn(); output->GetCellData()->CopyAllocate(input->GetCellData(), numCells); if (this->PassThroughCellIds) { this->OriginalCellIds = vtkIdTypeArray::New(); this->OriginalCellIds->SetName(this->GetOriginalCellIdsName()); this->OriginalCellIds->SetNumberOfComponents(1); this->OriginalCellIds->Allocate(numCells); output->GetCellData()->AddArray(this->OriginalCellIds); } if (this->PassThroughPointIds) { this->OriginalPointIds = vtkIdTypeArray::New(); this->OriginalPointIds->SetName(this->GetOriginalPointIdsName()); this->OriginalPointIds->SetNumberOfComponents(1); this->OriginalPointIds->Allocate(numPoints); output->GetPointData()->AddArray(this->OriginalPointIds); } // xMin face if (extractface[0]) this->ExecuteFaceQuads(input, output, 0, ext, 0, 1, 2, wholeExt, true); // xMax face if (extractface[1]) this->ExecuteFaceQuads(input, output, 1, ext, 0, 2, 1, wholeExt, true); // yMin face if (extractface[2]) this->ExecuteFaceQuads(input, output, 0, ext, 1, 2, 0, wholeExt, true); // yMax face if (extractface[3]) this->ExecuteFaceQuads(input, output, 1, ext, 1, 0, 2, wholeExt, true); // zMin face if (extractface[4]) this->ExecuteFaceQuads(input, output, 0, ext, 2, 0, 1, wholeExt, true); // zMax face if (extractface[5]) this->ExecuteFaceQuads(input, output, 1, ext, 2, 1, 0, wholeExt, true); output->Squeeze(); this->PassThroughCellIds = originalPassThroughCellIds; if (this->OriginalPointIds) { this->OriginalPointIds->Delete(); this->OriginalPointIds = nullptr; } if (this->OriginalCellIds) { this->OriginalCellIds->Delete(); this->OriginalCellIds = nullptr; } return 1; } //------------------------------------------------------------------------------ int vtkDataSetSurfaceFilter::StructuredExecute( vtkDataSet* input, vtkPolyData* output, vtkIdType* ext, vtkIdType* wholeExt) { if (this->Delegation) { vtkLogScopeF( TRACE, "StructuredExecute Using GeometryFilter (fastMode=%d)", (int)this->GetFastMode()); vtkNew geometryFilter; vtkGeometryFilterHelper::CopyFilterParams(this, geometryFilter); int wholeExtent[6]; std::copy(wholeExt, wholeExt + 6, wholeExtent); return geometryFilter->StructuredExecute(input, output, wholeExtent, nullptr, nullptr); } if (::StructuredExecuteWithBlanking(vtkImageData::SafeDownCast(input), output, this) || ::StructuredExecuteWithBlanking(vtkStructuredGrid::SafeDownCast(input), output, this) || ::StructuredExecuteWithBlanking(vtkRectilinearGrid::SafeDownCast(input), output, this)) { return 1; } return this->StructuredExecuteNoBlanking(input, output, ext, wholeExt); } //------------------------------------------------------------------------------ // It is a pain that structured data sets do not share a common super class // other than data set, and data set does not allow access to extent! int vtkDataSetSurfaceFilter::StructuredExecuteNoBlanking( vtkDataSet* input, vtkPolyData* output, vtkIdType* ext, vtkIdType* wholeExt) { vtkRectilinearGrid* rgrid = vtkRectilinearGrid::SafeDownCast(input); vtkStructuredGrid* sgrid = vtkStructuredGrid::SafeDownCast(input); if (rgrid || sgrid) { // Fetch the grid dimension int iext[6]; std::copy(ext, ext + 6, iext); int dimension = vtkStructuredData::GetDataDimension(iext); if (dimension == 1) { // Use specialized filter in case of 1D grid if (rgrid) { vtkNew filter; filter->SetInputData(input); filter->SetExtent(ext[0], ext[1], ext[2], ext[3], ext[4], ext[5]); filter->SetContainerAlgorithm(this); filter->Update(); output->ShallowCopy(filter->GetOutput()); return 1; } else if (sgrid) { vtkNew filter; filter->SetInputData(input); filter->SetExtent(ext[0], ext[1], ext[2], ext[3], ext[4], ext[5]); filter->SetContainerAlgorithm(this); filter->Update(); output->ShallowCopy(filter->GetOutput()); return 1; } } } vtkIdType numPoints, cellArraySize; vtkCellArray* outPolys; vtkPoints* outPoints; // Cell Array Size is a pretty good estimate. // Lets figure out how many cells and points we are going to have. // It may be overkill computing the exact amount, but we can do it, so ... cellArraySize = numPoints = 0; // xMin face if (ext[0] == wholeExt[0] && ext[2] != ext[3] && ext[4] != ext[5] && ext[0] != ext[1]) { cellArraySize += (ext[3] - ext[2]) * (ext[5] - ext[4]); numPoints += (ext[3] - ext[2] + 1) * (ext[5] - ext[4] + 1); } // xMax face if (ext[1] == wholeExt[1] && ext[2] != ext[3] && ext[4] != ext[5]) { cellArraySize += (ext[3] - ext[2]) * (ext[5] - ext[4]); numPoints += (ext[3] - ext[2] + 1) * (ext[5] - ext[4] + 1); } // yMin face if (ext[2] == wholeExt[2] && ext[0] != ext[1] && ext[4] != ext[5] && ext[2] != ext[3]) { cellArraySize += (ext[1] - ext[0]) * (ext[5] - ext[4]); numPoints += (ext[1] - ext[0] + 1) * (ext[5] - ext[4] + 1); } // yMax face if (ext[3] == wholeExt[3] && ext[0] != ext[1] && ext[4] != ext[5]) { cellArraySize += (ext[1] - ext[0]) * (ext[5] - ext[4]); numPoints += (ext[1] - ext[0] + 1) * (ext[5] - ext[4] + 1); } // zMin face if (ext[4] == wholeExt[4] && ext[0] != ext[1] && ext[2] != ext[3] && ext[4] != ext[5]) { cellArraySize += (ext[1] - ext[0]) * (ext[3] - ext[2]); numPoints += (ext[1] - ext[0] + 1) * (ext[3] - ext[2] + 1); } // zMax face if (ext[5] == wholeExt[5] && ext[0] != ext[1] && ext[2] != ext[3]) { cellArraySize += (ext[1] - ext[0]) * (ext[3] - ext[2]); numPoints += (ext[1] - ext[0] + 1) * (ext[3] - ext[2] + 1); } int originalPassThroughCellIds = this->PassThroughCellIds; outPolys = vtkCellArray::New(); outPolys->AllocateEstimate(cellArraySize, 4); output->SetPolys(outPolys); outPolys->Delete(); outPoints = vtkPoints::New(); int dataType; switch (input->GetDataObjectType()) { case VTK_RECTILINEAR_GRID: { dataType = rgrid->GetXCoordinates()->GetDataType(); break; } case VTK_STRUCTURED_GRID: { dataType = sgrid->GetPoints()->GetDataType(); break; } case VTK_UNIFORM_GRID: case VTK_STRUCTURED_POINTS: case VTK_IMAGE_DATA: { dataType = VTK_DOUBLE; break; } default: dataType = VTK_DOUBLE; vtkErrorMacro("Invalid data set type: " << input->GetDataObjectType()); outPoints->Delete(); return 1; } outPoints->SetDataType(dataType); outPoints->Allocate(numPoints); output->SetPoints(outPoints); outPoints->Delete(); // Allocate attributes for copying. output->GetPointData()->CopyGlobalIdsOn(); output->GetPointData()->CopyAllocate(input->GetPointData(), numPoints); output->GetCellData()->CopyGlobalIdsOn(); output->GetCellData()->CopyAllocate(input->GetCellData(), cellArraySize); if (this->PassThroughCellIds) { this->OriginalCellIds = vtkIdTypeArray::New(); this->OriginalCellIds->SetName(this->GetOriginalCellIdsName()); this->OriginalCellIds->SetNumberOfComponents(1); this->OriginalCellIds->Allocate(cellArraySize); output->GetCellData()->AddArray(this->OriginalCellIds); } if (this->PassThroughPointIds) { this->OriginalPointIds = vtkIdTypeArray::New(); this->OriginalPointIds->SetName(this->GetOriginalPointIdsName()); this->OriginalPointIds->SetNumberOfComponents(1); this->OriginalPointIds->Allocate(numPoints); output->GetPointData()->AddArray(this->OriginalPointIds); } // xMin face this->ExecuteFaceQuads(input, output, 0, ext, 0, 1, 2, wholeExt); // xMax face this->ExecuteFaceQuads(input, output, 1, ext, 0, 2, 1, wholeExt); // yMin face this->ExecuteFaceQuads(input, output, 0, ext, 1, 2, 0, wholeExt); // yMax face this->ExecuteFaceQuads(input, output, 1, ext, 1, 0, 2, wholeExt); // zMin face this->ExecuteFaceQuads(input, output, 0, ext, 2, 0, 1, wholeExt); // zMax face this->ExecuteFaceQuads(input, output, 1, ext, 2, 1, 0, wholeExt); output->Squeeze(); if (this->OriginalCellIds != nullptr) { this->OriginalCellIds->Delete(); this->OriginalCellIds = nullptr; } if (this->OriginalPointIds != nullptr) { this->OriginalPointIds->Delete(); this->OriginalPointIds = nullptr; } this->PassThroughCellIds = originalPassThroughCellIds; this->CheckAbort(); return 1; } //------------------------------------------------------------------------------ void vtkDataSetSurfaceFilter::ExecuteFaceQuads(vtkDataSet* input, vtkPolyData* output, int maxFlag, vtkIdType* ext, int aAxis, int bAxis, int cAxis, vtkIdType* wholeExt, bool checkVisibility) { vtkPoints* outPts; vtkCellArray* outPolys; vtkPointData *inPD, *outPD; vtkCellData *inCD, *outCD; vtkIdType pInc[3]; vtkIdType qInc[3]; vtkIdType cOutInc; double pt[3]; vtkIdType inStartPtId; vtkIdType inStartCellId; vtkIdType outStartPtId; vtkIdType outPtId; vtkIdType inId, outId; vtkIdType ib, ic; int aA2, bA2, cA2; outPts = output->GetPoints(); outPD = output->GetPointData(); inPD = input->GetPointData(); outCD = output->GetCellData(); inCD = input->GetCellData(); pInc[0] = 1; pInc[1] = (ext[1] - ext[0] + 1); pInc[2] = (ext[3] - ext[2] + 1) * pInc[1]; // quad increments (cell increments, but cInc could be confused with c axis). qInc[0] = 1; qInc[1] = ext[1] - ext[0]; // The conditions are for when we have one or more degenerate axes (2d or 1d cells). if (qInc[1] == 0) { qInc[1] = 1; } qInc[2] = (ext[3] - ext[2]) * qInc[1]; if (qInc[2] == 0) { qInc[2] = qInc[1]; } // Temporary variables to avoid many multiplications. aA2 = aAxis * 2; bA2 = bAxis * 2; cA2 = cAxis * 2; // We might as well put the test for this face here. if (ext[bA2] == ext[bA2 + 1] || ext[cA2] == ext[cA2 + 1]) { return; } if (maxFlag) { if (ext[aA2 + 1] < wholeExt[aA2 + 1]) { return; } } else { // min faces have a slightly different condition to avoid coincident faces. if (ext[aA2] == ext[aA2 + 1] || ext[aA2] > wholeExt[aA2]) { return; } } // Assuming no ghost cells ... inStartPtId = inStartCellId = 0; // I put this confusing conditional to fix a regression test. // If we are creating a maximum face, then we indeed have to offset // the input cell Ids. However, vtkGeometryFilter created a 2d // image as a max face, but the cells are copied as a min face (no // offset). Hence maxFlag = 1 and there should be no offset. if (maxFlag && ext[aA2] < ext[1 + aA2]) { inStartPtId = pInc[aAxis] * (ext[aA2 + 1] - ext[aA2]); inStartCellId = qInc[aAxis] * (ext[aA2 + 1] - ext[aA2] - 1); } vtkUniformGrid* grid = static_cast(input); assert(grid != nullptr); outStartPtId = outPts->GetNumberOfPoints(); // Make the points for this face. for (ic = ext[cA2]; ic <= ext[cA2 + 1]; ++ic) { for (ib = ext[bA2]; ib <= ext[bA2 + 1]; ++ib) { inId = inStartPtId + (ib - ext[bA2]) * pInc[bAxis] + (ic - ext[cA2]) * pInc[cAxis]; input->GetPoint(inId, pt); outId = outPts->InsertNextPoint(pt); // Copy point data. outPD->CopyData(inPD, inId, outId); this->RecordOrigPointId(outId, inId); } } // Do the cells. cOutInc = ext[bA2 + 1] - ext[bA2] + 1; outPolys = output->GetPolys(); // Old method for creating quads (needed for cell data.). for (ic = ext[cA2]; ic < ext[cA2 + 1]; ++ic) { for (ib = ext[bA2]; ib < ext[bA2 + 1]; ++ib) { outPtId = outStartPtId + (ib - ext[bA2]) + (ic - ext[cA2]) * cOutInc; inId = inStartCellId + (ib - ext[bA2]) * qInc[bAxis] + (ic - ext[cA2]) * qInc[cAxis]; if (checkVisibility && grid->IsCellVisible(inId)) { outId = outPolys->InsertNextCell(4); outPolys->InsertCellPoint(outPtId); outPolys->InsertCellPoint(outPtId + cOutInc); outPolys->InsertCellPoint(outPtId + cOutInc + 1); outPolys->InsertCellPoint(outPtId + 1); // Copy cell data. outCD->CopyData(inCD, inId, outId); this->RecordOrigCellId(outId, inId); } } } } //------------------------------------------------------------------------------ void vtkDataSetSurfaceFilter::ExecuteFaceQuads(vtkDataSet* input, vtkPolyData* output, int maxFlag, vtkIdType* ext, int aAxis, int bAxis, int cAxis, vtkIdType* wholeExt) { vtkPoints* outPts; vtkCellArray* outPolys; vtkPointData *inPD, *outPD; vtkCellData *inCD, *outCD; vtkIdType pInc[3]; vtkIdType qInc[3]; vtkIdType cOutInc; double pt[3]; vtkIdType inStartPtId; vtkIdType inStartCellId; vtkIdType outStartPtId; vtkIdType outPtId; vtkIdType inId, outId; vtkIdType ib, ic; int aA2, bA2, cA2; outPts = output->GetPoints(); outPD = output->GetPointData(); inPD = input->GetPointData(); outCD = output->GetCellData(); inCD = input->GetCellData(); pInc[0] = 1; pInc[1] = (ext[1] - ext[0] + 1); pInc[2] = (ext[3] - ext[2] + 1) * pInc[1]; // quad increments (cell increments, but cInc could be confused with c axis). qInc[0] = 1; qInc[1] = ext[1] - ext[0]; // The conditions are for when we have one or more degenerate axes (2d or 1d cells). if (qInc[1] == 0) { qInc[1] = 1; } qInc[2] = (ext[3] - ext[2]) * qInc[1]; if (qInc[2] == 0) { qInc[2] = qInc[1]; } // Temporary variables to avoid many multiplications. aA2 = aAxis * 2; bA2 = bAxis * 2; cA2 = cAxis * 2; // We might as well put the test for this face here. if (ext[bA2] == ext[bA2 + 1] || ext[cA2] == ext[cA2 + 1]) { return; } if (maxFlag) { if (ext[aA2 + 1] < wholeExt[aA2 + 1]) { return; } } else { // min faces have a slightly different condition to avoid coincident faces. if (ext[aA2] == ext[aA2 + 1] || ext[aA2] > wholeExt[aA2]) { return; } } // Assuming no ghost cells ... inStartPtId = inStartCellId = 0; // I put this confusing conditional to fix a regression test. // If we are creating a maximum face, then we indeed have to offset // the input cell Ids. However, vtkGeometryFilter created a 2d // image as a max face, but the cells are copied as a min face (no // offset). Hence maxFlag = 1 and there should be no offset. if (maxFlag && ext[aA2] < ext[1 + aA2]) { inStartPtId = pInc[aAxis] * (ext[aA2 + 1] - ext[aA2]); inStartCellId = qInc[aAxis] * (ext[aA2 + 1] - ext[aA2] - 1); } outStartPtId = outPts->GetNumberOfPoints(); // Make the points for this face. for (ic = ext[cA2]; ic <= ext[cA2 + 1]; ++ic) { for (ib = ext[bA2]; ib <= ext[bA2 + 1]; ++ib) { inId = inStartPtId + (ib - ext[bA2]) * pInc[bAxis] + (ic - ext[cA2]) * pInc[cAxis]; input->GetPoint(inId, pt); outId = outPts->InsertNextPoint(pt); // Copy point data. outPD->CopyData(inPD, inId, outId); this->RecordOrigPointId(outId, inId); } } // Do the cells. cOutInc = ext[bA2 + 1] - ext[bA2] + 1; outPolys = output->GetPolys(); // Old method for creating quads (needed for cell data.). for (ic = ext[cA2]; ic < ext[cA2 + 1]; ++ic) { for (ib = ext[bA2]; ib < ext[bA2 + 1]; ++ib) { outPtId = outStartPtId + (ib - ext[bA2]) + (ic - ext[cA2]) * cOutInc; inId = inStartCellId + (ib - ext[bA2]) * qInc[bAxis] + (ic - ext[cA2]) * qInc[cAxis]; outId = outPolys->InsertNextCell(4); outPolys->InsertCellPoint(outPtId); outPolys->InsertCellPoint(outPtId + cOutInc); outPolys->InsertCellPoint(outPtId + cOutInc + 1); outPolys->InsertCellPoint(outPtId + 1); // Copy cell data. outCD->CopyData(inCD, inId, outId); this->RecordOrigCellId(outId, inId); } } } //------------------------------------------------------------------------------ int vtkDataSetSurfaceFilter::DataSetExecute(vtkDataSet* input, vtkPolyData* output) { vtkIdType cellId, newCellId; int i, j; vtkIdType numPts = input->GetNumberOfPoints(); vtkIdType numCells = input->GetNumberOfCells(); vtkCell* face; double x[3]; vtkIdList* cellIds; vtkIdList* pts; vtkPoints* newPts; vtkIdType ptId, pt; int npts; vtkPointData* pd = input->GetPointData(); vtkCellData* cd = input->GetCellData(); vtkPointData* outputPD = output->GetPointData(); vtkCellData* outputCD = output->GetCellData(); if (numCells == 0) { vtkDebugMacro(<< "Number of cells is zero, no data to process."); return 1; } if (this->PassThroughCellIds) { this->OriginalCellIds = vtkIdTypeArray::New(); this->OriginalCellIds->SetName(this->GetOriginalCellIdsName()); this->OriginalCellIds->SetNumberOfComponents(1); this->OriginalCellIds->Allocate(numCells); outputCD->AddArray(this->OriginalCellIds); } if (this->PassThroughPointIds) { this->OriginalPointIds = vtkIdTypeArray::New(); this->OriginalPointIds->SetName(this->GetOriginalPointIdsName()); this->OriginalPointIds->SetNumberOfComponents(1); this->OriginalPointIds->Allocate(numPts); outputPD->AddArray(this->OriginalPointIds); } cellIds = vtkIdList::New(); pts = vtkIdList::New(); vtkDebugMacro(<< "Executing geometry filter"); // Allocate // newPts = vtkPoints::New(); // we don't know what type of data the input points are so // we keep the output points to have the default type (float) newPts->Allocate(numPts, numPts / 2); output->AllocateEstimate(numCells, 3); outputPD->CopyGlobalIdsOn(); outputPD->CopyAllocate(pd, numPts, numPts / 2); outputCD->CopyGlobalIdsOn(); outputCD->CopyAllocate(cd, numCells, numCells / 2); // Traverse cells to extract geometry // bool abort = false; vtkIdType progressInterval = numCells / 20 + 1; for (cellId = 0; cellId < numCells && !abort; cellId++) { // Progress and abort method support if (!(cellId % progressInterval)) { vtkDebugMacro(<< "Process cell #" << cellId); this->UpdateProgress(static_cast(cellId) / numCells); abort = this->CheckAbort(); } vtkCell* cell = input->GetCell(cellId); switch (cell->GetCellDimension()) { // create new points and then cell case 0: case 1: case 2: { int type = cell->GetCellType(); if (type == VTK_EMPTY_CELL) { // Empty cells are not supported by vtkPolyData break; } npts = cell->GetNumberOfPoints(); pts->Reset(); for (i = 0; i < npts; i++) { ptId = cell->GetPointId(i); input->GetPoint(ptId, x); pt = newPts->InsertNextPoint(x); outputPD->CopyData(pd, ptId, pt); this->RecordOrigPointId(pt, ptId); pts->InsertId(i, pt); } newCellId = output->InsertNextCell(type, pts); if (newCellId > 0) { outputCD->CopyData(cd, cellId, newCellId); this->RecordOrigCellId(newCellId, cellId); } break; } case 3: for (j = 0; j < cell->GetNumberOfFaces(); j++) { face = cell->GetFace(j); input->GetCellNeighbors(cellId, face->PointIds, cellIds); bool noNeighbors = cellIds->GetNumberOfIds() <= 0; if (noNeighbors) { npts = face->GetNumberOfPoints(); pts->Reset(); for (i = 0; i < npts; i++) { ptId = face->GetPointId(i); input->GetPoint(ptId, x); pt = newPts->InsertNextPoint(x); outputPD->CopyData(pd, ptId, pt); this->RecordOrigPointId(pt, ptId); pts->InsertId(i, pt); } newCellId = output->InsertNextCell(face->GetCellType(), pts); if (newCellId > 0) { outputCD->CopyData(cd, cellId, newCellId); this->RecordOrigCellId(newCellId, cellId); } } } break; } // switch } // for all cells vtkDebugMacro(<< "Extracted " << newPts->GetNumberOfPoints() << " points," << output->GetNumberOfCells() << " cells."); // Update ourselves and release memory // output->SetPoints(newPts); newPts->Delete(); if (this->OriginalCellIds) { this->OriginalCellIds->Delete(); this->OriginalCellIds = nullptr; } if (this->OriginalPointIds) { this->OriginalPointIds->Delete(); this->OriginalPointIds = nullptr; } // free storage output->Squeeze(); cellIds->Delete(); pts->Delete(); return 1; } //------------------------------------------------------------------------------ int vtkDataSetSurfaceFilter::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; } //------------------------------------------------------------------------------ int vtkDataSetSurfaceFilter::FillInputPortInformation(int, vtkInformation* info) { info->Set(vtkAlgorithm::INPUT_REQUIRED_DATA_TYPE(), "vtkDataSet"); return 1; } //------------------------------------------------------------------------------ void vtkDataSetSurfaceFilter::PrintSelf(ostream& os, vtkIndent indent) { this->Superclass::PrintSelf(os, indent); 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; os << indent << "FastMode: " << this->GetFastMode() << endl; os << indent << "AllowInterpolation: " << this->GetAllowInterpolation() << endl; os << indent << "Delegation: " << this->GetDelegation() << endl; } //======================================================================== // Coordinate the delegation process. int vtkDataSetSurfaceFilter::UnstructuredGridExecute(vtkDataSet* dataSetInput, vtkPolyData* output) { switch (dataSetInput->GetDataObjectType()) { case VTK_UNSTRUCTURED_GRID: return this->UnstructuredGridExecute(dataSetInput, output, nullptr); case VTK_UNSTRUCTURED_GRID_BASE: return this->UnstructuredGridBaseExecute(dataSetInput, output); default: return 0; } } //------------------------------------------------------------------------------ // This method may delegate to vtkGeometryFilter. The "info", if passed in, // provides information about the unstructured grid. This avoids the possibility of // repeated evaluations, and back and forth delegation, as vtkGeometryFilter and // vtkDataSetSurfaceFilter coordinate their efforts. int vtkDataSetSurfaceFilter::UnstructuredGridExecute( vtkDataSet* dataSetInput, vtkPolyData* output, vtkGeometryFilterHelper* info) { vtkUnstructuredGrid* input = vtkUnstructuredGrid::SafeDownCast(dataSetInput); // 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 && this->Delegation); bool info_owned = false; if (info == nullptr) { info = vtkGeometryFilterHelper::CharacterizeUnstructuredGrid(input); info_owned = true; } bool handleSubdivision = (!info->IsLinear); // Before we start doing anything interesting, check if we need handle // non-linear cells using sub-division. if (info->IsLinear && mayDelegate) { vtkNew gf; vtkGeometryFilterHelper::CopyFilterParams(this, gf.Get()); gf->UnstructuredGridExecute(dataSetInput, output, info, nullptr); delete info; return 1; } if (info_owned) { delete info; } // If here, the data is gnarly and this filter will process it. return this->UnstructuredGridExecuteInternal(input, output, handleSubdivision); } //------------------------------------------------------------------------------ // Unoptimized version of UnstructuredGridExecute for non vtkUnstructuredGrid instances int vtkDataSetSurfaceFilter::UnstructuredGridBaseExecute( vtkDataSet* dataSetInput, vtkPolyData* output) { vtkUnstructuredGridBase* input = vtkUnstructuredGridBase::SafeDownCast(dataSetInput); // Before we start doing anything interesting, check if we need handle // non-linear cells using sub-division. bool handleSubdivision = false; if (this->NonlinearSubdivisionLevel >= 1) { // Check to see if the data actually has nonlinear cells. Handling // nonlinear cells adds unnecessary work if we only have linear cells. vtkIdType numCells = input->GetNumberOfCells(); if (input->IsHomogeneous()) { if (numCells >= 1) { handleSubdivision = !vtkCellTypeUtilities::IsLinear(input->GetCellType(0)); } } else { for (vtkIdType cellId = 0; cellId < numCells; ++cellId) { if (!vtkCellTypeUtilities::IsLinear(input->GetCellType(cellId))) { handleSubdivision = true; break; } } } } return this->UnstructuredGridExecuteInternal(input, output, handleSubdivision); } //======================================================================== // Tris are now degenerate quads so we only need one hash table. // We might want to change the method names from QuadHash to just Hash. int vtkDataSetSurfaceFilter::UnstructuredGridExecuteInternal( vtkUnstructuredGridBase* input, vtkPolyData* output, bool handleSubdivision) { vtkSmartPointer tempInput; if (handleSubdivision) { // Since this filter only properly subdivides 2D cells past // level 1, we convert 3D cells to 2D by using // vtkUnstructuredGridGeometryFilter. vtkNew uggf; vtkNew clone; clone->ShallowCopy(input); uggf->SetInputData(clone); uggf->SetPassThroughCellIds(this->PassThroughCellIds); uggf->SetOriginalCellIdsName(this->GetOriginalCellIdsName()); uggf->SetPassThroughPointIds(this->PassThroughPointIds); uggf->SetMatchBoundariesIgnoringCellOrder(this->MatchBoundariesIgnoringCellOrder); uggf->SetOriginalPointIdsName(this->GetOriginalPointIdsName()); uggf->DuplicateGhostCellClippingOff(); uggf->SetContainerAlgorithm(this); // Disable point merging as it may prevent the correct visualization // of non-continuous attributes. uggf->MergingOff(); uggf->Update(); tempInput = vtkSmartPointer::New(); tempInput->ShallowCopy(uggf->GetOutputDataObject(0)); input = tempInput; if (this->CheckAbort()) { return 1; } } vtkUnsignedCharArray* ghosts = input->GetPointGhostArray(); vtkUnsignedCharArray* ghostCells = input->GetCellGhostArray(); vtkCellArray* newVerts; vtkCellArray* newLines; vtkCellArray* newPolys; vtkPoints* newPts; int progressCount; vtkIdType i, j, k; int cellType; vtkIdType numPts = input->GetNumberOfPoints(); vtkIdType numCells = input->GetNumberOfCells(); vtkGenericCell* cell; vtkNew pointIdList; const vtkIdType* ids; vtkIdType numFacePts; vtkIdType inPtId, outPtId, numCellPts; vtkPointData* inputPD = input->GetPointData(); vtkCellData* inputCD = input->GetCellData(); vtkFieldData* inputFD = input->GetFieldData(); vtkCellData* cd = input->GetCellData(); vtkPointData* outputPD = output->GetPointData(); vtkCellData* outputCD = output->GetCellData(); vtkFieldData* outputFD = output->GetFieldData(); vtkFastGeomQuad* q; // Shallow copy field data not associated with points or cells outputFD->ShallowCopy(inputFD); // These are for the default case/ vtkIdList* pts; vtkCell* face; int flag2D = 0; // These are for subdividing quadratic cells std::vector parametricCoords; std::unique_ptr localEdgeMap(new vtkEdgeInterpolationMap()); vtkIdList* outPts; vtkIdList* pts2; pts = vtkIdList::New(); outPts = vtkIdList::New(); pts2 = vtkIdList::New(); cell = vtkGenericCell::New(); std::vector weights; this->NumberOfNewCells = 0; this->InitializeQuadHash(numPts); // Allocate // newPts = vtkPoints::New(); newPts->SetDataType(input->GetPoints()->GetData()->GetDataType()); newPts->Allocate(numPts); newPolys = vtkCellArray::New(); newPolys->AllocateEstimate(numCells, 3); newVerts = vtkCellArray::New(); newLines = vtkCellArray::New(); if (this->NonlinearSubdivisionLevel < 2) { outputPD->CopyGlobalIdsOn(); outputPD->CopyAllocate(inputPD, numPts, numPts / 2); } else { outputPD->InterpolateAllocate(inputPD, numPts, numPts / 2); } outputCD->CopyGlobalIdsOn(); outputCD->CopyAllocate(inputCD, numCells, numCells / 2); if (this->PassThroughCellIds) { this->OriginalCellIds = vtkIdTypeArray::New(); this->OriginalCellIds->SetName(this->GetOriginalCellIdsName()); this->OriginalCellIds->SetNumberOfComponents(1); } if (this->PassThroughPointIds) { this->OriginalPointIds = vtkIdTypeArray::New(); this->OriginalPointIds->SetName(this->GetOriginalPointIdsName()); this->OriginalPointIds->SetNumberOfComponents(1); } // First insert all points. Points have to come first in poly data. for (vtkIdType cellId = 0; cellId < numCells; cellId++) { cellType = input->GetCellType(cellId); // A couple of common cases to see if things go faster. if (cellType == VTK_VERTEX || cellType == VTK_POLY_VERTEX) { input->GetCellPoints(cellId, numCellPts, ids, pointIdList); newVerts->InsertNextCell(numCellPts); for (i = 0; i < numCellPts; i++) { outPtId = this->GetOutputPointId(ids[i], input, newPts, outputPD); newVerts->InsertCellPoint(outPtId); } this->RecordOrigCellId(this->NumberOfNewCells, cellId); outputCD->CopyData(cd, cellId, this->NumberOfNewCells++); } } // Traverse cells to extract geometry // progressCount = 0; bool abort = false; vtkIdType progressInterval = numCells / 20 + 1; // First insert all points lines in output and 3D geometry in hash. // Save 2D geometry for second pass. for (vtkIdType cellId = 0; cellId < numCells && !abort; cellId++) { // We skip cells marked as hidden if (ghostCells && (ghostCells->GetValue(cellId) & vtkDataSetAttributes::CellGhostTypes::HIDDENCELL)) { continue; } // Progress and abort method support if (progressCount >= progressInterval) { vtkDebugMacro(<< "Process cell #" << cellId); this->UpdateProgress(static_cast(cellId) / numCells); abort = this->CheckAbort(); progressCount = 0; } progressCount++; cellType = input->GetCellType(cellId); switch (cellType) { case VTK_VERTEX: case VTK_POLY_VERTEX: case VTK_EMPTY_CELL: // Do nothing -- these were handled previously. break; case VTK_LINE: case VTK_POLY_LINE: input->GetCellPoints(cellId, numCellPts, ids, pointIdList); newLines->InsertNextCell(numCellPts); for (i = 0; i < numCellPts; i++) { outPtId = this->GetOutputPointId(ids[i], input, newPts, outputPD); newLines->InsertCellPoint(outPtId); } this->RecordOrigCellId(this->NumberOfNewCells, cellId); outputCD->CopyData(cd, cellId, this->NumberOfNewCells++); break; case VTK_LAGRANGE_CURVE: case VTK_QUADRATIC_EDGE: case VTK_CUBIC_LINE: { input->GetCellPoints(cellId, numCellPts, ids, pointIdList); if (this->NonlinearSubdivisionLevel <= 1) { int numCellPtsAfterSubdivision = this->NonlinearSubdivisionLevel == 0 ? 2 : numCellPts; newLines->InsertNextCell(numCellPtsAfterSubdivision); outPtId = this->GetOutputPointId(ids[0], input, newPts, outputPD); newLines->InsertCellPoint(outPtId); for (i = 2; i < numCellPtsAfterSubdivision; i++) { outPtId = this->GetOutputPointId(ids[i], input, newPts, outputPD); newLines->InsertCellPoint(outPtId); } outPtId = this->GetOutputPointId(ids[1], input, newPts, outputPD); newLines->InsertCellPoint(outPtId); } else { int numDeltaPtsAfterSubdivision = std::pow(2, this->NonlinearSubdivisionLevel - 1); int numCellPtsAfterSubdivision = numDeltaPtsAfterSubdivision * (numCellPts - 1) + 1; newLines->InsertNextCell(numCellPtsAfterSubdivision); outPtId = this->GetOutputPointId(ids[0], input, newPts, outputPD); newLines->InsertCellPoint(outPtId); double paramCoordDelta = 1. / (numCellPtsAfterSubdivision - 1); input->GetCell(cellId, cell); weights.resize(cell->GetNumberOfPoints()); double inParamCoords[3]; inParamCoords[1] = inParamCoords[2] = 0.; for (i = 0; i < (numCellPts - 1); i++) { for (j = 0; j < numDeltaPtsAfterSubdivision - 1; j++) { inParamCoords[0] = paramCoordDelta * (numDeltaPtsAfterSubdivision * i + j + 1); outPtId = GetInterpolatedPointId( input, cell, inParamCoords, weights.data(), newPts, outputPD); newLines->InsertCellPoint(outPtId); } if (i < numCellPts - 2) { outPtId = this->GetOutputPointId(ids[i + 2], input, newPts, outputPD); newLines->InsertCellPoint(outPtId); } } outPtId = this->GetOutputPointId(ids[1], input, newPts, outputPD); newLines->InsertCellPoint(outPtId); } this->RecordOrigCellId(this->NumberOfNewCells, cellId); outputCD->CopyData(cd, cellId, this->NumberOfNewCells++); break; } case VTK_BEZIER_CURVE: { input->GetCellPoints(cellId, numCellPts, ids, pointIdList); if (this->NonlinearSubdivisionLevel == 0 || !AllowInterpolation) { int numCellPtsAfterSubdivision = this->NonlinearSubdivisionLevel == 0 ? 2 : numCellPts; newLines->InsertNextCell(numCellPtsAfterSubdivision); outPtId = this->GetOutputPointId(ids[0], input, newPts, outputPD); newLines->InsertCellPoint(outPtId); for (i = 2; i < numCellPtsAfterSubdivision; i++) { outPtId = this->GetOutputPointId(ids[i], input, newPts, outputPD); newLines->InsertCellPoint(outPtId); } outPtId = this->GetOutputPointId(ids[1], input, newPts, outputPD); newLines->InsertCellPoint(outPtId); } else { int numDeltaPtsAfterSubdivision = std::pow(2, this->NonlinearSubdivisionLevel - 1); int numCellPtsAfterSubdivision = numDeltaPtsAfterSubdivision * (numCellPts - 1) + 1; newLines->InsertNextCell(numCellPtsAfterSubdivision); input->GetCell(cellId, cell); input->SetCellOrderAndRationalWeights(cellId, cell); weights.resize(cell->GetNumberOfPoints()); double* pc = cell->GetParametricCoords(); outPtId = this->GetOutputPointId(ids[0], input, newPts, outputPD); newLines->InsertCellPoint(outPtId); if (this->NonlinearSubdivisionLevel == 1) { for (i = 2; i < numCellPts; i++) { outPtId = this->GetOutputPointIdAndInterpolate( i, input, cell, pc, weights.data(), newPts, outputPD); newLines->InsertCellPoint(outPtId); } } else { double paramCoordDelta = 1. / (numCellPtsAfterSubdivision - 1); double inParamCoords[3]; inParamCoords[1] = inParamCoords[2] = 0.; for (i = 0; i < (numCellPts - 1); i++) { for (j = 0; j < numDeltaPtsAfterSubdivision - 1; j++) { inParamCoords[0] = paramCoordDelta * (numDeltaPtsAfterSubdivision * i + j + 1); outPtId = GetInterpolatedPointId( input, cell, inParamCoords, weights.data(), newPts, outputPD); newLines->InsertCellPoint(outPtId); } if (i < numCellPts - 2) { outPtId = this->GetOutputPointIdAndInterpolate( i + 2, input, cell, pc, weights.data(), newPts, outputPD); newLines->InsertCellPoint(outPtId); } } } outPtId = this->GetOutputPointId(ids[1], input, newPts, outputPD); newLines->InsertCellPoint(outPtId); } this->RecordOrigCellId(this->NumberOfNewCells, cellId); outputCD->CopyData(cd, cellId, this->NumberOfNewCells++); break; } case VTK_HEXAHEDRON: input->GetCellPoints(cellId, numCellPts, ids, pointIdList); this->InsertQuadInHash(ids[0], ids[1], ids[5], ids[4], cellId); this->InsertQuadInHash(ids[0], ids[3], ids[2], ids[1], cellId); this->InsertQuadInHash(ids[0], ids[4], ids[7], ids[3], cellId); this->InsertQuadInHash(ids[1], ids[2], ids[6], ids[5], cellId); this->InsertQuadInHash(ids[2], ids[3], ids[7], ids[6], cellId); this->InsertQuadInHash(ids[4], ids[5], ids[6], ids[7], cellId); break; case VTK_VOXEL: input->GetCellPoints(cellId, numCellPts, ids, pointIdList); this->InsertQuadInHash(ids[0], ids[1], ids[5], ids[4], cellId); this->InsertQuadInHash(ids[0], ids[2], ids[3], ids[1], cellId); this->InsertQuadInHash(ids[0], ids[4], ids[6], ids[2], cellId); this->InsertQuadInHash(ids[1], ids[3], ids[7], ids[5], cellId); this->InsertQuadInHash(ids[2], ids[6], ids[7], ids[3], cellId); this->InsertQuadInHash(ids[4], ids[5], ids[7], ids[6], cellId); break; case VTK_TETRA: input->GetCellPoints(cellId, numCellPts, ids, pointIdList); this->InsertTriInHash(ids[0], ids[1], ids[3], cellId, 2); this->InsertTriInHash(ids[0], ids[2], ids[1], cellId, 3); this->InsertTriInHash(ids[0], ids[3], ids[2], cellId, 1); this->InsertTriInHash(ids[1], ids[2], ids[3], cellId, 0); break; case VTK_PENTAGONAL_PRISM: input->GetCellPoints(cellId, numCellPts, ids, pointIdList); this->InsertQuadInHash(ids[0], ids[1], ids[6], ids[5], cellId); this->InsertQuadInHash(ids[1], ids[2], ids[7], ids[6], cellId); this->InsertQuadInHash(ids[2], ids[3], ids[8], ids[7], cellId); this->InsertQuadInHash(ids[3], ids[4], ids[9], ids[8], cellId); this->InsertQuadInHash(ids[4], ids[0], ids[5], ids[9], cellId); this->InsertPolygonInHash(ids, 5, cellId); this->InsertPolygonInHash(&ids[5], 5, cellId); break; case VTK_HEXAGONAL_PRISM: input->GetCellPoints(cellId, numCellPts, ids, pointIdList); this->InsertQuadInHash(ids[0], ids[1], ids[7], ids[6], cellId); this->InsertQuadInHash(ids[1], ids[2], ids[8], ids[7], cellId); this->InsertQuadInHash(ids[2], ids[3], ids[9], ids[8], cellId); this->InsertQuadInHash(ids[3], ids[4], ids[10], ids[9], cellId); this->InsertQuadInHash(ids[4], ids[5], ids[11], ids[10], cellId); this->InsertQuadInHash(ids[5], ids[0], ids[6], ids[11], cellId); this->InsertPolygonInHash(ids, 6, cellId); this->InsertPolygonInHash(&ids[6], 6, cellId); break; case VTK_PYRAMID: input->GetCellPoints(cellId, numCellPts, ids, pointIdList); this->InsertQuadInHash(ids[3], ids[2], ids[1], ids[0], cellId); this->InsertTriInHash(ids[0], ids[1], ids[4], cellId); this->InsertTriInHash(ids[1], ids[2], ids[4], cellId); this->InsertTriInHash(ids[2], ids[3], ids[4], cellId); this->InsertTriInHash(ids[3], ids[0], ids[4], cellId); break; case VTK_WEDGE: input->GetCellPoints(cellId, numCellPts, ids, pointIdList); this->InsertQuadInHash(ids[0], ids[2], ids[5], ids[3], cellId); this->InsertQuadInHash(ids[1], ids[0], ids[3], ids[4], cellId); this->InsertQuadInHash(ids[2], ids[1], ids[4], ids[5], cellId); this->InsertTriInHash(ids[0], ids[1], ids[2], cellId); this->InsertTriInHash(ids[3], ids[5], ids[4], cellId); break; case VTK_PIXEL: case VTK_QUAD: case VTK_TRIANGLE: case VTK_POLYGON: case VTK_TRIANGLE_STRIP: case VTK_QUADRATIC_TRIANGLE: case VTK_BIQUADRATIC_TRIANGLE: case VTK_QUADRATIC_QUAD: case VTK_QUADRATIC_LINEAR_QUAD: case VTK_BIQUADRATIC_QUAD: case VTK_QUADRATIC_POLYGON: case VTK_LAGRANGE_TRIANGLE: case VTK_LAGRANGE_QUADRILATERAL: case VTK_BEZIER_TRIANGLE: case VTK_BEZIER_QUADRILATERAL: // save 2D cells for third pass flag2D = 1; break; default: { // Default way of getting faces. Differentiates between linear // and higher order cells. input->GetCell(cellId, cell); if (cell->IsLinear()) { if (cell->GetCellDimension() == 3) { int numFaces = cell->GetNumberOfFaces(); for (j = 0; j < numFaces; j++) { face = cell->GetFace(j); numFacePts = face->GetNumberOfPoints(); if (numFacePts == 4) { this->InsertQuadInHash(face->PointIds->GetId(0), face->PointIds->GetId(1), face->PointIds->GetId(2), face->PointIds->GetId(3), cellId); } else if (numFacePts == 3) { this->InsertTriInHash(face->PointIds->GetId(0), face->PointIds->GetId(1), face->PointIds->GetId(2), cellId); } else { this->InsertPolygonInHash( face->PointIds->GetPointer(0), face->PointIds->GetNumberOfIds(), cellId); } } // for all cell faces } // if 3D else { vtkDebugMacro("Missing cell type."); } } // a linear cell type else // process nonlinear cells via triangulation { input->SetCellOrderAndRationalWeights(cellId, cell); if (cell->GetCellDimension() == 1) { cell->TriangulateIds(0, pts); for (i = 0; i < pts->GetNumberOfIds(); i += 2) { newLines->InsertNextCell(2); inPtId = pts->GetId(i); this->RecordOrigCellId(this->NumberOfNewCells, cellId); outputCD->CopyData(cd, cellId, this->NumberOfNewCells++); outPtId = this->GetOutputPointId(inPtId, input, newPts, outputPD); newLines->InsertCellPoint(outPtId); inPtId = pts->GetId(i + 1); outPtId = this->GetOutputPointId(inPtId, input, newPts, outputPD); newLines->InsertCellPoint(outPtId); } } else if (cell->GetCellDimension() == 2) { vtkWarningMacro(<< "2-D nonlinear cells must be processed with all other 2-D cells."); } else // 3D nonlinear cell { vtkIdList* cellIds = vtkIdList::New(); int numFaces = cell->GetNumberOfFaces(); for (j = 0; j < numFaces; j++) { face = cell->GetFace(j); input->GetCellNeighbors(cellId, face->PointIds, cellIds); if (cellIds->GetNumberOfIds() <= 0) { // FIXME: Face could not be consistent. vtkOrderedTriangulator is a better option if (this->NonlinearSubdivisionLevel >= 1) { // TODO: Handle NonlinearSubdivisionLevel > 1 correctly. face->TriangulateIds(0, pts); for (i = 0; i < pts->GetNumberOfIds(); i += 3) { this->InsertTriInHash( pts->GetId(i), pts->GetId(i + 1), pts->GetId(i + 2), cellId); } } else { switch (face->GetCellType()) { case VTK_QUADRATIC_TRIANGLE: case VTK_LAGRANGE_TRIANGLE: case VTK_BEZIER_TRIANGLE: this->InsertTriInHash(face->PointIds->GetId(0), face->PointIds->GetId(1), face->PointIds->GetId(2), cellId); break; case VTK_QUADRATIC_QUAD: case VTK_BIQUADRATIC_QUAD: case VTK_QUADRATIC_LINEAR_QUAD: case VTK_LAGRANGE_QUADRILATERAL: case VTK_BEZIER_QUADRILATERAL: this->InsertQuadInHash(face->PointIds->GetId(0), face->PointIds->GetId(1), face->PointIds->GetId(2), face->PointIds->GetId(3), cellId); break; default: vtkWarningMacro(<< "Encountered unknown nonlinear face."); break; } // switch cell type } // subdivision level } // cell has ids } // for faces cellIds->Delete(); } // 3d cell } // nonlinear cell } // default switch case } // switch(cellType) } // for all cells. // It would be possible to add these (except for polygons with 5+ sides) // to the hashes. Alternatively, the higher order 2d cells could be handled // in the following loop. // Now insert 2DCells. Because of poly datas (cell data) ordering, // the 2D cells have to come after points and lines. for (vtkIdType cellId = 0; cellId < numCells && !abort && flag2D; ++cellId) { // We skip cells marked as hidden if (ghostCells && (ghostCells->GetValue(cellId) & vtkDataSetAttributes::CellGhostTypes::HIDDENCELL)) { continue; } cellType = input->GetCellType(cellId); input->GetCellPoints(cellId, numCellPts, ids, pointIdList); // If we have a quadratic face and our subdivision level is zero, just treat // it as a linear cell. This should work so long as the first points of the // quadratic cell correspond to all those of the equivalent linear cell // (which all the current definitions do). if (this->NonlinearSubdivisionLevel < 1) { switch (cellType) { case VTK_QUADRATIC_TRIANGLE: case VTK_LAGRANGE_TRIANGLE: case VTK_BEZIER_TRIANGLE: cellType = VTK_TRIANGLE; numCellPts = 3; break; case VTK_QUADRATIC_QUAD: case VTK_BIQUADRATIC_QUAD: case VTK_QUADRATIC_LINEAR_QUAD: case VTK_LAGRANGE_QUADRILATERAL: case VTK_BEZIER_QUADRILATERAL: cellType = VTK_QUAD; numCellPts = 4; break; } } // A couple of common cases to see if things go faster. if (cellType == VTK_PIXEL) { // Do we really want to insert the 2D cells into a hash? pts->Reset(); pts->InsertId(0, this->GetOutputPointId(ids[0], input, newPts, outputPD)); pts->InsertId(1, this->GetOutputPointId(ids[1], input, newPts, outputPD)); pts->InsertId(2, this->GetOutputPointId(ids[3], input, newPts, outputPD)); pts->InsertId(3, this->GetOutputPointId(ids[2], input, newPts, outputPD)); newPolys->InsertNextCell(pts); this->RecordOrigCellId(this->NumberOfNewCells, cellId); outputCD->CopyData(cd, cellId, this->NumberOfNewCells++); } else if (cellType == VTK_POLYGON || cellType == VTK_TRIANGLE || cellType == VTK_QUAD) { pts->Reset(); for (i = 0; i < numCellPts; i++) { outPtId = this->GetOutputPointId(ids[i], input, newPts, outputPD); pts->InsertId(i, outPtId); } newPolys->InsertNextCell(pts); this->RecordOrigCellId(this->NumberOfNewCells, cellId); outputCD->CopyData(cd, cellId, this->NumberOfNewCells++); } else if (cellType == VTK_TRIANGLE_STRIP) { // Change strips to triangles so we do not have to worry about order. int toggle = 0; vtkIdType ptIds[3]; // This check is not really necessary. It was put here because of another (now fixed) bug. if (numCellPts > 1) { ptIds[0] = this->GetOutputPointId(ids[0], input, newPts, outputPD); ptIds[1] = this->GetOutputPointId(ids[1], input, newPts, outputPD); for (i = 2; i < numCellPts; ++i) { ptIds[2] = this->GetOutputPointId(ids[i], input, newPts, outputPD); newPolys->InsertNextCell(3, ptIds); this->RecordOrigCellId(this->NumberOfNewCells, cellId); outputCD->CopyData(cd, cellId, this->NumberOfNewCells++); ptIds[toggle] = ptIds[2]; toggle = !toggle; } } } else if (cellType == VTK_QUADRATIC_TRIANGLE || cellType == VTK_BIQUADRATIC_TRIANGLE || cellType == VTK_QUADRATIC_QUAD || cellType == VTK_BIQUADRATIC_QUAD || cellType == VTK_QUADRATIC_LINEAR_QUAD || cellType == VTK_QUADRATIC_POLYGON || cellType == VTK_LAGRANGE_TRIANGLE || cellType == VTK_LAGRANGE_QUADRILATERAL || cellType == VTK_BEZIER_TRIANGLE || cellType == VTK_BEZIER_QUADRILATERAL) { // If one of the points is hidden (meaning invalid), do not // extract surface cell. // Removed checking for whether all points are ghost, because that's an // incorrect assumption. bool oneHidden = false; if (ghosts) { for (i = 0; i < numCellPts; i++) { unsigned char val = ghosts->GetValue(ids[i]); if (val & vtkDataSetAttributes::HIDDENPOINT) { oneHidden = true; break; } } } if (oneHidden) { continue; } // Note: we should not be here if this->NonlinearSubdivisionLevel is less // than 1. See the check above. input->GetCell(cellId, cell); double* pc = cell->GetParametricCoords(); // If the cell is of Bezier type, the weights might be rational and the degree nonuniform. // This need to be initiated. input->SetCellOrderAndRationalWeights(cellId, cell); // Get the triangulation of the first subdivision level. // Note that the output of TriangulateLocalIds records triangles in pts where each 3 points // defines a triangle. The returned ids are local ids with respect to the cell. cell->TriangulateLocalIds(0, pts); assert(pts->GetNumberOfIds() % 3 == 0); // Start to fill outPts with the cell points numFacePts = cell->GetNumberOfPoints(); outPts->Reset(); weights.resize(numFacePts); // For Bezier cells, the points that are not at the corners are overload to get the // projection of the non-interpolate points. numFacePtsToCopy is the number of points to be // copied, and numFacePts - numFacePtsToCopy will be the number of points that are // interpolated. vtkIdType numFacePtsToCopy; if (!this->AllowInterpolation || (cellType != VTK_BEZIER_QUADRILATERAL && cellType != VTK_BEZIER_TRIANGLE)) { numFacePtsToCopy = numFacePts; } else { numFacePtsToCopy = (cellType == VTK_BEZIER_QUADRILATERAL ? 4 : 3); } // Points that are copied: for (i = 0; i < numFacePtsToCopy; i++) { outPts->InsertNextId(this->GetOutputPointId(cell->GetPointId(i), input, newPts, outputPD)); } // Points that are interpolated (only for Bezier cells when AllowInterpolation is true ) for (i = numFacePtsToCopy; i < numFacePts; i++) { outPts->InsertNextId(this->GetOutputPointIdAndInterpolate( i, input, cell, pc, weights.data(), newPts, outputPD)); } bool isDegenerateCell = false; auto isDegeneratedSubTriangle = [&](vtkIdType ii) { return outPts->GetId(pts->GetId(ii)) == outPts->GetId(pts->GetId(ii + 1)) || outPts->GetId(pts->GetId(ii)) == outPts->GetId(pts->GetId(ii + 2)) || outPts->GetId(pts->GetId(ii + 1)) == outPts->GetId(pts->GetId(ii + 2)); }; // Do any further subdivision if necessary. if (this->NonlinearSubdivisionLevel > 1 && pc) { for (i = 0; i < pts->GetNumberOfIds(); i += 3) { if (isDegeneratedSubTriangle(i)) { isDegenerateCell = true; break; } } vtkIdType maxNumberOfIds = std::pow(4, this->NonlinearSubdivisionLevel - 1) * pts->GetNumberOfIds(); pts2->Allocate(maxNumberOfIds); // We are going to need parametric coordinates to further subdivide. parametricCoords.resize(maxNumberOfIds * 3); std::copy(&pc[0], &pc[0] + numFacePts * 3, parametricCoords.begin()); // localEdgeMap is similar to this->EdgeMap, but only stores local ids localEdgeMap->clear(); auto isEqualTo1Or0 = [](double a, double e = 1e-10) { return (std::abs(a) <= e) || (std::abs(a - 1) <= e); }; vtkIdType localIdCpt = numFacePts; vtkIdType pt1, pt2, id; vtkIdType inPts[6]; // Subdivide these triangles as many more times as necessary. Remember // that we have already done the first subdivision. for (j = 1; j < this->NonlinearSubdivisionLevel; j++) { pts2->Reset(); if (isDegenerateCell) { // For degenerate cells, we can have multiple parametric points linked to the same // output point. But we need to select a single one. The rule is to give priority to // the points that are on the contour of the parametric space. This is necessary for // connecting adjacent cells. The way we give this priority is by calling // this->EdgeMap->FindEgde/AddEdge for those points first. So a first iteration over pts // is performed to add those points. During the second iteration (the one not specific // to degenerate cells), when trying to add a duplicate point, the edge map will return // the output id of the already existing point. double coords[3]; for (i = 0; i < pts->GetNumberOfIds(); i += 3) { for (k = 0; k < 3; k++) { pt1 = pts->GetId(i + k); pt2 = pts->GetId(i + ((k < 2) ? (k + 1) : 0)); { coords[0] = 0.5 * (parametricCoords[pt1 * 3] + parametricCoords[pt2 * 3]); coords[1] = 0.5 * (parametricCoords[pt1 * 3 + 1] + parametricCoords[pt2 * 3 + 1]); coords[2] = 0.5 * (parametricCoords[pt1 * 3 + 2] + parametricCoords[pt2 * 3 + 2]); if (isEqualTo1Or0(coords[0]) || isEqualTo1Or0(coords[1])) { this->GetInterpolatedPointId(outPts->GetId(pt1), outPts->GetId(pt2), input, cell, coords, weights.data(), newPts, outputPD); } } } } } // Each triangle will be split into 4 triangles. for (i = 0; i < pts->GetNumberOfIds(); i += 3) { // Hold the input point ids and parametric coordinates. First 3 // indices are the original points. Second three are the midpoints // in the edges (0,1), (1,2) and (2,0), respectively (see comment // below). for (k = 0; k < 3; k++) { inPts[k] = pts->GetId(i + k); pt1 = inPts[k]; pt2 = pts->GetId(i + ((k < 2) ? (k + 1) : 0)); id = localEdgeMap->FindEdge(pt1, pt2); if (id == -1) { id = localIdCpt; parametricCoords[id * 3] = 0.5 * (parametricCoords[pt1 * 3] + parametricCoords[pt2 * 3]); parametricCoords[id * 3 + 1] = 0.5 * (parametricCoords[pt1 * 3 + 1] + parametricCoords[pt2 * 3 + 1]); parametricCoords[id * 3 + 2] = 0.5 * (parametricCoords[pt1 * 3 + 2] + parametricCoords[pt2 * 3 + 2]); localEdgeMap->AddEdge(pt1, pt2, id); outPts->InsertNextId( this->GetInterpolatedPointId(outPts->GetId(pt1), outPts->GetId(pt2), input, cell, ¶metricCoords[id * 3], weights.data(), newPts, outputPD)); localIdCpt++; } inPts[k + 3] = id; } // * 0 // / \ Use the 6 points recorded // / \ in inPts and paramCoords // 3 *-----* 5 to create the 4 triangles // / \ / \ shown here. // / \ / \ . // *-----*-----* // 1 4 2 static const int subtriangles[12] = { 0, 3, 5, 3, 1, 4, 3, 4, 5, 5, 4, 2 }; for (int subId : subtriangles) { pts2->InsertNextId(inPts[subId]); } } // Iterate over triangles // Now that we have recorded the subdivided triangles in pts2 , swap them with pts to // make them the current ones. std::swap(pts, pts2); } // Iterate over subdivision levels } for (i = 0; i < pts->GetNumberOfIds(); i += 3) { if (isDegenerateCell && isDegeneratedSubTriangle(i)) { continue; // Do not record the degenerate triangle } newPolys->InsertNextCell(3); newPolys->InsertCellPoint(outPts->GetId(pts->GetId(i))); newPolys->InsertCellPoint(outPts->GetId(pts->GetId(i + 1))); newPolys->InsertCellPoint(outPts->GetId(pts->GetId(i + 2))); this->RecordOrigCellId(this->NumberOfNewCells, cellId); outputCD->CopyData(cd, cellId, this->NumberOfNewCells++); } } } // for all cells. // Now transfer geometry from hash to output (only triangles and quads). this->InitQuadHashTraversal(); while ((q = this->GetNextVisibleQuadFromHash())) { // If one of the points is hidden (meaning invalid), do not // extract surface cell. // Removed checking for whether all points are ghost, because that's an // incorrect assumption. bool oneHidden = false; // handle all polys for (i = 0; i < q->numPts; i++) { if (ghosts) { unsigned char val = ghosts->GetValue(q->ptArray[i]); if (val & vtkDataSetAttributes::HIDDENPOINT) { oneHidden = true; } } q->ptArray[i] = this->GetOutputPointId(q->ptArray[i], input, newPts, outputPD); } if (oneHidden) { continue; } newPolys->InsertNextCell(q->numPts, q->ptArray); this->RecordOrigCellId(this->NumberOfNewCells, q); outputCD->CopyData(inputCD, q->SourceId, this->NumberOfNewCells++); } if (this->PassThroughCellIds) { outputCD->AddArray(this->OriginalCellIds); } if (this->PassThroughPointIds) { outputPD->AddArray(this->OriginalPointIds); } // Update ourselves and release memory // cell->Delete(); pts->Delete(); outPts->Delete(); pts2->Delete(); output->SetPoints(newPts); newPts->Delete(); output->SetPolys(newPolys); newPolys->Delete(); if (newVerts->GetNumberOfCells() > 0) { output->SetVerts(newVerts); } newVerts->Delete(); newVerts = nullptr; if (newLines->GetNumberOfCells() > 0) { output->SetLines(newLines); } newLines->Delete(); // free storage output->Squeeze(); if (this->OriginalCellIds != nullptr) { this->OriginalCellIds->Delete(); this->OriginalCellIds = nullptr; } if (this->OriginalPointIds != nullptr) { this->OriginalPointIds->Delete(); this->OriginalPointIds = nullptr; } this->DeleteQuadHash(); return 1; } //------------------------------------------------------------------------------ void vtkDataSetSurfaceFilter::InitializeQuadHash(vtkIdType numPoints) { vtkIdType i; if (this->QuadHash) { this->DeleteQuadHash(); } // Prepare our special quad allocator (for efficiency). this->InitFastGeomQuadAllocation(numPoints); this->QuadHash = new vtkFastGeomQuad*[numPoints]; this->QuadHashLength = numPoints; this->PointMap = new vtkIdType[numPoints]; for (i = 0; i < numPoints; ++i) { this->QuadHash[i] = nullptr; this->PointMap[i] = -1; } this->EdgeMap = new vtkEdgeInterpolationMap; } //------------------------------------------------------------------------------ void vtkDataSetSurfaceFilter::DeleteQuadHash() { vtkIdType i; this->DeleteAllFastGeomQuads(); for (i = 0; i < this->QuadHashLength; ++i) { this->QuadHash[i] = nullptr; } delete[] this->QuadHash; this->QuadHash = nullptr; this->QuadHashLength = 0; delete[] this->PointMap; this->PointMap = nullptr; delete this->EdgeMap; this->EdgeMap = nullptr; } //------------------------------------------------------------------------------ void vtkDataSetSurfaceFilter::InsertQuadInHash( vtkIdType a, vtkIdType b, vtkIdType c, vtkIdType d, vtkIdType sourceId) { vtkIdType tmp; vtkFastGeomQuad *quad, **end; // Reorder to get smallest id in a. if (b < a && b < c && b < d) { tmp = a; a = b; b = c; c = d; d = tmp; } else if (c < a && c < b && c < d) { tmp = a; a = c; c = tmp; tmp = b; b = d; d = tmp; } else if (d < a && d < b && d < c) { tmp = a; a = d; d = c; c = b; b = tmp; } // Look for existing quad in the hash; end = this->QuadHash + a; quad = *end; while (quad) { end = &(quad->Next); // a has to match in this bin. // c should be independent of point order. if (quad->numPts == 4 && c == quad->ptArray[2]) { // Check both orders for b and d. if ((b == quad->ptArray[1] && d == quad->ptArray[3]) || (b == quad->ptArray[3] && d == quad->ptArray[1])) { // We have a match. quad->SourceId = -1; // That is all we need to do. Hide any quad shared by two or more cells. return; } } quad = *end; } // Create a new quad and add it to the hash. quad = this->NewFastGeomQuad(4); quad->Next = nullptr; quad->SourceId = sourceId; quad->ptArray[0] = a; quad->ptArray[1] = b; quad->ptArray[2] = c; quad->ptArray[3] = d; *end = quad; } //------------------------------------------------------------------------------ void vtkDataSetSurfaceFilter::InsertTriInHash( vtkIdType a, vtkIdType b, vtkIdType c, vtkIdType sourceId, vtkIdType vtkNotUsed(faceId) /*= -1*/) { vtkIdType tmp; vtkFastGeomQuad *quad, **end; // Reorder to get smallest id in a. if (b < a && b < c) { tmp = a; a = b; b = c; c = tmp; } else if (c < a && c < b) { tmp = a; a = c; c = b; b = tmp; } // We can't put the second smallest in b because it might change the order // of the vertices in the final triangle. // Look for existing tri in the hash; end = this->QuadHash + a; quad = *end; while (quad) { end = &(quad->Next); // a has to match in this bin. if (quad->numPts == 3) { if ((b == quad->ptArray[1] && c == quad->ptArray[2]) || (b == quad->ptArray[2] && c == quad->ptArray[1])) { // We have a match. quad->SourceId = -1; // That is all we need to do. Hide any tri shared by two or more cells. return; } } quad = *end; } // Create a new quad and add it to the hash. quad = this->NewFastGeomQuad(3); quad->Next = nullptr; quad->SourceId = sourceId; quad->ptArray[0] = a; quad->ptArray[1] = b; quad->ptArray[2] = c; *end = quad; } // Insert a polygon into the hash. // Input: an array of vertex ids // the start index of the polygon in the array // the end index of the polygon in the array // the cellId of the polygon //------------------------------------------------------------------------------ void vtkDataSetSurfaceFilter::InsertPolygonInHash( const vtkIdType* ids, int numPts, vtkIdType sourceId) { // sanity check if (numPts == 0) { return; } vtkFastGeomQuad *quad, **end; // find the index to the smallest id vtkIdType offset = 0; for (int i = 0; i < numPts; i++) { if (ids[i] < ids[offset]) { offset = i; } } // copy ids into ordered array with smallest id first vtkIdType* tab = new vtkIdType[numPts]; for (int i = 0; i < numPts; i++) { tab[i] = ids[(offset + i) % numPts]; } // Look for existing hex in the hash; end = this->QuadHash + tab[0]; quad = *end; while (quad) { end = &(quad->Next); // a has to match in this bin. // first just check the polygon size. bool match = true; if (numPts == quad->numPts) { if (tab[0] == quad->ptArray[0]) { // if the first two points match loop through forwards // checking all points if (numPts > 1 && tab[1] == quad->ptArray[1]) { for (int i = 2; i < numPts; ++i) { if (tab[i] != quad->ptArray[i]) { match = false; break; } } } else { // check if the points go in the opposite direction for (int i = 1; i < numPts; ++i) { if (tab[numPts - i] != quad->ptArray[i]) { match = false; break; } } } } else { match = false; } } else { match = false; } if (match) { // We have a match. quad->SourceId = -1; // That is all we need to do. Hide any tri shared by two or more cells. delete[] tab; return; } quad = *end; } // Create a new quad and add it to the hash. quad = this->NewFastGeomQuad(numPts); // mark the structure as a polygon quad->Next = nullptr; quad->SourceId = sourceId; for (int i = 0; i < numPts; i++) { quad->ptArray[i] = tab[i]; } *end = quad; delete[] tab; } //------------------------------------------------------------------------------ void vtkDataSetSurfaceFilter::InitFastGeomQuadAllocation(vtkIdType numberOfCells) { int idx; this->DeleteAllFastGeomQuads(); // Allocate 100 pointers to arrays. // This should be plenty (unless we have triangle strips) ... this->NumberOfFastGeomQuadArrays = 100; this->FastGeomQuadArrays = new unsigned char*[this->NumberOfFastGeomQuadArrays]; // Initialize all to nullptr; for (idx = 0; idx < this->NumberOfFastGeomQuadArrays; ++idx) { this->FastGeomQuadArrays[idx] = nullptr; } // Set pointer to the beginning. this->NextArrayIndex = 0; this->NextQuadIndex = 0; // size the chunks based on the size of a quadrilateral int quadSize = sizeofFastQuad(4); // Lets keep the chunk size relatively small. if (numberOfCells < 100) { this->FastGeomQuadArrayLength = 50 * quadSize; } else { this->FastGeomQuadArrayLength = (numberOfCells / 2) * quadSize; } } //------------------------------------------------------------------------------ void vtkDataSetSurfaceFilter::DeleteAllFastGeomQuads() { for (int idx = 0; idx < this->NumberOfFastGeomQuadArrays; ++idx) { delete[] this->FastGeomQuadArrays[idx]; this->FastGeomQuadArrays[idx] = nullptr; } delete[] this->FastGeomQuadArrays; this->FastGeomQuadArrays = nullptr; this->FastGeomQuadArrayLength = 0; this->NumberOfFastGeomQuadArrays = 0; this->NextArrayIndex = 0; this->NextQuadIndex = 0; } //------------------------------------------------------------------------------ vtkFastGeomQuad* vtkDataSetSurfaceFilter::NewFastGeomQuad(int numPts) { if (this->FastGeomQuadArrayLength == 0) { vtkErrorMacro("Face hash allocation has not been initialized."); return nullptr; } // see if there's room for this one int polySize = sizeofFastQuad(numPts); if (this->NextQuadIndex + polySize > this->FastGeomQuadArrayLength) { ++(this->NextArrayIndex); this->NextQuadIndex = 0; } // Although this should not happen often, check first. if (this->NextArrayIndex >= this->NumberOfFastGeomQuadArrays) { int idx, num; unsigned char** newArrays; num = this->NumberOfFastGeomQuadArrays * 2; newArrays = new unsigned char*[num]; for (idx = 0; idx < num; ++idx) { newArrays[idx] = nullptr; if (idx < this->NumberOfFastGeomQuadArrays) { newArrays[idx] = this->FastGeomQuadArrays[idx]; } } delete[] this->FastGeomQuadArrays; this->FastGeomQuadArrays = newArrays; this->NumberOfFastGeomQuadArrays = num; } // Next: allocate a new array if necessary. if (this->FastGeomQuadArrays[this->NextArrayIndex] == nullptr) { this->FastGeomQuadArrays[this->NextArrayIndex] = new unsigned char[this->FastGeomQuadArrayLength]; } vtkFastGeomQuad* q = reinterpret_cast( this->FastGeomQuadArrays[this->NextArrayIndex] + this->NextQuadIndex); q->numPts = numPts; q->ptArray = (vtkIdType*)q + FSizeDivSizeId; this->NextQuadIndex += polySize; return q; } //------------------------------------------------------------------------------ void vtkDataSetSurfaceFilter::InitQuadHashTraversal() { this->QuadHashTraversalIndex = 0; if (this->QuadHashLength == 0) { this->QuadHashTraversal = nullptr; } else { this->QuadHashTraversal = this->QuadHash[0]; } } //------------------------------------------------------------------------------ vtkFastGeomQuad* vtkDataSetSurfaceFilter::GetNextVisibleQuadFromHash() { vtkFastGeomQuad* quad; quad = this->QuadHashTraversal; // Move till traversal until we have a quad to return. // Note: the current traversal has not been returned yet. while (quad == nullptr || quad->SourceId == -1) { if (quad) { // The quad must be hidden. Move to the next. quad = quad->Next; } else { // must be the end of the linked list. Move to the next bin. this->QuadHashTraversalIndex += 1; if (this->QuadHashTraversalIndex >= this->QuadHashLength) { // There are no more bins. this->QuadHashTraversal = nullptr; return nullptr; } quad = this->QuadHash[this->QuadHashTraversalIndex]; } } // Now we have a quad to return. Set the traversal to the next entry. this->QuadHashTraversal = quad->Next; return quad; } //------------------------------------------------------------------------------ vtkIdType vtkDataSetSurfaceFilter::GetOutputPointId( vtkIdType inPtId, vtkDataSet* input, vtkPoints* outPts, vtkPointData* outPD) { vtkIdType outPtId; outPtId = this->PointMap[inPtId]; if (outPtId == -1) { outPtId = outPts->InsertNextPoint(input->GetPoint(inPtId)); outPD->CopyData(input->GetPointData(), inPtId, outPtId); this->PointMap[inPtId] = outPtId; this->RecordOrigPointId(outPtId, inPtId); } return outPtId; } //------------------------------------------------------------------------------ vtkIdType vtkDataSetSurfaceFilter::GetOutputPointIdAndInterpolate(vtkIdType cellPtId, vtkDataSet* input, vtkCell* cell, double* pc, double* weights, vtkPoints* outPts, vtkPointData* outPD) { vtkIdType outPtId; vtkIdType inPtId = cell->GetPointId(cellPtId); outPtId = this->PointMap[inPtId]; if (outPtId == -1) { int subId = -1; double wcoords[3]; cell->EvaluateLocation(subId, pc + 3 * cellPtId, wcoords, weights); outPtId = outPts->InsertNextPoint(wcoords); outPD->InterpolatePoint(input->GetPointData(), outPtId, cell->GetPointIds(), weights); this->PointMap[inPtId] = outPtId; this->RecordOrigPointId(outPtId, inPtId); } return outPtId; } //------------------------------------------------------------------------------ vtkIdType vtkDataSetSurfaceFilter::GetInterpolatedPointId(vtkIdType edgePtA, vtkIdType edgePtB, vtkDataSet* input, vtkCell* cell, double* pcoords, double* weights, vtkPoints* outPts, vtkPointData* outPD) { vtkIdType outPtId = this->EdgeMap->FindEdge(edgePtA, edgePtB); if (outPtId == -1) { int subId = -1; double wcoords[3]; cell->EvaluateLocation(subId, pcoords, wcoords, weights); outPtId = outPts->InsertNextPoint(wcoords); outPD->InterpolatePoint(input->GetPointData(), outPtId, cell->GetPointIds(), weights); this->RecordOrigPointId(outPtId, -1); this->EdgeMap->AddEdge(edgePtA, edgePtB, outPtId); } return outPtId; } vtkIdType vtkDataSetSurfaceFilter::GetInterpolatedPointId(vtkDataSet* input, vtkCell* cell, double pcoords[3], double* weights, vtkPoints* outPts, vtkPointData* outPD) { int subId = -1; double wcoords[3]; cell->EvaluateLocation(subId, pcoords, wcoords, weights); vtkIdType outPtId = outPts->InsertNextPoint(wcoords); outPD->InterpolatePoint(input->GetPointData(), outPtId, cell->GetPointIds(), weights); this->RecordOrigPointId(outPtId, -1); return outPtId; } //------------------------------------------------------------------------------ void vtkDataSetSurfaceFilter::RecordOrigCellId(vtkIdType destIndex, vtkIdType originalId) { if (this->OriginalCellIds != nullptr) { this->OriginalCellIds->InsertValue(destIndex, originalId); } } //------------------------------------------------------------------------------ void vtkDataSetSurfaceFilter::RecordOrigCellId(vtkIdType destIndex, vtkFastGeomQuad* quad) { if (this->OriginalCellIds != nullptr) { this->OriginalCellIds->InsertValue(destIndex, quad->SourceId); } } //------------------------------------------------------------------------------ void vtkDataSetSurfaceFilter::RecordOrigPointId(vtkIdType destIndex, vtkIdType originalId) { if (this->OriginalPointIds != nullptr) { this->OriginalPointIds->InsertValue(destIndex, originalId); } } VTK_ABI_NAMESPACE_END