// SPDX-FileCopyrightText: Copyright (c) Ken Martin, Will Schroeder, Bill Lorensen // SPDX-License-Identifier: BSD-3-Clause #include "vtkVolumeContourSpectrumFilter.h" #include "vtkDataArray.h" #include "vtkDataSetAttributes.h" #include "vtkEdgeListIterator.h" #include "vtkIdList.h" #include "vtkInformation.h" #include "vtkInformationVector.h" #include "vtkObjectFactory.h" #include "vtkPointData.h" #include "vtkReebGraph.h" #include "vtkTable.h" #include "vtkTetra.h" #include "vtkUnstructuredGrid.h" #include "vtkVariantArray.h" VTK_ABI_NAMESPACE_BEGIN vtkStandardNewMacro(vtkVolumeContourSpectrumFilter); //------------------------------------------------------------------------------ vtkVolumeContourSpectrumFilter::vtkVolumeContourSpectrumFilter() { this->SetNumberOfInputPorts(2); this->ArcId = 0; this->FieldId = 0; this->NumberOfSamples = 100; } //------------------------------------------------------------------------------ vtkVolumeContourSpectrumFilter::~vtkVolumeContourSpectrumFilter() = default; //------------------------------------------------------------------------------ int vtkVolumeContourSpectrumFilter::FillInputPortInformation(int portNumber, vtkInformation* info) { switch (portNumber) { case 0: info->Remove(vtkAlgorithm::INPUT_REQUIRED_DATA_TYPE()); info->Append(vtkAlgorithm::INPUT_REQUIRED_DATA_TYPE(), "vtkUnstructuredGrid"); break; case 1: info->Remove(vtkAlgorithm::INPUT_REQUIRED_DATA_TYPE()); info->Append(vtkAlgorithm::INPUT_REQUIRED_DATA_TYPE(), "vtkReebGraph"); break; } return 1; } //------------------------------------------------------------------------------ int vtkVolumeContourSpectrumFilter::FillOutputPortInformation( int vtkNotUsed(portNumber), vtkInformation* info) { info->Set(vtkDataObject::DATA_TYPE_NAME(), "vtkTable"); return 1; } //------------------------------------------------------------------------------ void vtkVolumeContourSpectrumFilter::PrintSelf(ostream& os, vtkIndent indent) { this->Superclass::PrintSelf(os, indent); os << indent << "Arc Id: " << this->ArcId << "\n"; os << indent << "Number of Samples: " << this->NumberOfSamples << "\n"; os << indent << "Field Id: " << this->FieldId << "\n"; } //------------------------------------------------------------------------------ vtkTable* vtkVolumeContourSpectrumFilter::GetOutput() { return vtkTable::SafeDownCast(this->GetOutputDataObject(0)); } //------------------------------------------------------------------------------ int vtkVolumeContourSpectrumFilter::RequestData(vtkInformation* vtkNotUsed(request), vtkInformationVector** inputVector, vtkInformationVector* outputVector) { vtkInformation *inInfoMesh = inputVector[0]->GetInformationObject(0), *inInfoGraph = inputVector[1]->GetInformationObject(0); if ((!inInfoMesh) || (!inInfoGraph)) { return 0; } vtkUnstructuredGrid* inputMesh = vtkUnstructuredGrid::SafeDownCast(inInfoMesh->Get(vtkUnstructuredGrid::DATA_OBJECT())); vtkReebGraph* inputGraph = vtkReebGraph::SafeDownCast(inInfoGraph->Get(vtkReebGraph::DATA_OBJECT())); if ((inputMesh) && (inputGraph)) { vtkInformation* outInfo = outputVector->GetInformationObject(0); vtkTable* output = vtkTable::SafeDownCast(outInfo->Get(vtkDataObject::DATA_OBJECT())); if (output) { // Retrieve the arc given by ArcId vtkVariantArray* edgeInfo = vtkArrayDownCast( inputGraph->GetEdgeData()->GetAbstractArray("Vertex Ids")); // Invalid Reeb graph (no information associated to the edges) if (!edgeInfo) return 0; // Retrieve the information to get the critical vertices Ids vtkDataArray* criticalPointIds = vtkArrayDownCast(inputGraph->GetVertexData()->GetAbstractArray("Vertex Ids")); // Invalid Reeb graph (no information associated to the vertices) if (!criticalPointIds) return 0; vtkAbstractArray* vertexList = edgeInfo->GetPointer(ArcId)->ToArray(); // the arc defined by ArcId does not exist (out of bound?) if (!vertexList) return 0; vtkDataArray* scalarField = inputMesh->GetPointData()->GetArray(FieldId); if (!scalarField) return 0; // parse the input vertex list (region in which the connectivity of the // level sets does not change) and compute the area signature double cumulativeVolume = 0; std::vector scalarValues, volumeSignature; std::vector vertexIds; std::vector visitedTetrahedra; visitedTetrahedra.resize(inputMesh->GetNumberOfCells()); vertexIds.resize(vertexList->GetNumberOfTuples() + 2); scalarValues.resize(vertexIds.size()); volumeSignature.resize(vertexIds.size()); // include the critical points in the computation // - iterates through the edges of the Reeb graph until we found the arc // we're looking for // - retrieve the Source and Target of the edge // - pick the corresponding mesh vertex Ids in the VertexData. std::pair criticalPoints; vtkEdgeListIterator* eIt = vtkEdgeListIterator::New(); inputGraph->GetEdges(eIt); do { vtkEdgeType e = eIt->Next(); if (e.Id == ArcId) { if ((criticalPointIds->GetTuple(e.Source)) && (criticalPointIds->GetTuple(e.Target))) { criticalPoints.first = (int)*(criticalPointIds->GetTuple(e.Source)); criticalPoints.second = (int)*(criticalPointIds->GetTuple(e.Target)); } else // invalid Reeb graph return 0; } } while (eIt->HasNext()); eIt->Delete(); vertexIds[0] = criticalPoints.first; vertexIds[vertexIds.size() - 1] = criticalPoints.second; // NB: the vertices of vertexList are already in sorted order of function // value. for (int i = 0; i < vertexList->GetNumberOfTuples(); i++) vertexIds[i + 1] = vertexList->GetVariantValue(i).ToInt(); // mark all the input triangles as non visited. for (unsigned int i = 0; i < visitedTetrahedra.size(); i++) visitedTetrahedra[i] = false; // now do the parsing double min = scalarField->GetComponent(vertexIds[0], 0), max = scalarField->GetComponent(vertexIds[vertexIds.size() - 1], 0); for (unsigned int i = 0; i < vertexIds.size(); i++) { if (this->CheckAbort()) { break; } scalarValues[i] = scalarField->GetComponent(vertexIds[i], 0); vtkIdList* starTetrahedronList = vtkIdList::New(); inputMesh->GetPointCells(vertexIds[i], starTetrahedronList); for (int j = 0; j < starTetrahedronList->GetNumberOfIds(); j++) { vtkIdType tId = starTetrahedronList->GetId(j); if (!visitedTetrahedra[tId]) { vtkTetra* t = vtkTetra::SafeDownCast(inputMesh->GetCell(tId)); if ((scalarField->GetComponent(t->GetPointIds()->GetId(0), 0) <= scalarValues[i]) && (scalarField->GetComponent(t->GetPointIds()->GetId(1), 0) <= scalarValues[i]) && (scalarField->GetComponent(t->GetPointIds()->GetId(2), 0) <= scalarValues[i]) && (scalarField->GetComponent(t->GetPointIds()->GetId(3), 0) <= scalarValues[i]) && (scalarField->GetComponent(t->GetPointIds()->GetId(0), 0) >= min) && (scalarField->GetComponent(t->GetPointIds()->GetId(1), 0) >= min) && (scalarField->GetComponent(t->GetPointIds()->GetId(2), 0) >= min) && (scalarField->GetComponent(t->GetPointIds()->GetId(3), 0) >= min)) { // make sure the triangle is strictly in the covered function // span. double p0[3], p1[3], p2[3], p3[3]; inputMesh->GetPoint(t->GetPointIds()->GetId(0), p0); inputMesh->GetPoint(t->GetPointIds()->GetId(1), p1); inputMesh->GetPoint(t->GetPointIds()->GetId(2), p2); inputMesh->GetPoint(t->GetPointIds()->GetId(3), p3); cumulativeVolume += vtkTetra::ComputeVolume(p0, p1, p2, p3); visitedTetrahedra[tId] = true; } } } volumeSignature[i] = cumulativeVolume; starTetrahedronList->Delete(); } // now adjust to the desired sampling std::vector> samples(NumberOfSamples); unsigned int pos = 0; for (int i = 0; i < NumberOfSamples; i++) { samples[i].first = 0; samples[i].second = 0; double temp = min + (i + 1.0) * ((max - min) / ((double)NumberOfSamples)); while ((pos < scalarValues.size()) && (scalarValues[pos] < temp)) { samples[i].first++; samples[i].second += volumeSignature[pos]; pos++; } if (samples[i].first) { samples[i].second /= samples[i].first; } } // no value at the start? put 0 if (!samples[0].first) { samples[0].first = 1; samples[0].second = 0; } // no value at the end? put the cumulative area if (!samples[samples.size() - 1].first) { samples[samples.size() - 1].first = 1; samples[samples.size() - 1].second = cumulativeVolume; } // fill out the blanks int lastSample = 0; for (int i = 0; i < NumberOfSamples; i++) { if (!samples[i].first) { // not enough vertices in the region for the number of desired // samples. we have to interpolate. // first, search for the next valid sample int nextSample = i; for (; nextSample < NumberOfSamples; nextSample++) { if (samples[nextSample].first) break; } // next interpolate samples[i].second = samples[lastSample].second + (i - lastSample) * (samples[nextSample].second - samples[lastSample].second) / (nextSample - lastSample); } else lastSample = i; } // now prepare the output vtkVariantArray* outputSignature = vtkVariantArray::New(); outputSignature->SetNumberOfTuples(static_cast(samples.size())); for (unsigned int i = 0; i < samples.size(); i++) { outputSignature->SetValue(i, samples[i].second); } output->Initialize(); output->AddColumn(outputSignature); outputSignature->Delete(); } return 1; } return 0; } VTK_ABI_NAMESPACE_END