vtk-m/vtkm/worklet/Tube.h
Kenneth Moreland cd30bc5875 Fix compiler warning in Tube worklet
Older GCC compilers were giving warnings in the operator of the
GeneratePoints worklets for tubes because they could not completely
determine that variables were being properly initialized in the first
loop iteration (and therefore initialized for every subsequent
iteration). Fixed that by moving the initialization for the first
iteration outside of the loop.
2019-11-06 17:06:11 -07:00

642 lines
23 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.
//============================================================================
#ifndef vtk_m_worklet_tube_h
#define vtk_m_worklet_tube_h
#include <typeinfo>
#include <vtkm/VectorAnalysis.h>
#include <vtkm/cont/Algorithm.h>
#include <vtkm/cont/CellSetExplicit.h>
#include <vtkm/cont/DataSet.h>
#include <vtkm/worklet/DispatcherMapTopology.h>
#include <vtkm/worklet/WorkletMapField.h>
#include <vtkm/worklet/WorkletMapTopology.h>
namespace vtkm
{
namespace worklet
{
class Tube
{
public:
//Helper worklet to count various things in each polyline.
class CountSegments : public vtkm::worklet::WorkletVisitCellsWithPoints
{
public:
VTKM_CONT
CountSegments(const bool& capping, const vtkm::Id& n)
: Capping(capping)
, NumSides(n)
, NumVertsPerCell(3)
{
}
using ControlSignature = void(CellSetIn,
FieldOut ptsPerPolyline,
FieldOut ptsPerTube,
FieldOut numTubeConnIds,
FieldOut linesPerPolyline);
using ExecutionSignature = void(CellShape shapeType,
PointCount numPoints,
_2 ptsPerPolyline,
_3 ptsPerTube,
_4 numTubeConnIds,
_5 linesPerPolyline);
using InputDomain = _1;
template <typename CellShapeTag>
VTKM_EXEC void operator()(const CellShapeTag& shapeType,
const vtkm::IdComponent& numPoints,
vtkm::Id& ptsPerPolyline,
vtkm::Id& ptsPerTube,
vtkm::Id& numTubeConnIds,
vtkm::Id& linesPerPolyline) const
{
// We only support polylines that contain 2 or more points.
if (shapeType.Id == vtkm::CELL_SHAPE_POLY_LINE && numPoints > 1)
{
ptsPerPolyline = numPoints;
ptsPerTube = this->NumSides * numPoints;
// (two tris per segment) X (numSides) X numVertsPerCell
numTubeConnIds = (numPoints - 1) * 2 * this->NumSides * this->NumVertsPerCell;
linesPerPolyline = numPoints - 1;
//Capping adds center vertex in middle of cap, plus NumSides triangles for cap.
if (this->Capping)
{
ptsPerTube += 2;
numTubeConnIds += (2 * this->NumSides * this->NumVertsPerCell);
}
}
else
{
ptsPerPolyline = 0;
ptsPerTube = 0;
numTubeConnIds = 0;
linesPerPolyline = 0;
}
}
private:
bool Capping;
vtkm::Id NumSides;
vtkm::Id NumVertsPerCell;
};
//Helper worklet to generate normals at each point in the polyline.
class GenerateNormals : public vtkm::worklet::WorkletVisitCellsWithPoints
{
static constexpr vtkm::FloatDefault vecMagnitudeEps = static_cast<vtkm::FloatDefault>(1e-3);
public:
VTKM_CONT
GenerateNormals()
: DefaultNorm(0, 0, 1)
{
}
using ControlSignature = void(CellSetIn cellset,
WholeArrayIn pointCoords,
FieldInCell polylineOffset,
WholeArrayOut newNormals);
using ExecutionSignature = void(CellShape shapeType,
PointCount numPoints,
PointIndices ptIndices,
_2 inPts,
_3 polylineOffset,
_4 outNormals);
using InputDomain = _1;
template <typename InPointsType, typename PointIndexType>
VTKM_EXEC vtkm::IdComponent FindValidSegment(const InPointsType& inPts,
const PointIndexType& ptIndices,
const vtkm::IdComponent& numPoints,
vtkm::IdComponent start) const
{
auto ps = inPts.Get(ptIndices[start]);
vtkm::IdComponent end = start + 1;
while (end < numPoints)
{
auto pe = inPts.Get(ptIndices[end]);
if (vtkm::Magnitude(pe - ps) > 0)
return end - 1;
end++;
}
return numPoints;
}
template <typename CellShapeTag,
typename PointIndexType,
typename InPointsType,
typename OutNormalType>
VTKM_EXEC void operator()(const CellShapeTag& shapeType,
const vtkm::IdComponent& numPoints,
const PointIndexType& ptIndices,
const InPointsType& inPts,
const vtkm::Id& polylineOffset,
OutNormalType& outNormals) const
{
//Ignore non-polyline and polyline with less than 2 points.
if (shapeType.Id != vtkm::CELL_SHAPE_POLY_LINE || numPoints < 2)
return;
else
{
//The following follows the VTK implementation in:
//vtkPolyLine::GenerateSlidingNormals
vtkm::Vec3f sPrev, sNext, normal, p0, p1;
vtkm::IdComponent sNextId = FindValidSegment(inPts, ptIndices, numPoints, 0);
if (sNextId != numPoints) // at least one valid segment
{
p0 = inPts.Get(ptIndices[sNextId]);
p1 = inPts.Get(ptIndices[sNextId + 1]);
sPrev = vtkm::Normal(p1 - p0);
}
else // no valid segments. Set everything to the default normal.
{
for (vtkm::Id i = 0; i < numPoints; i++)
outNormals.Set(polylineOffset + i, this->DefaultNorm);
return;
}
// find the next valid, non-parallel segment
while (++sNextId < numPoints)
{
sNextId = FindValidSegment(inPts, ptIndices, numPoints, sNextId);
if (sNextId != numPoints)
{
p0 = inPts.Get(ptIndices[sNextId]);
p1 = inPts.Get(ptIndices[sNextId + 1]);
sNext = vtkm::Normal(p1 - p0);
// now the starting normal should simply be the cross product
// in the following if statement we check for the case where
// the two segments are parallel, in which case, continue searching
// for the next valid segment
auto n = vtkm::Cross(sPrev, sNext);
if (vtkm::Magnitude(n) > vecMagnitudeEps)
{
normal = n;
sPrev = sNext;
break;
}
}
}
//only one valid segment...
if (sNextId >= numPoints)
{
for (vtkm::IdComponent j = 0; j < 3; j++)
if (sPrev[j] != 0)
{
normal[(j + 2) % 3] = 0;
normal[(j + 1) % 3] = 1;
normal[j] = -sPrev[(j + 1) % 3] / sPrev[j];
break;
}
}
vtkm::Normalize(normal);
vtkm::Id lastNormalId = 0;
while (++sNextId < numPoints)
{
sNextId = FindValidSegment(inPts, ptIndices, numPoints, sNextId);
if (sNextId == numPoints)
break;
p0 = inPts.Get(ptIndices[sNextId]);
p1 = inPts.Get(ptIndices[sNextId + 1]);
sNext = vtkm::Normal(p1 - p0);
auto q = vtkm::Cross(sNext, sPrev);
if (vtkm::Magnitude(q) <= vtkm::Epsilon<vtkm::FloatDefault>()) //can't use this segment
continue;
vtkm::Normalize(q);
vtkm::FloatDefault f1 = vtkm::Dot(q, normal);
vtkm::FloatDefault f2 = 1 - (f1 * f1);
if (f2 > 0)
f2 = vtkm::Sqrt(f2);
else
f2 = 0;
auto c = vtkm::Normal(sNext + sPrev);
auto w = vtkm::Cross(c, q);
c = vtkm::Cross(sPrev, q);
if ((vtkm::Dot(normal, c) * vtkm::Dot(w, c)) < 0)
f2 = -f2;
for (vtkm::Id i = lastNormalId; i < sNextId; i++)
outNormals.Set(polylineOffset + i, normal);
lastNormalId = sNextId;
sPrev = sNext;
normal = (f1 * q) + (f2 * w);
}
for (vtkm::Id i = lastNormalId; i < numPoints; i++)
outNormals.Set(polylineOffset + i, normal);
}
}
private:
vtkm::Vec3f DefaultNorm;
};
//Helper worklet to generate the tube points
class GeneratePoints : public vtkm::worklet::WorkletVisitCellsWithPoints
{
public:
VTKM_CONT
GeneratePoints(const bool& capping, const vtkm::Id& n, const vtkm::FloatDefault& r)
: Capping(capping)
, NumSides(n)
, Radius(r)
, Theta(2 * static_cast<vtkm::FloatDefault>(vtkm::Pi()) / static_cast<vtkm::FloatDefault>(n))
{
}
using ControlSignature = void(CellSetIn cellset,
WholeArrayIn pointCoords,
WholeArrayIn normals,
FieldInCell tubePointOffsets,
FieldInCell polylineOffset,
WholeArrayOut newPointCoords,
WholeArrayOut outPointSrcIdx);
using ExecutionSignature = void(CellShape shapeType,
PointCount numPoints,
PointIndices ptIndices,
_2 inPts,
_3 inNormals,
_4 tubePointOffsets,
_5 polylineOffset,
_6 outPts,
_7 outPointSrcIdx);
using InputDomain = _1;
template <typename CellShapeTag,
typename PointIndexType,
typename InPointsType,
typename InNormalsType,
typename OutPointsType,
typename OutPointSrcIdxType>
VTKM_EXEC void operator()(const CellShapeTag& shapeType,
const vtkm::IdComponent& numPoints,
const PointIndexType& ptIndices,
const InPointsType& inPts,
const InNormalsType& inNormals,
const vtkm::Id& tubePointOffsets,
const vtkm::Id& polylineOffset,
OutPointsType& outPts,
OutPointSrcIdxType& outPointSrcIdx) const
{
if (shapeType.Id != vtkm::CELL_SHAPE_POLY_LINE || numPoints < 2)
return;
else
{
vtkm::Id outIdx = tubePointOffsets;
vtkm::Id pIdx = ptIndices[0];
vtkm::Id pNextIdx = ptIndices[1];
vtkm::Vec3f p = inPts.Get(pIdx);
vtkm::Vec3f pNext = inPts.Get(pNextIdx);
vtkm::Vec3f sNext = pNext - p;
vtkm::Vec3f sPrev = sNext;
for (vtkm::IdComponent j = 0; j < numPoints; j++)
{
if (j == 0) //first point
{
//Variables initialized before loop started.
}
else if (j == numPoints - 1) //last point
{
sPrev = sNext;
p = pNext;
pIdx = pNextIdx;
}
else
{
p = pNext;
pIdx = pNextIdx;
pNextIdx = ptIndices[j + 1];
pNext = inPts.Get(pNextIdx);
sPrev = sNext;
sNext = pNext - p;
}
vtkm::Vec3f n = inNormals.Get(polylineOffset + j);
//Coincident points.
if (vtkm::Magnitude(sNext) <= vtkm::Epsilon<vtkm::FloatDefault>())
this->RaiseError("Coincident points in Tube worklet.");
vtkm::Normalize(sNext);
auto s = (sPrev + sNext) / 2.;
if (vtkm::Magnitude(s) <= vtkm::Epsilon<vtkm::FloatDefault>())
s = vtkm::Cross(sPrev, n);
vtkm::Normalize(s);
auto w = vtkm::Cross(s, n);
//Bad normal
if (vtkm::Magnitude(w) <= vtkm::Epsilon<vtkm::FloatDefault>())
this->RaiseError("Bad normal in Tube worklet.");
vtkm::Normalize(w);
//create orthogonal coordinate system.
auto nP = vtkm::Cross(w, s);
vtkm::Normalize(nP);
//Add the start cap vertex. This is just a point at the center of the tube (on the polyline).
if (this->Capping && j == 0)
{
outPts.Set(outIdx, p);
outPointSrcIdx.Set(outIdx, pIdx);
outIdx++;
}
//this only implements the 'sides share vertices' line 476
vtkm::Vec3f normal;
for (vtkm::IdComponent k = 0; k < this->NumSides; k++)
{
vtkm::FloatDefault angle = static_cast<vtkm::FloatDefault>(k) * this->Theta;
vtkm::FloatDefault cosValue = vtkm::Cos(angle);
vtkm::FloatDefault sinValue = vtkm::Sin(angle);
normal = w * cosValue + nP * sinValue;
auto newPt = p + this->Radius * normal;
outPts.Set(outIdx, newPt);
outPointSrcIdx.Set(outIdx, pIdx);
outIdx++;
}
//Add the end cap vertex. This is just a point at the center of the tube (on the polyline).
if (this->Capping && j == numPoints - 1)
{
outPts.Set(outIdx, p);
outPointSrcIdx.Set(outIdx, pIdx);
outIdx++;
}
}
}
}
private:
bool Capping;
vtkm::Id NumSides;
vtkm::FloatDefault Radius;
vtkm::FloatDefault Theta;
};
//Helper worklet to generate the tube cells
class GenerateCells : public vtkm::worklet::WorkletVisitCellsWithPoints
{
public:
VTKM_CONT
GenerateCells(const bool& capping, const vtkm::Id& n)
: Capping(capping)
, NumSides(n)
{
}
using ControlSignature = void(CellSetIn cellset,
FieldInCell tubePointOffsets,
FieldInCell tubeConnOffsets,
FieldInCell segOffset,
WholeArrayOut outConnectivity,
WholeArrayOut outCellSrcIdx);
using ExecutionSignature = void(CellShape shapeType,
PointCount numPoints,
_2 tubePointOffset,
_3 tubeConnOffsets,
_4 segOffset,
_5 outConn,
_6 outCellSrcIdx);
using InputDomain = _1;
template <typename CellShapeTag, typename OutConnType, typename OutCellSrcIdxType>
VTKM_EXEC void operator()(const CellShapeTag& shapeType,
const vtkm::IdComponent& numPoints,
const vtkm::Id& tubePointOffset,
const vtkm::Id& tubeConnOffset,
const vtkm::Id& segOffset,
OutConnType& outConn,
OutCellSrcIdxType& outCellSrcIdx) const
{
if (shapeType.Id != vtkm::CELL_SHAPE_POLY_LINE || numPoints < 2)
return;
else
{
vtkm::Id outIdx = tubeConnOffset;
vtkm::Id tubePtOffset = (this->Capping ? tubePointOffset + 1 : tubePointOffset);
for (vtkm::IdComponent i = 0; i < numPoints - 1; i++)
{
for (vtkm::Id j = 0; j < this->NumSides; j++)
{
//Triangle 1: verts 0,1,2
outConn.Set(outIdx + 0, tubePtOffset + i * this->NumSides + j);
outConn.Set(outIdx + 1, tubePtOffset + i * this->NumSides + (j + 1) % this->NumSides);
outConn.Set(outIdx + 2,
tubePtOffset + (i + 1) * this->NumSides + (j + 1) % this->NumSides);
outCellSrcIdx.Set(outIdx / 3, segOffset + static_cast<vtkm::Id>(i));
outIdx += 3;
//Triangle 2: verts 0,2,3
outConn.Set(outIdx + 0, tubePtOffset + i * this->NumSides + j);
outConn.Set(outIdx + 1,
tubePtOffset + (i + 1) * this->NumSides + (j + 1) % this->NumSides);
outConn.Set(outIdx + 2, tubePtOffset + (i + 1) * this->NumSides + j);
outCellSrcIdx.Set(outIdx / 3, segOffset + static_cast<vtkm::Id>(i));
outIdx += 3;
}
}
if (this->Capping)
{
//start cap triangles
vtkm::Id startCenterPt = 0 + tubePointOffset;
for (vtkm::Id j = 0; j < this->NumSides; j++)
{
outConn.Set(outIdx + 0, startCenterPt);
outConn.Set(outIdx + 1, startCenterPt + 1 + j);
outConn.Set(outIdx + 2, startCenterPt + 1 + ((j + 1) % this->NumSides));
outCellSrcIdx.Set(outIdx / 3, segOffset);
outIdx += 3;
}
//end cap triangles
vtkm::Id endCenterPt = (tubePointOffset + 1) + (numPoints * this->NumSides);
vtkm::Id endOffsetPt = endCenterPt - this->NumSides;
for (vtkm::Id j = 0; j < this->NumSides; j++)
{
outConn.Set(outIdx + 0, endCenterPt);
outConn.Set(outIdx + 1, endOffsetPt + j);
outConn.Set(outIdx + 2, endOffsetPt + ((j + 1) % this->NumSides));
outCellSrcIdx.Set(outIdx / 3, segOffset + static_cast<vtkm::Id>(numPoints - 2));
outIdx += 3;
}
}
}
}
private:
bool Capping;
vtkm::Id NumSides;
};
class MapField : public vtkm::worklet::WorkletMapField
{
public:
using ControlSignature = void(FieldIn sourceIdx, WholeArrayIn sourceArray, FieldOut output);
using ExecutionSignature = void(_1 sourceIdx, _2 sourceArray, _3 output);
using InputDomain = _1;
VTKM_CONT
MapField() {}
template <typename SourceArrayType, typename T>
VTKM_EXEC void operator()(const vtkm::Id& sourceIdx,
const SourceArrayType& sourceArray,
T& output) const
{
output = sourceArray.Get(sourceIdx);
}
};
VTKM_CONT
Tube()
: Capping(false)
, NumSides(0)
, Radius(0)
{
}
VTKM_CONT
Tube(const bool& capping, const vtkm::Id& n, const vtkm::FloatDefault& r)
: Capping(capping)
, NumSides(n)
, Radius(r)
{
}
VTKM_CONT
void SetCapping(bool v) { this->Capping = v; }
VTKM_CONT
void SetNumberOfSides(vtkm::Id n) { this->NumSides = n; }
VTKM_CONT
void SetRadius(vtkm::FloatDefault r) { this->Radius = r; }
template <typename Storage>
VTKM_CONT void Run(const vtkm::cont::ArrayHandle<vtkm::Vec3f, Storage>& coords,
const vtkm::cont::DynamicCellSet& cellset,
vtkm::cont::ArrayHandle<vtkm::Vec3f>& newPoints,
vtkm::cont::CellSetSingleType<>& newCells)
{
using NormalsType = vtkm::cont::ArrayHandle<vtkm::Vec3f>;
if (!cellset.IsSameType(vtkm::cont::CellSetExplicit<>()) &&
!cellset.IsSameType(vtkm::cont::CellSetSingleType<>()))
{
throw vtkm::cont::ErrorBadValue("Tube filter only supported for polyline data.");
}
//Count number of polyline pts, tube pts and tube cells
vtkm::cont::ArrayHandle<vtkm::Id> ptsPerPolyline, ptsPerTube, numTubeConnIds, segPerPolyline;
CountSegments countSegs(this->Capping, this->NumSides);
vtkm::worklet::DispatcherMapTopology<CountSegments> countInvoker(countSegs);
countInvoker.Invoke(cellset, ptsPerPolyline, ptsPerTube, numTubeConnIds, segPerPolyline);
vtkm::Id totalPolylinePts = vtkm::cont::Algorithm::Reduce(ptsPerPolyline, vtkm::Id(0));
if (totalPolylinePts == 0)
throw vtkm::cont::ErrorBadValue("Tube filter only supported for polyline data.");
vtkm::Id totalTubePts = vtkm::cont::Algorithm::Reduce(ptsPerTube, vtkm::Id(0));
vtkm::Id totalTubeConnIds = vtkm::cont::Algorithm::Reduce(numTubeConnIds, vtkm::Id(0));
//All cells are triangles, so cell count is simple to compute.
vtkm::Id totalTubeCells = totalTubeConnIds / 3;
vtkm::cont::ArrayHandle<vtkm::Id> polylinePtOffset, tubePointOffsets, tubeConnOffsets,
segOffset;
vtkm::cont::Algorithm::ScanExclusive(ptsPerPolyline, polylinePtOffset);
vtkm::cont::Algorithm::ScanExclusive(ptsPerTube, tubePointOffsets);
vtkm::cont::Algorithm::ScanExclusive(numTubeConnIds, tubeConnOffsets);
vtkm::cont::Algorithm::ScanExclusive(segPerPolyline, segOffset);
//Generate normals at each point on all polylines
NormalsType normals;
normals.Allocate(totalPolylinePts);
vtkm::worklet::DispatcherMapTopology<GenerateNormals> genNormalsDisp;
genNormalsDisp.Invoke(cellset, coords, polylinePtOffset, normals);
//Generate the tube points
newPoints.Allocate(totalTubePts);
this->OutputPointSourceIndex.Allocate(totalTubePts);
GeneratePoints genPts(this->Capping, this->NumSides, this->Radius);
vtkm::worklet::DispatcherMapTopology<GeneratePoints> genPtsDisp(genPts);
genPtsDisp.Invoke(cellset,
coords,
normals,
tubePointOffsets,
polylinePtOffset,
newPoints,
this->OutputPointSourceIndex);
//Generate tube cells
vtkm::cont::ArrayHandle<vtkm::Id> newConnectivity;
newConnectivity.Allocate(totalTubeConnIds);
this->OutputCellSourceIndex.Allocate(totalTubeCells);
GenerateCells genCells(this->Capping, this->NumSides);
vtkm::worklet::DispatcherMapTopology<GenerateCells> genCellsDisp(genCells);
genCellsDisp.Invoke(cellset,
tubePointOffsets,
tubeConnOffsets,
segOffset,
newConnectivity,
this->OutputCellSourceIndex);
newCells.Fill(totalTubePts, vtkm::CELL_SHAPE_TRIANGLE, 3, newConnectivity);
}
template <typename T, typename StorageType>
vtkm::cont::ArrayHandle<T> ProcessPointField(
const vtkm::cont::ArrayHandle<T, StorageType>& input) const
{
vtkm::cont::ArrayHandle<T> output;
vtkm::worklet::DispatcherMapField<MapField> mapFieldDisp;
output.Allocate(this->OutputPointSourceIndex.GetNumberOfValues());
mapFieldDisp.Invoke(this->OutputPointSourceIndex, input, output);
return output;
}
template <typename T, typename StorageType>
vtkm::cont::ArrayHandle<T> ProcessCellField(
const vtkm::cont::ArrayHandle<T, StorageType>& input) const
{
vtkm::cont::ArrayHandle<T> output;
vtkm::worklet::DispatcherMapField<MapField> mapFieldDisp;
output.Allocate(this->OutputCellSourceIndex.GetNumberOfValues());
mapFieldDisp.Invoke(this->OutputCellSourceIndex, input, output);
return output;
}
private:
bool Capping;
vtkm::Id NumSides;
vtkm::FloatDefault Radius;
vtkm::cont::ArrayHandle<vtkm::Id> OutputCellSourceIndex;
vtkm::cont::ArrayHandle<vtkm::Id> OutputPointSourceIndex;
};
}
}
#endif // vtk_m_worklet_tube_h