// SPDX-FileCopyrightText: Copyright (c) Ken Martin, Will Schroeder, Bill Lorensen // SPDX-License-Identifier: BSD-3-Clause #include "vtkSelectEnclosedPoints.h" #include "vtkCellData.h" #include "vtkDataSet.h" #include "vtkExecutive.h" #include "vtkFeatureEdges.h" #include "vtkGarbageCollector.h" #include "vtkGenericCell.h" #include "vtkIdList.h" #include "vtkIdTypeArray.h" #include "vtkInformation.h" #include "vtkInformationVector.h" #include "vtkMath.h" #include "vtkObjectFactory.h" #include "vtkPointData.h" #include "vtkPolyData.h" #include "vtkRandomPool.h" #include "vtkSMPThreadLocal.h" #include "vtkSMPThreadLocalObject.h" #include "vtkSMPTools.h" #include "vtkStaticCellLocator.h" #include "vtkUnsignedCharArray.h" VTK_ABI_NAMESPACE_BEGIN vtkStandardNewMacro(vtkSelectEnclosedPoints); //------------------------------------------------------------------------------ // Classes support threading. Each point can be processed separately, so the // in/out containment check is threaded. namespace { //------------------------------------------------------------------------------ // The threaded core of the algorithm. Thread on point type. struct SelectInOutCheck { vtkIdType NumPts; vtkDataSet* DataSet; vtkPolyData* Surface; double Bounds[6]; double Length; double Tolerance; vtkStaticCellLocator* Locator; unsigned char* Hits; vtkSelectEnclosedPoints* Selector; vtkTypeBool InsideOut; vtkRandomPool* Sequence; vtkSMPThreadLocal Counter; vtkSelectEnclosedPoints* Filter; // Don't want to allocate working arrays on every thread invocation. Thread local // storage eliminates lots of new/delete. vtkSMPThreadLocalObject CellIds; vtkSMPThreadLocalObject Cell; SelectInOutCheck(vtkIdType numPts, vtkDataSet* ds, vtkPolyData* surface, double bds[6], double tol, vtkStaticCellLocator* loc, unsigned char* hits, vtkSelectEnclosedPoints* sel, vtkTypeBool io, vtkSelectEnclosedPoints* filter) : NumPts(numPts) , DataSet(ds) , Surface(surface) , Tolerance(tol) , Locator(loc) , Hits(hits) , Selector(sel) , InsideOut(io) , Filter(filter) { this->Bounds[0] = bds[0]; this->Bounds[1] = bds[1]; this->Bounds[2] = bds[2]; this->Bounds[3] = bds[3]; this->Bounds[4] = bds[4]; this->Bounds[5] = bds[5]; this->Length = sqrt((bds[1] - bds[0]) * (bds[1] - bds[0]) + (bds[3] - bds[2]) * (bds[3] - bds[2]) + (bds[5] - bds[4]) * (bds[5] - bds[4])); // Precompute a sufficiently large enough random sequence this->Sequence = vtkRandomPool::New(); this->Sequence->SetSize((numPts > 1500 ? numPts : 1500)); this->Sequence->GeneratePool(); } ~SelectInOutCheck() { this->Sequence->Delete(); } void Initialize() { vtkIdList*& cellIds = this->CellIds.Local(); cellIds->Allocate(512); vtkIntersectionCounter& counter = this->Counter.Local(); counter.SetTolerance(this->Tolerance); } void operator()(vtkIdType ptId, vtkIdType endPtId) { double x[3]; unsigned char* hits = this->Hits + ptId; vtkGenericCell*& cell = this->Cell.Local(); vtkIdList*& cellIds = this->CellIds.Local(); vtkIntersectionCounter& counter = this->Counter.Local(); bool isFirst = vtkSMPTools::GetSingleThread(); for (; ptId < endPtId; ++ptId) { if (isFirst) { this->Filter->CheckAbort(); } if (this->Filter->GetAbortOutput()) { break; } this->DataSet->GetPoint(ptId, x); if (vtkSelectEnclosedPoints::IsInsideSurface(x, this->Surface, this->Bounds, this->Length, this->Tolerance, this->Locator, cellIds, cell, counter, this->Sequence, ptId)) { *hits++ = (this->InsideOut ? 0 : 1); } else { *hits++ = (this->InsideOut ? 1 : 0); } } } void Reduce() {} static void Execute(vtkIdType numPts, vtkDataSet* ds, vtkPolyData* surface, double bds[6], double tol, vtkStaticCellLocator* loc, unsigned char* hits, vtkSelectEnclosedPoints* sel) { SelectInOutCheck inOut(numPts, ds, surface, bds, tol, loc, hits, sel, sel->GetInsideOut(), sel); vtkSMPTools::For(0, numPts, inOut); } }; // SelectInOutCheck } // anonymous namespace //------------------------------------------------------------------------------ // Construct object. vtkSelectEnclosedPoints::vtkSelectEnclosedPoints() { this->SetNumberOfInputPorts(2); this->CheckSurface = false; this->InsideOut = 0; this->Tolerance = 0.0001; this->InsideOutsideArray = nullptr; // These are needed to support backward compatibility this->CellLocator = vtkStaticCellLocator::New(); this->CellIds = vtkIdList::New(); this->Cell = vtkGenericCell::New(); } //------------------------------------------------------------------------------ vtkSelectEnclosedPoints::~vtkSelectEnclosedPoints() { if (this->InsideOutsideArray) { this->InsideOutsideArray->Delete(); } if (this->CellLocator) { vtkAbstractCellLocator* loc = this->CellLocator; this->CellLocator = nullptr; loc->Delete(); } this->CellIds->Delete(); this->Cell->Delete(); } //------------------------------------------------------------------------------ int vtkSelectEnclosedPoints::RequestData(vtkInformation* vtkNotUsed(request), vtkInformationVector** inputVector, vtkInformationVector* outputVector) { // get the info objects vtkInformation* inInfo = inputVector[0]->GetInformationObject(0); vtkInformation* in2Info = inputVector[1]->GetInformationObject(0); vtkInformation* outInfo = outputVector->GetInformationObject(0); // get the two inputs and output vtkDataSet* input = vtkDataSet::SafeDownCast(inInfo->Get(vtkDataObject::DATA_OBJECT())); vtkPolyData* surface = vtkPolyData::SafeDownCast(in2Info->Get(vtkDataObject::DATA_OBJECT())); vtkDataSet* output = vtkDataSet::SafeDownCast(outInfo->Get(vtkDataObject::DATA_OBJECT())); vtkDebugMacro("Selecting enclosed points"); // If requested, check that the surface is closed if (this->CheckSurface && !vtkSelectEnclosedPoints::IsSurfaceClosed(surface)) { return 0; } // Initialize search structures this->Initialize(surface); // Create array to mark inside/outside if (this->InsideOutsideArray) { this->InsideOutsideArray->Delete(); } this->InsideOutsideArray = vtkUnsignedCharArray::New(); vtkUnsignedCharArray* hits = this->InsideOutsideArray; // Loop over all input points determining inside/outside vtkIdType numPts = input->GetNumberOfPoints(); hits->SetNumberOfValues(numPts); unsigned char* hitsPtr = hits->GetPointer(0); // Process the points in parallel SelectInOutCheck::Execute( numPts, input, surface, this->Bounds, this->Tolerance, this->CellLocator, hitsPtr, this); // Copy all the input geometry and data to the output. output->CopyStructure(input); output->GetPointData()->PassData(input->GetPointData()); output->GetCellData()->PassData(input->GetCellData()); // Add the new scalars array to the output. hits->SetName("SelectedPoints"); output->GetPointData()->AddArray(hits); output->GetPointData()->SetActiveScalars(hits->GetName()); // release memory this->Complete(); return 1; } //------------------------------------------------------------------------------ int vtkSelectEnclosedPoints::IsSurfaceClosed(vtkPolyData* surface) { vtkPolyData* checker = vtkPolyData::New(); checker->CopyStructure(surface); vtkFeatureEdges* features = vtkFeatureEdges::New(); features->SetInputData(checker); features->BoundaryEdgesOn(); features->NonManifoldEdgesOn(); features->ManifoldEdgesOff(); features->FeatureEdgesOff(); features->Update(); vtkIdType numCells = features->GetOutput()->GetNumberOfCells(); features->Delete(); checker->Delete(); if (numCells > 0) { return 0; } else { return 1; } } //------------------------------------------------------------------------------ void vtkSelectEnclosedPoints::Initialize(vtkPolyData* surface) { if (!this->CellLocator) { this->CellLocator = vtkStaticCellLocator::New(); } this->Surface = surface; surface->GetBounds(this->Bounds); this->Length = surface->GetLength(); // Set up structures for acceleration ray casting this->CellLocator->SetDataSet(surface); this->CellLocator->BuildLocator(); } //------------------------------------------------------------------------------ int vtkSelectEnclosedPoints::IsInside(vtkIdType inputPtId) { if (!this->InsideOutsideArray || this->InsideOutsideArray->GetValue(inputPtId) == 0) { return 0; } else { return 1; } } //------------------------------------------------------------------------------ int vtkSelectEnclosedPoints::IsInsideSurface(double x, double y, double z) { double xyz[3]; xyz[0] = x; xyz[1] = y; xyz[2] = z; return this->IsInsideSurface(xyz); } //------------------------------------------------------------------------------ // This is done to preserve backward compatibility. However it is not thread // safe due to the use of the data member CellIds and Cell. int vtkSelectEnclosedPoints::IsInsideSurface(double x[3]) { vtkIntersectionCounter counter(this->Tolerance, this->Length); return vtkSelectEnclosedPoints::IsInsideSurface(x, this->Surface, this->Bounds, this->Length, this->Tolerance, this->CellLocator, this->CellIds, this->Cell, counter); } //------------------------------------------------------------------------------ // General method uses ray casting to determine in/out. Since this is a // numerically delicate operation, we use a crude "statistical" method (based // on voting) to provide a better answer. Plus there is a process to merge // nearly coincident points along the intersection rays. // // This is a static method so it can be used by other filters; hence the // many parameters used. // // Provision for reproducible threaded random number generation is made by // supporting the precomputation of a random sequence (see vtkRandomPool). // #define VTK_MAX_ITER 10 // Maximum iterations for ray-firing #define VTK_VOTE_THRESHOLD 2 // Vote margin for test int vtkSelectEnclosedPoints::IsInsideSurface(double x[3], vtkPolyData* surface, double bds[6], double length, double tolerance, vtkAbstractCellLocator* locator, vtkIdList* cellIds, vtkGenericCell* genCell, vtkIntersectionCounter& counter, vtkRandomPool* seq, vtkIdType seqIdx) { // do a quick inside bounds check against the surface bounds if (x[0] < bds[0] || x[0] > bds[1] || x[1] < bds[2] || x[1] > bds[3] || x[2] < bds[4] || x[2] > bds[5]) { return 0; } // Shortly we are going to start firing rays. It's important that the rays // are long enough to go from the test point all the way through the // enclosing surface. So compute a vector from the test point to the center // of the surface, and then add in the length (diagonal of bounding box) of // the surface. double offset[3], totalLength; offset[0] = x[0] - ((bds[0] + bds[1]) / 2.0); offset[1] = x[1] - ((bds[2] + bds[3]) / 2.0); offset[2] = x[2] - ((bds[4] + bds[5]) / 2.0); totalLength = length + vtkMath::Norm(offset); // Perform in/out by shooting random rays. Multiple rays are fired // to improve accuracy of the result. // // The variable iterNumber counts the number of rays fired and is // limited by the defined variable VTK_MAX_ITER. // // The variable deltaVotes keeps track of the number of votes for // "in" versus "out" of the surface. When deltaVotes > 0, more votes // have counted for "in" than "out". When deltaVotes < 0, more votes // have counted for "out" than "in". When the delta_vote exceeds or // equals the defined variable VTK_VOTE_THRESHOLD, then the // appropriate "in" or "out" status is returned. // double rayMag, ray[3], xray[3], t, pcoords[3], xint[3]; int i, numInts, iterNumber, deltaVotes, subId; vtkIdType idx, numCells; double tol = tolerance * length; for (deltaVotes = 0, iterNumber = 1; (iterNumber < VTK_MAX_ITER) && (abs(deltaVotes) < VTK_VOTE_THRESHOLD); iterNumber++) { // Define a random ray to fire. rayMag = 0.0; while (rayMag == 0.0) { if (seq == nullptr) // in serial mode { ray[0] = vtkMath::Random(-1.0, 1.0); ray[1] = vtkMath::Random(-1.0, 1.0); ray[2] = vtkMath::Random(-1.0, 1.0); } else // threading, have to scale sequence -1<=x<=1 { ray[0] = 2.0 * (0.5 - seq->GetValue(seqIdx++)); ray[1] = 2.0 * (0.5 - seq->GetValue(seqIdx++)); ray[2] = 2.0 * (0.5 - seq->GetValue(seqIdx++)); } rayMag = vtkMath::Norm(ray); } // The ray must be appropriately sized wrt the bounding box. (It has to // go all the way through the bounding box. Remember though that an // "inside bounds" check was done previously so diagonal length should // be long enough.) for (i = 0; i < 3; i++) { xray[i] = x[i] + 2.0 * totalLength * (ray[i] / rayMag); } // Retrieve the candidate cells from the locator to limit the // intersections to be attempted. locator->FindCellsAlongLine(x, xray, tol, cellIds); numCells = cellIds->GetNumberOfIds(); counter.Reset(); for (idx = 0; idx < numCells; idx++) { surface->GetCell(cellIds->GetId(idx), genCell); if (genCell->IntersectWithLine(x, xray, tol, t, xint, pcoords, subId)) { counter.AddIntersection(t); } } // for all candidate cells along this ray numInts = counter.CountIntersections(); if ((numInts % 2) == 0) // if outside { --deltaVotes; } else // if inside { ++deltaVotes; } } // try another ray // If the number of votes is positive, the point is inside // return (deltaVotes < 0 ? 0 : 1); } #undef VTK_MAX_ITER #undef VTK_VOTE_THRESHOLD //------------------------------------------------------------------------------ // Specify the second enclosing surface input via a connection void vtkSelectEnclosedPoints::SetSurfaceConnection(vtkAlgorithmOutput* algOutput) { this->SetInputConnection(1, algOutput); } //------------------------------------------------------------------------------ // Specify the second enclosing surface input data void vtkSelectEnclosedPoints::SetSurfaceData(vtkPolyData* pd) { this->SetInputData(1, pd); } //------------------------------------------------------------------------------ // Return the enclosing surface vtkPolyData* vtkSelectEnclosedPoints::GetSurface() { return vtkPolyData::SafeDownCast(this->GetExecutive()->GetInputData(1, 0)); } //------------------------------------------------------------------------------ vtkPolyData* vtkSelectEnclosedPoints::GetSurface(vtkInformationVector* sourceInfo) { vtkInformation* info = sourceInfo->GetInformationObject(1); if (!info) { return nullptr; } return vtkPolyData::SafeDownCast(info->Get(vtkDataObject::DATA_OBJECT())); } //------------------------------------------------------------------------------ int vtkSelectEnclosedPoints::FillInputPortInformation(int port, vtkInformation* info) { if (port == 0) { info->Set(vtkAlgorithm::INPUT_REQUIRED_DATA_TYPE(), "vtkDataSet"); } 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(), "vtkPolyData"); } return 1; } //------------------------------------------------------------------------------ void vtkSelectEnclosedPoints::Complete() { this->CellLocator->FreeSearchStructure(); } //------------------------------------------------------------------------------ void vtkSelectEnclosedPoints::ReportReferences(vtkGarbageCollector* collector) { this->Superclass::ReportReferences(collector); // These filters share our input and are therefore involved in a // reference loop. vtkGarbageCollectorReport(collector, this->CellLocator, "CellLocator"); } //------------------------------------------------------------------------------ void vtkSelectEnclosedPoints::PrintSelf(ostream& os, vtkIndent indent) { this->Superclass::PrintSelf(os, indent); os << indent << "Check Surface: " << (this->CheckSurface ? "On\n" : "Off\n"); os << indent << "Inside Out: " << (this->InsideOut ? "On\n" : "Off\n"); os << indent << "Tolerance: " << this->Tolerance << "\n"; } VTK_ABI_NAMESPACE_END