// SPDX-FileCopyrightText: Copyright (c) Ken Martin, Will Schroeder, Bill Lorensen // SPDX-License-Identifier: BSD-3-Clause #include "vtkCookieCutter.h" #include "vtkCellData.h" #include "vtkDoubleArray.h" #include "vtkExecutive.h" #include "vtkInformation.h" #include "vtkInformationVector.h" #include "vtkLine.h" #include "vtkLogger.h" #include "vtkMergePoints.h" #include "vtkPointData.h" #include "vtkPolygon.h" #include "vtkStreamingDemandDrivenPipeline.h" #include #include #include VTK_ABI_NAMESPACE_BEGIN vtkStandardNewMacro(vtkCookieCutter); vtkCxxSetObjectMacro(vtkCookieCutter, Locator, vtkIncrementalPointLocator); // Helper functions------------------------------------------------------------ // Precision in the parametric coordinate system. Note that nearly coincident // intersection points, and/or parallel segments routinely occur during // processing. This tolerance is used to merge nearly coincident points. #define VTK_DEGENERATE_TOL 0.001 namespace { // Helper function to create cutting/trim loops from the second filter input, // which may consist of polylines and/or polygons. Note that the polylines // are assumed to close in on themselves, meaning that the first and last // points are repeated, or if not, implicitly assumed to close and also to // not self-intersect. Also, polylines and polygons must consist of three // points or more (it is assumed that they define a non-zero area; if the // area is zero the cutting process will likely fail). The valid loops are // placed into a cell array and returned. If the returned value is a nullptr, // then no trim lines/polygons are available. vtkSmartPointer BuildTrimLoops(vtkPolyData* loops) { // See what we have vtkCellArray* loopLines = loops->GetLines(); vtkCellArray* loopPolys = loops->GetPolys(); vtkIdType numLoopLines = loopLines->GetNumberOfCells(); vtkIdType numLoopPolys = loopPolys->GetNumberOfCells(); // If no lines, just return the polygon array if (numLoopLines < 1 && numLoopPolys < 1) { return nullptr; } else if (numLoopLines > 0 && numLoopPolys < 1) { return loopLines; } // Just has trim lines else if (numLoopPolys > 0 && numLoopLines < 1) { return loopPolys; } // Just has trim polygons else // Have to create a combined collection of cut polygons and lines { vtkNew cutLoops; vtkIdType npts; const vtkIdType* pts; for (loopLines->InitTraversal(); loopLines->GetNextCell(npts, pts);) { // The polyline closes in on itself, to make it a polygon, we eliminate // the last point. This is far from foolproof, especially if the polyline // self-intersects etc. npts = (pts[0] == pts[npts - 1] ? npts - 1 : npts); if (npts > 2) { cutLoops->InsertNextCell(npts, pts); } } // Process polygons for (loopPolys->InitTraversal(); loopPolys->GetNextCell(npts, pts);) { if (npts > 2) { cutLoops->InsertNextCell(npts, pts); } } return cutLoops; } // Combination of trim lines and polygons } // BuildTrimLoops // Note on the definition of parametric coordinates: Given a sequence of // line segments (vi,vi+1) that form a primitive (e.g., polyline or // polygon), the parametric coordinate t along the primitive is // [i,i+1). Any point on a segment (like an intersection point on the // segment) has parametric coordinate i+t, where 0 <= t < 1. // Infrastructure for cropping---------------------------------------------- // Note that the classification Class data member is has multiple meanings, // being applied to points as well as line segments. Initially the vertices // are classified. Then the segments [i,(i+1)) are classified by combining // the vertex classifications (and possibly other information like in/out // tests). For example, the INSIDE or OUTSIDE classification refers to a // segment that lies inside or outside the boundary of the loop // (respectively), or to a point that may be inside or outside. The // INTERSECTION classification refers to a point generated by the // intersection of two edges. MULT_INTS refers to multiple >1 merged // intersection points. The ON classification refers to a point (maybe // merged) that has at least one ON point. The ON classification for a // segment also holds if both of the end points are ON and the OnId of both // are the same. struct SortPoint { // vertex and segment classification enum ClassEnum { VERTEX = 0, OUTSIDE = 1, INSIDE = 2, INTERSECTION = 4, MULT_INTS = 8, ON = 16 }; double T; // parametric coordinate along primitive (line or polygon) int Class; // classification of vertex or segment vtkIdType Id; // Id assigned to INTERSECTION or ON point, used later to build loops vtkIdType OnId; // Id of edge pair if classification of point is ON double X[3]; // position of point SortPoint(double t, int c, vtkIdType id, vtkIdType onId, double x[3]) : T(t) , Class(c) , Id(id) , OnId(onId) { this->X[0] = x[0]; this->X[1] = x[1]; this->X[2] = x[2]; } }; // Special sort operation of intersection points via parametric coordinate T--- bool PointSorter(SortPoint const& lhs, SortPoint const& rhs) { return lhs.T < rhs.T; } // Vectors are used to hold points, eventually sorted around the perimeter of // polygons, or along lines. using SortedPointsType = std::vector; // This is an adjunct to the SortPoint struct. It is used when passing point // data through to the output, and used by vtkAttributeManager. It collects // information about the point data so that when points are inserted into the // final polydata output, the appropriate interpolation (or CopyData()) // process can be used. These point attributes (which are referenced via // SortPoint::Id in the vtkAttributeManager) may originate from a mesh vertex // (MeshAttribute); a loop vertex (LoopAttribute); interpolation along a mesh // edge (MeshEdgeAttribute); or interpolation along a loop edge // (LoopEdgeAttribute). struct PointAttribute { enum PointAttributeEnum { MeshVertex = 0, LoopVertex = 1, Intersection = 2, MeshEdge = 3, LoopEdge = 4 }; // Note that V0 may be the originating point is from which to copy // data, or (V0,V1) may be the edge from which to interpolate data. int AttributeType; // one of PointAttributeEnum vtkIdType V0; // Vertex id(s) vtkIdType V1; double T; // the parametric coordinate along the interpolating edge PointAttribute(int attType, vtkIdType v0, vtkIdType v1, double t) : AttributeType(attType) , V0(v0) , V1(v1) , T(t) { } }; // Vectors are used to hold point attributes, eventually processed during the // crop operation to produce output point data. using PointAttributesType = std::vector; // Used to analyze and merge nearly coincident points---------------------- struct MergeRange { // points to merge [StartIdx,EndIdx) possibly modulo around loop int StartIdx; int EndIdx; MergeRange(int start, int end) : StartIdx(start) , EndIdx(end) { } }; // Vectors are used to hold merge regions using MergeRangeType = std::vector; // Convenience function classifies a segment of a loop or polyline-------- int ClassifySegment(SortedPointsType& sortedPoints, int i, int j, vtkIdType npts, double* p, double bds[6], double n[3]) { double midSegment[3]; midSegment[0] = 0.5 * (sortedPoints[i].X[0] + sortedPoints[j].X[0]); midSegment[1] = 0.5 * (sortedPoints[i].X[1] + sortedPoints[j].X[1]); midSegment[2] = 0.5 * (sortedPoints[i].X[2] + sortedPoints[j].X[2]); if (vtkPolygon::PointInPolygon(midSegment, npts, p, bds, n) == 1) { return SortPoint::INSIDE; } else { return SortPoint::OUTSIDE; } } // Merge the list of coincident intersections along a polyline------------- // The points are assumed sorted in parametric coordinates, not closed. int CleanSortedPolyline(SortedPointsType& sortedPoints) { int num, i, ip, j; double t, tp; bool needsAnalysis = false; vtkIdType maxId; // First do a quick check. If there are no degenerate situations just // return. num = static_cast(sortedPoints.size()); maxId = sortedPoints[0].Id; for (i = 0; i < (num - 1); ++i) { ip = i + 1; t = sortedPoints[i].T; tp = sortedPoints[ip].T; maxId = (sortedPoints[ip].Id > maxId ? sortedPoints[ip].Id : maxId); if (fabs(tp - t) <= VTK_DEGENERATE_TOL) { needsAnalysis = true; } } if (!needsAnalysis) { return maxId + 1; } // If here a deeper analysis is required. We need to segment groups of // points and/or vertices; deleting some and updating others. int start = 0, end = 0; MergeRangeType mergeRange; // segment nearly coincident points into groups [start,end) int imax = num - 1; for (i = 0; i < num;) { t = sortedPoints[i].T; if (end == (num - 1)) { // last point may require special treatment mergeRange.emplace_back(end, end + 1); break; } ip = i + 1; tp = sortedPoints[ip].T; // special treatment for first point in the loop while (fabs(tp - t) <= VTK_DEGENERATE_TOL && ip < imax) { ip++; tp = sortedPoints[ip].T; } end = ip; // Add new segment mergeRange.emplace_back(start, end); // Move to next segment i = start = end; } // Now perform the analysis and update the sorted points SortedPointsType newSortedPoints; vtkIdType minId, onId, minX = 0; int spc, nints, sze, numMR = static_cast(mergeRange.size()); double minT; for (i = 0; i < numMR; ++i) { start = mergeRange[i].StartIdx; end = mergeRange[i].EndIdx; sze = (end > start ? end - start : (end + num) - start); if (sze == 1) // just copy vertex { newSortedPoints.emplace_back(sortedPoints[start]); continue; } minId = onId = VTK_ID_MAX; minT = sortedPoints[start].T; minX = start; spc = SortPoint::VERTEX; for (nints = 0, j = 0; j < sze; ++j) { ip = start + j; if (sortedPoints[ip].Id >= 0) { nints++; minId = (sortedPoints[ip].Id >= 0 && sortedPoints[ip].Id < minId ? sortedPoints[ip].Id : minId); onId = (sortedPoints[ip].OnId >= 0 && sortedPoints[ip].OnId < onId ? sortedPoints[ip].OnId : onId); if (sortedPoints[ip].T < minT) { minT = sortedPoints[ip].T; minX = ip; } spc |= sortedPoints[ip].Class; } } if (spc == SortPoint::INTERSECTION && nints > 1) { spc = SortPoint::MULT_INTS; } newSortedPoints.emplace_back(minT, spc, minId, onId, sortedPoints[minX].X); } // across all merge ranges // Update the sorted points array, now clean! sortedPoints = newSortedPoints; // Max id is used for allocating some space shortly num = static_cast(sortedPoints.size()); for (maxId = 0, i = 0; i < num; ++i) { maxId = (sortedPoints[i].Id > maxId ? sortedPoints[i].Id : maxId); } return maxId + 1; } // Clean up the list of intersections around the polygon------------------- // The points are assumed sorted in parametric coordinates, closed // loop. Nearly coincident points are merged. // void CleanSortedPolygon(vtkIdType npts, SortedPointsType& sortedPoints) { int num, i, im, ip, j; double t, tm, tp = 0, moduloOffset = static_cast(npts); bool needsAnalysis = false; // First do a quick check. If there are no degenerate situations just // return. num = static_cast(sortedPoints.size()); for (i = 0; i < num; ++i) { ip = (i + 1) % num; t = sortedPoints[i].T; if ((tp = sortedPoints[ip].T) < t) { // in case of modulo wrap tp += moduloOffset; } if (fabs(tp - t) <= VTK_DEGENERATE_TOL) { needsAnalysis = true; } } if (!needsAnalysis) { return; } // If here a deeper analysis is required. We need to segment groups of // points and/or vertices; eventually deleting some and updating the // classification of the remaining point. int start = 0, end = 0; MergeRangeType mergeRange; // Segment nearly coincident points into groups [start,end) int imax = num; for (i = 0; i < imax;) { if (i == (imax - 1)) { mergeRange.emplace_back(i, imax); break; } t = sortedPoints[i].T; ip = (i + 1) % num; tp = sortedPoints[ip].T; // Special treatment for first point in the loop (due to modulo wrap) if (i == 0) { im = num - 1; tm = npts - sortedPoints[im].T; while (fabs(t - tm) <= VTK_DEGENERATE_TOL) { im--; tm = npts - sortedPoints[im].T; } start = (im + 1) % num; imax = (start == 0 ? num : start); } // Now proceed in forward direction while (fabs(tp - t) <= VTK_DEGENERATE_TOL && ip < imax) { ip++; tp = sortedPoints[ip % num].T; } end = ip; // Add new segment mergeRange.emplace_back(start, end); // Move to next segment i = start = end; } // Now perform the analysis and update the sorted points SortedPointsType newSortedPoints; vtkIdType minId, onId, minX = 0; int spc, nints, sze, numMR = static_cast(mergeRange.size()); double minT; for (i = 0; i < numMR; ++i) { start = mergeRange[i].StartIdx; end = mergeRange[i].EndIdx; sze = (end > start ? end - start : (end + num) - start); if (sze == 1) // just copy vertex { newSortedPoints.emplace_back(sortedPoints[start]); continue; } // Aggregate the final classification, point id, and other information // for merged point. minId = onId = VTK_ID_MAX; minT = sortedPoints[start].T; minX = start; spc = SortPoint::VERTEX; for (nints = 0, j = 0; j < sze; ++j) { ip = (start + j) % num; if (sortedPoints[ip].Class != SortPoint::VERTEX) { nints++; minId = (sortedPoints[ip].Id >= 0 && sortedPoints[ip].Id < minId ? sortedPoints[ip].Id : minId); onId = (sortedPoints[ip].OnId >= 0 && sortedPoints[ip].OnId < onId ? sortedPoints[ip].OnId : onId); spc |= sortedPoints[ip].Class; if (sortedPoints[ip].T < minT) { minT = sortedPoints[ip].T; minX = ip; } } } if (spc == SortPoint::INTERSECTION && nints > 1) { spc = SortPoint::MULT_INTS; } newSortedPoints.emplace_back(minT, spc, minId, onId, sortedPoints[minX].X); } // across all merge ranges // Update the sorted points array, now clean! sortedPoints = newSortedPoints; } // Classify polyline segments---------------------------------------------- // Entering this function the points are classified as either VERTEX, // INTERSECTION, MULT_INTS, or ON. Combine this information to classify each // segment of the polyline. Note that whenever possible a previous segment // classification is propagated to the next segment to avoid extra in/out tests. int ClassifyPolyline( SortedPointsType& sortedPoints, vtkIdType npts, double* p, double bds[6], double n[3]) { // Check for Degenerate case. Can happen when all points are merged to one. int num = static_cast(sortedPoints.size()); if (num < 2) { sortedPoints[0].Class = SortPoint::OUTSIDE; return 1; } // Classify the initial segment. int prevSegClass; if (sortedPoints[0].Class >= SortPoint::ON && sortedPoints[1].Class >= SortPoint::ON && sortedPoints[0].OnId == sortedPoints[1].OnId) { prevSegClass = SortPoint::ON; } else // perform in/out test { prevSegClass = ClassifySegment(sortedPoints, 0, 1, npts, p, bds, n); sortedPoints[0].Class = prevSegClass; } // Okay now work along the polyline, propagating classification when possible // and otherwise determining the classification of each segment. int i, ip; int currClass, nextClass; for (i = 1; i < (num - 1); ++i) { ip = i + 1; currClass = sortedPoints[i].Class; nextClass = sortedPoints[ip].Class; if (currClass == SortPoint::VERTEX) { // propagate forward } else if (currClass == SortPoint::INTERSECTION) { // Flip classification prevSegClass = (prevSegClass == SortPoint::INSIDE ? SortPoint::OUTSIDE : SortPoint::INSIDE); } else if (currClass >= SortPoint::ON && nextClass >= SortPoint::ON && sortedPoints[i].OnId == sortedPoints[ip].OnId) { // Segment is on loop segment prevSegClass = SortPoint::ON; } else { // complicated, do an in/out check prevSegClass = ClassifySegment(sortedPoints, i, ip, npts, p, bds, n); } sortedPoints[i].Class = prevSegClass; } return 1; } // Classify polygon segments---------------------------------------------- // Entering this function the points are classified as either VERTEX, // INTERSECTION, MULT_INTS, or ON. Combine this information to classify each // segment of the polygon. Note that whenever possible a previous segment // classification is propagated to the next segment to avoid extra in/out tests. // This deals with closed, modulo processing. Return a non-zero value if the // loop is complex, i.e., has a "ON" segment classification. int ClassifyPolygon( SortedPointsType& sortedPoints, vtkIdType npts, double* p, double bds[6], double n[3]) { // Check for Degenerate case. Can happen when all points are merged to one or two. int num = static_cast(sortedPoints.size()); if (num < 3) { for (int i = 0; i < num; ++i) { sortedPoints[i].Class = SortPoint::OUTSIDE; } return 0; } // Classify the initial segment. int segClass, startClass = sortedPoints[0].Class, hasOnClassification = 0; if (sortedPoints[0].Class >= SortPoint::ON && sortedPoints[1].Class >= SortPoint::ON && sortedPoints[0].OnId == sortedPoints[1].OnId) { segClass = SortPoint::ON; hasOnClassification = 1; } else // perform in/out test { segClass = ClassifySegment(sortedPoints, 0, 1, npts, p, bds, n); } sortedPoints[0].Class = segClass; // Okay now work around the polygon, propagating segment classification // when possible and otherwise determining the classification of each // segment. int i, ip; int currClass, nextClass; for (i = 1; i < num; ++i) { currClass = sortedPoints[i].Class; ip = (i + 1) % num; nextClass = (ip == 0 ? startClass : sortedPoints[ip].Class); // remember start if (currClass == SortPoint::VERTEX) { // Propagate the classification forward } else if (currClass == SortPoint::INTERSECTION) { // Flip the classification segClass = (segClass == SortPoint::INSIDE ? SortPoint::OUTSIDE : SortPoint::INSIDE); } else if (currClass >= SortPoint::ON && nextClass >= SortPoint::ON && sortedPoints[i].OnId == sortedPoints[ip].OnId) { // Segment is on loop segment segClass = SortPoint::ON; hasOnClassification = 1; } else { // complicated, do an in/out check segClass = ClassifySegment(sortedPoints, i, ip, npts, p, bds, n); } sortedPoints[i].Class = segClass; } return hasOnClassification; } // A helper class used to manage point and cell attributes, and simplify the // overall cookie cutting code. struct vtkAttributeManager { // Support cell data processing vtkCellData* InCellData; vtkCellData* OutCellData; // Support point data processing int PointInterpolation; // indicate how to interpolate point data PointAttributesType PointAttributes; // point attributes for this cell SortedPointsType* LoopPts; SortedPointsType* PolyPts; vtkSmartPointer MeshPtData; vtkSmartPointer LoopPtData; vtkPointData* OutPtData; // Helper function to determine if two vtkDataSetAttributes are equivalent and may // be used together for copying and interpolating point data. bool HaveEquivalentAttributes(vtkDataSetAttributes* dsa0, vtkDataSetAttributes* dsa1) { // Check that the number of arrays is the same int numArrays0 = dsa0->GetNumberOfArrays(); int numArrays1 = dsa1->GetNumberOfArrays(); if (numArrays0 != numArrays1) { return false; } // Check that identical arrays exist in both (name, number of components, order) for (auto arrayNum = 0; arrayNum < numArrays0; ++arrayNum) { const char *name0, *name1; vtkDataArray* da0 = dsa0->GetArray(arrayNum); vtkDataArray* da1 = dsa1->GetArray(arrayNum); if (!da0 || !da1 || da0->GetDataType() != da1->GetDataType() || da0->GetNumberOfComponents() != da1->GetNumberOfComponents() || !(name0 = dsa0->GetArrayName(arrayNum)) || !(name1 = dsa1->GetArrayName(arrayNum)) || strcmp(name0, name1) != 0) { return false; } } return true; } // Build point data attributes that are the set intersection of the mesh // point data attributes, and the loop point data attributes. The // attributes must be equivalent because of the two-way interpolation/copy // data process that occurs. void IntersectAttributes(vtkPointData* meshPD, vtkPointData* loopPD) { this->MeshPtData.TakeReference(vtkPointData::New()); this->LoopPtData.TakeReference(vtkPointData::New()); // Loop over the the first attributes determining what data arrays are // common. int numMeshPDArrays = meshPD->GetNumberOfArrays(); for (auto arrayNum = 0; arrayNum < numMeshPDArrays; ++arrayNum) { vtkDataArray *da0 = meshPD->GetArray(arrayNum), *da1; const char* name = meshPD->GetArrayName(arrayNum); if ((da1 = loopPD->GetArray(name)) != nullptr && da1->GetDataType() == da0->GetDataType() && da1->GetNumberOfComponents() == da0->GetNumberOfComponents()) { this->MeshPtData->AddArray(da0); this->LoopPtData->AddArray(da1); } } } // Constructor: Initialize the process of attribute processing vtkAttributeManager(vtkPolyData* input, vtkPolyData* loops, vtkPolyData* output, bool passCD, bool passPD, int ptInterpolation) { // Process cell data if requested this->InCellData = nullptr; this->OutCellData = nullptr; if (passCD) { this->InCellData = input->GetCellData(); this->OutCellData = output->GetCellData(); this->OutCellData->CopyAllocate(this->InCellData); } // Process point data if requested. Note the complication due to how the // new point data is interpolated from line-line intersections. this->PointInterpolation = ptInterpolation; this->MeshPtData = nullptr; this->LoopPtData = nullptr; this->OutPtData = nullptr; if (passPD) { vtkPointData* inPD = input->GetPointData(); vtkPointData* loopPD = loops->GetPointData(); // If trim edge interpoltion is specified, make sure that the trim loop point // attributes match the mesh point attributes. If not, point data is not passed // to the output. if (this->HaveEquivalentAttributes(inPD, loopPD)) { this->MeshPtData = inPD; this->LoopPtData = loopPD; } else { vtkLog(WARNING, "Mesh and trim loop point data attributes are different, only common point data arrays " "will be processed"); this->IntersectAttributes(inPD, loopPD); } // if point attributes identical this->OutPtData = output->GetPointData(); this->OutPtData->CopyAllocate(this->MeshPtData); } // if passing point data } // constructor // Copy cell data from the input to the output. void CopyCellData(vtkIdType inCellId, vtkIdType outCellId) { if (this->InCellData) { this->OutCellData->CopyData(this->InCellData, inCellId, outCellId); } } // Processing point data is tricky because two sets of point attributes may // be in play. Point which are passed through, can just use the // conventional vtkDataSetAttributes::CopyData() approach (copy from input // mesh to output mesh). However, intersection points may be interpolated // from the mesh edges, or from the trim loop edges. This means using the // correct point data to interpolate from (via InterpolateEdge()). // In some simple cases, a straightforward copying of point data from the // input mesh to the output mesh can be used. void CopyPointData(vtkIdType inPtId, vtkIdType outPtId) { if (this->MeshPtData) { this->OutPtData->CopyData(this->MeshPtData, inPtId, outPtId); } } // Because the cookie cutter algorithm processes cells on a cell-by-cell // basis, before processing each cell the point data management process // must be reset. Access to the attribute data is through the // sorted points list. void InitializePointAttributeCollection(SortedPointsType* polyPts, SortedPointsType* loopPts) { this->PolyPts = polyPts; this->LoopPts = loopPts; this->PointAttributes.clear(); } // Add point attribute data to the vector of attributes. Note that there is // an implicit ordering of points consisted with SortPoints: the // SortPoint::Id refers to the position of the point attribute in the // PointAttributes vector. void AddPointAttribute(int attType, vtkIdType m0 = (-1), vtkIdType m1 = (-1), vtkIdType l0 = (-1), vtkIdType l1 = (-1), double tm = 0.0, double tl = 0.0) { // See if we are collecting point attribute data if (this->OutPtData == nullptr) { return; } // See if it's a vertex (CopyData) if (attType < PointAttribute::Intersection) { this->PointAttributes.emplace_back(attType, m0, m1, tm); return; } // See if it's an edge (InterpolateData) if (this->PointInterpolation == vtkCookieCutter::USE_MESH_EDGES) { this->PointAttributes.emplace_back(PointAttribute::MeshEdge, m0, m1, tm); } else // if ( this->PointInterpolation == vtkCookieCutter::USE_LOOP_EDGES ) { this->PointAttributes.emplace_back(PointAttribute::LoopEdge, l0, l1, tl); } } // Interpolate or pass point data from either the input mesh or trim loop // edges, to the output mesh. void InterpolatePointData(vtkIdType ptId, vtkIdType outPtId) { // Make sure we actually want to process point data if (!this->OutPtData) { return; } // Okay, good to process, grab the point attributes. PointAttribute& ptAttr = this->PointAttributes[ptId]; // Maybe just copying data from mesh or trim loop to output if (ptAttr.AttributeType == PointAttribute::MeshVertex) { this->OutPtData->CopyData(this->MeshPtData, ptAttr.V0, outPtId); } else if (ptAttr.AttributeType == PointAttribute::LoopVertex) { this->OutPtData->CopyData(this->LoopPtData, ptAttr.V0, outPtId); } // Otherwise interpolating along an edge else { if (ptAttr.AttributeType == PointAttribute::MeshEdge) { this->OutPtData->InterpolateEdge(this->MeshPtData, outPtId, ptAttr.V0, ptAttr.V1, ptAttr.T); } else // if ( ptAttr.AttributeType == PointAttribute::LoopEdge ) { this->OutPtData->InterpolateEdge(this->LoopPtData, outPtId, ptAttr.V0, ptAttr.V1, ptAttr.T); } } } }; // vtkAttributeManager // A helper class used to crop vtkCookieCutter input polys and lines. class vtkCookieCutterHelper { public: vtkCookieCutterHelper(vtkIncrementalPointLocator* locator, vtkPoints* inPts, vtkPoints* outPts, vtkCellArray* outLines, vtkCellArray* outPolys, vtkAttributeManager* attMgr); virtual ~vtkCookieCutterHelper(); // Process a polyline void CropLine(vtkIdType cellId, vtkIdType cellOffset, vtkIdType npts, const vtkIdType* pts, vtkPolygon* loop, double* l, double loopBds[6], double n[3]); // Process a polygon void CropPoly(vtkIdType cellId, vtkIdType cellOffset, vtkPolygon* poly, vtkIdType npts, const vtkIdType* pts, vtkPolygon* loop, double* l, double loopBds[6], double n[3]); protected: // Convenience method inline void InsertPoint( vtkAttributeManager* attrMgr, vtkIdType ptId, double x[3], vtkCellArray* ca); // Check and clean up potentially non manifold situations // Used to clean up complex sets of lines assumed to form one or more loops // (i.e., polygons). At this point, these lines are classified "ON" or are // classified "INSIDE". Make sure they are manifold; trim out non-manifold // loops. int ResolveTopology(vtkPolyData* pd); public: vtkIncrementalPointLocator* Locator; vtkPoints* InPoints; vtkPoints* OutPoints; vtkCellArray* OutLines; vtkCellArray* OutPolys; vtkAttributeManager* AttributeManager; }; // vtkCookieCutterHelper // vtkCookieCutterHelper methods follow vtkCookieCutterHelper::vtkCookieCutterHelper(vtkIncrementalPointLocator* locator, vtkPoints* inPts, vtkPoints* outPts, vtkCellArray* outLines, vtkCellArray* outPolys, vtkAttributeManager* attMgr) : Locator(locator) , InPoints(inPts) , OutPoints(outPts) , OutLines(outLines) , OutPolys(outPolys) , AttributeManager(attMgr) { } vtkCookieCutterHelper::~vtkCookieCutterHelper() = default; void vtkCookieCutterHelper::InsertPoint( vtkAttributeManager* attrMgr, vtkIdType attrId, double x[3], vtkCellArray* ca) { vtkIdType ptId; if (this->Locator->InsertUniquePoint(x, ptId)) { attrMgr->InterpolatePointData(attrId, ptId); } ca->InsertCellPoint(ptId); } void vtkCookieCutterHelper::CropLine(vtkIdType cellId, vtkIdType cellOffset, vtkIdType npts, const vtkIdType* pts, vtkPolygon* loop, double* l, double loopBds[6], double n[3]) { // Make sure that this is a valid line if (npts < 2) { return; } // Create a vector of points with parametric coordinates, etc. which will // be sorted later. The tricky part is getting the classification of each // point correctly. // First insert all of the vertices defining the polyline vtkIdType i, j, newCellId; double t, u, v, x[3], x0[3], x1[3], y0[3], y1[3]; int result; SortedPointsType sortedPoints; // Begin gathering attribute information for this line. vtkAttributeManager* attrMgr = this->AttributeManager; attrMgr->InitializePointAttributeCollection(&sortedPoints, nullptr); // Insert all polyline points for (i = 0; i < npts; ++i) { t = static_cast(i); this->InPoints->GetPoint(pts[i], x); sortedPoints.emplace_back(t, SortPoint::VERTEX, -1, -1, x); attrMgr->AddPointAttribute(PointAttribute::MeshVertex, pts[i]); } // Now insert any intersection points vtkIdType numInts = 0, numLoopPts = loop->Points->GetNumberOfPoints(); for (numInts = 0, i = 0; i < (npts - 1); ++i) { vtkIdType m0 = pts[i]; vtkIdType m1 = pts[i + 1]; this->InPoints->GetPoint(m0, x0); this->InPoints->GetPoint(m1, x1); // Traverse polygon loop intersecting each polygon segment for (j = 0; j < numLoopPts; ++j) { vtkIdType l0 = j; vtkIdType l1 = (j + 1) % numLoopPts; loop->Points->GetPoint(l0, y0); loop->Points->GetPoint(l1, y1); if ((result = vtkLine::Intersection(x0, x1, y0, y1, u, v)) == vtkLine::Intersect) { x[0] = x0[0] + u * (x1[0] - x0[0]); x[1] = x0[1] + u * (x1[1] - x0[1]); x[2] = x0[2] + u * (x1[2] - x0[2]); attrMgr->AddPointAttribute(PointAttribute::Intersection, m0, m1, l0, l1, u, v); u += static_cast(i); v += static_cast(j); sortedPoints.emplace_back(u, SortPoint::INTERSECTION, numInts, -1, x); numInts++; } else if (result == 3) // parallel lines { double x10[3], c[3], c2[3]; vtkMath::Subtract(x1, x0, x10); double tol2 = 1.0e-08 * sqrt(vtkMath::Dot(x10, x10)); if (vtkLine::DistanceBetweenLines(x0, x1, y0, y1, c, c2, u, v) > tol2) { continue; // no intersection just move ahead } else { vtkIdType onId = i * numLoopPts + j; vtkLine::DistanceToLine(x0, y0, y1, u, c); if (-VTK_DEGENERATE_TOL <= u && u <= (1.0 + VTK_DEGENERATE_TOL)) { sortedPoints.emplace_back(static_cast(i), SortPoint::ON, numInts, onId, c); attrMgr->AddPointAttribute(PointAttribute::MeshVertex, m0); numInts++; } vtkLine::DistanceToLine(x1, y0, y1, u, c); if (-VTK_DEGENERATE_TOL <= u && u <= (1.0 + VTK_DEGENERATE_TOL)) { sortedPoints.emplace_back( static_cast(i) + 1.0, SortPoint::ON, numInts, onId, c); attrMgr->AddPointAttribute(PointAttribute::MeshVertex, m1); numInts++; } vtkLine::DistanceToLine(y0, x0, x1, u, c); if (-VTK_DEGENERATE_TOL <= u && u <= (1.0 + VTK_DEGENERATE_TOL)) { sortedPoints.emplace_back(static_cast(i) + u, SortPoint::ON, numInts, onId, c); attrMgr->AddPointAttribute(PointAttribute::LoopVertex, l0); numInts++; } vtkLine::DistanceToLine(y1, x0, x1, u, c); if (-VTK_DEGENERATE_TOL <= u && u <= (1.0 + VTK_DEGENERATE_TOL)) { sortedPoints.emplace_back(static_cast(i) + u, SortPoint::ON, numInts, onId, c); attrMgr->AddPointAttribute(PointAttribute::LoopVertex, l1); numInts++; } } // within tolerance of other line } // parallel line } // intersect all line segments that form the loop } // for all line segments that make up this polyline // Sort in parametric space std::sort(sortedPoints.begin(), sortedPoints.end(), &PointSorter); // Clean up coincident points CleanSortedPolyline(sortedPoints); // Classify the segments of the polyline ClassifyPolyline(sortedPoints, numLoopPts, l, loopBds, n); // If here, then pieces of the intersected polyline are output. Note that // a "ON" classification for a polyline should be output. vtkIdType num = static_cast(sortedPoints.size()); vtkIdType startIdx = 0, endIdx = 0, numInsertedPts; while (startIdx < (num - 1)) { // move to the beginning of the next interior strand. while (startIdx < (num - 1) && sortedPoints[startIdx].Class == SortPoint::OUTSIDE) { startIdx++; } if (startIdx >= (num - 1)) { continue; // all done } // Find the end of the run, i.e., link together interior strands. endIdx = startIdx + 1; while (endIdx < (num - 1) && (sortedPoints[endIdx].Class == SortPoint::INSIDE || sortedPoints[endIdx].Class == SortPoint::ON)) { endIdx++; } // Output line segments numInsertedPts = endIdx - startIdx + 1; newCellId = this->OutLines->InsertNextCell(numInsertedPts) + cellOffset; this->AttributeManager->CopyCellData(cellId, newCellId); for (i = startIdx; i <= endIdx; ++i) { InsertPoint(attrMgr, i, sortedPoints[i].X, this->OutLines); } startIdx = endIdx; } // over all sorted points } // CropLine int vtkCookieCutterHelper::ResolveTopology(vtkPolyData* pd) { vtkIdType numLines = pd->GetNumberOfLines(); if (numLines < 3) // non-manifold, on-edge lines don't form a loop { return 0; } // Make a pass and make sure all points are manifold, or not used at all vtkPoints* pts = pd->GetPoints(); vtkIdType pid, *cells, numPts = pts->GetNumberOfPoints(); int numBoundary = 0, numNonManifold = 0; vtkIdType ncells; for (pid = 0; pid < numPts; ++pid) { pd->GetPointCells(pid, ncells, cells); if (ncells == 0 || ncells == 2) { // skip is ok } else if (ncells == 1) { numBoundary++; } else { numNonManifold++; } } // over all points // Return if everything is cool if (numBoundary == 0 && numNonManifold == 0) { return 1; } // Special case: non-manifolds must be balanced with boundary. // Otherwise things are really messed up. else if (numBoundary != numNonManifold) { return 0; } // At this point, we have to erode away the boundary segments and // leave just manifolds. It's rare but does happen. bool resolved = false; while (!resolved) { resolved = true; for (pid = 0; pid < numPts; ++pid) { pd->GetPointCells(pid, ncells, cells); if (ncells == 1) { pd->RemoveCellReference(cells[0]); resolved = false; } } // over all points } // while not resolved // Do a last sanity check for (pid = 0; pid < numPts; ++pid) { pd->GetPointCells(pid, ncells, cells); if (ncells == 1 || ncells > 2) { return 0; } } // over all points return 1; } void vtkCookieCutterHelper::CropPoly(vtkIdType cellId, vtkIdType cellOffset, vtkPolygon* poly, vtkIdType npts, const vtkIdType* pts, vtkPolygon* loop, double* l, double loopBds[6], double n[3]) { // Make sure that this is a valid polygon if (npts < 3) { return; } // Make sure that the polygons actually overlap double polyBds[6]; vtkBoundingBox bbox(poly->GetBounds()); bbox.Inflate(0, 0, 0.01); bbox.GetBounds(polyBds); if (loopBds[0] > polyBds[1] || loopBds[1] < polyBds[0] || loopBds[2] > polyBds[3] || loopBds[3] < polyBds[2] || loopBds[4] > polyBds[5] || loopBds[5] < polyBds[4]) { return; } // Run around the polygon and loop inserting intersection points into two // (eventually sorted) arrays. These sorted arrays will alternate inside // paths with outside paths, sort of like a "braid". To construct polygon // loops, the braid is woven together to form the loops. double* p = vtkDoubleArray::FastDownCast(poly->Points->GetData())->GetPointer(0); vtkIdType i, j, newCellId, numPts = 0; double t, u, v, x[3], x0[3], x1[3], y0[3], y1[3]; int result; SortedPointsType loopPoints, polyPoints; vtkIdType numLoopPts = loop->Points->GetNumberOfPoints(); // Begin gathering attribute information for this polygon vtkAttributeManager* attrMgr = this->AttributeManager; attrMgr->InitializePointAttributeCollection(&loopPoints, &polyPoints); // The polygon points for (i = 0; i < npts; ++i) { t = static_cast(i); this->InPoints->GetPoint(pts[i], x); polyPoints.emplace_back(t, SortPoint::VERTEX, numPts, -1, x); attrMgr->AddPointAttribute(PointAttribute::MeshVertex, pts[i]); numPts++; } // The loop points for (i = 0; i < numLoopPts; ++i) { t = static_cast(i); loop->Points->GetPoint(i, x); loopPoints.emplace_back(t, SortPoint::VERTEX, numPts, -1, x); attrMgr->AddPointAttribute(PointAttribute::LoopVertex, i); numPts++; } // Now insert intersection points. Note that the attribute data is collected // with an implicit attribute id in lock step with numPts. for (i = 0; i < npts; ++i) { // Define an edge from the polygon (m0,m1) vtkIdType m0 = pts[i]; vtkIdType m1 = pts[(i + 1) % npts]; this->InPoints->GetPoint(m0, x0); this->InPoints->GetPoint(m1, x1); // Traverse polygon loop intersecting each polygon segment for (j = 0; j < numLoopPts; ++j) { // Define an edge from the loop (j,j+1) vtkIdType l0 = j; vtkIdType l1 = (j + 1) % numLoopPts; loop->Points->GetPoint(l0, y0); loop->Points->GetPoint(l1, y1); if ((result = vtkLine::Intersection(x0, x1, y0, y1, u, v)) == vtkLine::Intersect) { x[0] = x0[0] + u * (x1[0] - x0[0]); x[1] = x0[1] + u * (x1[1] - x0[1]); x[2] = x0[2] + u * (x1[2] - x0[2]); attrMgr->AddPointAttribute(PointAttribute::Intersection, m0, m1, l0, l1, u, v); u += static_cast(i); v += static_cast(j); polyPoints.emplace_back(u, SortPoint::INTERSECTION, numPts, -1, x); loopPoints.emplace_back(v, SortPoint::INTERSECTION, numPts, -1, x); numPts++; } else if (result == 3) // parallel lines { double x10[3], c[3], c2[3]; vtkMath::Subtract(x1, x0, x10); double tol2 = 1.0e-08 * sqrt(vtkMath::Dot(x10, x10)); if (vtkLine::DistanceBetweenLines(x0, x1, y0, y1, c, c2, u, v) > tol2) { continue; // no intersection just move ahead } else { vtkIdType onId = i * numLoopPts + j; vtkLine::DistanceToLine(x0, y0, y1, u, c); if (-VTK_DEGENERATE_TOL <= u && u <= (1.0 + VTK_DEGENERATE_TOL)) { polyPoints.emplace_back(static_cast(i), SortPoint::ON, numPts, onId, c); loopPoints.emplace_back(static_cast(j) + u, SortPoint::ON, numPts, onId, c); attrMgr->AddPointAttribute(PointAttribute::MeshVertex, m0); numPts++; } vtkLine::DistanceToLine(x1, y0, y1, u, c); if (-VTK_DEGENERATE_TOL <= u && u <= (1.0 + VTK_DEGENERATE_TOL)) { polyPoints.emplace_back(static_cast(i) + 1.0, SortPoint::ON, numPts, onId, c); loopPoints.emplace_back(static_cast(j) + u, SortPoint::ON, numPts, onId, c); attrMgr->AddPointAttribute(PointAttribute::MeshVertex, m1); numPts++; } vtkLine::DistanceToLine(y0, x0, x1, u, c); if (-VTK_DEGENERATE_TOL <= u && u <= (1.0 + VTK_DEGENERATE_TOL)) { polyPoints.emplace_back(static_cast(i) + u, SortPoint::ON, numPts, onId, c); loopPoints.emplace_back(static_cast(j), SortPoint::ON, numPts, onId, c); attrMgr->AddPointAttribute(PointAttribute::LoopVertex, l0); numPts++; } vtkLine::DistanceToLine(y1, x0, x1, u, c); if (-VTK_DEGENERATE_TOL <= u && u <= (1.0 + VTK_DEGENERATE_TOL)) { polyPoints.emplace_back(static_cast(i) + u, SortPoint::ON, numPts, onId, c); loopPoints.emplace_back(static_cast(j) + 1.0, SortPoint::ON, numPts, onId, c); attrMgr->AddPointAttribute(PointAttribute::LoopVertex, l1); numPts++; } } } } // intersect all line segments that form the loop } // for all line segments that make up this polygon // Sort in parametric coordinates around the intersected polygon and // loop. std::sort(polyPoints.begin(), polyPoints.end(), &PointSorter); std::sort(loopPoints.begin(), loopPoints.end(), &PointSorter); // Clean loops of nearly coincident points CleanSortedPolygon(npts, polyPoints); CleanSortedPolygon(numLoopPts, loopPoints); // If here, we identify the strands from the polygon and the loop. A // strand is the "inside" region between two intersection points. Strands // will be braided later to form loops. Note that potentially non-manifold // conditions (classified "ON") require a more complex loop building process. ClassifyPolygon(polyPoints, numLoopPts, l, loopBds, n); ClassifyPolygon(loopPoints, npts, p, polyBds, n); // Insert "INSIDE" or "ON" edge segments into polydata (polylines) and // build adjacency information. We are using vtkPolyData because it does // everything we want, although there is a lot of allocation / deallocation // going on which is a potential area of speed improvement. vtkNew pDataPts; pDataPts->SetNumberOfPoints(numPts); vtkNew pDataLines; vtkNew pData; pData->SetPoints(pDataPts); pData->SetLines(pDataLines); pData->EditableOn(); SortedPointsType* loops[2]; loops[0] = &polyPoints; loops[1] = &loopPoints; SortedPointsType* curLoop; int loopIdx; size_t sze, ii, ip; std::set edgeExist; vtkIdType eId, id0, id1; for (loopIdx = 0; loopIdx < 2; ++loopIdx) { curLoop = loops[loopIdx]; sze = curLoop->size(); for (ii = 0; ii < sze; ++ii) { ip = (ii + 1) % sze; id0 = (*curLoop)[ii].Id; id1 = (*curLoop)[ip].Id; // Since the number of edges is assumed small, create an unique edge id from // the two vertex ids (id0,id1) where id0 < id1. eId = (id0 < id1 ? id1 * numPts + id0 : id0 * numPts + id1); if (((*curLoop)[ii].Class == SortPoint::INSIDE || (*curLoop)[ii].Class == SortPoint::ON) && edgeExist.find(eId) == edgeExist.end()) { edgeExist.insert(eId); pDataPts->SetPoint(id0, (*curLoop)[ii].X); pDataPts->SetPoint(id1, (*curLoop)[ip].X); pDataLines->InsertNextCell(2); pDataLines->InsertCellPoint(id0); pDataLines->InsertCellPoint(id1); } } } pData->BuildLinks(); // Check the topology of the edges and ensure that it is valid. If there // are "ON" classifications, may need to remove potential non-manifold // edges. Bail out if can't fix any problems. if (!this->ResolveTopology(pData)) { return; } // Build loops (i.e., output polygons) vtkIdType numInsertedPts, *cells, thisCell, nextId, startId; vtkIdType ncells; vtkIdType thisNPts; const vtkIdType* thisPts; std::vector visited(numPts, 0); // Each unvisited, connected point generates a loop. The variable // npts is the number of points, mesh+loop+intersection points, inserted // into the loops. for (i = 0; i < numPts; ++i) { if (!visited[i]) { startId = nextId = i; pData->GetPointCells(nextId, ncells, cells); if (ncells != 2) // unused or merged point { continue; } numInsertedPts = 0; thisCell = cells[0]; newCellId = this->OutPolys->InsertNextCell(numInsertedPts) + cellOffset; this->AttributeManager->CopyCellData(cellId, newCellId); do { visited[nextId] = 1; numInsertedPts++; InsertPoint(attrMgr, nextId, pDataPts->GetPoint(nextId), this->OutPolys); pData->GetCellPoints(thisCell, thisNPts, thisPts); nextId = (thisPts[0] != nextId ? thisPts[0] : thisPts[1]); pData->GetPointCells(nextId, ncells, cells); thisCell = (cells[0] != thisCell ? cells[0] : cells[1]); } while (nextId != startId); this->OutPolys->UpdateCellCount(numInsertedPts); } } } // That was easy! CropPoly } // anonymous namespace //------------------------------------------------------------------------------ // Instantiate object with empty loop. vtkCookieCutter::vtkCookieCutter() { this->PassCellData = true; this->PassPointData = true; this->PointInterpolation = USE_MESH_EDGES; this->Locator = nullptr; this->SetNumberOfInputPorts(2); } //------------------------------------------------------------------------------ vtkCookieCutter::~vtkCookieCutter() { this->SetLocator(nullptr); } //------------------------------------------------------------------------------ void vtkCookieCutter::SetLoopsConnection(vtkAlgorithmOutput* algOutput) { this->SetInputConnection(1, algOutput); } //------------------------------------------------------------------------------ vtkAlgorithmOutput* vtkCookieCutter::GetLoopsConnection() { return this->GetInputConnection(1, 0); } //------------------------------------------------------------------------------ void vtkCookieCutter::SetLoopsData(vtkDataObject* input) { this->SetInputData(1, input); } //------------------------------------------------------------------------------ vtkDataObject* vtkCookieCutter::GetLoops() { if (this->GetNumberOfInputConnections(1) < 1) { return nullptr; } return this->GetExecutive()->GetInputData(1, 0); } //------------------------------------------------------------------------------ int vtkCookieCutter::RequestData(vtkInformation* vtkNotUsed(request), vtkInformationVector** inputVector, vtkInformationVector* outputVector) { // get the info objects vtkInformation* inInfo = inputVector[0]->GetInformationObject(0); vtkInformation* loopInfo = inputVector[1]->GetInformationObject(0); vtkInformation* outInfo = outputVector->GetInformationObject(0); // get the input and output vtkPolyData* input = vtkPolyData::SafeDownCast(inInfo->Get(vtkDataObject::DATA_OBJECT())); vtkPolyData* loops = vtkPolyData::SafeDownCast(loopInfo->Get(vtkDataObject::DATA_OBJECT())); vtkPolyData* output = vtkPolyData::SafeDownCast(outInfo->Get(vtkDataObject::DATA_OBJECT())); // Initialize and check data vtkDebugMacro(<< "Cookie cutting..."); if (input->GetNumberOfPoints() < 1) { vtkErrorMacro("Input contains no points"); return 1; } // Process loops from second input. Make sure valid trim loops // are available. vtkSmartPointer loopPolys = BuildTrimLoops(loops); if (loopPolys == nullptr) { vtkErrorMacro("No trim lines/polygons defined"); return 1; } // Create output data objects and prepare for processing vtkPoints* inPts = input->GetPoints(); vtkPoints* loopPts = loops->GetPoints(); vtkSmartPointer outPts; outPts.TakeReference(inPts->NewInstance()); // This class assists with the copying and interpolation of point and cell // data. While cell data is easy to process, point data can be complex // because of the merging of coincident points, and the interpolation of // data across mesh edges and/or tril loop edges. vtkAttributeManager attrMgr( input, loops, output, this->PassCellData, this->PassPointData, this->PointInterpolation); vtkCellArray* inVerts = input->GetVerts(); vtkNew outVerts; outVerts->AllocateCopy(inVerts); vtkCellArray* inLines = input->GetLines(); vtkNew outLines; outLines->AllocateCopy(inLines); vtkCellArray* inPolys = input->GetPolys(); vtkCellArray* inStrips = input->GetStrips(); vtkNew outPolys; outPolys->AllocateCopy(inPolys); // Locator used to merge potentially duplicate points. Make sure that // the bounding box provides a little breathing room for proper // bounds-bounds intersection checks. vtkBoundingBox bbox(loops->GetBounds()); double tol = 0.001 * bbox.GetDiagonalLength(); bbox.Inflate(tol); if (this->Locator == nullptr) { this->CreateDefaultLocator(); } double bds[6]; bbox.GetBounds(bds); this->Locator->InitPointInsertion(outPts, bds); // Initialize and create polygon representing the loop double n[3]; vtkNew loop; vtkNew poly; // Setup the lines and polys cropping helper std::unique_ptr helper( new vtkCookieCutterHelper(this->Locator, inPts, outPts, outLines, outPolys, &attrMgr)); // For each trim loop, crop the input mesh cells. Cells are cropped // repeatedly one-by-one in a loop. vtkIdType npts; const vtkIdType* pts; for (loopPolys->InitTraversal(); loopPolys->GetNextCell(npts, pts);) { if (this->CheckAbort()) { break; } vtkIdType cellId = 0; vtkIdType numLoopPts = npts; loop->Initialize(npts, pts, loopPts); bbox.SetBounds(loop->GetBounds()); bbox.Inflate(tol); // Handle exactly aligned x-y plane numerical fuzz bbox.GetBounds(bds); vtkPolygon::ComputeNormal(loop->Points, n); double* l = vtkDoubleArray::FastDownCast(loop->Points->GetData())->GetPointer(0); // Start by processing the verts. A simple in/out check. if (inVerts->GetNumberOfCells() > 0) { for (inVerts->InitTraversal(); inVerts->GetNextCell(npts, pts); ++cellId) { for (vtkIdType i = 0; i < npts; ++i) { double x[3]; inPts->GetPoint(pts[i], x); if (vtkPolygon::PointInPolygon(x, numLoopPts, l, bds, n) == 1) { vtkIdType ptId; this->Locator->InsertUniquePoint(x, ptId); attrMgr.CopyPointData(pts[i], ptId); vtkIdType newCellId = outVerts->InsertNextCell(1, &ptId); attrMgr.CopyCellData(cellId, newCellId); } // if point inside trim loop } // for all points composing vert cell } // for all vert cells } // if vert cells // Now process lines if (inLines->GetNumberOfCells() > 0) { vtkIdType cellOffset = outVerts->GetNumberOfCells(); for (inLines->InitTraversal(); inLines->GetNextCell(npts, pts); ++cellId) { helper->CropLine(cellId, cellOffset, npts, pts, loop, l, bds, n); } } // if line cells // Now process polygons if (inPolys->GetNumberOfCells() > 0) { vtkIdType cellOffset = outVerts->GetNumberOfCells() + outLines->GetNumberOfCells(); for (inPolys->InitTraversal(); inPolys->GetNextCell(npts, pts); ++cellId) { poly->Initialize(npts, pts, inPts); helper->CropPoly(cellId, cellOffset, poly, npts, pts, loop, l, bds, n); } } // if polygonal cells // Now process triangle strips if (inStrips->GetNumberOfCells() > 0) { vtkIdType cellOffset = outVerts->GetNumberOfCells() + outLines->GetNumberOfCells(); for (inStrips->InitTraversal(); inStrips->GetNextCell(npts, pts); ++cellId) { vtkIdType numTriPts = 3, triPts[3]; for (vtkIdType i = 0; i < (npts - 2); ++i) { triPts[0] = pts[i]; triPts[1] = pts[i + 1]; triPts[2] = pts[i + 2]; poly->Initialize(3, triPts, inPts); helper->CropPoly(cellId, cellOffset, poly, numTriPts, triPts, loop, l, bds, n); } } } // if polygonal cells } // for all loops // Assign output as appropriate output->SetPoints(outPts); output->SetVerts(outVerts); output->SetLines(outLines); output->SetPolys(outPolys); return 1; } //------------------------------------------------------------------------------ int vtkCookieCutter::RequestUpdateExtent(vtkInformation* vtkNotUsed(request), vtkInformationVector** inputVector, vtkInformationVector* outputVector) { // get the info objects vtkInformation* inInfo = inputVector[0]->GetInformationObject(0); vtkInformation* loopInfo = inputVector[1]->GetInformationObject(0); vtkInformation* outInfo = outputVector->GetInformationObject(0); if (loopInfo) { loopInfo->Set(vtkStreamingDemandDrivenPipeline::UPDATE_PIECE_NUMBER(), 0); loopInfo->Set(vtkStreamingDemandDrivenPipeline::UPDATE_NUMBER_OF_PIECES(), 1); loopInfo->Set(vtkStreamingDemandDrivenPipeline::UPDATE_NUMBER_OF_GHOST_LEVELS(), 0); } inInfo->Set(vtkStreamingDemandDrivenPipeline::UPDATE_PIECE_NUMBER(), outInfo->Get(vtkStreamingDemandDrivenPipeline::UPDATE_PIECE_NUMBER())); inInfo->Set(vtkStreamingDemandDrivenPipeline::UPDATE_NUMBER_OF_PIECES(), outInfo->Get(vtkStreamingDemandDrivenPipeline::UPDATE_NUMBER_OF_PIECES())); inInfo->Set(vtkStreamingDemandDrivenPipeline::UPDATE_NUMBER_OF_GHOST_LEVELS(), outInfo->Get(vtkStreamingDemandDrivenPipeline::UPDATE_NUMBER_OF_GHOST_LEVELS())); inInfo->Set(vtkStreamingDemandDrivenPipeline::EXACT_EXTENT(), 1); return 1; } //------------------------------------------------------------------------------ int vtkCookieCutter::FillInputPortInformation(int port, vtkInformation* info) { if (port == 0) { info->Set(vtkAlgorithm::INPUT_REQUIRED_DATA_TYPE(), "vtkPolyData"); return 1; } else if (port == 1) { info->Set(vtkAlgorithm::INPUT_REQUIRED_DATA_TYPE(), "vtkPolyData"); return 1; } return 0; } //------------------------------------------------------------------------------ void vtkCookieCutter::CreateDefaultLocator() { if (this->Locator == nullptr) { this->Locator = vtkMergePoints::New(); this->Locator->Register(this); this->Locator->Delete(); } } //------------------------------------------------------------------------------ void vtkCookieCutter::PrintSelf(ostream& os, vtkIndent indent) { this->Superclass::PrintSelf(os, indent); os << indent << "Pass Cell Data: " << (this->PassCellData ? "On\n" : "Off\n"); os << indent << "Pass Point Data: " << (this->PassPointData ? "On\n" : "Off\n"); os << indent << "Point Interpolation: " << this->PointInterpolation << "\n"; if (this->Locator) { os << indent << "Locator: " << this->Locator << "\n"; } else { os << indent << "Locator: (none)\n"; } } VTK_ABI_NAMESPACE_END