//============================================================================ // 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 National Technology & Engineering Solutions of Sandia, LLC (NTESS). // Copyright 2014 UT-Battelle, LLC. // Copyright 2014 Los Alamos National Security. // // Under the terms of Contract DE-NA0003525 with NTESS, // 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_VertexClustering_h #define vtk_m_worklet_VertexClustering_h #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include //#define __VTKM_VERTEX_CLUSTERING_BENCHMARK #ifdef __VTKM_VERTEX_CLUSTERING_BENCHMARK #include #endif namespace vtkm { namespace worklet { namespace internal { /// Selects the representative point somewhat randomly from the pool of points /// in a cluster. struct SelectRepresentativePoint : public vtkm::worklet::WorkletReduceByKey { typedef void ControlSignature(KeysIn clusterIds, ValuesIn<> points, ReducedValuesOut<> repPoints); typedef _3 ExecutionSignature(_2); using InputDomain = _1; template VTKM_EXEC typename PointsInVecType::ComponentType operator()( const PointsInVecType& pointsIn) const { // Grab the point from the middle of the set. This usually does a decent // job of selecting a representative point that won't emphasize the cluster // partitions. // // Note that we must use the stable sorting with the worklet::Keys for this // to be reproducible across backends. return pointsIn[pointsIn.GetNumberOfComponents() / 2]; } struct RunTrampoline { template VTKM_CONT void operator()(const InputPointsArrayType& points, const vtkm::worklet::Keys& keys, vtkm::cont::DynamicArrayHandle& output, DeviceAdapterTag) const { vtkm::cont::ArrayHandle out; vtkm::worklet::DispatcherReduceByKey dispatcher; dispatcher.Invoke(keys, points, out); output = out; } }; template VTKM_CONT static vtkm::cont::DynamicArrayHandle Run( const vtkm::worklet::Keys& keys, const InputDynamicPointsArrayType& inputPoints, DeviceAdapterTag tag) { vtkm::cont::DynamicArrayHandle output; RunTrampoline trampoline; vtkm::cont::CastAndCall(inputPoints, trampoline, keys, output, tag); return output; } }; template VTKM_CONT vtkm::cont::ArrayHandle ConcretePermutationArray( const IndexArrayType& indices, const vtkm::cont::ArrayHandle& values, DeviceAdapter) { vtkm::cont::ArrayHandle result; auto tmp = vtkm::cont::make_ArrayHandlePermutation(indices, values); vtkm::cont::DeviceAdapterAlgorithm::Copy(tmp, result); return result; } template vtkm::cont::ArrayHandle copyFromVec(vtkm::cont::ArrayHandle> const& other, DeviceAdapter) { const T* vmem = reinterpret_cast(&*other.GetPortalConstControl().GetIteratorBegin()); vtkm::cont::ArrayHandle mem = vtkm::cont::make_ArrayHandle(vmem, other.GetNumberOfValues() * N); vtkm::cont::ArrayHandle result; vtkm::cont::ArrayCopy(mem, result, DeviceAdapter()); return result; } } // namespace internal struct VertexClustering { using PointIdMapType = vtkm::cont::ArrayHandlePermutation, vtkm::cont::ArrayHandle>; struct GridInfo { vtkm::Vec dim; vtkm::Vec origin; vtkm::Vec bin_size; vtkm::Vec inv_bin_size; }; // input: points output: cid of the points class MapPointsWorklet : public vtkm::worklet::WorkletMapField { private: GridInfo Grid; public: typedef void ControlSignature(FieldIn, FieldOut); typedef void ExecutionSignature(_1, _2); VTKM_CONT MapPointsWorklet(const GridInfo& grid) : Grid(grid) { } /// determine grid resolution for clustering template VTKM_EXEC vtkm::Id GetClusterId(const PointType& p) const { typedef typename PointType::ComponentType ComponentType; PointType gridOrigin(static_cast(this->Grid.origin[0]), static_cast(this->Grid.origin[1]), static_cast(this->Grid.origin[2])); PointType p_rel = (p - gridOrigin) * this->Grid.inv_bin_size; vtkm::Id x = vtkm::Min(static_cast(p_rel[0]), this->Grid.dim[0] - 1); vtkm::Id y = vtkm::Min(static_cast(p_rel[1]), this->Grid.dim[1] - 1); vtkm::Id z = vtkm::Min(static_cast(p_rel[2]), this->Grid.dim[2] - 1); return x + this->Grid.dim[0] * (y + this->Grid.dim[1] * z); // get a unique hash value } template VTKM_EXEC void operator()(const PointType& point, vtkm::Id& cid) const { cid = this->GetClusterId(point); VTKM_ASSERT(cid >= 0); // the id could overflow if too many cells } }; class MapCellsWorklet : public vtkm::worklet::WorkletMapPointToCell { public: typedef void ControlSignature(CellSetIn cellset, FieldInPoint pointClusterIds, FieldOutCell cellClusterIds); typedef void ExecutionSignature(_2, _3); VTKM_CONT MapCellsWorklet() {} // TODO: Currently only works with Triangle cell types template VTKM_EXEC void operator()(const ClusterIdsVecType& pointClusterIds, vtkm::Id3& cellClusterId) const { cellClusterId[0] = pointClusterIds[0]; cellClusterId[1] = pointClusterIds[1]; cellClusterId[2] = pointClusterIds[2]; } }; /// pass 3 class IndexingWorklet : public vtkm::worklet::WorkletMapField { public: typedef void ControlSignature(FieldIn, WholeArrayOut); typedef void ExecutionSignature(WorkIndex, _1, _2); // WorkIndex: use vtkm indexing template VTKM_EXEC void operator()(const vtkm::Id& counter, const vtkm::Id& cid, const OutPortalType& outPortal) const { outPortal.Set(cid, counter); } }; class Cid2PointIdWorklet : public vtkm::worklet::WorkletMapField { vtkm::Id NPoints; VTKM_EXEC void rotate(vtkm::Id3& ids) const { vtkm::Id temp = ids[0]; ids[0] = ids[1]; ids[1] = ids[2]; ids[2] = temp; } public: typedef void ControlSignature(FieldIn, FieldOut, WholeArrayIn); typedef void ExecutionSignature(_1, _2, _3); VTKM_CONT Cid2PointIdWorklet(vtkm::Id nPoints) : NPoints(nPoints) { } template VTKM_EXEC void operator()(const vtkm::Id3& cid3, vtkm::Id3& pointId3, const InPortalType& inPortal) const { if (cid3[0] == cid3[1] || cid3[0] == cid3[2] || cid3[1] == cid3[2]) { pointId3[0] = pointId3[1] = pointId3[2] = this->NPoints; // invalid cell to be removed } else { pointId3[0] = inPortal.Get(cid3[0]); pointId3[1] = inPortal.Get(cid3[1]); pointId3[2] = inPortal.Get(cid3[2]); // Sort triangle point ids so that the same triangle will have the same signature // Rotate these ids making the first one the smallest if (pointId3[0] > pointId3[1] || pointId3[0] > pointId3[2]) { rotate(pointId3); if (pointId3[0] > pointId3[1] || pointId3[0] > pointId3[2]) { rotate(pointId3); } } } } }; struct TypeInt64 : vtkm::ListTagBase { }; class Cid3HashWorklet : public vtkm::worklet::WorkletMapField { private: vtkm::Int64 NPoints; public: typedef void ControlSignature(FieldIn, FieldOut); typedef void ExecutionSignature(_1, _2); VTKM_CONT Cid3HashWorklet(vtkm::Id nPoints) : NPoints(nPoints) { } VTKM_EXEC void operator()(const vtkm::Id3& cid, vtkm::Int64& cidHash) const { cidHash = cid[0] + this->NPoints * (cid[1] + this->NPoints * cid[2]); // get a unique hash value } }; class Cid3UnhashWorklet : public vtkm::worklet::WorkletMapField { private: vtkm::Int64 NPoints; public: typedef void ControlSignature(FieldIn, FieldOut); typedef void ExecutionSignature(_1, _2); VTKM_CONT Cid3UnhashWorklet(vtkm::Id nPoints) : NPoints(nPoints) { } VTKM_EXEC void operator()(const vtkm::Int64& cidHash, vtkm::Id3& cid) const { cid[0] = static_cast(cidHash % this->NPoints); vtkm::Int64 t = cidHash / this->NPoints; cid[1] = static_cast(t % this->NPoints); cid[2] = static_cast(t / this->NPoints); } }; public: /////////////////////////////////////////////////// /// \brief VertexClustering: Mesh simplification /// template vtkm::cont::DataSet Run(const DynamicCellSetType& cellSet, const DynamicCoordinateHandleType& coordinates, const vtkm::Bounds& bounds, const vtkm::Id3& nDivisions, DeviceAdapter) { /// determine grid resolution for clustering GridInfo gridInfo; { gridInfo.origin[0] = bounds.X.Min; gridInfo.origin[1] = bounds.Y.Min; gridInfo.origin[2] = bounds.Z.Min; gridInfo.dim[0] = nDivisions[0]; gridInfo.dim[1] = nDivisions[1]; gridInfo.dim[2] = nDivisions[2]; gridInfo.bin_size[0] = bounds.X.Length() / static_cast(nDivisions[0]); gridInfo.bin_size[1] = bounds.Y.Length() / static_cast(nDivisions[1]); gridInfo.bin_size[2] = bounds.Z.Length() / static_cast(nDivisions[2]); gridInfo.inv_bin_size[0] = 1. / gridInfo.bin_size[0]; gridInfo.inv_bin_size[1] = 1. / gridInfo.bin_size[1]; gridInfo.inv_bin_size[2] = 1. / gridInfo.bin_size[2]; } #ifdef __VTKM_VERTEX_CLUSTERING_BENCHMARK vtkm::cont::Timer<> totalTimer; vtkm::cont::Timer<> timer; #endif ////////////////////////////////////////////// /// start algorithm /// pass 1 : assign points with (cluster) ids based on the grid it falls in /// /// map points vtkm::cont::ArrayHandle pointCidArray; vtkm::worklet::DispatcherMapField(MapPointsWorklet(gridInfo)) .Invoke(coordinates, pointCidArray); #ifdef __VTKM_VERTEX_CLUSTERING_BENCHMARK std::cout << "Time map points (s): " << timer.GetElapsedTime() << std::endl; timer.Reset(); #endif /// pass 2 : Choose a representative point from each cluster for the output: vtkm::cont::DynamicArrayHandle repPointArray; { vtkm::worklet::Keys keys; keys.BuildArrays( pointCidArray, vtkm::worklet::Keys::SortType::Stable, DeviceAdapter()); // For mapping properties, this map will select an arbitrary point from // the cluster: this->PointIdMap = vtkm::cont::make_ArrayHandlePermutation(keys.GetOffsets(), keys.GetSortedValuesMap()); // Compute representative points from each cluster (may not match the // PointIdMap indexing) repPointArray = internal::SelectRepresentativePoint::Run(keys, coordinates, DeviceAdapter()); } auto repPointCidArray = vtkm::cont::make_ArrayHandlePermutation(this->PointIdMap, pointCidArray); #ifdef __VTKM_VERTEX_CLUSTERING_BENCHMARK std::cout << "Time after reducing points (s): " << timer.GetElapsedTime() << std::endl; timer.Reset(); #endif /// Pass 3 : Decimated mesh generation /// For each original triangle, only output vertices from /// three different clusters /// map each triangle vertex to the cluster id's /// of the cell vertices vtkm::cont::ArrayHandle cid3Array; vtkm::worklet::DispatcherMapTopology(MapCellsWorklet()) .Invoke(cellSet, pointCidArray, cid3Array); #ifdef __VTKM_VERTEX_CLUSTERING_BENCHMARK std::cout << "Time after clustering cells (s): " << timer.GetElapsedTime() << std::endl; timer.Reset(); #endif /// preparation: Get the indexes of the clustered points to prepare for new cell array vtkm::cont::ArrayHandle cidIndexArray; cidIndexArray.PrepareForOutput(gridInfo.dim[0] * gridInfo.dim[1] * gridInfo.dim[2], DeviceAdapter()); vtkm::worklet::DispatcherMapField().Invoke(repPointCidArray, cidIndexArray); pointCidArray.ReleaseResources(); repPointCidArray.ReleaseResources(); /// /// map: convert each triangle vertices from original point id to the new cluster indexes /// If the triangle is degenerated, set the ids to /// This ensures it will be placed at the end of the array when sorted. /// vtkm::Id nPoints = repPointArray.GetNumberOfValues(); vtkm::cont::ArrayHandle pointId3Array; vtkm::worklet::DispatcherMapField( Cid2PointIdWorklet(nPoints)) .Invoke(cid3Array, pointId3Array, cidIndexArray); cid3Array.ReleaseResources(); cidIndexArray.ReleaseResources(); bool doHashing = (nPoints < (1 << 21)); // Check whether we can hash Id3 into 64-bit integers if (doHashing) { /// Create hashed array vtkm::cont::ArrayHandle pointId3HashArray; vtkm::worklet::DispatcherMapField(Cid3HashWorklet(nPoints)) .Invoke(pointId3Array, pointId3HashArray); pointId3Array.ReleaseResources(); #ifdef __VTKM_VERTEX_CLUSTERING_BENCHMARK std::cout << "Time before sort and unique with hashing (s): " << timer.GetElapsedTime() << std::endl; timer.Reset(); #endif this->CellIdMap = vtkm::worklet::StableSortIndices::Sort(pointId3HashArray); vtkm::worklet::StableSortIndices::Unique(pointId3HashArray, this->CellIdMap); #ifdef __VTKM_VERTEX_CLUSTERING_BENCHMARK std::cout << "Time after sort and unique with hashing (s): " << timer.GetElapsedTime() << std::endl; timer.Reset(); #endif // Create a temporary permutation array and use that for unhashing. auto tmpPerm = vtkm::cont::make_ArrayHandlePermutation(this->CellIdMap, pointId3HashArray); // decode vtkm::worklet::DispatcherMapField( Cid3UnhashWorklet(nPoints)) .Invoke(tmpPerm, pointId3Array); } else { #ifdef __VTKM_VERTEX_CLUSTERING_BENCHMARK std::cout << "Time before sort and unique [no hashing] (s): " << timer.GetElapsedTime() << std::endl; timer.Reset(); #endif this->CellIdMap = vtkm::worklet::StableSortIndices::Sort(pointId3Array); vtkm::worklet::StableSortIndices::Unique(pointId3Array, this->CellIdMap); #ifdef __VTKM_VERTEX_CLUSTERING_BENCHMARK std::cout << "Time after sort and unique [no hashing] (s): " << timer.GetElapsedTime() << std::endl; timer.Reset(); #endif // Permute the connectivity array into a basic array handle. Use a // temporary array handle to avoid memory aliasing. { vtkm::cont::ArrayHandle tmp; tmp = internal::ConcretePermutationArray(this->CellIdMap, pointId3Array, DeviceAdapter()); pointId3Array = tmp; } } // remove the last element if invalid vtkm::Id cells = pointId3Array.GetNumberOfValues(); if (cells > 0 && pointId3Array.GetPortalConstControl().Get(cells - 1)[2] >= nPoints) { cells--; pointId3Array.Shrink(cells); this->CellIdMap.Shrink(cells); } /// output vtkm::cont::DataSet output; output.AddCoordinateSystem( vtkm::cont::CoordinateSystem("coordinates", vtkm::cont::DynamicArrayHandle(repPointArray))); vtkm::cont::CellSetSingleType<> triangles("cells"); triangles.Fill(repPointArray.GetNumberOfValues(), vtkm::CellShapeTagTriangle::Id, 3, internal::copyFromVec(pointId3Array, DeviceAdapter())); output.AddCellSet(triangles); #ifdef __VTKM_VERTEX_CLUSTERING_BENCHMARK std::cout << "Wrap-up (s): " << timer.GetElapsedTime() << std::endl; vtkm::Float64 t = totalTimer.GetElapsedTime(); std::cout << "Time (s): " << t << std::endl; std::cout << "number of output points: " << repPointArray.GetNumberOfValues() << std::endl; std::cout << "number of output cells: " << pointId3Array.GetNumberOfValues() << std::endl; #endif return output; } template vtkm::cont::ArrayHandle ProcessPointField( const vtkm::cont::ArrayHandle& input, const DeviceAdapter&) const { return internal::ConcretePermutationArray(this->PointIdMap, input, DeviceAdapter()); } template vtkm::cont::ArrayHandle ProcessCellField( const vtkm::cont::ArrayHandle& input, const DeviceAdapter&) const { return internal::ConcretePermutationArray(this->CellIdMap, input, DeviceAdapter()); } private: PointIdMapType PointIdMap; vtkm::cont::ArrayHandle CellIdMap; }; // struct VertexClustering } } // namespace vtkm::worklet #endif // vtk_m_worklet_VertexClustering_h