//============================================================================ // Copyright (c) Kitware, Inc. // All rights reserved. // See LICENSE.txt 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. //============================================================================ #ifndef vtk_m_worklet_contour_MarchingCells_h #define vtk_m_worklet_contour_MarchingCells_h #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include namespace vtkm { namespace worklet { namespace marching_cells { // ----------------------------------------------------------------------------- template vtkm::cont::ArrayHandle make_ScalarField( const vtkm::cont::ArrayHandle& ah) { return ah; } template vtkm::cont::ArrayHandle make_ScalarField( const vtkm::cont::ArrayHandle& ah) { return ah; } template vtkm::cont::ArrayHandleCast> make_ScalarField(const vtkm::cont::ArrayHandle& ah) { return vtkm::cont::make_ArrayHandleCast(ah, vtkm::FloatDefault()); } template vtkm::cont::ArrayHandleCast> make_ScalarField(const vtkm::cont::ArrayHandle& ah) { return vtkm::cont::make_ArrayHandleCast(ah, vtkm::FloatDefault()); } // --------------------------------------------------------------------------- template class ClassifyCell : public vtkm::worklet::WorkletVisitCellsWithPoints { public: using ControlSignature = void(WholeArrayIn isoValues, FieldInPoint fieldIn, CellSetIn cellSet, FieldOutCell outNumTriangles, ExecObject classifyTable); using ExecutionSignature = void(CellShape, _1, _2, _4, _5); using InputDomain = _3; template VTKM_EXEC void operator()(CellShapeType shape, const IsoValuesType& isovalues, const FieldInType& fieldIn, vtkm::IdComponent& numTriangles, const ClassifyTableType& classifyTable) const { vtkm::IdComponent sum = 0; vtkm::IdComponent numIsoValues = static_cast(isovalues.GetNumberOfValues()); vtkm::IdComponent numVerticesPerCell = classifyTable.GetNumVerticesPerCell(shape.Id); for (vtkm::Id i = 0; i < numIsoValues; ++i) { vtkm::IdComponent caseNumber = 0; for (vtkm::IdComponent j = 0; j < numVerticesPerCell; ++j) { caseNumber |= (fieldIn[j] > isovalues[i]) << j; } sum += classifyTable.GetNumTriangles(shape.Id, caseNumber); } numTriangles = sum; } }; /// \brief Used to store data need for the EdgeWeightGenerate worklet. /// This information is not passed as part of the arguments to the worklet as /// that dramatically increase compile time by 200% // TODO: remove unused data members. // ----------------------------------------------------------------------------- class EdgeWeightGenerateMetaData : vtkm::cont::ExecutionObjectBase { public: class ExecObject { template using ReadPortalType = typename vtkm::cont::ArrayHandle::ReadPortalType; template using WritePortalType = typename vtkm::cont::ArrayHandle::WritePortalType; public: ExecObject() = default; VTKM_CONT ExecObject(vtkm::Id size, vtkm::cont::ArrayHandle& interpWeights, vtkm::cont::ArrayHandle& interpIds, vtkm::cont::ArrayHandle& interpCellIds, vtkm::cont::ArrayHandle& interpContourId, vtkm::cont::DeviceAdapterId device, vtkm::cont::Token& token) : InterpWeightsPortal(interpWeights.PrepareForOutput(3 * size, device, token)) , InterpIdPortal(interpIds.PrepareForOutput(3 * size, device, token)) , InterpCellIdPortal(interpCellIds.PrepareForOutput(3 * size, device, token)) , InterpContourPortal(interpContourId.PrepareForOutput(3 * size, device, token)) { // Interp needs to be 3 times longer than size as they are per point of the // output triangle } WritePortalType InterpWeightsPortal; WritePortalType InterpIdPortal; WritePortalType InterpCellIdPortal; WritePortalType InterpContourPortal; }; VTKM_CONT EdgeWeightGenerateMetaData(vtkm::Id size, vtkm::cont::ArrayHandle& interpWeights, vtkm::cont::ArrayHandle& interpIds, vtkm::cont::ArrayHandle& interpCellIds, vtkm::cont::ArrayHandle& interpContourId) : Size(size) , InterpWeights(interpWeights) , InterpIds(interpIds) , InterpCellIds(interpCellIds) , InterpContourId(interpContourId) { } VTKM_CONT ExecObject PrepareForExecution(vtkm::cont::DeviceAdapterId device, vtkm::cont::Token& token) { return ExecObject(this->Size, this->InterpWeights, this->InterpIds, this->InterpCellIds, this->InterpContourId, device, token); } private: vtkm::Id Size; vtkm::cont::ArrayHandle InterpWeights; vtkm::cont::ArrayHandle InterpIds; vtkm::cont::ArrayHandle InterpCellIds; vtkm::cont::ArrayHandle InterpContourId; }; /// \brief Compute the weights for each edge that is used to generate /// a point in the resulting iso-surface // ----------------------------------------------------------------------------- template class EdgeWeightGenerate : public vtkm::worklet::WorkletVisitCellsWithPoints { public: using ScatterType = vtkm::worklet::ScatterCounting; template VTKM_CONT static ScatterType MakeScatter(const ArrayHandleType& numOutputTrisPerCell) { return ScatterType(numOutputTrisPerCell); } typedef void ControlSignature(CellSetIn cellset, // Cell set WholeArrayIn isoValues, FieldInPoint fieldIn, // Input point field defining the contour ExecObject metaData, // Metadata for edge weight generation ExecObject classifyTable, ExecObject triTable); using ExecutionSignature = void(CellShape, _2, _3, _4, _5, _6, InputIndex, WorkIndex, VisitIndex, PointIndices); using InputDomain = _1; template VTKM_EXEC void operator()(const CellShape shape, const IsoValuesType& isovalues, const FieldInType& fieldIn, // Input point field defining the contour const EdgeWeightGenerateMetaData::ExecObject& metaData, const ClassifyTableType& classifyTable, const TriTableType& triTable, vtkm::Id inputCellId, vtkm::Id outputCellId, vtkm::IdComponent visitIndex, const IndicesVecType& indices) const { const vtkm::Id outputPointId = 3 * outputCellId; using FieldType = typename vtkm::VecTraits::ComponentType; vtkm::IdComponent sum = 0, caseNumber = 0; vtkm::IdComponent i = 0, numIsoValues = static_cast(isovalues.GetNumberOfValues()); vtkm::IdComponent numVerticesPerCell = classifyTable.GetNumVerticesPerCell(shape.Id); for (i = 0; i < numIsoValues; ++i) { const FieldType ivalue = isovalues[i]; // Compute the Marching Cubes case number for this cell. We need to iterate // the isovalues until the sum >= our visit index. But we need to make // sure the caseNumber is correct before stopping caseNumber = 0; for (vtkm::IdComponent j = 0; j < numVerticesPerCell; ++j) { caseNumber |= (fieldIn[j] > ivalue) << j; } sum += classifyTable.GetNumTriangles(shape.Id, caseNumber); if (sum > visitIndex) { break; } } visitIndex = sum - visitIndex - 1; // Interpolate for vertex positions and associated scalar values for (vtkm::IdComponent triVertex = 0; triVertex < 3; triVertex++) { auto edgeVertices = triTable.GetEdgeVertices(shape.Id, caseNumber, visitIndex, triVertex); const FieldType fieldValue0 = fieldIn[edgeVertices.first]; const FieldType fieldValue1 = fieldIn[edgeVertices.second]; // Store the input cell id so that we can properly generate the normals // in a subsequent call, after we have merged duplicate points metaData.InterpCellIdPortal.Set(outputPointId + triVertex, inputCellId); metaData.InterpContourPortal.Set(outputPointId + triVertex, static_cast(i)); metaData.InterpIdPortal.Set( outputPointId + triVertex, vtkm::Id2(indices[edgeVertices.first], indices[edgeVertices.second])); vtkm::FloatDefault interpolant = static_cast(isovalues[i] - fieldValue0) / static_cast(fieldValue1 - fieldValue0); metaData.InterpWeightsPortal.Set(outputPointId + triVertex, interpolant); } } }; // --------------------------------------------------------------------------- struct MultiContourLess { template VTKM_EXEC_CONT bool operator()(const T& a, const T& b) const { return a < b; } template VTKM_EXEC_CONT bool operator()(const vtkm::Pair& a, const vtkm::Pair& b) const { return (a.first < b.first) || (!(b.first < a.first) && (a.second < b.second)); } template VTKM_EXEC_CONT bool operator()(const vtkm::internal::ArrayPortalValueReference& a, const U& b) const { U&& t = static_cast(a); return t < b; } }; // --------------------------------------------------------------------------- struct MergeDuplicateValues : vtkm::worklet::WorkletReduceByKey { using ControlSignature = void(KeysIn keys, ValuesIn valuesIn1, ValuesIn valuesIn2, ReducedValuesOut valueOut1, ReducedValuesOut valueOut2); using ExecutionSignature = void(_1, _2, _3, _4, _5); using InputDomain = _1; template VTKM_EXEC void operator()(const T&, const ValuesInType& values1, const Values2InType& values2, ValuesOutType& valueOut1, Values2OutType& valueOut2) const { valueOut1 = values1[0]; valueOut2 = values2[0]; } }; // --------------------------------------------------------------------------- struct CopyEdgeIds : vtkm::worklet::WorkletMapField { using ControlSignature = void(FieldIn, FieldOut); using ExecutionSignature = void(_1, _2); using InputDomain = _1; VTKM_EXEC void operator()(const vtkm::Id2& input, vtkm::Id2& output) const { output = input; } template VTKM_EXEC void operator()(const vtkm::Pair& input, vtkm::Id2& output) const { output = input.second; } }; // --------------------------------------------------------------------------- template void MergeDuplicates(const vtkm::cont::Invoker& invoker, const vtkm::cont::ArrayHandle& original_keys, vtkm::cont::ArrayHandle& weights, vtkm::cont::ArrayHandle& edgeIds, vtkm::cont::ArrayHandle& cellids, vtkm::cont::ArrayHandle& connectivity) { vtkm::cont::ArrayHandle input_keys; vtkm::cont::ArrayCopy(original_keys, input_keys); vtkm::worklet::Keys keys(input_keys); input_keys.ReleaseResources(); { vtkm::cont::ArrayHandle writeCells; vtkm::cont::ArrayHandle writeWeights; invoker(MergeDuplicateValues{}, keys, weights, cellids, writeWeights, writeCells); weights = writeWeights; cellids = writeCells; } //need to build the new connectivity auto uniqueKeys = keys.GetUniqueKeys(); vtkm::cont::Algorithm::LowerBounds( uniqueKeys, original_keys, connectivity, marching_cells::MultiContourLess()); //update the edge ids invoker(CopyEdgeIds{}, uniqueKeys, edgeIds); } // ----------------------------------------------------------------------------- template struct EdgeVertex { VTKM_EXEC vtkm::Id operator()(const vtkm::Id2& edge) const { return edge[Comp]; } }; class NormalsWorkletPass1 : public vtkm::worklet::WorkletVisitPointsWithCells { private: using PointIdsArray = vtkm::cont::ArrayHandleTransform, EdgeVertex<0>>; public: using ControlSignature = void(CellSetIn, WholeCellSetIn, WholeArrayIn pointCoordinates, WholeArrayIn inputField, FieldOutPoint normals); using ExecutionSignature = void(CellCount, CellIndices, InputIndex, _2, _3, _4, _5); using InputDomain = _1; using ScatterType = vtkm::worklet::ScatterPermutation; VTKM_CONT static ScatterType MakeScatter(const vtkm::cont::ArrayHandle& edges) { return ScatterType(vtkm::cont::make_ArrayHandleTransform(edges, EdgeVertex<0>())); } template VTKM_EXEC void operator()(const vtkm::IdComponent& numCells, const FromIndexType& cellIds, vtkm::Id pointId, const CellSetInType& geometry, const WholeCoordinatesIn& pointCoordinates, const WholeFieldIn& inputField, NormalType& normal) const { vtkm::worklet::gradient::PointGradient gradient; gradient(numCells, cellIds, pointId, geometry, pointCoordinates, inputField, normal); } template VTKM_EXEC void operator()(const vtkm::IdComponent& vtkmNotUsed(numCells), const FromIndexType& vtkmNotUsed(cellIds), vtkm::Id pointId, vtkm::exec::ConnectivityStructured& geometry, const WholeCoordinatesIn& pointCoordinates, const WholeFieldIn& inputField, NormalType& normal) const { //Optimization for structured cellsets so we can call StructuredPointGradient //and have way faster gradients vtkm::exec::ConnectivityStructured pointGeom(geometry); vtkm::exec::arg::ThreadIndicesPointNeighborhood tpn(pointId, pointId, 0, pointId, pointGeom); const auto& boundary = tpn.GetBoundaryState(); auto pointPortal = pointCoordinates.GetPortal(); auto fieldPortal = inputField.GetPortal(); vtkm::exec::FieldNeighborhood points(pointPortal, boundary); vtkm::exec::FieldNeighborhood field(fieldPortal, boundary); vtkm::worklet::gradient::StructuredPointGradient gradient; gradient(boundary, points, field, normal); } }; class NormalsWorkletPass2 : public vtkm::worklet::WorkletVisitPointsWithCells { private: using PointIdsArray = vtkm::cont::ArrayHandleTransform, EdgeVertex<1>>; public: typedef void ControlSignature(CellSetIn, WholeCellSetIn, WholeArrayIn pointCoordinates, WholeArrayIn inputField, WholeArrayIn weights, FieldInOutPoint normals); using ExecutionSignature = void(CellCount, CellIndices, InputIndex, _2, _3, _4, WorkIndex, _5, _6); using InputDomain = _1; using ScatterType = vtkm::worklet::ScatterPermutation; VTKM_CONT static ScatterType MakeScatter(const vtkm::cont::ArrayHandle& edges) { return ScatterType(vtkm::cont::make_ArrayHandleTransform(edges, EdgeVertex<1>())); } template VTKM_EXEC void operator()(const vtkm::IdComponent& numCells, const FromIndexType& cellIds, vtkm::Id pointId, const CellSetInType& geometry, const WholeCoordinatesIn& pointCoordinates, const WholeFieldIn& inputField, vtkm::Id edgeId, const WholeWeightsIn& weights, NormalType& normal) const { vtkm::worklet::gradient::PointGradient gradient; NormalType grad1; gradient(numCells, cellIds, pointId, geometry, pointCoordinates, inputField, grad1); NormalType grad0 = normal; auto weight = weights.Get(edgeId); normal = vtkm::Normal(vtkm::Lerp(grad0, grad1, weight)); } template VTKM_EXEC void operator()(const vtkm::IdComponent& vtkmNotUsed(numCells), const FromIndexType& vtkmNotUsed(cellIds), vtkm::Id pointId, vtkm::exec::ConnectivityStructured& geometry, const WholeCoordinatesIn& pointCoordinates, const WholeFieldIn& inputField, vtkm::Id edgeId, const WholeWeightsIn& weights, NormalType& normal) const { //Optimization for structured cellsets so we can call StructuredPointGradient //and have way faster gradients vtkm::exec::ConnectivityStructured pointGeom(geometry); vtkm::exec::arg::ThreadIndicesPointNeighborhood tpn(pointId, pointId, 0, pointId, pointGeom); const auto& boundary = tpn.GetBoundaryState(); auto pointPortal = pointCoordinates.GetPortal(); auto fieldPortal = inputField.GetPortal(); vtkm::exec::FieldNeighborhood points(pointPortal, boundary); vtkm::exec::FieldNeighborhood field(fieldPortal, boundary); vtkm::worklet::gradient::StructuredPointGradient gradient; NormalType grad1; gradient(boundary, points, field, grad1); NormalType grad0 = normal; auto weight = weights.Get(edgeId); normal = vtkm::Lerp(grad0, grad1, weight); const auto mag2 = vtkm::MagnitudeSquared(normal); if (mag2 > 0.) { normal = normal * vtkm::RSqrt(mag2); } } }; struct GenerateNormals { template void operator()(const CoordinateSystem& coordinates, const vtkm::cont::Invoker& invoker, vtkm::cont::ArrayHandle>& normals, const vtkm::cont::ArrayHandle& field, const CellSet cellset, const vtkm::cont::ArrayHandle& edges, const vtkm::cont::ArrayHandle& weights) const { // To save memory, the normals computation is done in two passes. In the first // pass the gradient at the first vertex of each edge is computed and stored in // the normals array. In the second pass the gradient at the second vertex is // computed and the gradient of the first vertex is read from the normals array. // The final normal is interpolated from the two gradient values and stored // in the normals array. // auto scalarField = marching_cells::make_ScalarField(field); invoker(NormalsWorkletPass1{}, NormalsWorkletPass1::MakeScatter(edges), cellset, cellset, coordinates, scalarField, normals); invoker(NormalsWorkletPass2{}, NormalsWorkletPass2::MakeScatter(edges), cellset, cellset, coordinates, scalarField, weights, normals); } }; //---------------------------------------------------------------------------- template vtkm::cont::CellSetSingleType<> execute( const CellSetType& cells, const CoordinateSystem& coordinateSystem, const std::vector& isovalues, const vtkm::cont::ArrayHandle& inputField, vtkm::cont::ArrayHandle, StorageTagVertices>& vertices, vtkm::cont::ArrayHandle, StorageTagNormals>& normals, vtkm::worklet::contour::CommonState& sharedState) { using vtkm::worklet::contour::MapPointField; using vtkm::worklet::marching_cells::ClassifyCell; using vtkm::worklet::marching_cells::EdgeWeightGenerate; using vtkm::worklet::marching_cells::EdgeWeightGenerateMetaData; vtkm::worklet::marching_cells::CellClassifyTable classTable; vtkm::worklet::marching_cells::TriangleGenerationTable triTable; // Setup the invoker vtkm::cont::Invoker invoker; vtkm::cont::ArrayHandle isoValuesHandle = vtkm::cont::make_ArrayHandle(isovalues, vtkm::CopyFlag::Off); // Call the ClassifyCell functor to compute the Marching Cubes case numbers // for each cell, and the number of vertices to be generated vtkm::cont::ArrayHandle numOutputTrisPerCell; { marching_cells::ClassifyCell classifyCell; invoker(classifyCell, isoValuesHandle, inputField, cells, numOutputTrisPerCell, classTable); } //Pass 2 Generate the edges vtkm::cont::ArrayHandle contourIds; vtkm::cont::ArrayHandle originalCellIdsForPoints; { auto scatter = EdgeWeightGenerate::MakeScatter(numOutputTrisPerCell); // Maps output cells to input cells. Store this for cell field mapping. sharedState.CellIdMap = scatter.GetOutputToInputMap(); EdgeWeightGenerateMetaData metaData( scatter.GetOutputRange(numOutputTrisPerCell.GetNumberOfValues()), sharedState.InterpolationWeights, sharedState.InterpolationEdgeIds, originalCellIdsForPoints, contourIds); invoker(EdgeWeightGenerate{}, scatter, cells, //cast to a scalar field if not one, as cellderivative only works on those isoValuesHandle, inputField, metaData, classTable, triTable); } if (isovalues.size() <= 1 || !sharedState.MergeDuplicatePoints) { //release memory early that we are not going to need again contourIds.ReleaseResources(); } vtkm::cont::ArrayHandle connectivity; if (sharedState.MergeDuplicatePoints) { // In all the below cases you will notice that only interpolation ids // are updated. That is because MergeDuplicates will internally update // the InterpolationWeights and InterpolationOriginCellIds arrays to be the correct for the // output. But for InterpolationEdgeIds we need to do it manually once done if (isovalues.size() == 1) { marching_cells::MergeDuplicates(invoker, sharedState.InterpolationEdgeIds, //keys sharedState.InterpolationWeights, //values sharedState.InterpolationEdgeIds, //values originalCellIdsForPoints, //values connectivity); // computed using lower bounds } else { marching_cells::MergeDuplicates( invoker, vtkm::cont::make_ArrayHandleZip(contourIds, sharedState.InterpolationEdgeIds), //keys sharedState.InterpolationWeights, //values sharedState.InterpolationEdgeIds, //values originalCellIdsForPoints, //values connectivity); // computed using lower bounds } } else { //when we don't merge points, the connectivity array can be represented //by a counting array. The danger of doing it this way is that the output //type is unknown. That is why we copy it into an explicit array vtkm::cont::ArrayHandleIndex temp(sharedState.InterpolationEdgeIds.GetNumberOfValues()); vtkm::cont::ArrayCopy(temp, connectivity); } //generate the vertices's invoker(MapPointField{}, sharedState.InterpolationEdgeIds, sharedState.InterpolationWeights, coordinateSystem, vertices); //assign the connectivity to the cell set vtkm::cont::CellSetSingleType<> outputCells; outputCells.Fill(vertices.GetNumberOfValues(), vtkm::CELL_SHAPE_TRIANGLE, 3, connectivity); //now that the vertices have been generated we can generate the normals if (sharedState.GenerateNormals) { GenerateNormals genNorms; genNorms(coordinateSystem, invoker, normals, inputField, cells, sharedState.InterpolationEdgeIds, sharedState.InterpolationWeights); } return outputCells; } } } } // namespace vtkm::worklet::marching_cells #endif // vtk_m_worklet_contour_MarchingCells_h