// SPDX-FileCopyrightText: Copyright (c) Ken Martin, Will Schroeder, Bill Lorensen // SPDX-License-Identifier: BSD-3-Clause #include "vtkFitToHeightMapFilter.h" #include "vtkArrayDispatch.h" #include "vtkArrayDispatchDataSetArrayList.h" #include "vtkCellData.h" #include "vtkDataArrayRange.h" #include "vtkGenericCell.h" #include "vtkImageData.h" #include "vtkInformation.h" #include "vtkInformationVector.h" #include "vtkObjectFactory.h" #include "vtkPixel.h" #include "vtkPointData.h" #include "vtkPolyData.h" #include "vtkSMPThreadLocalObject.h" #include "vtkSMPTools.h" #include "vtkStreamingDemandDrivenPipeline.h" VTK_ABI_NAMESPACE_BEGIN vtkStandardNewMacro(vtkFitToHeightMapFilter); namespace { //------------------------------------------------------------------------------ // The threaded core of the algorithm for projecting points. template struct FitPointsFunctor { TPointArray* InPoints; TScalarArray* Scalars; TPointArray* OutPoints; double Dims[3]; double Origin[3]; double H[3]; vtkFitToHeightMapFilter* Filter; FitPointsFunctor(TPointArray* inPts, TScalarArray* s, TPointArray* outPts, int dims[3], double o[3], double h[3], vtkFitToHeightMapFilter* filter) : InPoints(inPts) , Scalars(s) , OutPoints(outPts) , Filter(filter) { for (int i = 0; i < 3; ++i) { this->Dims[i] = static_cast(dims[i]); this->Origin[i] = o[i]; this->H[i] = h[i]; } } void Initialize() {} void operator()(vtkIdType ptId, vtkIdType endPtId) { auto xi = vtk::DataArrayValueRange<3>(this->InPoints, 3 * ptId).begin(); auto xo = vtk::DataArrayValueRange<3>(this->OutPoints, 3 * ptId).begin(); const double* d = this->Dims; const double* o = this->Origin; const double* h = this->H; int ii, jj; double i, j, z, pc[2], ix, iy, w[4]; auto scalars = vtk::DataArrayValueRange(this->Scalars).begin(); int s0, s1, s2, s3; bool isFirst = vtkSMPTools::GetSingleThread(); for (; ptId < endPtId; ++ptId, xi += 3, xo += 3) { if (isFirst) { this->Filter->CheckAbort(); } if (this->Filter->GetAbortOutput()) { break; } // Location in image i = (xi[0] - o[0]) / h[0]; j = (xi[1] - o[1]) / h[1]; // Clamp to image. i,j is integral index into image pixels. It has to be done carefully // to manage the parametric coordinates correctly. if (i < 0.0) { ix = 0.0; pc[0] = 0.0; } else if (i >= (d[0] - 1)) { ix = d[0] - 2; pc[0] = 1.0; } else { pc[0] = modf(i, &ix); } if (j < 0.0) { iy = 0.0; pc[1] = 0.0; } else if (j >= (d[1] - 1)) { iy = d[1] - 2; pc[1] = 1.0; } else { pc[1] = modf(j, &iy); } // Parametric coordinates for interpolation vtkPixel::InterpolationFunctions(pc, w); ii = static_cast(ix); jj = static_cast(iy); // Interpolate height from surrounding data values s0 = ii + jj * d[0]; s1 = s0 + 1; s2 = s0 + d[0]; s3 = s2 + 1; z = w[0] * scalars[s0] + w[1] * scalars[s1] + w[2] * scalars[s2] + w[3] * scalars[s3]; // Set the output point coordinates with a new z-value. xo[0] = xi[0]; xo[1] = xi[1]; xo[2] = z; } } void Reduce() {} }; // FitPoints struct FitPointsWorker { template void operator()(TPointArray* inPts, TScalarArray* scalars, vtkDataArray* outPts, int dims[3], double origin[3], double h[3], vtkFitToHeightMapFilter* filter) { FitPointsFunctor fit( inPts, scalars, TPointArray::FastDownCast(outPts), dims, origin, h, filter); vtkSMPTools::For(0, inPts->GetNumberOfTuples(), fit); } }; //------------------------------------------------------------------------------ // The threaded core of the algorithm when projecting cells. template struct FitCellsFunctor { TScalarArray* Scalars; int Strategy; vtkPolyData* Mesh; double* CellHeights; double Dims[3]; double Origin[3]; double H[3]; // Avoid repeated allocation vtkSMPThreadLocalObject Cell; vtkSMPThreadLocalObject Prims; vtkSMPThreadLocalObject PrimPts; vtkFitToHeightMapFilter* Filter; FitCellsFunctor(TScalarArray* s, int strat, vtkPolyData* mesh, double* cellHts, int dims[3], double o[3], double h[3], vtkFitToHeightMapFilter* filter) : Scalars(s) , Strategy(strat) , Mesh(mesh) , CellHeights(cellHts) , Filter(filter) { for (int i = 0; i < 3; ++i) { this->Dims[i] = static_cast(dims[i]); this->Origin[i] = o[i]; this->H[i] = h[i]; } } void Initialize() { vtkGenericCell*& cell = this->Cell.Local(); cell->PointIds->Allocate(128); cell->Points->Allocate(128); vtkIdList*& prims = this->Prims.Local(); prims->Allocate(128); // allocate some memory vtkPoints*& primPts = this->PrimPts.Local(); primPts->Allocate(128); // allocate some memory } void operator()(vtkIdType cellId, vtkIdType endCellId) { double* cellHts = this->CellHeights + cellId; auto scalars = vtk::DataArrayValueRange(this->Scalars).begin(); vtkGenericCell*& cell = this->Cell.Local(); vtkIdList*& prims = this->Prims.Local(); vtkPoints*& primPts = this->PrimPts.Local(); const double* d = this->Dims; const double* o = this->Origin; const double* h = this->H; vtkIdType p, pi, numPrims; int cellDim, ii, jj; double i, j, z, pc[2], ix, iy, w[4]; double x0[3], center[2]; int s0, s1, s2, s3; double sum, min, max; bool isFirst = vtkSMPTools::GetSingleThread(); // Process all cells of different types and dimensions for (; cellId < endCellId; ++cellId) { if (isFirst) { this->Filter->CheckAbort(); } if (this->Filter->GetAbortOutput()) { break; } this->Mesh->GetCell(cellId, cell); cellDim = cell->GetCellDimension(); cell->Triangulate(0, prims, primPts); numPrims = prims->GetNumberOfIds() / (cellDim + 1); // Loop over each primitive from the tessellation min = VTK_FLOAT_MAX; max = VTK_FLOAT_MIN; sum = 0.0; for (p = 0; p < numPrims; p++) { // Compute center of primitive center[0] = center[1] = 0.0; for (pi = 0; pi <= cellDim; ++pi) { primPts->GetPoint((cellDim + 1) * p + pi, x0); center[0] += x0[0]; center[1] += x0[1]; } center[0] /= static_cast(cellDim + 1); center[1] /= static_cast(cellDim + 1); // Location in image i = (center[0] - o[0]) / h[0]; j = (center[1] - o[1]) / h[1]; // Clamp to image. i,j is integral index into image pixels. It has to be done carefully // to manage the parametric coordinates correctly. if (i < 0.0) { ix = 0.0; pc[0] = 0.0; } else if (i >= (d[0] - 1)) { ix = d[0] - 2; pc[0] = 1.0; } else { pc[0] = modf(i, &ix); } if (j < 0.0) { iy = 0.0; pc[1] = 0.0; } else if (j >= (d[1] - 1)) { iy = d[1] - 2; pc[1] = 1.0; } else { pc[1] = modf(j, &iy); } // Parametric coordinates for interpolation vtkPixel::InterpolationFunctions(pc, w); ii = static_cast(ix); jj = static_cast(iy); // Interpolate height from surrounding data values s0 = ii + jj * d[0]; s1 = s0 + 1; s2 = s0 + d[0]; s3 = s2 + 1; z = w[0] * scalars[s0] + w[1] * scalars[s1] + w[2] * scalars[s2] + w[3] * scalars[s3]; // Gather information for final determination of height z min = std::min(z, min); max = std::max(z, max); sum += z; // to compute average } // for all tessellated primitives // Now set the cell height if (this->Strategy == vtkFitToHeightMapFilter::CELL_AVERAGE_HEIGHT) { z = fabs(sum / static_cast(numPrims)); } else if (this->Strategy == vtkFitToHeightMapFilter::CELL_MINIMUM_HEIGHT) { z = min; } else // if ( this->Strategy == vtkFitToHeightMapFilter::CELL_MAXIMUM_HEIGHT ) { z = max; } *cellHts++ = z; } } void Reduce() {} }; // FitCells struct FitCellsWorker { template void operator()(TScalarArray* scalars, int strategy, vtkPolyData* mesh, double* cellHts, int dims[3], double origin[3], double h[3], vtkFitToHeightMapFilter* filter) { FitCellsFunctor functor( scalars, strategy, mesh, cellHts, dims, origin, h, filter); vtkSMPTools::For(0, mesh->GetNumberOfCells(), functor); } }; } // anonymous namespace //------------------------------------------------------------------------------ // Construct object. Two inputs are mandatory. vtkFitToHeightMapFilter::vtkFitToHeightMapFilter() { this->SetNumberOfInputPorts(2); this->FittingStrategy = vtkFitToHeightMapFilter::POINT_PROJECTION; this->UseHeightMapOffset = true; this->Offset = 0.0; } //------------------------------------------------------------------------------ vtkFitToHeightMapFilter::~vtkFitToHeightMapFilter() = default; //------------------------------------------------------------------------------ int vtkFitToHeightMapFilter::RequestData(vtkInformation* vtkNotUsed(request), vtkInformationVector** inputVector, vtkInformationVector* outputVector) { vtkInformation* inInfo = inputVector[0]->GetInformationObject(0); vtkInformation* in2Info = inputVector[1]->GetInformationObject(0); vtkInformation* outInfo = outputVector->GetInformationObject(0); vtkDebugMacro(<< "Executing fit to height map"); // get the two inputs and output vtkPolyData* input = vtkPolyData::SafeDownCast(inInfo->Get(vtkDataObject::DATA_OBJECT())); vtkImageData* image = vtkImageData::SafeDownCast(in2Info->Get(vtkDataObject::DATA_OBJECT())); vtkPolyData* output = vtkPolyData::SafeDownCast(outInfo->Get(vtkDataObject::DATA_OBJECT())); // Check input if (input == nullptr || image == nullptr || output == nullptr) { vtkErrorMacro(<< "Bad input"); return 1; } vtkIdType numPts = input->GetNumberOfPoints(); vtkIdType numCells = input->GetNumberOfCells(); if (numPts < 1 || numCells < 1) { vtkDebugMacro(<< "Empty input"); return 1; } // Only process real-type points vtkPoints* inPts = input->GetPoints(); // Looking for a xy-oriented image int dims[3]; double origin[3], h[3]; image->GetDimensions(dims); image->GetOrigin(origin); image->GetSpacing(h); if (dims[0] < 2 || dims[1] < 2 || dims[2] != 1) { vtkErrorMacro(<< "Filter operates on 2D x-y images"); return 1; } // Finally warn if the image data does not fully contain the // input polydata. double inputBds[6], imageBds[6]; input->GetBounds(inputBds); image->GetBounds(imageBds); if (inputBds[0] < imageBds[0] || inputBds[1] > imageBds[1] || inputBds[2] < imageBds[2] || inputBds[3] > imageBds[3]) { vtkWarningMacro(<< "Input polydata extends beyond height map"); } this->Offset = (this->UseHeightMapOffset ? imageBds[4] : 0.0); // Okay we are ready to rock and roll... output->CopyStructure(input); vtkNew newPts; newPts->SetDataType(inPts->GetDataType()); newPts->SetNumberOfPoints(numPts); vtkPointData* pd = input->GetPointData(); vtkPointData* outputPD = output->GetPointData(); outputPD->CopyNormalsOff(); // normals are almost certainly messed up outputPD->PassData(pd); vtkCellData* cd = input->GetCellData(); vtkCellData* outputCD = output->GetCellData(); outputCD->PassData(cd); // We need random access to cells if (output->NeedToBuildCells()) { output->BuildCells(); } // This performs the projection of the points or cells. // We either process points or cells depending on strategy if (this->FittingStrategy <= vtkFitToHeightMapFilter::POINT_AVERAGE_HEIGHT) { using Dispatcher = vtkArrayDispatch::Dispatch2ByArray; FitPointsWorker worker; if (!Dispatcher::Execute(inPts->GetData(), image->GetPointData()->GetScalars(), worker, newPts->GetData(), dims, origin, h, this)) { worker(inPts->GetData(), image->GetPointData()->GetScalars(), newPts->GetData(), dims, origin, h, this); } // Now final rollup and adjustment of points this->AdjustPoints(output, numCells, newPts); } // points strategies else // We are processing cells { vtkNew> cellHtsArray; cellHtsArray->SetNumberOfValues(numCells); FitCellsWorker worker; if (!vtkArrayDispatch::Dispatch::Execute(image->GetPointData()->GetScalars(), worker, this->FittingStrategy, output, cellHtsArray->GetPointer(0), dims, origin, h, this)) { worker(image->GetPointData()->GetScalars(), this->FittingStrategy, output, cellHtsArray->GetPointer(0), dims, origin, h, this); } // Now final rollup and adjustment of points this->AdjustCells(output, numCells, cellHtsArray->GetPointer(0), inPts, newPts); } // cells strategies // Clean up and get out. Replace the output's shallow-copied points with // the new, projected points. output->SetPoints(newPts); return 1; } //------------------------------------------------------------------------------ // Based on the fitting strategy, adjust the point coordinates. void vtkFitToHeightMapFilter::AdjustPoints( vtkPolyData* output, vtkIdType numCells, vtkPoints* newPts) { vtkIdType cellId; vtkIdType npts; const vtkIdType* ptIds; vtkIdType pId; vtkIdType i; double sum, min, max, p0[3], z; vtkIdType numHits; // Nothing to do except adjust offset if point projection if (this->FittingStrategy == vtkFitToHeightMapFilter::POINT_PROJECTION) { if (this->UseHeightMapOffset) { npts = newPts->GetNumberOfPoints(); for (pId = 0; pId < npts; ++pId) { newPts->GetPoint(pId, p0); newPts->SetPoint(pId, p0[0], p0[1], p0[2] + this->Offset); } } return; } // Otherwise fancier point adjustment for (cellId = 0; cellId < numCells; ++cellId) { if (this->CheckAbort()) { break; } output->GetCellPoints(cellId, npts, ptIds); // Gather information about cell min = VTK_FLOAT_MAX; max = VTK_FLOAT_MIN; sum = 0.0; numHits = 0; for (i = 0; i < npts; ++i) { pId = ptIds[i]; numHits++; newPts->GetPoint(pId, p0); z = p0[2]; min = std::min(z, min); max = std::max(z, max); sum += z; // to compute average } // over primitive points // Adjust points as specified. if (numHits > 0) { if (this->FittingStrategy == vtkFitToHeightMapFilter::POINT_AVERAGE_HEIGHT) { z = fabs(sum / static_cast(numHits)); } else if (this->FittingStrategy == vtkFitToHeightMapFilter::POINT_MINIMUM_HEIGHT) { z = min; } else // if ( this->FittingStrategy == vtkFitToHeightMapFilter::POINT_MAXIMUM_HEIGHT ) { z = max; } for (i = 0; i < npts; ++i) { pId = ptIds[i]; newPts->GetPoint(pId, p0); newPts->SetPoint(pId, p0[0], p0[1], z + this->Offset); } } // if valid polygon } // for all cells } //------------------------------------------------------------------------------ // Based on the fitting strategy, adjust the points based on cell height // information. void vtkFitToHeightMapFilter::AdjustCells( vtkPolyData* output, vtkIdType numCells, double* cellHts, vtkPoints* inPts, vtkPoints* newPts) { vtkIdType cellId; vtkIdType npts; const vtkIdType* ptIds; vtkIdType pId; vtkIdType i; double z, p0[3]; for (cellId = 0; cellId < numCells; ++cellId) { if (this->CheckAbort()) { break; } z = cellHts[cellId]; output->GetCellPoints(cellId, npts, ptIds); for (i = 0; i < npts; ++i) { pId = ptIds[i]; inPts->GetPoint(pId, p0); newPts->SetPoint(pId, p0[0], p0[1], z + this->Offset); } } // for all cells } //------------------------------------------------------------------------------ // Specify the height map connection void vtkFitToHeightMapFilter::SetHeightMapConnection(vtkAlgorithmOutput* algOutput) { this->SetInputConnection(1, algOutput); } //------------------------------------------------------------------------------ // Specify the height map data void vtkFitToHeightMapFilter::SetHeightMapData(vtkImageData* id) { this->SetInputData(1, id); } //------------------------------------------------------------------------------ // Get a pointer to a source object at a specified table location. vtkImageData* vtkFitToHeightMapFilter::GetHeightMap() { return vtkImageData::SafeDownCast(this->GetExecutive()->GetInputData(1, 0)); } //------------------------------------------------------------------------------ vtkImageData* vtkFitToHeightMapFilter::GetHeightMap(vtkInformationVector* sourceInfo) { vtkInformation* info = sourceInfo->GetInformationObject(1); if (!info) { return nullptr; } return vtkImageData::SafeDownCast(info->Get(vtkDataObject::DATA_OBJECT())); } //------------------------------------------------------------------------------ int vtkFitToHeightMapFilter::FillInputPortInformation(int port, vtkInformation* info) { if (port == 0) { info->Set(vtkAlgorithm::INPUT_IS_REPEATABLE(), 0); info->Set(vtkAlgorithm::INPUT_IS_OPTIONAL(), 0); info->Set(vtkAlgorithm::INPUT_REQUIRED_DATA_TYPE(), "vtkPolyData"); } else if (port == 1) { info->Set(vtkAlgorithm::INPUT_IS_REPEATABLE(), 0); info->Set(vtkAlgorithm::INPUT_IS_OPTIONAL(), 0); info->Set(vtkAlgorithm::INPUT_REQUIRED_DATA_TYPE(), "vtkImageData"); } return 1; } //------------------------------------------------------------------------------ void vtkFitToHeightMapFilter::PrintSelf(ostream& os, vtkIndent indent) { this->Superclass::PrintSelf(os, indent); os << indent << "Fitting Strategy: " << this->FittingStrategy << "\n"; os << indent << "Use Height Map Offset: " << (this->UseHeightMapOffset ? "On\n" : "Off\n"); } VTK_ABI_NAMESPACE_END