Fix cell derivatives for polygon cell shape

For polygon cell shapes (that are not triangles or quadrilaterals),
interpolations are done by finding the center point and creating a
triangle fan around that point. Previously, the gradient was computed in
the same way as interpolation: identifying the correct triangle and
computing the gradient for that triangle.

The problem with that approach is that makes the gradient discontinuous
at the boundaries of this implicit triangle fan. To make things worse,
this discontinuity happens right at each vertex where gradient
calculations happen frequently. This means that when you ask for the
gradient at the vertex, you might get wildly different answers based on
floating point imprecision.

Get around this problem by creating a small triangle around the point in
question, interpolating values to that triangle, and use that for the
gradient. This makes for a smoother gradient transition around these
internal boundaries.
This commit is contained in:
Kenneth Moreland 2019-08-29 17:37:42 -06:00
parent b0b000263e
commit 75a060a243
3 changed files with 128 additions and 84 deletions

@ -0,0 +1,19 @@
# Fix cell derivatives for polygon cell shape
For polygon cell shapes (that are not triangles or quadrilaterals),
interpolations are done by finding the center point and creating a triangle
fan around that point. Previously, the gradient was computed in the same
way as interpolation: identifying the correct triangle and computing the
gradient for that triangle.
The problem with that approach is that makes the gradient discontinuous at
the boundaries of this implicit triangle fan. To make things worse, this
discontinuity happens right at each vertex where gradient calculations
happen frequently. This means that when you ask for the gradient at the
vertex, you might get wildly different answers based on floating point
imprecision.
Get around this problem by creating a small triangle around the point in
question, interpolating values to that triangle, and use that for the
gradient. This makes for a smoother gradient transition around these
internal boundaries.

