vtk-m/vtkm/worklet/VertexClustering.h
Kenneth Moreland 713cf4228a Make it not possible to create a cell set without specifying num points
The CellSetExplicit and CellSetSingleType classes have an ivar that
marks the number of points. There were several instances of code
creating cell sets without specifying the number of points. This can be
very bad if subsequent code needs that information.
2017-01-12 13:02:10 -07:00

503 lines
17 KiB
C++

//============================================================================
// 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_VertexClustering_h
#define vtk_m_worklet_VertexClustering_h
#include <vtkm/Types.h>
#include <vtkm/cont/ArrayHandle.h>
#include <vtkm/cont/ArrayHandleConstant.h>
#include <vtkm/cont/DataSet.h>
#include <vtkm/cont/DeviceAdapterAlgorithm.h>
#include <vtkm/cont/DynamicArrayHandle.h>
#include <vtkm/worklet/AverageByKey.h>
#include <vtkm/worklet/DispatcherMapField.h>
#include <vtkm/worklet/DispatcherMapTopology.h>
#include <vtkm/worklet/WorkletMapField.h>
#include <vtkm/worklet/WorkletMapTopology.h>
//#define __VTKM_VERTEX_CLUSTERING_BENCHMARK
//#include <vtkm/cont/Timer.h>
namespace vtkm {
namespace worklet {
namespace internal {
template<typename T, vtkm::IdComponent N, typename DeviceAdapter>
vtkm::cont::ArrayHandle<T> copyFromVec(vtkm::cont::ArrayHandle<vtkm::Vec<T, N> > const& other,
DeviceAdapter)
{
const T *vmem = reinterpret_cast< const T *>(& *other.GetPortalConstControl().GetIteratorBegin());
vtkm::cont::ArrayHandle<T> mem = vtkm::cont::make_ArrayHandle(vmem, other.GetNumberOfValues()*N);
vtkm::cont::ArrayHandle<T> result;
vtkm::cont::DeviceAdapterAlgorithm<DeviceAdapter>::Copy(mem,result);
return result;
}
template <typename KeyArrayIn, typename KeyArrayOut, typename DeviceAdapter>
class AverageByKeyDynamicValue
{
private:
typedef typename KeyArrayIn::ValueType KeyType;
public:
VTKM_CONT
AverageByKeyDynamicValue(const KeyArrayIn &inputKeys,
KeyArrayOut &outputKeys,
vtkm::cont::DynamicArrayHandle &outputValues)
: InputKeys(inputKeys), OutputKeys(&outputKeys), OutputValues(&outputValues)
{ }
template <typename ValueArrayIn>
VTKM_CONT
void operator()(const ValueArrayIn& coordinates) const
{
typedef typename ValueArrayIn::ValueType ValueType;
vtkm::cont::ArrayHandle<ValueType> outArray;
vtkm::worklet::AverageByKey(InputKeys,
coordinates,
*(this->OutputKeys),
outArray,
DeviceAdapter());
*(this->OutputValues) = vtkm::cont::DynamicArrayHandle(outArray);
}
private:
KeyArrayIn InputKeys;
KeyArrayOut *OutputKeys;
vtkm::cont::DynamicArrayHandle *OutputValues;
};
} // namespace internal
struct VertexClustering
{
struct GridInfo
{
vtkm::Id dim[3];
vtkm::Vec<vtkm::Float64, 3> origin;
vtkm::Float64 grid_width;
vtkm::Float64 inv_grid_width; // = 1/grid_width
};
// input: points output: cid of the points
class MapPointsWorklet : public vtkm::worklet::WorkletMapField
{
private:
GridInfo Grid;
public:
typedef void ControlSignature(FieldIn<Vec3> , FieldOut<IdType>);
typedef void ExecutionSignature(_1, _2);
VTKM_CONT
MapPointsWorklet(const GridInfo &grid)
: Grid(grid)
{ }
/// determine grid resolution for clustering
template<typename PointType>
VTKM_EXEC
vtkm::Id GetClusterId(const PointType &p) const
{
typedef typename PointType::ComponentType ComponentType;
PointType gridOrigin(
static_cast<ComponentType>(this->Grid.origin[0]),
static_cast<ComponentType>(this->Grid.origin[1]),
static_cast<ComponentType>(this->Grid.origin[2]));
PointType p_rel = (p - gridOrigin) *
static_cast<ComponentType>(this->Grid.inv_grid_width);
vtkm::Id x = vtkm::Min((vtkm::Id)p_rel[0], this->Grid.dim[0]-1);
vtkm::Id y = vtkm::Min((vtkm::Id)p_rel[1], this->Grid.dim[1]-1);
vtkm::Id z = vtkm::Min((vtkm::Id)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<typename PointType>
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<IdType> pointClusterIds,
FieldOutCell<Id3Type> cellClusterIds);
typedef void ExecutionSignature(_2, _3);
VTKM_CONT
MapCellsWorklet()
{ }
// TODO: Currently only works with Triangle cell types
template<typename ClusterIdsVecType>
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<IdType>,
WholeArrayOut<IdType>);
typedef void ExecutionSignature(WorkIndex, _1, _2); // WorkIndex: use vtkm indexing
template<typename OutPortalType>
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<Id3Type>,
FieldOut<Id3Type>,
WholeArrayIn<IdType>);
typedef void ExecutionSignature(_1, _2, _3);
VTKM_CONT
Cid2PointIdWorklet( vtkm::Id nPoints ):
NPoints(nPoints)
{}
template<typename InPortalType>
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<vtkm::Int64> { };
class Cid3HashWorklet : public vtkm::worklet::WorkletMapField
{
private:
vtkm::Int64 NPoints;
public:
typedef void ControlSignature(FieldIn<Id3Type> , FieldOut<TypeInt64>);
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<TypeInt64> , FieldOut<Id3Type>);
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<vtkm::Id>( cidHash % this->NPoints );
vtkm::Int64 t = cidHash / this->NPoints ;
cid[1] = static_cast<vtkm::Id>( t % this->NPoints );
cid[2] = static_cast<vtkm::Id>( t / this->NPoints );
}
};
class Id3Less
{
public:
VTKM_EXEC
bool operator() (const vtkm::Id3 & a, const vtkm::Id3 & b) const
{
if (a[0] < 0)
{
// invalid id: place at the last after sorting
// (comparing to 0 is faster than matching -1)
return false;
}
return b[0] < 0 ||
a[0] < b[0] ||
(a[0]==b[0] && a[1] < b[1]) ||
(a[0]==b[0] && a[1]==b[1] && a[2] < b[2]);
}
};
public:
///////////////////////////////////////////////////
/// \brief VertexClustering: Mesh simplification
///
template<typename DynamicCellSetType,
typename DynamicCoordinateHandleType,
typename DeviceAdapter>
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;
{
vtkm::Vec<vtkm::Float64,3> res(
bounds.X.Length()/static_cast<vtkm::Float64>(nDivisions[0]),
bounds.Y.Length()/static_cast<vtkm::Float64>(nDivisions[1]),
bounds.Z.Length()/static_cast<vtkm::Float64>(nDivisions[2]));
gridInfo.grid_width = vtkm::Max(res[0], vtkm::Max(res[1], res[2]));
vtkm::Float64 inv_grid_width = gridInfo.inv_grid_width = vtkm::Float64(1) / gridInfo.grid_width;
//printf("Bounds: %lf, %lf, %lf, %lf, %lf, %lf\n", bounds[0], bounds[1], bounds[2], bounds[3], bounds[4], bounds[5]);
gridInfo.dim[0] = vtkm::Min((vtkm::Id)vtkm::Ceil((bounds.X.Length())*inv_grid_width), nDivisions[0]);
gridInfo.dim[1] = vtkm::Min((vtkm::Id)vtkm::Ceil((bounds.Y.Length())*inv_grid_width), nDivisions[1]);
gridInfo.dim[2] = vtkm::Min((vtkm::Id)vtkm::Ceil((bounds.Z.Length())*inv_grid_width), nDivisions[2]);
// center the mesh in the grids
vtkm::Vec<vtkm::Float64,3> center = bounds.Center();
gridInfo.origin[0] = center[0] - gridInfo.grid_width*static_cast<vtkm::Float64>(gridInfo.dim[0])*.5;
gridInfo.origin[1] = center[1] - gridInfo.grid_width*static_cast<vtkm::Float64>(gridInfo.dim[1])*.5;
gridInfo.origin[2] = center[2] - gridInfo.grid_width*static_cast<vtkm::Float64>(gridInfo.dim[2])*.5;
}
//construct the scheduler that will execute all the worklets
#ifdef __VTKM_VERTEX_CLUSTERING_BENCHMARK
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<vtkm::Id> pointCidArray;
vtkm::worklet::DispatcherMapField<MapPointsWorklet, DeviceAdapter>(
MapPointsWorklet(gridInfo)).Invoke(coordinates, pointCidArray);
#ifdef __VTKM_VERTEX_CLUSTERING_BENCHMARK
std::cout << "Time map points (s): " << timer.GetElapsedTime() << std::endl;
#endif
/// pass 2 : compute average point position for each cluster,
/// using pointCidArray as the key
///
vtkm::cont::ArrayHandle<vtkm::Id> pointCidArrayReduced;
vtkm::cont::DynamicArrayHandle repPointArray; // representative point
internal::AverageByKeyDynamicValue<vtkm::cont::ArrayHandle<vtkm::Id>,
vtkm::cont::ArrayHandle<vtkm::Id>,
DeviceAdapter>
averageByKey(pointCidArray,
pointCidArrayReduced,
repPointArray);
CastAndCall(coordinates, averageByKey);
#ifdef __VTKM_VERTEX_CLUSTERING_BENCHMARK
std::cout << "Time after averaging (s): " << timer.GetElapsedTime() << std::endl;
#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<vtkm::Id3> cid3Array;
vtkm::worklet::DispatcherMapTopology<MapCellsWorklet, DeviceAdapter>(
MapCellsWorklet()).Invoke(cellSet, pointCidArray, cid3Array);
pointCidArray.ReleaseResources();
/// preparation: Get the indexes of the clustered points to prepare for new cell array
vtkm::cont::ArrayHandle<vtkm::Id> cidIndexArray;
cidIndexArray.PrepareForOutput(gridInfo.dim[0]*gridInfo.dim[1]*gridInfo.dim[2],
DeviceAdapter());
vtkm::worklet::DispatcherMapField<
IndexingWorklet,
DeviceAdapter>().Invoke(pointCidArrayReduced, cidIndexArray);
pointCidArrayReduced.ReleaseResources();
///
/// map: convert each triangle vertices from original point id to the new cluster indexes
/// If the triangle is degenerated, set the ids to <-1, -1, -1>
///
vtkm::Id nPoints = repPointArray.GetNumberOfValues();
vtkm::cont::ArrayHandle<vtkm::Id3> pointId3Array;
vtkm::worklet::DispatcherMapField<Cid2PointIdWorklet, DeviceAdapter>(
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<vtkm::Int64> pointId3HashArray;
vtkm::worklet::DispatcherMapField<Cid3HashWorklet, DeviceAdapter>(
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;
#endif
vtkm::cont::DeviceAdapterAlgorithm<DeviceAdapter>::Sort(pointId3HashArray);
vtkm::cont::DeviceAdapterAlgorithm<DeviceAdapter>::Unique(pointId3HashArray);
#ifdef __VTKM_VERTEX_CLUSTERING_BENCHMARK
std::cout << "Time after sort and unique with hashing (s): " << timer.GetElapsedTime() << std::endl;
#endif
// decode
vtkm::worklet::DispatcherMapField<Cid3UnhashWorklet, DeviceAdapter>(
Cid3UnhashWorklet(nPoints)).Invoke( pointId3HashArray, pointId3Array );
}
else
{
#ifdef __VTKM_VERTEX_CLUSTERING_BENCHMARK
std::cout << "Time before sort and unique [no hashing] (s): " << timer.GetElapsedTime() << std::endl;
#endif
vtkm::cont::DeviceAdapterAlgorithm<DeviceAdapter>::Sort(pointId3Array);
vtkm::cont::DeviceAdapterAlgorithm<DeviceAdapter>::Unique(pointId3Array);
#ifdef __VTKM_VERTEX_CLUSTERING_BENCHMARK
std::cout << "Time after sort and unique [no hashing] (s): " << timer.GetElapsedTime() << std::endl;
#endif
}
// 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);
}
/// output
vtkm::cont::DataSet output;
output.AddCoordinateSystem(vtkm::cont::CoordinateSystem("coordinates", 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
vtkm::Float64 t = timer.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;
}
}; // struct VertexClustering
}
} // namespace vtkm::worklet
#endif // vtk_m_worklet_VertexClustering_h