//============================================================================ // 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. // // Copyright 2014 Sandia Corporation. // Copyright 2014 UT-Battelle, LLC. // Copyright 2014 Los Alamos National Security. // // Under the terms of Contract DE-AC04-94AL85000 with Sandia Corporation, // the U.S. Government retains certain rights in this software. // // Under the terms of Contract DE-AC52-06NA25396 with Los Alamos National // Laboratory (LANL), the U.S. Government retains certain rights in // this software. //============================================================================ #ifndef vtk_m_worklet_MarchingCubes_h #define vtk_m_worklet_MarchingCubes_h #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include namespace vtkm { namespace worklet { namespace marchingcubes { // ----------------------------------------------------------------------------- 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 VTKM_EXEC int GetHexahedronClassification(const T& values, const U isoValue) { return ((values[0] > isoValue) | (values[1] > isoValue) << 1 | (values[2] > isoValue) << 2 | (values[3] > isoValue) << 3 | (values[4] > isoValue) << 4 | (values[5] > isoValue) << 5 | (values[6] > isoValue) << 6 | (values[7] > isoValue) << 7); } // --------------------------------------------------------------------------- template class ClassifyCell : public vtkm::worklet::WorkletMapPointToCell { public: struct ClassifyCellTagType : vtkm::ListTagBase { }; typedef void ControlSignature( FieldInPoint< ClassifyCellTagType > inNodes, CellSetIn cellset, FieldOutCell< IdComponentType > outNumTriangles, WholeArrayIn< IdComponentType > numTrianglesTable); typedef void ExecutionSignature(_1, _3, _4); typedef _2 InputDomain; T Isovalue; VTKM_CONT ClassifyCell(T isovalue) : Isovalue(isovalue) { } template VTKM_EXEC void operator()(const FieldInType &fieldIn, vtkm::IdComponent &numTriangles, const NumTrianglesTablePortalType &numTrianglesTable) const { typedef typename vtkm::VecTraits::ComponentType FieldType; const FieldType iso = static_cast(this->Isovalue); const vtkm::IdComponent caseNumber = GetHexahedronClassification(fieldIn, iso); numTriangles = numTrianglesTable.Get(caseNumber); } }; /// \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% // ----------------------------------------------------------------------------- template< typename ScalarType, typename NormalStorageType, typename DeviceAdapter > class EdgeWeightGenerateMetaData { template struct PortalTypes { typedef vtkm::cont::ArrayHandle HandleType; typedef typename HandleType::template ExecutionTypes ExecutionTypes; typedef typename ExecutionTypes::Portal Portal; typedef typename ExecutionTypes::PortalConst PortalConst; }; struct NormalPortalTypes { typedef vtkm::cont::ArrayHandle, NormalStorageType> HandleType; typedef typename HandleType::template ExecutionTypes ExecutionTypes; typedef typename ExecutionTypes::Portal Portal; }; public: VTKM_CONT EdgeWeightGenerateMetaData( vtkm::Id size, vtkm::cont::ArrayHandle< vtkm::Vec, NormalStorageType >& normals, vtkm::cont::ArrayHandle< vtkm::FloatDefault >& interpWeights, vtkm::cont::ArrayHandle& interpIds, const vtkm::cont::ArrayHandle< vtkm::IdComponent >& edgeTable, const vtkm::cont::ArrayHandle< vtkm::IdComponent >& numTriTable, const vtkm::cont::ArrayHandle< vtkm::IdComponent >& triTable, const vtkm::worklet::ScatterCounting& scatter): NormalPortal( normals.PrepareForOutput( 3*size, DeviceAdapter() ) ), InterpWeightsPortal( interpWeights.PrepareForOutput( 3*size, DeviceAdapter()) ), InterpIdPortal( interpIds.PrepareForOutput( 3*size, DeviceAdapter() ) ), EdgeTable( edgeTable.PrepareForInput(DeviceAdapter()) ), NumTriTable( numTriTable.PrepareForInput(DeviceAdapter()) ), TriTable( triTable.PrepareForInput(DeviceAdapter()) ), Scatter(scatter) { //any way we can easily build an interface so that we don't need to hold //onto a billion portals? //Normal and Interp need to be 3 times longer than size as they //are per point of the output triangle } typename NormalPortalTypes::Portal NormalPortal; typename PortalTypes::Portal InterpWeightsPortal; typename PortalTypes::Portal InterpIdPortal; typename PortalTypes::PortalConst EdgeTable; typename PortalTypes::PortalConst NumTriTable; typename PortalTypes::PortalConst TriTable; vtkm::worklet::ScatterCounting Scatter; }; /// \brief Compute the weights for each edge that is used to generate /// a point in the resulting iso-surface // ----------------------------------------------------------------------------- template< typename ScalarType, typename NormalStorageType, typename DeviceAdapter > class EdgeWeightGenerate : public vtkm::worklet::WorkletMapPointToCell { public: typedef vtkm::worklet::ScatterCounting ScatterType; typedef void ControlSignature( CellSetIn cellset, // Cell set FieldInPoint fieldIn, // Input point field defining the contour FieldInPoint pcoordIn // Input point coordinates ); typedef void ExecutionSignature(CellShape, _2, _3, WorkIndex, VisitIndex, FromIndices); typedef _1 InputDomain; VTKM_CONT EdgeWeightGenerate(vtkm::Float64 isovalue, bool genNormals, const EdgeWeightGenerateMetaData& meta) : Isovalue(isovalue), GenerateNormals(genNormals), MetaData( meta ) { } template VTKM_EXEC void operator()( vtkm::CellShapeTagGeneric shape, const FieldInType & fieldIn, // Input point field defining the contour const CoordType & coords, // Input point coordinates vtkm::Id outputCellId, vtkm::IdComponent visitIndex, const IndicesVecType & indices) const { //covers when we have hexs coming from unstructured data VTKM_ASSUME( shape.Id == CELL_SHAPE_HEXAHEDRON ); this->operator()(vtkm::CellShapeTagHexahedron(), fieldIn, coords, outputCellId, visitIndex, indices); } template VTKM_EXEC void operator()( CellShapeTagQuad vtkmNotUsed(shape), const FieldInType & vtkmNotUsed(fieldIn), // Input point field defining the contour const CoordType & vtkmNotUsed(coords), // Input point coordinates vtkm::Id vtkmNotUsed(outputCellId), vtkm::IdComponent vtkmNotUsed(visitIndex), const IndicesVecType & vtkmNotUsed(indices) ) const { //covers when we have quads coming from 2d structured data } template VTKM_EXEC void operator()( vtkm::CellShapeTagHexahedron shape, const FieldInType &fieldIn, // Input point field defining the contour const CoordType &coords, // Input point coordinates vtkm::Id outputCellId, vtkm::IdComponent visitIndex, const IndicesVecType &indices) const { //covers when we have hexs coming from 3d structured data const vtkm::Id outputPointId = 3 * outputCellId; typedef typename vtkm::VecTraits::ComponentType FieldType; const FieldType iso = static_cast(this->Isovalue); // Compute the Marching Cubes case number for this cell const vtkm::IdComponent caseNumber = GetHexahedronClassification(fieldIn, iso); // Interpolate for vertex positions and associated scalar values const vtkm::Id triTableOffset = static_cast(caseNumber*16 + visitIndex*3); for (vtkm::IdComponent triVertex = 0; triVertex < 3; triVertex++) { const vtkm::IdComponent edgeIndex = MetaData.TriTable.Get(triTableOffset + triVertex); const vtkm::IdComponent edgeVertex0 = MetaData.EdgeTable.Get(2*edgeIndex + 0); const vtkm::IdComponent edgeVertex1 = MetaData.EdgeTable.Get(2*edgeIndex + 1); const FieldType fieldValue0 = fieldIn[edgeVertex0]; const FieldType fieldValue1 = fieldIn[edgeVertex1]; //need to factor in outputCellId MetaData.InterpIdPortal.Set( outputPointId+triVertex, vtkm::Id2(indices[edgeVertex0], indices[edgeVertex1])); vtkm::FloatDefault interpolant = static_cast(iso - fieldValue0) / static_cast(fieldValue1 - fieldValue0); //need to factor in outputCellId MetaData.InterpWeightsPortal.Set(outputPointId+triVertex, interpolant); if(this->GenerateNormals) { const vtkm::Vec edgePCoord0 = vtkm::exec::ParametricCoordinatesPoint( fieldIn.GetNumberOfComponents(), edgeVertex0, shape, *this); const vtkm::Vec edgePCoord1 = vtkm::exec::ParametricCoordinatesPoint( fieldIn.GetNumberOfComponents(), edgeVertex1, shape, *this); const vtkm::Vec interpPCoord = vtkm::Lerp(edgePCoord0, edgePCoord1, interpolant); //need to factor in outputCellId MetaData.NormalPortal.Set(outputPointId+triVertex, vtkm::Normal(vtkm::exec::CellDerivative( fieldIn, coords, interpPCoord, shape, *this)) ); } } } VTKM_CONT ScatterType GetScatter() const { return this->MetaData.Scatter; } private: const vtkm::Float64 Isovalue; const bool GenerateNormals; EdgeWeightGenerateMetaData MetaData; // Not implemented void operator=(const EdgeWeightGenerate &); }; // --------------------------------------------------------------------------- class ApplyToField : public vtkm::worklet::WorkletMapField { public: typedef void ControlSignature(FieldIn< Id2Type > interpolation_ids, FieldIn< Scalar > interpolation_weights, WholeArrayIn<> inputField, FieldOut<> output ); typedef void ExecutionSignature(_1, _2, _3, _4); typedef _1 InputDomain; VTKM_CONT ApplyToField() {} template VTKM_EXEC void operator()(const vtkm::Id2& low_high, const WeightType &weight, const InFieldPortalType& inPortal, OutFieldType &result) const { //fetch the low / high values from inPortal result = vtkm::Lerp(inPortal.Get(low_high[0]), inPortal.Get(low_high[1]), weight); } }; // --------------------------------------------------------------------------- struct FirstValueSame { template VTKM_EXEC_CONT bool operator()(const vtkm::Pair& a, const vtkm::Pair& b) const { return (a.first == b.first); } }; } /// \brief Compute the isosurface for a uniform grid data set class MarchingCubes { public: //---------------------------------------------------------------------------- MarchingCubes(bool mergeDuplicates=true): MergeDuplicatePoints(mergeDuplicates), EdgeTable(), NumTrianglesTable(), TriangleTable(), InterpolationWeights(), InterpolationIds() { // Set up the Marching Cubes case tables as part of the filter so that // we cache these tables in the execution environment between execution runs this->EdgeTable = vtkm::cont::make_ArrayHandle(vtkm::worklet::internal::edgeTable, 24); this->NumTrianglesTable = vtkm::cont::make_ArrayHandle(vtkm::worklet::internal::numTrianglesTable, 256); this->TriangleTable = vtkm::cont::make_ArrayHandle(vtkm::worklet::internal::triTable, 256*16); } //---------------------------------------------------------------------------- void SetMergeDuplicatePoints(bool merge) { this->MergeDuplicatePoints = merge; } //---------------------------------------------------------------------------- bool GetMergeDuplicatePoints( ) const { return this->MergeDuplicatePoints; } //---------------------------------------------------------------------------- template vtkm::cont::CellSetSingleType< > Run(const ValueType &isovalue, const CellSetType& cells, const CoordinateSystem& coordinateSystem, const vtkm::cont::ArrayHandle& input, vtkm::cont::ArrayHandle< vtkm::Vec, StorageTagVertices > vertices, const DeviceAdapter& device) { vtkm::cont::ArrayHandle< vtkm::Vec > normals; return this->DoRun(isovalue,cells,coordinateSystem,input,vertices,normals,false,device); } //---------------------------------------------------------------------------- template vtkm::cont::CellSetSingleType< > Run(const ValueType &isovalue, const CellSetType& cells, const CoordinateSystem& coordinateSystem, const vtkm::cont::ArrayHandle& input, vtkm::cont::ArrayHandle< vtkm::Vec, StorageTagVertices > vertices, vtkm::cont::ArrayHandle< vtkm::Vec, StorageTagNormals > normals, const DeviceAdapter& device) { return this->DoRun(isovalue,cells,coordinateSystem,input,vertices,normals,true,device); } //---------------------------------------------------------------------------- template void MapFieldOntoIsosurface(const ArrayHandleIn& input, ArrayHandleOut& output, const DeviceAdapter&) { using vtkm::worklet::marchingcubes::ApplyToField; ApplyToField applyToField; vtkm::worklet::DispatcherMapField applyFieldDispatcher(applyToField); //todo: need to use the policy to get the correct storage tag for output applyFieldDispatcher.Invoke(this->InterpolationIds, this->InterpolationWeights, input, output); } private: //---------------------------------------------------------------------------- template vtkm::cont::CellSetSingleType< > DoRun(const ValueType &isovalue, const CellSetType& cells, const CoordinateSystem& coordinateSystem, const vtkm::cont::ArrayHandle& inputField, vtkm::cont::ArrayHandle< vtkm::Vec, StorageTagVertices > vertices, vtkm::cont::ArrayHandle< vtkm::Vec, StorageTagNormals > normals, bool withNormals, const DeviceAdapter& ) { using vtkm::worklet::marchingcubes::ApplyToField; using vtkm::worklet::marchingcubes::EdgeWeightGenerate; using vtkm::worklet::marchingcubes::EdgeWeightGenerateMetaData; using vtkm::worklet::marchingcubes::ClassifyCell; // Setup the Dispatcher Typedefs typedef typename vtkm::worklet::DispatcherMapTopology< ClassifyCell, DeviceAdapter > ClassifyDispatcher; typedef typename vtkm::worklet::DispatcherMapTopology< EdgeWeightGenerate, DeviceAdapter > GenerateDispatcher; // Call the ClassifyCell functor to compute the Marching Cubes case numbers // for each cell, and the number of vertices to be generated ClassifyCell classifyCell( isovalue ); ClassifyDispatcher classifyCellDispatcher(classifyCell); vtkm::cont::ArrayHandle numOutputTrisPerCell; classifyCellDispatcher.Invoke(inputField, cells, numOutputTrisPerCell, this->NumTrianglesTable); //Pass 2 Generate the edges vtkm::worklet::ScatterCounting scatter(numOutputTrisPerCell, DeviceAdapter()); EdgeWeightGenerateMetaData< CoordinateType, StorageTagNormals, DeviceAdapter > metaData( scatter.GetOutputRange(numOutputTrisPerCell.GetNumberOfValues()), normals, this->InterpolationWeights, this->InterpolationIds, this->EdgeTable, this->NumTrianglesTable, this->TriangleTable, scatter ); EdgeWeightGenerate weightGenerate( isovalue, withNormals, metaData); GenerateDispatcher edgeDispatcher(weightGenerate); edgeDispatcher.Invoke( cells, //cast to a scalar field if not one, as cellderivative only works on those marchingcubes::make_ScalarField(inputField), coordinateSystem ); //Now that we have the edge interpolation finished we can generate the //following: //1. Coordinates ( with option to do point merging ) // // typedef vtkm::cont::DeviceAdapterAlgorithm Algorithm; vtkm::cont::DataSet output; vtkm::cont::ArrayHandle< vtkm::Id > connectivity; typedef vtkm::cont::ArrayHandle< vtkm::Id2 > Id2HandleType; typedef vtkm::cont::ArrayHandle WeightHandleType; if(this->MergeDuplicatePoints) { //Do merge duplicate points we need to do the following: //1. Copy the interpolation Ids Id2HandleType uniqueIds; Algorithm::Copy(this->InterpolationIds, uniqueIds); if(withNormals) { typedef vtkm::cont::ArrayHandle< vtkm::Vec, StorageTagNormals > NormalHandlType; typedef vtkm::cont::ArrayHandleZip KeyType; KeyType keys = vtkm::cont::make_ArrayHandleZip(this->InterpolationWeights, normals); //2. now we need to do a sort by key, making duplicate ids be adjacent Algorithm::SortByKey(uniqueIds, keys); //3. lastly we need to do a unique by key, but since vtkm doesn't // offer that feature, we use a zip handle. // We need to use a custom comparison operator as we only want to compare // the id2 which is the first entry in the zip pair vtkm::cont::ArrayHandleZip zipped = vtkm::cont::make_ArrayHandleZip(uniqueIds,keys); Algorithm::Unique( zipped, marchingcubes::FirstValueSame()); } else { //2. now we need to do a sort by key, making duplicate ids be adjacent Algorithm::SortByKey(uniqueIds, this->InterpolationWeights); //3. lastly we need to do a unique by key, but since vtkm doesn't // offer that feature, we use a zip handle. // We need to use a custom comparison operator as we only want to compare // the id2 which is the first entry in the zip pair vtkm::cont::ArrayHandleZip zipped = vtkm::cont::make_ArrayHandleZip(uniqueIds, this->InterpolationWeights); Algorithm::Unique( zipped, marchingcubes::FirstValueSame()); } //4. //LowerBounds generates the output cell connections. It does this by //finding for each interpolationId where it would be inserted in the //sorted & unique subset, which generates an index value aka the lookup //value. // Algorithm::LowerBounds(uniqueIds, this->InterpolationIds, connectivity); //5. //We re-assign the shortened version of unique ids back into the //member variable so that 'DoMapField' will work properly this->InterpolationIds = uniqueIds; } 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 use a CellSetSingleType with explicit //storage; { vtkm::cont::ArrayHandleIndex temp(this->InterpolationIds.GetNumberOfValues()); Algorithm::Copy(temp, connectivity); } } //generate the vertices's ApplyToField applyToField; vtkm::worklet::DispatcherMapField applyFieldDispatcher(applyToField); applyFieldDispatcher.Invoke(this->InterpolationIds, this->InterpolationWeights, coordinateSystem, vertices); //assign the connectivity to the cell set vtkm::cont::CellSetSingleType< > outputCells("contour"); outputCells.Fill( vertices.GetNumberOfValues(), vtkm::CELL_SHAPE_TRIANGLE, 3, connectivity ); return outputCells; } bool MergeDuplicatePoints; vtkm::cont::ArrayHandle EdgeTable; vtkm::cont::ArrayHandle NumTrianglesTable; vtkm::cont::ArrayHandle TriangleTable; vtkm::cont::ArrayHandle InterpolationWeights; vtkm::cont::ArrayHandle InterpolationIds; }; } } // namespace vtkm::worklet #endif // vtk_m_worklet_MarchingCubes_h