@ -769,83 +769,86 @@ VTKM_EXEC vtkm::Vec<typename FieldVecType::ComponentType, 3> CellDerivative(
return detail::TriangleDerivative(field, wpoints);
}
//-----------------------------------------------------------------------------
template <typename ParametricCoordType>
VTKM_EXEC void PolygonComputeIndices(const vtkm::Vec<ParametricCoordType, 3>& pcoords,
vtkm::IdComponent numPoints,
vtkm::IdComponent& firstPointIndex,
vtkm::IdComponent& secondPointIndex)
namespace detail
{
ParametricCoordType angle;
if ((vtkm::Abs(pcoords[0] - 0.5f) < 4 * vtkm::Epsilon<ParametricCoordType>()) &&
(vtkm::Abs(pcoords[1] - 0.5f) < 4 * vtkm::Epsilon<ParametricCoordType>()))
//-----------------------------------------------------------------------------
template <typename FieldVecType, typename WorldCoordType, typename ParametricCoordType>
VTKM_EXEC void PolygonSubTriangle(const FieldVecType& inField,
const WorldCoordType& inWCoords,
const vtkm::Vec<ParametricCoordType, 3>& pcoords,
vtkm::Vec<typename FieldVecType::ComponentType, 3>& outField,
vtkm::Vec<typename WorldCoordType::ComponentType, 3>& outWCoords,
const vtkm::exec::FunctorBase& worklet)
{
// To find the gradient in a polygon (of 5 or more points), we will extract a small triangle near
// the desired parameteric coordinates (pcoords). We return the field values (outField) and world
// coordinates (outWCoords) for this triangle, which is all that is needed to find the gradient
// in a triangle.
//
// The trangle will be "pointing" away from the center of the polygon, and pcoords will be placed
// at the apex of the triangle. This is because if pcoords is at or near the edge of the polygon,
// we do not want to push any of the points over the edge, and it is not trivial to determine
// exactly where the edge of the polygon is.
// First point is right at pcoords
outField[0] = vtkm::exec::CellInterpolate(inField, pcoords, vtkm::CellShapeTagPolygon{}, worklet);
outWCoords[0] =
vtkm::exec::CellInterpolate(inWCoords, pcoords, vtkm::CellShapeTagPolygon{}, worklet);
// Find the unit vector pointing from the center of the polygon to pcoords
vtkm::Vec<ParametricCoordType, 2> radialVector = { pcoords[0] - 0.5, pcoords[1] - 0.5 };
ParametricCoordType magnitudeSquared = vtkm::MagnitudeSquared(radialVector);
if (magnitudeSquared > 8 * vtkm::Epsilon<ParametricCoordType>())
{
angle = 0;
radialVector = vtkm::RSqrt(magnitudeSquared) * radialVector;
}
else
{
angle = vtkm::ATan2(pcoords[1] - 0.5f, pcoords[0] - 0.5f);
if (angle < 0)
{
angle += static_cast<ParametricCoordType>(2 * vtkm::Pi());
}
// pcoords is in the center of the polygon. Just point in an arbitrary direction
radialVector = { 1.0, 0.0 };
}
const ParametricCoordType deltaAngle =
static_cast<ParametricCoordType>(2 * vtkm::Pi() / numPoints);
firstPointIndex = static_cast<vtkm::IdComponent>(vtkm::Floor(angle / deltaAngle));
secondPointIndex = firstPointIndex + 1;
if (secondPointIndex == numPoints)
{
secondPointIndex = 0;
}
// We want the two points away from pcoords to be back toward the center but moved at 45 degrees
// off the radius. Simple geometry shows us that the (not quite unit) vectors of those two
// directions are (-radialVector[1] - radialVector[0], radialVector[0] - radialVector[1]) and
// (radialVector[1] - radialVector[0], -radialVector[0] - radialVector[1]).
//
// *\ (-radialVector[1], radialVector[0]) //
// | \ //
// | \ (-radialVector[1] - radialVector[0], radialVector[0] - radialVector[1]) //
// | \ //
// +-------* radialVector //
// | / //
// | / (radialVector[1] - radialVector[0], -radialVector[0] - radialVector[1]) //
// | / //
// */ (radialVector[1], -radialVector[0]) //
// This scaling value is somewhat arbitrary. It is small enough to be "close" to the selected
// point and small enough to be guaranteed to be inside the polygon, but large enough to to
// get an accurate gradient.
static constexpr ParametricCoordType scale = 0.05;
vtkm::Vec<ParametricCoordType, 3> backPcoord = {
pcoords[0] + scale * (-radialVector[1] - radialVector[0]),
pcoords[1] + scale * (radialVector[0] - radialVector[1]),
0
};
outField[1] =
vtkm::exec::CellInterpolate(inField, backPcoord, vtkm::CellShapeTagPolygon{}, worklet);
outWCoords[1] =
vtkm::exec::CellInterpolate(inWCoords, backPcoord, vtkm::CellShapeTagPolygon{}, worklet);
backPcoord = { pcoords[0] + scale * (radialVector[1] - radialVector[0]),
pcoords[1] + scale * (-radialVector[0] - radialVector[1]),
0 };
outField[2] =
vtkm::exec::CellInterpolate(inField, backPcoord, vtkm::CellShapeTagPolygon{}, worklet);
outWCoords[2] =
vtkm::exec::CellInterpolate(inWCoords, backPcoord, vtkm::CellShapeTagPolygon{}, worklet);
}
//-----------------------------------------------------------------------------
template <typename FieldVecType, typename WorldCoordType>
VTKM_EXEC vtkm::Vec<typename FieldVecType::ComponentType, 3> PolygonDerivative(
const FieldVecType& field,
const WorldCoordType& wCoords,
vtkm::IdComponent numPoints,
vtkm::IdComponent firstPointIndex,
vtkm::IdComponent secondPointIndex)
{
// If we are here, then there are 5 or more points on this polygon.
// Arrange the points such that they are on the circle circumscribed in the
// unit square from 0 to 1. That is, the point are on the circle centered at
// coordinate 0.5,0.5 with radius 0.5. The polygon is divided into regions
// defined by they triangle fan formed by the points around the center. This
// is C0 continuous but not necessarily C1 continuous. It is also possible to
// have a non 1 to 1 mapping between parametric coordinates world coordinates
// if the polygon is not planar or convex.
using FieldType = typename FieldVecType::ComponentType;
using WCoordType = typename WorldCoordType::ComponentType;
// Find the interpolation for the center point.
FieldType fieldCenter = field[0];
WCoordType wcoordCenter = wCoords[0];
for (vtkm::IdComponent pointIndex = 1; pointIndex < numPoints; pointIndex++)
{
fieldCenter = fieldCenter + field[pointIndex];
wcoordCenter = wcoordCenter + wCoords[pointIndex];
}
fieldCenter = fieldCenter * FieldType(1.0f / static_cast<float>(numPoints));
wcoordCenter = wcoordCenter * WCoordType(1.0f / static_cast<float>(numPoints));
// Set up parameters for triangle that pcoords is in
vtkm::Vec<FieldType, 3> triangleField(
fieldCenter, field[firstPointIndex], field[secondPointIndex]);
vtkm::Vec<WCoordType, 3> triangleWCoords(
wcoordCenter, wCoords[firstPointIndex], wCoords[secondPointIndex]);
// Now use the triangle derivative. pcoords is actually invalid for the
// triangle, but that does not matter as the derivative for a triangle does
// not depend on it.
return detail::TriangleDerivative(triangleField, triangleWCoords);
}
} // namespace detail
//-----------------------------------------------------------------------------
template <typename FieldVecType, typename WorldCoordType, typename ParametricCoordType>
@ -873,9 +876,10 @@ VTKM_EXEC vtkm::Vec<typename FieldVecType::ComponentType, 3> CellDerivative(
return CellDerivative(field, wCoords, pcoords, vtkm::CellShapeTagQuad(), worklet);
}
vtkm::IdComponent firstPointIndex, secondPointIndex;
PolygonComputeIndices(pcoords, numPoints, firstPointIndex, secondPointIndex);
return PolygonDerivative(field, wCoords, numPoints, firstPointIndex, secondPointIndex);
vtkm::Vec<typename FieldVecType::ComponentType, 3> triangleField;
vtkm::Vec<typename WorldCoordType::ComponentType, 3> triangleWCoords;
detail::PolygonSubTriangle(field, wCoords, pcoords, triangleField, triangleWCoords, worklet);
return detail::TriangleDerivative(triangleField, triangleWCoords);
}
//-----------------------------------------------------------------------------

