You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
468 lines
18 KiB
468 lines
18 KiB
// David Eberly, Geometric Tools, Redmond WA 98052
|
|
// Copyright (c) 1998-2021
|
|
// Distributed under the Boost Software License, Version 1.0.
|
|
// https://www.boost.org/LICENSE_1_0.txt
|
|
// https://www.geometrictools.com/License/Boost/LICENSE_1_0.txt
|
|
// Version: 4.0.2020.11.16
|
|
|
|
#pragma once
|
|
|
|
#include <Mathematics/Delaunay2.h>
|
|
#include <Mathematics/ContScribeCircle2.h>
|
|
#include <Mathematics/DistPointAlignedBox.h>
|
|
|
|
// Quadratic interpolation of a network of triangles whose vertices are of
|
|
// the form (x,y,f(x,y)). This code is an implementation of the algorithm
|
|
// found in
|
|
//
|
|
// Zoltan J. Cendes and Steven H. Wong,
|
|
// C1 quadratic interpolation over arbitrary point sets,
|
|
// IEEE Computer Graphics & Applications,
|
|
// pp. 8-16, 1987
|
|
//
|
|
// The TriangleMesh interface must support the following:
|
|
// int GetNumVertices() const;
|
|
// int GetNumTriangles() const;
|
|
// Vector2<Real> const* GetVertices() const;
|
|
// int const* GetIndices() const;
|
|
// bool GetVertices(int, std::array<Vector2<Real>, 3>&) const;
|
|
// bool GetIndices(int, std::array<int, 3>&) const;
|
|
// bool GetAdjacencies(int, std::array<int, 3>&) const;
|
|
// bool GetBarycentrics(int, Vector2<Real> const&,
|
|
// std::array<Real, 3>&) const;
|
|
// int GetContainingTriangle(Vector2<Real> const&) const;
|
|
|
|
namespace gte
|
|
{
|
|
template <typename Real, typename TriangleMesh>
|
|
class IntpQuadraticNonuniform2
|
|
{
|
|
public:
|
|
// Construction.
|
|
//
|
|
// The first constructor requires only F and a measure of the rate of
|
|
// change of the function values relative to changes in the spatial
|
|
// variables. The df/dx and df/dy values are estimated at the sample
|
|
// points using mesh normals and spatialDelta.
|
|
//
|
|
// The second constructor requires you to specify function values F
|
|
// and first-order partial derivative values df/dx and df/dy.
|
|
|
|
IntpQuadraticNonuniform2(TriangleMesh const& mesh, Real const* F, Real spatialDelta)
|
|
:
|
|
mMesh(&mesh),
|
|
mF(F),
|
|
mFX(nullptr),
|
|
mFY(nullptr)
|
|
{
|
|
EstimateDerivatives(spatialDelta);
|
|
ProcessTriangles();
|
|
}
|
|
|
|
IntpQuadraticNonuniform2(TriangleMesh const& mesh, Real const* F, Real const* FX, Real const* FY)
|
|
:
|
|
mMesh(&mesh),
|
|
mF(F),
|
|
mFX(FX),
|
|
mFY(FY)
|
|
{
|
|
ProcessTriangles();
|
|
}
|
|
|
|
// Quadratic interpolation. The return value is 'true' if and only if
|
|
// the input point is in the convex hull of the input vertices, in
|
|
// which case the interpolation is valid.
|
|
bool operator()(Vector2<Real> const& P, Real& F, Real& FX, Real& FY) const
|
|
{
|
|
auto t = mMesh->GetContainingTriangle(P);
|
|
if (t == mMesh->GetInvalidIndex())
|
|
{
|
|
// The point is outside the triangulation.
|
|
return false;
|
|
}
|
|
|
|
// Get the vertices of the triangle.
|
|
std::array<Vector2<Real>, 3> V;
|
|
mMesh->GetVertices(t, V);
|
|
|
|
// Get the additional information for the triangle.
|
|
TriangleData const& tData = mTData[t];
|
|
|
|
// Determine which of the six subtriangles contains the target
|
|
// point. Theoretically, P must be in one of these subtriangles.
|
|
Vector2<Real> sub0 = tData.center;
|
|
Vector2<Real> sub1;
|
|
Vector2<Real> sub2 = tData.intersect[2];
|
|
Vector3<Real> bary;
|
|
int index;
|
|
|
|
Real const zero = (Real)0, one = (Real)1;
|
|
AlignedBox3<Real> barybox({ zero, zero, zero }, { one, one, one });
|
|
DCPQuery<Real, Vector3<Real>, AlignedBox3<Real>> pbQuery;
|
|
int minIndex = 0;
|
|
Real minDistance = (Real)-1;
|
|
Vector3<Real> minBary{ (Real)0, (Real)0, (Real)0 };
|
|
Vector2<Real> minSub0{ (Real)0, (Real)0 };
|
|
Vector2<Real> minSub1{ (Real)0, (Real)0 };
|
|
Vector2<Real> minSub2{ (Real)0, (Real)0 };
|
|
|
|
for (index = 1; index <= 6; ++index)
|
|
{
|
|
sub1 = sub2;
|
|
if (index % 2)
|
|
{
|
|
sub2 = V[index / 2];
|
|
}
|
|
else
|
|
{
|
|
sub2 = tData.intersect[index / 2 - 1];
|
|
}
|
|
|
|
bool valid = ComputeBarycentrics(P, sub0, sub1, sub2, &bary[0]);
|
|
if (valid
|
|
&& zero <= bary[0] && bary[0] <= one
|
|
&& zero <= bary[1] && bary[1] <= one
|
|
&& zero <= bary[2] && bary[2] <= one)
|
|
{
|
|
// P is in triangle <Sub0,Sub1,Sub2>
|
|
break;
|
|
}
|
|
|
|
// When computing with floating-point arithmetic, rounding
|
|
// errors can cause us to reach this code when, theoretically,
|
|
// the point is in the subtriangle. Keep track of the
|
|
// (b0,b1,b2) that is closest to the barycentric cube [0,1]^3
|
|
// and choose the triangle corresponding to it when all 6
|
|
// tests previously fail.
|
|
Real distance = pbQuery(bary, barybox).distance;
|
|
if (minIndex == 0 || distance < minDistance)
|
|
{
|
|
minDistance = distance;
|
|
minIndex = index;
|
|
minBary = bary;
|
|
minSub0 = sub0;
|
|
minSub1 = sub1;
|
|
minSub2 = sub2;
|
|
}
|
|
}
|
|
|
|
// If the subtriangle was not found, rounding errors caused
|
|
// problems. Choose the barycentric point closest to the box.
|
|
if (index > 6)
|
|
{
|
|
index = minIndex;
|
|
bary = minBary;
|
|
sub0 = minSub0;
|
|
sub1 = minSub1;
|
|
sub2 = minSub2;
|
|
}
|
|
|
|
// Fetch Bezier control points.
|
|
Real bez[6] =
|
|
{
|
|
tData.coeff[0],
|
|
tData.coeff[12 + index],
|
|
tData.coeff[13 + (index % 6)],
|
|
tData.coeff[index],
|
|
tData.coeff[6 + index],
|
|
tData.coeff[1 + (index % 6)]
|
|
};
|
|
|
|
// Evaluate Bezier quadratic.
|
|
F = bary[0] * (bez[0] * bary[0] + bez[1] * bary[1] + bez[2] * bary[2]) +
|
|
bary[1] * (bez[1] * bary[0] + bez[3] * bary[1] + bez[4] * bary[2]) +
|
|
bary[2] * (bez[2] * bary[0] + bez[4] * bary[1] + bez[5] * bary[2]);
|
|
|
|
// Evaluate barycentric derivatives of F.
|
|
Real FU = ((Real)2) * (bez[0] * bary[0] + bez[1] * bary[1] +
|
|
bez[2] * bary[2]);
|
|
Real FV = ((Real)2) * (bez[1] * bary[0] + bez[3] * bary[1] +
|
|
bez[4] * bary[2]);
|
|
Real FW = ((Real)2) * (bez[2] * bary[0] + bez[4] * bary[1] +
|
|
bez[5] * bary[2]);
|
|
Real duw = FU - FW;
|
|
Real dvw = FV - FW;
|
|
|
|
// Convert back to (x,y) coordinates.
|
|
Real m00 = sub0[0] - sub2[0];
|
|
Real m10 = sub0[1] - sub2[1];
|
|
Real m01 = sub1[0] - sub2[0];
|
|
Real m11 = sub1[1] - sub2[1];
|
|
Real inv = ((Real)1) / (m00 * m11 - m10 * m01);
|
|
|
|
FX = inv * (m11 * duw - m10 * dvw);
|
|
FY = inv * (m00 * dvw - m01 * duw);
|
|
return true;
|
|
}
|
|
|
|
private:
|
|
void EstimateDerivatives(Real spatialDelta)
|
|
{
|
|
auto numVertices = mMesh->GetNumVertices();
|
|
Vector2<Real> const* vertices = mMesh->GetVertices();
|
|
auto numTriangles = mMesh->GetNumTriangles();
|
|
int const* indices = mMesh->GetIndices();
|
|
|
|
mFXStorage.resize(numVertices);
|
|
mFYStorage.resize(numVertices);
|
|
std::vector<Real> FZ(numVertices);
|
|
std::fill(mFXStorage.begin(), mFXStorage.end(), (Real)0);
|
|
std::fill(mFYStorage.begin(), mFYStorage.end(), (Real)0);
|
|
std::fill(FZ.begin(), FZ.end(), (Real)0);
|
|
|
|
mFX = &mFXStorage[0];
|
|
mFY = &mFYStorage[0];
|
|
|
|
// Accumulate normals at spatial locations (averaging process).
|
|
for (auto t = 0; t < numTriangles; ++t)
|
|
{
|
|
// Get three vertices of triangle.
|
|
int v0 = *indices++;
|
|
int v1 = *indices++;
|
|
int v2 = *indices++;
|
|
|
|
// Compute normal vector of triangle (with positive
|
|
// z-component).
|
|
Real dx1 = vertices[v1][0] - vertices[v0][0];
|
|
Real dy1 = vertices[v1][1] - vertices[v0][1];
|
|
Real dz1 = mF[v1] - mF[v0];
|
|
Real dx2 = vertices[v2][0] - vertices[v0][0];
|
|
Real dy2 = vertices[v2][1] - vertices[v0][1];
|
|
Real dz2 = mF[v2] - mF[v0];
|
|
Real nx = dy1 * dz2 - dy2 * dz1;
|
|
Real ny = dz1 * dx2 - dz2 * dx1;
|
|
Real nz = dx1 * dy2 - dx2 * dy1;
|
|
if (nz < (Real)0)
|
|
{
|
|
nx = -nx;
|
|
ny = -ny;
|
|
nz = -nz;
|
|
}
|
|
|
|
mFXStorage[v0] += nx; mFYStorage[v0] += ny; FZ[v0] += nz;
|
|
mFXStorage[v1] += nx; mFYStorage[v1] += ny; FZ[v1] += nz;
|
|
mFXStorage[v2] += nx; mFYStorage[v2] += ny; FZ[v2] += nz;
|
|
}
|
|
|
|
// Scale the normals to form (x,y,-1).
|
|
for (auto i = 0; i < numVertices; ++i)
|
|
{
|
|
if (FZ[i] != (Real)0)
|
|
{
|
|
Real inv = -spatialDelta / FZ[i];
|
|
mFXStorage[i] *= inv;
|
|
mFYStorage[i] *= inv;
|
|
}
|
|
else
|
|
{
|
|
mFXStorage[i] = (Real)0;
|
|
mFYStorage[i] = (Real)0;
|
|
}
|
|
}
|
|
}
|
|
|
|
void ProcessTriangles()
|
|
{
|
|
// Add degenerate triangles to boundary triangles so that
|
|
// interpolation at the boundary can be treated in the same way
|
|
// as interpolation in the interior.
|
|
|
|
// Compute centers of inscribed circles for triangles.
|
|
Vector2<Real> const* vertices = mMesh->GetVertices();
|
|
auto numTriangles = mMesh->GetNumTriangles();
|
|
int const* indices = mMesh->GetIndices();
|
|
mTData.resize(numTriangles);
|
|
for (auto t = 0; t < numTriangles; ++t)
|
|
{
|
|
int v0 = *indices++;
|
|
int v1 = *indices++;
|
|
int v2 = *indices++;
|
|
Circle2<Real> circle;
|
|
Inscribe(vertices[v0], vertices[v1], vertices[v2], circle);
|
|
mTData[t].center = circle.center;
|
|
}
|
|
|
|
// Compute cross-edge intersections.
|
|
for (auto t = 0; t < numTriangles; ++t)
|
|
{
|
|
ComputeCrossEdgeIntersections(t);
|
|
}
|
|
|
|
// Compute Bezier coefficients.
|
|
for (auto t = 0; t < numTriangles; ++t)
|
|
{
|
|
ComputeCoefficients(t);
|
|
}
|
|
}
|
|
|
|
void ComputeCrossEdgeIntersections(int t)
|
|
{
|
|
// Get the vertices of the triangle.
|
|
std::array<Vector2<Real>, 3> V;
|
|
mMesh->GetVertices(t, V);
|
|
|
|
// Get the centers of adjacent triangles.
|
|
TriangleData& tData = mTData[t];
|
|
std::array<int, 3> adjacencies = { 0, 0, 0 };
|
|
mMesh->GetAdjacencies(t, adjacencies);
|
|
for (int j0 = 2, j1 = 0; j1 < 3; j0 = j1++)
|
|
{
|
|
int a = adjacencies[j0];
|
|
if (a >= 0)
|
|
{
|
|
// Get center of adjacent triangle's inscribing circle.
|
|
Vector2<Real> U = mTData[a].center;
|
|
Real m00 = V[j0][1] - V[j1][1];
|
|
Real m01 = V[j1][0] - V[j0][0];
|
|
Real m10 = tData.center[1] - U[1];
|
|
Real m11 = U[0] - tData.center[0];
|
|
Real r0 = m00 * V[j0][0] + m01 * V[j0][1];
|
|
Real r1 = m10 * tData.center[0] + m11 * tData.center[1];
|
|
Real invDet = ((Real)1) / (m00 * m11 - m01 * m10);
|
|
tData.intersect[j0][0] = (m11 * r0 - m01 * r1) * invDet;
|
|
tData.intersect[j0][1] = (m00 * r1 - m10 * r0) * invDet;
|
|
}
|
|
else
|
|
{
|
|
// No adjacent triangle, use center of edge.
|
|
tData.intersect[j0] = (Real)0.5 * (V[j0] + V[j1]);
|
|
}
|
|
}
|
|
}
|
|
|
|
void ComputeCoefficients(int t)
|
|
{
|
|
// Get the vertices of the triangle.
|
|
std::array<Vector2<Real>, 3> V;
|
|
mMesh->GetVertices(t, V);
|
|
|
|
// Get the additional information for the triangle.
|
|
TriangleData& tData = mTData[t];
|
|
|
|
// Get the sample data at main triangle vertices.
|
|
std::array<int, 3> indices = { 0, 0, 0 };
|
|
mMesh->GetIndices(t, indices);
|
|
std::array<Jet, 3> jet;
|
|
for (int j = 0; j < 3; ++j)
|
|
{
|
|
int k = indices[j];
|
|
jet[j].F = mF[k];
|
|
jet[j].FX = mFX[k];
|
|
jet[j].FY = mFY[k];
|
|
}
|
|
|
|
// Get centers of adjacent triangles.
|
|
std::array<int, 3> adjacencies = { 0, 0, 0 };
|
|
mMesh->GetAdjacencies(t, adjacencies);
|
|
Vector2<Real> U[3];
|
|
for (int j0 = 2, j1 = 0; j1 < 3; j0 = j1++)
|
|
{
|
|
int a = adjacencies[j0];
|
|
if (a >= 0)
|
|
{
|
|
// Get center of adjacent triangle's circumscribing
|
|
// circle.
|
|
U[j0] = mTData[a].center;
|
|
}
|
|
else
|
|
{
|
|
// No adjacent triangle, use center of edge.
|
|
U[j0] = ((Real)0.5) * (V[j0] + V[j1]);
|
|
}
|
|
}
|
|
|
|
// Compute intermediate terms.
|
|
std::array<Real, 3> cenT, cen0, cen1, cen2;
|
|
mMesh->GetBarycentrics(t, tData.center, cenT);
|
|
mMesh->GetBarycentrics(t, U[0], cen0);
|
|
mMesh->GetBarycentrics(t, U[1], cen1);
|
|
mMesh->GetBarycentrics(t, U[2], cen2);
|
|
|
|
Real alpha = (cenT[1] * cen1[0] - cenT[0] * cen1[1]) / (cen1[0] - cenT[0]);
|
|
Real beta = (cenT[2] * cen2[1] - cenT[1] * cen2[2]) / (cen2[1] - cenT[1]);
|
|
Real gamma = (cenT[0] * cen0[2] - cenT[2] * cen0[0]) / (cen0[2] - cenT[2]);
|
|
Real oneMinusAlpha = (Real)1 - alpha;
|
|
Real oneMinusBeta = (Real)1 - beta;
|
|
Real oneMinusGamma = (Real)1 - gamma;
|
|
|
|
Real const zero = static_cast<Real>(0);
|
|
std::array<Real, 9> A = { zero, zero, zero, zero, zero, zero, zero, zero, zero };
|
|
std::array<Real, 9> B = { zero, zero, zero, zero, zero, zero, zero, zero, zero };
|
|
|
|
Real tmp = cenT[0] * V[0][0] + cenT[1] * V[1][0] + cenT[2] * V[2][0];
|
|
A[0] = (Real)0.5 * (tmp - V[0][0]);
|
|
A[1] = (Real)0.5 * (tmp - V[1][0]);
|
|
A[2] = (Real)0.5 * (tmp - V[2][0]);
|
|
A[3] = (Real)0.5 * beta * (V[2][0] - V[0][0]);
|
|
A[4] = (Real)0.5 * oneMinusGamma * (V[1][0] - V[0][0]);
|
|
A[5] = (Real)0.5 * gamma * (V[0][0] - V[1][0]);
|
|
A[6] = (Real)0.5 * oneMinusAlpha * (V[2][0] - V[1][0]);
|
|
A[7] = (Real)0.5 * alpha * (V[1][0] - V[2][0]);
|
|
A[8] = (Real)0.5 * oneMinusBeta * (V[0][0] - V[2][0]);
|
|
|
|
tmp = cenT[0] * V[0][1] + cenT[1] * V[1][1] + cenT[2] * V[2][1];
|
|
B[0] = (Real)0.5 * (tmp - V[0][1]);
|
|
B[1] = (Real)0.5 * (tmp - V[1][1]);
|
|
B[2] = (Real)0.5 * (tmp - V[2][1]);
|
|
B[3] = (Real)0.5 * beta * (V[2][1] - V[0][1]);
|
|
B[4] = (Real)0.5 * oneMinusGamma * (V[1][1] - V[0][1]);
|
|
B[5] = (Real)0.5 * gamma * (V[0][1] - V[1][1]);
|
|
B[6] = (Real)0.5 * oneMinusAlpha * (V[2][1] - V[1][1]);
|
|
B[7] = (Real)0.5 * alpha * (V[1][1] - V[2][1]);
|
|
B[8] = (Real)0.5 * oneMinusBeta * (V[0][1] - V[2][1]);
|
|
|
|
// Compute Bezier coefficients.
|
|
tData.coeff[2] = jet[0].F;
|
|
tData.coeff[4] = jet[1].F;
|
|
tData.coeff[6] = jet[2].F;
|
|
|
|
tData.coeff[14] = jet[0].F + A[0] * jet[0].FX + B[0] * jet[0].FY;
|
|
tData.coeff[7] = jet[0].F + A[3] * jet[0].FX + B[3] * jet[0].FY;
|
|
tData.coeff[8] = jet[0].F + A[4] * jet[0].FX + B[4] * jet[0].FY;
|
|
tData.coeff[16] = jet[1].F + A[1] * jet[1].FX + B[1] * jet[1].FY;
|
|
tData.coeff[9] = jet[1].F + A[5] * jet[1].FX + B[5] * jet[1].FY;
|
|
tData.coeff[10] = jet[1].F + A[6] * jet[1].FX + B[6] * jet[1].FY;
|
|
tData.coeff[18] = jet[2].F + A[2] * jet[2].FX + B[2] * jet[2].FY;
|
|
tData.coeff[11] = jet[2].F + A[7] * jet[2].FX + B[7] * jet[2].FY;
|
|
tData.coeff[12] = jet[2].F + A[8] * jet[2].FX + B[8] * jet[2].FY;
|
|
|
|
tData.coeff[5] = alpha * tData.coeff[10] + oneMinusAlpha * tData.coeff[11];
|
|
tData.coeff[17] = alpha * tData.coeff[16] + oneMinusAlpha * tData.coeff[18];
|
|
tData.coeff[1] = beta * tData.coeff[12] + oneMinusBeta * tData.coeff[7];
|
|
tData.coeff[13] = beta * tData.coeff[18] + oneMinusBeta * tData.coeff[14];
|
|
tData.coeff[3] = gamma * tData.coeff[8] + oneMinusGamma * tData.coeff[9];
|
|
tData.coeff[15] = gamma * tData.coeff[14] + oneMinusGamma * tData.coeff[16];
|
|
tData.coeff[0] = cenT[0] * tData.coeff[14] + cenT[1] * tData.coeff[16] + cenT[2] * tData.coeff[18];
|
|
}
|
|
|
|
class TriangleData
|
|
{
|
|
public:
|
|
Vector2<Real> center;
|
|
std::array<Vector2<Real>, 3> intersect;
|
|
std::array<Real, 19> coeff;
|
|
};
|
|
|
|
class Jet
|
|
{
|
|
public:
|
|
Jet()
|
|
:
|
|
F(static_cast<Real>(0)),
|
|
FX(static_cast<Real>(0)),
|
|
FY(static_cast<Real>(0))
|
|
{
|
|
}
|
|
|
|
Real F, FX, FY;
|
|
};
|
|
|
|
TriangleMesh const* mMesh;
|
|
Real const* mF;
|
|
Real const* mFX;
|
|
Real const* mFY;
|
|
std::vector<Real> mFXStorage;
|
|
std::vector<Real> mFYStorage;
|
|
std::vector<TriangleData> mTData;
|
|
};
|
|
}
|
|
|