/*========================================================================= Program: Visualization Toolkit Module: vtkContinuousScatterplot.cxx Copyright (c) Ken Martin, Will Schroeder, Bill Lorensen All rights reserved. See Copyright.txt or http://www.kitware.com/Copyright.htm for details. This software is distributed WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the above copyright notice for more information. =========================================================================*/ #include "vtkContinuousScatterplot.h" #include "vtkCell.h" #include "vtkCellArray.h" #include "vtkCellData.h" #include "vtkCellType.h" #include "vtkDataSetAttributes.h" #include "vtkExecutive.h" #include "vtkFloatArray.h" #include "vtkIdList.h" #include "vtkIdTypeArray.h" #include "vtkImageData.h" #include "vtkInformation.h" #include "vtkInformationVector.h" #include "vtkMassProperties.h" #include "vtkMath.h" #include "vtkMergePoints.h" #include "vtkObjectFactory.h" #include "vtkPointData.h" #include "vtkPoints.h" #include "vtkPolyData.h" #include "vtkPolygon.h" #include "vtkSmartPointer.h" #include "vtkTriangleFilter.h" #include "vtkUnstructuredGrid.h" vtkStandardNewMacro(vtkContinuousScatterplot); // Data structure to store the fragment faces. // Each face of the fragment can be represented using a vtkIdList. typedef std::vector>* Polytope; //------------------------------------------------------------------------------ vtkContinuousScatterplot::vtkContinuousScatterplot() { // value for floating comparison. Suppose two floating values a and b: // if fabs(a-b) <= dEpsilon : then a == b. // if (a-b) > dEpsilon : then a > b. // if (b-a) > dEpsilon : then b > a. this->Epsilon = 1.0e-6; // The number of output port is one. this->SetNumberOfOutputPorts(1); // Resolution of the output image is set to 100 as default. this->ResX = this->ResY = 100; // Create the vtkImageData object and pass to the output port. vtkImageData* output = vtkImageData::New(); this->GetExecutive()->SetOutputData(0, output); output->Delete(); this->Fields[0] = this->Fields[1] = nullptr; } //------------------------------------------------------------------------------ void vtkContinuousScatterplot::SetField1(const char* nm, vtkIdType xRes) { this->Fields[0] = nm; this->ResX = xRes; } //------------------------------------------------------------------------------ void vtkContinuousScatterplot::SetField2(const char* nm, vtkIdType yRes) { this->Fields[1] = nm; this->ResY = yRes; } //------------------------------------------------------------------------------ void vtkContinuousScatterplot::PrintSelf(ostream& os, vtkIndent indent) { this->Superclass::PrintSelf(os, indent); } //------------------------------------------------------------------------------ int vtkContinuousScatterplot::FillInputPortInformation(int, vtkInformation* info) { info->Set(vtkAlgorithm::INPUT_REQUIRED_DATA_TYPE(), "vtkUnstructuredGrid"); return 1; } //------------------------------------------------------------------------------ int vtkContinuousScatterplot::FillOutputPortInformation(int, vtkInformation* info) { info->Set(vtkDataObject::DATA_TYPE_NAME(), "vtkImageData"); return 1; } //------------------------------------------------------------------------------ int vtkContinuousScatterplot::RequestData( vtkInformation*, vtkInformationVector** inputVector, vtkInformationVector* outputVector) { // get the input and output ports information. vtkInformation* inInfo = inputVector[0]->GetInformationObject(0); vtkInformation* outInfo = outputVector->GetInformationObject(0); // get the input data set, which is required to be a vtkUnstructuredGrid. vtkUnstructuredGrid* input = vtkUnstructuredGrid::SafeDownCast(inInfo->Get(vtkDataObject::DATA_OBJECT())); // get the output data set, which is required to be a vtkImageData. vtkImageData* output = vtkImageData::SafeDownCast(outInfo->Get(vtkDataObject::DATA_OBJECT())); // get the number of points and cells of the input grid. vtkIdType numCells = input->GetNumberOfCells(); vtkIdType numPts = input->GetNumberOfPoints(); // get the dataset statistics and check if it is not an empty grid. if (numCells < 1 || numPts < 1) { vtkErrorMacro("No input data."); return 1; } // check if the output image resolution is a valid number. if (this->ResX <= 0 || this->ResY <= 0) { vtkErrorMacro("The resolution of the output image has to be a positive number."); return 1; } // check if the names of the input scalar fields are specified. if (!this->Fields[0] || !this->Fields[1]) { vtkErrorMacro("At least two fields need to be specified."); return 1; } // Input point data, which should include the arrays defining the range space. vtkDataSetAttributes* inPD = input->GetPointData(); // current data array of the given array names from the user input. vtkSmartPointer array; // scalar range in two fields, where fieldInterval[0] = f1_max - f1_min, // fieldInterval[1] = f2_max - f2_min float fieldInterval[2]; // fragment width of two fields, where fragWidth[0] = fieldInterval[0] / resX, // fragWidth[0] = fieldInterval[1] / resY, (resX, resY) is the output image // resolution. float fragWidth[2]; // Collect the first scalar field based on array names, array = inPD->GetArray(this->Fields[0]); // confirming that this field is present in the input. if (array) { // Collect field ranges for later use. fieldInterval[0] = array->GetRange()[1] - array->GetRange()[0]; // Interval between cutting planes in this field. fragWidth[0] = (float)fieldInterval[0] / this->ResX; } else { vtkErrorMacro("Array not found in input point data: " << this->Fields[0]); return 1; } // Collect the second field data array. array = inPD->GetArray(this->Fields[1]); // confirming that this field is present in the input. if (array) { // The range interval of the field. fieldInterval[1] = array->GetRange()[1] - array->GetRange()[0]; // Interval between cutting planes in this field. fragWidth[1] = (float)fieldInterval[1] / this->ResY; } else { vtkErrorMacro("Array not found in input point data: " << this->Fields[1]); return 1; } // Divide the tetrahedron into four faces. The index of each face. const int tetTemplate[4][3] = { { 0, 1, 2 }, { 0, 1, 3 }, { 0, 2, 3 }, { 1, 2, 3 } }; // fragments of current cell: each cell is placed into the inputQ, // then fresh fragments from current slice put into outputQ. std::vector inputQ = std::vector(); std::vector outputQ = std::vector(); // structure for storing the vertices of cutting planes used to subdivide the cell. vtkSmartPointer cut = vtkSmartPointer::New(); // Initially the points in the cutting plane are not ordered. These parameters are used // to compute convex hull for points lying on the cutting plane. The sorting process // utilizes the angular information between the plane edges. float theta[100]; vtkIdType index[100]; // number of points in the current cutting plane. int nrPoints; float base[3], vec[3]; float baseNorm, ang; vtkIdType pnt, pnt0, pnt1; // min and max scalar value of the current field in a tetrahedron float maxCell, minCell; // min and max scalar values of the current field in the whole domain. float minField = 0; float maxField = 0; // first and last cutting planes' field values. This threshold is used to traverse // through all of the cutting planes in the range. float initThreshold, lastThreshold; // variables for walking around a cell face. vtkIdType newPointId, thisPointId, prevPointId; // number of the vertices in the current cell face. int nrFaceIds; // For each cutting plane, determine its intersecting position along the current edge. int mask; // scalar values of the end points of the current edge. float thisScalar, prevScalar; // if the cutting plane intersects in the middle of the given edge, these parameters // are used to compute these intersecting points using linear interpolation double delta, t, p[3], p0[3], p1[3], p2[3]; // fragment: list of faces forming the fragments in the current cell. // residual: list of faces forming the rest of the cell (except fragments). // working: collect the residual faces for the next iteration of subdivision. Polytope fragment = nullptr, residual = nullptr, working = nullptr; // structure for storing the current framgent vertices in each cell face. vtkSmartPointer fragmentFace = nullptr; // structure for storing the vertices which are not belonging to the current // fragment in each cell face. vtkSmartPointer residualFace = nullptr; // reading/writing cells from input, output and cell arrays. vtkSmartPointer cell = nullptr; // reading/writing faces from input, output and cell arrays. vtkSmartPointer face = nullptr; // Each fragment can be mapped to a 2-D bin based on its range values. The density // value in each bin equals to the total geometric volume of the fragments in this bin. // The following structure creates and initialise such a 2-D bin with a resolution // ResX * ResY. // float imageBin[this->ResX][this->ResY]; float** imageBin = new float*[this->ResX]; for (int xIndex = 0; xIndex < this->ResX; ++xIndex) { imageBin[xIndex] = new float[this->ResY]; for (int yIndex = 0; yIndex < this->ResY; ++yIndex) { imageBin[xIndex][yIndex] = 0.0; } } // maximal volume value in the output image bin. float maxBinSize = 0; // The VTK filter for computing the fragment geometric volume. This filter only // accepts triangular mesh as input, while our fragments are stored as a polygonal // mesh. Therefore, we need a mesh conversion. vtkSmartPointer volume = vtkSmartPointer::New(); // This VTK filter convert a polygonal mesh to a triangular mesh. vtkSmartPointer triangleFilter = vtkSmartPointer::New(); // geometric volume of the current polyhedral fragments. float fragVolume; // mapped index of a fragment into the 2-D bin. vtkIdType binIndexFirst = 0, binIndexSecond = 0; // id for the newly interpolated fragment vertices. Since we only need to update point // structure, therefore this id is not of interest and can be ignored for the moment. vtkIdType ignored = 0; // point coordinates in the input grid vtkPoints* inputGridPoints = input->GetPoints(); // point coordinates in the output fragments vtkSmartPointer newPoints = vtkSmartPointer::New(); // Each fragment is composed of a number of points. This data structure contains // scalar values for all these points. vtkSmartPointer newPointsPD = vtkSmartPointer::New(); // locator for inserting new interpolated points into the structure. This search // structure ensures that redundant points will not be added to the point collection. vtkSmartPointer pointLocator = vtkSmartPointer::New(); // Each fragment will have a range value. This array records the range values // for all of the fragments. Since we iteratively subdivide the cell based on // two fields, therefore the first n elements in this array record the // range values of the fragments in the first field subdivision, and the following m // elements record the range values in the second field subdivision. vtkSmartPointer fragScalar = vtkSmartPointer::New(); // Each fragment will have range values from two fields. float fragRangeValue[2]; // number of fragments in the first field subdivision. size_t firstFragNr = 0; // data structure containing scalar values of the vertices of current tetrahedron of // the input grid. vtkSmartPointer tetraPD = vtkSmartPointer::New(); // scalar values of two fields in the current tetrahedron of the input grid. // these two arrays are added to the tetrahedron point data. vtkSmartPointer tetraF1 = vtkSmartPointer::New(), tetraF2 = vtkSmartPointer::New(); tetraF1->SetNumberOfComponents(1); tetraF1->SetNumberOfTuples(4); tetraF2->SetNumberOfComponents(1); tetraF2->SetNumberOfTuples(4); // add the two scalar arrays to the point data of the tetrahedron tetraPD->AddArray(tetraF1); tetraPD->AddArray(tetraF2); // structure containing the polygonal faces of polyhedral fragments vtkSmartPointer polyhedra = vtkSmartPointer::New(); // estimate the total number of cells of the fragments in each input cell. // consider edge can be maximally subdivided to resX number // of fragments for the first field and resY number of fragments for the second field. // In total, there will be maximal resX * resY number of new points in each edge. int estOutputPointSize = this->ResX * this->ResY * 4; // Allocate the memory for the framgent points. newPoints->Allocate(estOutputPointSize); // main loop ... // For each tetrahedron in a grid for (vtkIdType tetraIndex = 0; tetraIndex < input->GetNumberOfCells(); tetraIndex++) { // current tetrahedron vertex list. cell = vtkSmartPointer::New(); input->GetCellPoints(tetraIndex, cell); // Test if the current cell is a tetrahedron or not. If not, ignore this cell. if (input->GetCellType(tetraIndex) != VTK_TETRA || cell->GetNumberOfIds() != 4) { vtkWarningMacro("Current cell " << tetraIndex << " is not of a tetrahedron type."); continue; } // initialise the point structure for storing fragment vertices. newPoints->Reset(); // initialise the search structure for new points insertion. pointLocator->InitPointInsertion(newPoints, input->GetCell(tetraIndex)->GetBounds()); // initialise data structure containing the scalar values for the fragment vertices. newPointsPD->Initialize(); // Set the storage for interpolating the field values of the fragment vertices. newPointsPD->InterpolateAllocate(tetraPD, estOutputPointSize, ignored, false); newPointsPD->CopyScalarsOn(); // initialise data structure containing the scalar values of the whole fragment. fragScalar->Initialize(); fragScalar->Allocate(this->ResX * this->ResY); // two components are needed to store the bivariate fields of the framgent. fragScalar->SetNumberOfComponents(2); // Initialise the scalar values in this tetrahedral cell. for (vtkIdType cellIndex = 0; cellIndex < cell->GetNumberOfIds(); cellIndex++) { vtkIdType pointId = cell->GetId(cellIndex); pointLocator->InsertNextPoint(inputGridPoints->GetPoint(pointId)); tetraF1->SetComponent( cellIndex, 0, inPD->GetArray(this->Fields[0])->GetComponent(pointId, 0)); tetraF2->SetComponent( cellIndex, 0, inPD->GetArray(this->Fields[1])->GetComponent(pointId, 0)); } // The scalar values of the framgent points are based on the interpolation of the // point data of the tetrahedral cell (tetraPD). for (vtkIdType cellPDIndex = 0; cellPDIndex < tetraPD->GetNumberOfTuples(); cellPDIndex++) { newPointsPD->CopyData(tetraPD, cellPDIndex, cellPDIndex); } /* cout << "ARRAY SUMMARY:\n" << endl; for (int i = 0; i < tetraPD->GetNumberOfTuples(); i++) { cout << i << "\t" << inPD->GetArray(this->Fields[0])->GetComponent(i,0) << "\t" << inPD->GetArray(this->Fields[1])->GetComponent(i,0) << "\t" << newPointsPD->GetArray(0)->GetComponent(i,0) << "\t" << newPointsPD->GetArray(1)->GetComponent(i,0) << endl; } */ // Get the next cell from input, and place into the working queue. // We place into outputQ as each field takes the output of the // last step as its input, swapping queues BEFORE processing. // OutputQ : a list of faces of the output framgent Polytope ptp = new std::vector>(); outputQ.push_back(ptp); // min and max range value of the current cell minCell = maxField; maxCell = minField; // for each point in the cell for (int fnr = 0; fnr < 4; fnr++) { // get the faces of the cell and push into outputQ structure. face = vtkSmartPointer::New(); face->SetNumberOfIds(3); for (int pnr = 0; pnr < 3; pnr++) { face->SetId(pnr, tetTemplate[fnr][pnr]); } outputQ[0]->push_back(face); } // For each scalar field: for (size_t fieldNr = 0; fieldNr < 2; fieldNr++) { // Swap role of outputQ and inputQ std::swap(outputQ, inputQ); outputQ.clear(); // If this is the second round subdivision, then we need to record the number // of fragments produced in the first round. This number is used to locate // the range values of the output fragment. if (fieldNr == 1) { firstFragNr = inputQ.size(); } // min value of the whole field minField = inPD->GetArray(this->Fields[fieldNr])->GetRange()[0]; // max value of the whole field maxField = inPD->GetArray(this->Fields[fieldNr])->GetRange()[1]; // Initialize the value minCell = maxField; maxCell = minField; ////cout << "tet " << tetraIndex << ", field " << fieldNr << ", pnr: " << /// newPointsPD->GetNumberOfTuples() << endl; ////cout << "min/max init: " << minCell << ", " << maxCell << endl; // obtain the minimal and maximal scalar values of the cell. for (int pnr = 0; pnr < newPointsPD->GetNumberOfTuples(); pnr++) { double fval = newPointsPD->GetArray((int)fieldNr)->GetComponent(pnr, 0); ////cout << " fval: " << fval << endl; if (maxCell < fval) { maxCell = fval; } if (minCell > fval) { minCell = fval; } } ////cout << "D" << endl; ////cout << "Cell min/max: " << minCell << " " << maxCell << endl; ////cout << "field min/max[0] " << minField << " " << maxField << endl; ////cout << "field widths " << fragWidth[0] << " " << fragWidth[1] << endl; // in each field, the smallest threshold of cutting plane to start with. // since each field is sliced uniformly, in other words, the interval between // fragment in each field is a constant. Therefore in each cell, the smallest // threshold to start with, should be the first fragment value of the field which // is greater or equal to the minimal range value of the cell. initThreshold = minField + (1 + floor((minCell - minField) / fragWidth[fieldNr])) * fragWidth[fieldNr]; // Iterate through the faces of the current mesh. // 1. The first field is initially traversed and processed on the input grid. // The inputQ should be the initial tetrahedron mesh. And the outputQ should be // the collection of computed fragments with respect to the first field. // 2. The second field is then processed based on the outputQ of step 1. The inputQ // now becomes the fragments in the first field subdivision. The OutputQ // in this step should be the fragments of the bivariate fields. // Fragment: array records the faces of the fragments. // Residual: array records the faces of rest of the cell in addition to fragments. // Cut: array records the vertices of the current cutting plane. // For each faces of the input mesh. for (size_t cp = 0; cp < inputQ.size(); cp++) { // working[0] : faces of current input mesh. working = inputQ[cp]; // initialise the first and last cutting planes in this cell. lastThreshold = initThreshold; // Traverse from the minimal to the maximal scalar values in a cell, every time // the threshold is increased by one fragmentWidth for (double threshold = initThreshold; threshold < maxCell; threshold += fragWidth[fieldNr]) { // Initialise framgent face structure for the current cutting plane. delete fragment; delete residual; fragment = new std::vector>(); residual = new std::vector>(); // Create the new cutting plane. cut = vtkSmartPointer::New(); // Effectively, we start processing a new cell at this point. for (std::vector>::iterator faceIt = working->begin(); faceIt != working->end(); ++faceIt) { fragmentFace = vtkSmartPointer::New(); residualFace = vtkSmartPointer::New(); // number of points in the current cell face nrFaceIds = (*faceIt)->GetNumberOfIds(); // get the previous point id in the face prevPointId = (*faceIt)->GetId(nrFaceIds - 1); // the scalar value of the previous point in the face prevScalar = newPointsPD->GetArray((int)fieldNr)->GetComponent(prevPointId, 0); // Walk around the edge, comparing the range values between the current // cutting plane and the edge end points. Classify the each end point of the // edge into three classes based on the comparison result. Three classes // includes: // fragmentFace: if current edge point belongs to the fragment. // cut: if current edge point belongs to the cutting plane. // residual: if current edge point belongs to neither of the above classes. // For each edge for (int i = 0; i < nrFaceIds; i++) { // get the current point Id in the face thisPointId = (*faceIt)->GetId(i); // get scalar value of the current point thisScalar = newPointsPD->GetArray((int)fieldNr)->GetComponent(thisPointId, 0); ////cout << ">>> " << thisPointId << " " << thisScalar << " " << prevPointId << " " ///<< prevScalar << endl; // zero bitweise or to any value equals that value // threshold = minField + n * sw mask = 0; // thisScalar < threshold (0001) if (threshold - thisScalar > this->Epsilon) mask |= 1; // thisScalar > threshold (0010) if (thisScalar - threshold > this->Epsilon) mask |= 2; // prevScalar < threshold (0100) if (threshold - prevScalar > this->Epsilon) mask |= 4; // prevScalar > threshold (1000) if (prevScalar - threshold > this->Epsilon) mask |= 8; // thisScalar == threshold (0011) if (fabs(thisScalar - threshold) <= this->Epsilon) mask |= 3; // prevScalar == threshold (1100) if (fabs(prevScalar - threshold) <= this->Epsilon) mask |= 12; switch (mask) { // threshold == thisScalar case 3: case 7: // threshold == thisScalar, // threshold < preScalar case 11: // threshold == thisScalar // threshold == preScalar case 15: // fragment face array insert current point fragmentFace->InsertNextId(thisPointId); // residual face array insert current point residualFace->InsertNextId(thisPointId); // if the current point is not in the cut array if (cut->IsId(thisPointId) < 0) { cut->InsertNextId(thisPointId); } break; // threshold == thisScalar // threshold > preScalar case 5: // threshold > thisScalar // threshold == prevScalar case 13: fragmentFace->InsertNextId(thisPointId); break; // threshold < thisScalar // threshold < prevScalar case 10: // threshold < thisScalar // threshold == prevScalar case 14: residualFace->InsertNextId(thisPointId); break; // threshold > thisScalar // threshold < preScalar case 9: // Moving to this point crosses the threshold hi-lo // Insert interpolated point into both. delta = prevScalar - thisScalar; t = (threshold - thisScalar) / delta; // * PREV ------- T -------- THIS * newPoints->GetPoint(thisPointId, p1); newPoints->GetPoint(prevPointId, p2); for (int j = 0; j < 3; j++) { p[j] = p1[j] + t * (p2[j] - p1[j]); } if (pointLocator->InsertUniquePoint(p, newPointId)) { newPointsPD->InterpolateEdge( newPointsPD, newPointId, thisPointId, prevPointId, t); } fragmentFace->InsertNextId(newPointId); fragmentFace->InsertNextId(thisPointId); residualFace->InsertNextId(newPointId); // We have found a cut point, add it if first visit. if (cut->IsId(newPointId) < 0) { cut->InsertNextId(newPointId); } break; // threshold < thisScalar // threshold > preScalar case 6: // this >, prev < // Moving to this point crosses the threshold lo-hi // Insert interpolated point into both. delta = thisScalar - prevScalar; t = (threshold - prevScalar) / delta; newPoints->GetPoint(thisPointId, p1); newPoints->GetPoint(prevPointId, p2); for (int j = 0; j < 3; j++) { p[j] = p2[j] + t * (p1[j] - p2[j]); } if (pointLocator->InsertUniquePoint(p, newPointId)) { newPointsPD->InterpolateEdge( newPointsPD, newPointId, prevPointId, thisPointId, t); } fragmentFace->InsertNextId(newPointId); residualFace->InsertNextId(newPointId); residualFace->InsertNextId(thisPointId); if (cut->IsId(newPointId) < 0) { cut->InsertNextId(newPointId); } break; default: vtkErrorMacro("Incomparable scalars " << prevScalar << ", " << thisScalar << ", " << threshold); } // switch mask prevPointId = thisPointId; prevScalar = thisScalar; } // for-each edge // Output fragment and residue into new cells, as appropriate. // Test if the fragment and residual faces are well defined. if (fragmentFace->GetNumberOfIds() > 2) { fragmentFace->Squeeze(); fragment->push_back(fragmentFace); } if (residualFace->GetNumberOfIds() > 2) { residualFace->Squeeze(); residual->push_back(residualFace); } } // for each face. // We need to compute the face defined by the cut points. // We cannot guarantee that points in the cut-list are ordered wrt // polygon boundary, so we recompute an order by effectively working // the convex hull. // The cut-list is added to the framgent and residual array. if (cut->GetNumberOfIds() > 2) { nrPoints = cut->GetNumberOfIds(); pnt0 = cut->GetId(0); pnt1 = cut->GetId(1); // 2. Compute vector from p[0] to p[1]. newPoints->GetPoint(pnt0, p0); newPoints->GetPoint(pnt1, p1); for (int i = 0; i < 3; i++) { base[i] = p1[i] - p0[i]; } baseNorm = vtkMath::Norm(base); theta[0] = 0.0; index[0] = pnt1; // 3. For the remaining points p, compute angle between p-p0 and base. for (int i = 2; i < nrPoints; i++) { newPoints->GetPoint(cut->GetId(i), p1); for (int j = 0; j < 3; j++) { vec[j] = p1[j] - p0[j]; } theta[i - 1] = acos(vtkMath::Dot(base, vec) / (baseNorm * vtkMath::Norm(vec))); index[i - 1] = cut->GetId(i); } // 4. Sort angles. for (int j = 1; j < nrPoints - 1; j++) { ang = theta[j]; pnt = index[j]; int i = j - 1; while (i >= 0 && theta[i] > ang) { theta[i + 1] = theta[i]; index[i + 1] = index[i]; i--; } theta[i + 1] = ang; index[i + 1] = pnt; } cut->Reset(); cut->InsertNextId(pnt0); for (int i = 0; i < nrPoints - 1; i++) { cut->InsertNextId(index[i]); } fragment->push_back(cut); residual->push_back(cut); } // Generate cut plane. // Generate cut plane. // OUTPUT PHASE ----------------------------------------- // Have completed traversing each face. // Now update output with new fragment. // Iterate through each edge and record for each fragment, which are the // divided points included. // ------------------------------------------------------ if (fragment->size() > 3) { // add the current framgent to the outpuQ structure. outputQ.push_back(fragment); fragment = nullptr; // set threshold at which this fragment was created if (fieldNr) { // if this is the second field subdivision, we need to retrieve // the scalar value from the first field subdivision first. And insert the // retrieved first field value together with the current second field value // into the fragment scalar array. float firstFieldRange = fragScalar->GetComponent((vtkIdType)cp, 0); fragScalar->InsertNextTuple2(firstFieldRange, threshold); } else { // if this is the first field subdivisions, just insert the scalar value // into the array. fragScalar->InsertNextTuple2(threshold, 0); } } else { // Remove any partial fragments. while (!fragment->empty()) { fragment->pop_back(); } } // Faces defining the next working polyhedron are in the residual array. std::swap(working, residual); // Clear out anything left in residual while (!residual->empty()) { residual->pop_back(); } lastThreshold += fragWidth[fieldNr]; // Track final threshold } // for each threshold // If we are left with a residual, add it as a fragment. // However, note that final step in loop is to swap residual // with working, so need to look in the working list. if (working->size() > 3) { outputQ.push_back(working); // set threshold at which this fragment was created. if (fieldNr) { // if this is the second field subdivision, we need to retrieve // the scalar value from the first field subdivision first. And insert the // retrieved first field value together with the current second field value // into the fragment scalar array. float firstFieldRange = fragScalar->GetComponent((vtkIdType)cp, 0); fragScalar->InsertNextTuple2(firstFieldRange, lastThreshold); } else { // if this is the first field subdivisions, just insert the scalar value // into the array. fragScalar->InsertNextTuple2(lastThreshold, 0); } } else { while (!working->empty()) { working->pop_back(); } if (working) { delete working; working = nullptr; } } } // for each fragment of cell: faces, edges. // CutIds are the edges with current dividing fragments. } // for each field // OUTPUT PHASE: ---------------------------------------------- // Output generated fragments into main output dataset. // ------------------------------------------------------------ // For each output framgent, we compute its geometric volume and aggregate over // the cells. for (size_t co = 0; co < outputQ.size(); co++) { // The current fragment needs to be converted to a polygonal mesh. // Array for recording the vertices of the polygonal mesh. polyhedra->Initialize(); polyhedra->AllocateEstimate(static_cast(outputQ[co]->size()), 1); // for each face of the fragment vtkSmartPointer poly = vtkSmartPointer::New(); for (std::vector>::iterator fc = outputQ[co]->begin(); fc != outputQ[co]->end(); ++fc) { poly->Reset(); for (int pnr = 0; pnr < (*fc)->GetNumberOfIds(); pnr++) { poly->InsertNextId((*fc)->GetId(pnr)); } // insert the polygon face. polyhedra->InsertNextCell(VTK_POLYGON, poly); } polyhedra->SetPoints(newPoints); // convert the polygon faces to triangular faces. triangleFilter->SetInputData(polyhedra); triangleFilter->Update(); if (triangleFilter->GetOutput()->GetNumberOfCells() > 0) { // Compute the volume of the fragment volume->SetInputData(triangleFilter->GetOutput()); volume->Update(); fragVolume = volume->GetVolume(); } else { fragVolume = 0; } // Range values for the current fragment. fragRangeValue[0] = fragScalar->GetComponent((vtkIdType)(firstFragNr + co), 0); fragRangeValue[1] = fragScalar->GetComponent((vtkIdType)(firstFragNr + co), 1); // Map the current fragment into the 2-Dimensional bin based on its range values. binIndexFirst = (int)(this->ResX - 1) * (fragRangeValue[0] - inPD->GetArray(this->Fields[0])->GetRange()[0]) / fieldInterval[0]; binIndexSecond = (int)(this->ResY - 1) * (fragRangeValue[1] - inPD->GetArray(this->Fields[1])->GetRange()[0]) / fieldInterval[1]; ////cout << "biF: " << binIndexFirst << "\tbiS: " << binIndexSecond << "\t" << fragVolume << /// endl; // aggregate the fragment volumes in each bin if (binIndexFirst >= 0 && binIndexFirst < this->ResX && binIndexSecond >= 0 && binIndexSecond < this->ResY) { imageBin[binIndexFirst][binIndexSecond] += fragVolume; } // finding the largest volume in a bin. if (imageBin[binIndexFirst][binIndexSecond] > maxBinSize) { maxBinSize = imageBin[binIndexFirst][binIndexSecond]; } // Clear faces from current polytope in output queue. while (!outputQ[co]->empty()) { outputQ[co]->pop_back(); } } // end for loop in outputQ // Release the memory for the local structure. while (!outputQ.empty()) { Polytope plocal; plocal = outputQ.back(); outputQ.pop_back(); delete plocal; } outputQ.clear(); } // For each cell if (fragment) { delete fragment; fragment = nullptr; } if (residual) { delete residual; residual = nullptr; } // Create the output image data. output->SetExtent(0, this->ResX - 1, 0, this->ResY - 1, 0, 0); output->SetOrigin(0, 0, 0); output->SetSpacing(1, 1, 1); output->AllocateScalars(VTK_DOUBLE, 1); // A scalar array is attached onto the output vtkImageData. // This array records the total volume of the fragments in each bin. vtkSmartPointer volumeArray = vtkSmartPointer::New(); volumeArray->SetName("volume"); volumeArray->SetNumberOfComponents(1); volumeArray->SetNumberOfTuples(this->ResX * this->ResY); // Pixel density are computed by the fragment volume. for (int xIndex = 0; xIndex < this->ResX; xIndex++) { for (int yIndex = 0; yIndex < this->ResY; yIndex++) { float size = imageBin[xIndex][yIndex]; int idx = xIndex * this->ResY + yIndex; volumeArray->SetComponent(idx, 0, size); double* pixel = static_cast(output->GetScalarPointer(xIndex, yIndex, 0)); if (imageBin[xIndex][yIndex] > 0) { pixel[0] = 255.0 * imageBin[xIndex][yIndex] / maxBinSize; } else { pixel[0] = 0; } } } output->GetPointData()->AddArray(volumeArray); output->Squeeze(); for (int xIndex = 0; xIndex < this->ResX; ++xIndex) { delete[] imageBin[xIndex]; } delete[] imageBin; return 1; }