@ -34,7 +34,12 @@ void TestPointGradientUniform2D()
for (int i = 0; i < 2; ++i)
{
VTKM_TEST_ASSERT(test_equal(result.GetPortalConstControl().Get(i), expected[i]),
"Wrong result for PointGradient worklet on 2D uniform data");
"Wrong result for PointGradient worklet on 2D uniform data",
"\nExpected ",
expected[i],
"\nGot ",
result.GetPortalConstControl().Get(i),
"\n");
}
}
@ -60,7 +65,12 @@ void TestPointGradientUniform3D()
for (int i = 0; i < 4; ++i)
{
VTKM_TEST_ASSERT(test_equal(result.GetPortalConstControl().Get(i), expected[i]),
"Wrong result for PointGradient worklet on 3D uniform data");
"Wrong result for PointGradient worklet on 3D uniform data",
"\nExpected ",
expected[i],
"\nGot ",
result.GetPortalConstControl().Get(i),
"\n");
}
}
@ -206,13 +216,18 @@ void TestPointGradientExplicit3D()
for (int i = 0; i < nVerts; ++i)
{
VTKM_TEST_ASSERT(test_equal(result.GetPortalConstControl().Get(i), expected[i]),
"Wrong result for PointGradient worklet on 3D explicit data");
"Wrong result for PointGradient worklet on 3D explicit data",
"\nExpected ",
expected[i],
"\nGot ",
result.GetPortalConstControl().Get(i),
"\n");
}
}
void TestPointGradientExplicit2D()
{
std::cout << "Testing PointGradient Worklet on Explicit 3D data" << std::endl;
std::cout << "Testing PointGradient Worklet on Explicit 2D data" << std::endl;
vtkm::cont::testing::MakeTestDataSet testDataSet;
vtkm::cont::DataSet dataSet = testDataSet.Make2DExplicitDataSet0();
@ -225,18 +240,24 @@ void TestPointGradientExplicit2D()
//vtkm::cont::printSummary_ArrayHandle(result, std::cout, true);
const int nVerts = 16;
vtkm::Vec3f_32 expected[nVerts] = { { -22.0f, -7.0f, 0.0f }, { -25.5f, -7.0f, 0.0f },
{ -30.5f, 7.0f, 0.0f }, { -32.0f, 16.0f, 0.0f },
{ -23.0f, -42.0f, 0.0f }, { -23.25f, -17.0f, 0.0f },
{ -20.6667f, 1.33333f, 0.0f }, { -23.0f, 14.0f, 0.0f },
{ -8.0f, -42.0f, 0.0f }, { 1.33333f, -19.7222f, 0.0f },
{ -6.2963f, -4.03704f, 0.0f }, { -5.0f, 12.0f, 0.0f },
{ 22.5556f, -13.4444f, 0.0f }, { -19.8889f, 59.1111f, 0.0f },
{ 22.5556f, 15.4444f, 0.0f }, { 45.0f, 26.6667f, 0.0f } };
vtkm::Vec3f_32 expected[nVerts] = {
{ -22.0f, -7.0f, 0.0f }, { -25.5f, -7.0f, 0.0f }, { -30.5f, 7.0f, 0.0f },
{ -32.0f, 16.0f, 0.0f }, { -23.0f, -42.0f, 0.0f }, { -23.25f, -17.0f, 0.0f },
{ -20.6667f, 1.33333f, 0.0f }, { -23.0f, 14.0f, 0.0f }, { -8.0f, -42.0f, 0.0f },
{ 2.91546f, -24.8357f, 0.0f }, { -0.140736f, -7.71853f, 0.0f }, { -5.0f, 12.0f, 0.0f },
{ 31.8803f, 1.0f, 0.0f }, { -44.8148f, 20.5f, 0.0f }, { 38.5653f, 5.86938f, 0.0f },
{ 26.3967f, 86.7934f, 0.0f }
};
for (int i = 0; i < nVerts; ++i)
{
VTKM_TEST_ASSERT(test_equal(result.GetPortalConstControl().Get(i), expected[i]),
"Wrong result for PointGradient worklet on 2D explicit data");
"Wrong result for PointGradient worklet on 2D explicit data",
"\nExpected ",
expected[i],
"\nGot ",
result.GetPortalConstControl().Get(i),
"\n");
}
}