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.
 
 

936 lines
33 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.2019.08.13
#pragma once
#include <Mathematics/Logger.h>
#include <array>
#include <cstdint>
#include <map>
#include <memory>
#include <ostream>
#include <vector>
// Extract level surfaces using an adaptive approach to reduce the triangle
// count. The implementation is for the algorithm described in the paper
// Multiresolution Isosurface Extraction with Adaptive Skeleton Climbing
// Tim Poston, Tien-Tsin Wong and Pheng-Ann Heng
// Computer Graphics forum, volume 17, issue 3, September 1998
// pages 137-147
// https://onlinelibrary.wiley.com/doi/abs/10.1111/1467-8659.00261
namespace gte
{
// The image type T must be one of the integer types: int8_t, int16_t,
// int32_t, uint8_t, uint16_t or uint32_t. Internal integer computations
// are performed using int64_t. The type Real is for extraction to
// floating-point vertices.
template <typename T, typename Real>
class AdaptiveSkeletonClimbing2
{
public:
// Construction and destruction. The input image is assumed to
// contain (2^N+1)-by-(2^N+1) elements where N >= 0. The organization
// is row-major order for (x,y).
AdaptiveSkeletonClimbing2(int N, T const* inputPixels)
:
mTwoPowerN(1 << N),
mSize(mTwoPowerN + 1),
mInputPixels(inputPixels),
mXMerge(mSize),
mYMerge(mSize)
{
static_assert(std::is_integral<T>::value && sizeof(T) <= 4,
"Type T must be int{8,16,32}_t or uint{8,16,32}_t.");
if (N <= 0 || mInputPixels == nullptr)
{
LogError("Invalid input.");
}
for (int i = 0; i < mSize; ++i)
{
mXMerge[i] = std::make_shared<LinearMergeTree>(N);
mYMerge[i] = std::make_shared<LinearMergeTree>(N);
}
mXYMerge = std::make_unique<AreaMergeTree>(N, mXMerge, mYMerge);
}
// TODO: Refactor this class to have base class CurveExtractor.
typedef std::array<Real, 2> Vertex;
typedef std::array<int, 2> Edge;
void Extract(Real level, int depth,
std::vector<Vertex>& vertices, std::vector<Edge>& edges)
{
std::vector<Rectangle> rectangles;
std::vector<Vertex> localVertices;
std::vector<Edge> localEdges;
SetLevel(level, depth);
GetRectangles(rectangles);
for (auto& rectangle : rectangles)
{
if (rectangle.type > 0)
{
GetComponents(level, rectangle, localVertices, localEdges);
}
}
vertices = std::move(localVertices);
edges = std::move(localEdges);
}
void MakeUnique(std::vector<Vertex>& vertices, std::vector<Edge>& edges)
{
size_t numVertices = vertices.size();
size_t numEdges = edges.size();
if (numVertices == 0 || numEdges == 0)
{
return;
}
// Compute the map of unique vertices and assign to them new and
// unique indices.
std::map<Vertex, int> vmap;
int nextVertex = 0;
for (size_t v = 0; v < numVertices; ++v)
{
// Keep only unique vertices.
auto result = vmap.insert(std::make_pair(vertices[v], nextVertex));
if (result.second)
{
++nextVertex;
}
}
// Compute the map of unique edges and assign to them new and
// unique indices.
std::map<Edge, int> emap;
int nextEdge = 0;
for (size_t e = 0; e < numEdges; ++e)
{
// Replace old vertex indices by new vertex indices.
Edge& edge = edges[e];
for (int i = 0; i < 2; ++i)
{
auto iter = vmap.find(vertices[edge[i]]);
LogAssert(iter != vmap.end(), "Expecting the vertex to be in the vmap.");
edge[i] = iter->second;
}
// Keep only unique edges.
auto result = emap.insert(std::make_pair(edge, nextEdge));
if (result.second)
{
++nextEdge;
}
}
// Pack the vertices into an array.
vertices.resize(vmap.size());
for (auto const& element : vmap)
{
vertices[element.second] = element.first;
}
// Pack the edges into an array.
edges.resize(emap.size());
for (auto const& element : emap)
{
edges[element.second] = element.first;
}
}
private:
// Helper classes for the skeleton climbing.
struct QuadRectangle
{
QuadRectangle()
:
xOrigin(0),
yOrigin(0),
xStride(0),
yStride(0),
valid(false)
{
}
QuadRectangle(int inXOrigin, int inYOrigin, int inXStride, int inYStride)
{
Initialize(inXOrigin, inYOrigin, inXStride, inYStride);
}
void Initialize(int inXOrigin, int inYOrigin, int inXStride, int inYStride)
{
xOrigin = inXOrigin;
yOrigin = inYOrigin;
xStride = inXStride;
yStride = inYStride;
valid = true;
}
int xOrigin, yOrigin, xStride, yStride;
bool valid;
};
struct QuadNode
{
QuadNode()
{
// The members are uninitialized.
}
QuadNode(int xOrigin, int yOrigin, int xNext, int yNext, int stride)
:
r00(xOrigin, yOrigin, stride, stride),
r10(xNext, yOrigin, stride, stride),
r01(xOrigin, yNext, stride, stride),
r11(xNext, yNext, stride, stride)
{
}
void Initialize(int xOrigin, int yOrigin, int xNext, int yNext, int stride)
{
r00.Initialize(xOrigin, yOrigin, stride, stride);
r10.Initialize(xNext, yOrigin, stride, stride);
r01.Initialize(xOrigin, yNext, stride, stride);
r11.Initialize(xNext, yNext, stride, stride);
}
bool IsMono() const
{
return !r10.valid && !r01.valid && !r11.valid;
}
int GetQuantity() const
{
int quantity = 0;
if (r00.valid)
{
++quantity;
}
if (r10.valid)
{
++quantity;
}
if (r01.valid)
{
++quantity;
}
if (r11.valid)
{
++quantity;
}
return quantity;
}
QuadRectangle r00, r10, r01, r11;
};
class LinearMergeTree
{
public:
LinearMergeTree(int N)
:
mTwoPowerN(1 << N),
mNodes(2 * mTwoPowerN - 1)
{
}
enum
{
CFG_NONE,
CFG_INCR,
CFG_DECR,
CFG_MULT
};
// Member access.
int GetQuantity() const
{
return 2 * mTwoPowerN - 1;
}
int GetNode(int i) const
{
return mNodes[i];
}
int GetEdge(int i) const
{
// assert: mNodes[i] == CFG_INCR || mNodes[i] == CFG_DECR
// Traverse binary tree looking for incr or decr leaf node.
int const firstLeaf = mTwoPowerN - 1;
while (i < firstLeaf)
{
i = 2 * i + 1;
if (mNodes[i] == CFG_NONE)
{
++i;
}
}
return i - firstLeaf;
}
void SetLevel(Real level, T const* data, int offset, int stride)
{
// Assert: The 'level' is not an image value. Because T is
// an integer type, choose 'level' to be a Real-valued number
// that does not represent an integer.
// Determine the sign changes between pairs of consecutive
// samples.
int const firstLeaf = mTwoPowerN - 1;
for (int i = 0, leaf = firstLeaf; i < mTwoPowerN; ++i, ++leaf)
{
int base = offset + stride * i;
Real value0 = static_cast<Real>(data[base]);
Real value1 = static_cast<Real>(data[base + stride]);
if (value0 > level)
{
if (value1 > level)
{
mNodes[leaf] = CFG_NONE;
}
else
{
mNodes[leaf] = CFG_DECR;
}
}
else // value0 < level
{
if (value1 > level)
{
mNodes[leaf] = CFG_INCR;
}
else
{
mNodes[leaf] = CFG_NONE;
}
}
}
// Propagate the sign change information up the binary tree.
for (int i = firstLeaf - 1; i >= 0; --i)
{
int twoIp1 = 2 * i + 1;
int child0 = mNodes[twoIp1];
int child1 = mNodes[twoIp1 + 1];
mNodes[i] = (child0 | child1);
}
}
private:
int mTwoPowerN;
std::vector<int> mNodes;
};
struct Rectangle
{
Rectangle(int inXOrigin, int inYOrigin, int inXStride, int inYStride)
:
xOrigin(inXOrigin),
yOrigin(inYOrigin),
xStride(inXStride),
yStride(inYStride),
yOfXMin(-1),
yOfXMax(-1),
xOfYMin(-1),
xOfYMax(-1),
type(0)
{
}
int xOrigin, yOrigin, xStride, yStride;
int yOfXMin, yOfXMax, xOfYMin, xOfYMax;
// A 4-bit flag for how the level set intersects the rectangle
// boundary.
// bit 0 = xmin edge
// bit 1 = xmax edge
// bit 2 = ymin edge
// bit 3 = ymax edge
// A bit is set if the corresponding edge is intersected by the
// level set. This information is known from the CFG flags for
// LinearMergeTree. Intersection occurs whenever the flag is
// CFG_INCR or CFG_DECR.
unsigned int type;
};
class AreaMergeTree
{
public:
AreaMergeTree(int N,
std::vector<std::shared_ptr<LinearMergeTree>> const& xMerge,
std::vector<std::shared_ptr<LinearMergeTree>> const& yMerge)
:
mXMerge(xMerge),
mYMerge(yMerge),
mNodes(((1 << 2 * (N + 1)) - 1) / 3)
{
}
void ConstructMono(int A, int LX, int LY, int xOrigin, int yOrigin,
int stride, int depth)
{
if (stride > 1) // internal nodes
{
int hStride = stride / 2;
int ABase = 4 * A;
int A00 = ++ABase;
int A10 = ++ABase;
int A01 = ++ABase;
int A11 = ++ABase;
int LXBase = 2 * LX;
int LX0 = ++LXBase;
int LX1 = ++LXBase;
int LYBase = 2 * LY;
int LY0 = ++LYBase;
int LY1 = ++LYBase;
int xNext = xOrigin + hStride;
int yNext = yOrigin + hStride;
int depthM1 = depth - 1;
ConstructMono(A00, LX0, LY0, xOrigin, yOrigin, hStride, depthM1);
ConstructMono(A10, LX1, LY0, xNext, yOrigin, hStride, depthM1);
ConstructMono(A01, LX0, LY1, xOrigin, yNext, hStride, depthM1);
ConstructMono(A11, LX1, LY1, xNext, yNext, hStride, depthM1);
if (depth >= 0)
{
// Merging is prevented above the specified depth in
// the tree. This allows a single object to produce
// any resolution isocontour rather than using
// multiple objects to do so.
mNodes[A].Initialize(xOrigin, yOrigin, xNext, yNext, hStride);
return;
}
bool mono00 = mNodes[A00].IsMono();
bool mono10 = mNodes[A10].IsMono();
bool mono01 = mNodes[A01].IsMono();
bool mono11 = mNodes[A11].IsMono();
QuadNode node0(xOrigin, yOrigin, xNext, yNext, hStride);
QuadNode node1 = node0;
// Merge x first, y second.
if (mono00 && mono10)
{
DoXMerge(node0.r00, node0.r10, LX, yOrigin);
}
if (mono01 && mono11)
{
DoXMerge(node0.r01, node0.r11, LX, yNext);
}
if (mono00 && mono01)
{
DoYMerge(node0.r00, node0.r01, xOrigin, LY);
}
if (mono10 && mono11)
{
DoYMerge(node0.r10, node0.r11, xNext, LY);
}
// Merge y first, x second.
if (mono00 && mono01)
{
DoYMerge(node1.r00, node1.r01, xOrigin, LY);
}
if (mono10 && mono11)
{
DoYMerge(node1.r10, node1.r11, xNext, LY);
}
if (mono00 && mono10)
{
DoXMerge(node1.r00, node1.r10, LX, yOrigin);
}
if (mono01 && mono11)
{
DoXMerge(node1.r01, node1.r11, LX, yNext);
}
// Choose the merge that produced the smallest number of
// rectangles.
if (node0.GetQuantity() <= node1.GetQuantity())
{
mNodes[A] = node0;
}
else
{
mNodes[A] = node1;
}
}
else // leaf nodes
{
mNodes[A].r00.Initialize(xOrigin, yOrigin, 1, 1);
}
}
void GetRectangles(int A, int LX, int LY, int xOrigin, int yOrigin,
int stride, std::vector<Rectangle>& rectangles)
{
int hStride = stride / 2;
int ABase = 4 * A;
int A00 = ++ABase;
int A10 = ++ABase;
int A01 = ++ABase;
int A11 = ++ABase;
int LXBase = 2 * LX;
int LX0 = ++LXBase;
int LX1 = ++LXBase;
int LYBase = 2 * LY;
int LY0 = ++LYBase;
int LY1 = ++LYBase;
int xNext = xOrigin + hStride;
int yNext = yOrigin + hStride;
QuadRectangle const& r00 = mNodes[A].r00;
if (r00.valid)
{
if (r00.xStride == stride)
{
if (r00.yStride == stride)
{
rectangles.push_back(GetRectangle(r00, LX, LY));
}
else
{
rectangles.push_back(GetRectangle(r00, LX, LY0));
}
}
else
{
if (r00.yStride == stride)
{
rectangles.push_back(GetRectangle(r00, LX0, LY));
}
else
{
GetRectangles(A00, LX0, LY0, xOrigin, yOrigin, hStride, rectangles);
}
}
}
QuadRectangle const& r10 = mNodes[A].r10;
if (r10.valid)
{
if (r10.yStride == stride)
{
rectangles.push_back(GetRectangle(r10, LX1, LY));
}
else
{
GetRectangles(A10, LX1, LY0, xNext, yOrigin, hStride, rectangles);
}
}
QuadRectangle const& r01 = mNodes[A].r01;
if (r01.valid)
{
if (r01.xStride == stride)
{
rectangles.push_back(GetRectangle(r01, LX, LY1));
}
else
{
GetRectangles(A01, LX0, LY1, xOrigin, yNext, hStride, rectangles);
}
}
QuadRectangle const& r11 = mNodes[A].r11;
if (r11.valid)
{
GetRectangles(A11, LX1, LY1, xNext, yNext, hStride, rectangles);
}
}
private:
void DoXMerge(QuadRectangle& r0, QuadRectangle& r1, int LX, int yOrigin)
{
if (r0.valid && r1.valid && r0.yStride == r1.yStride)
{
// Rectangles are x-mergeable.
int incr = 0, decr = 0;
for (int y = 0; y <= r0.yStride; ++y)
{
switch (mXMerge[yOrigin + y]->GetNode(LX))
{
case LinearMergeTree::CFG_MULT:
return;
case LinearMergeTree::CFG_INCR:
++incr;
break;
case LinearMergeTree::CFG_DECR:
++decr;
break;
}
}
if (incr == 0 || decr == 0)
{
// Strongly mono, x-merge the rectangles.
r0.xStride *= 2;
r1.valid = false;
}
}
}
void DoYMerge(QuadRectangle& r0, QuadRectangle& r1, int xOrigin, int LY)
{
if (r0.valid && r1.valid && r0.xStride == r1.xStride)
{
// Rectangles are y-mergeable.
int incr = 0, decr = 0;
for (int x = 0; x <= r0.xStride; ++x)
{
switch (mYMerge[xOrigin + x]->GetNode(LY))
{
case LinearMergeTree::CFG_MULT:
return;
case LinearMergeTree::CFG_INCR:
++incr;
break;
case LinearMergeTree::CFG_DECR:
++decr;
break;
}
}
if (incr == 0 || decr == 0)
{
// Strongly mono, y-merge the rectangles.
r0.yStride *= 2;
r1.valid = false;
}
}
}
Rectangle GetRectangle(QuadRectangle const& qrect, int LX, int LY)
{
Rectangle rect(qrect.xOrigin, qrect.yOrigin, qrect.xStride, qrect.yStride);
// xmin edge
auto merge = mYMerge[qrect.xOrigin];
if (merge->GetNode(LY) != LinearMergeTree::CFG_NONE)
{
rect.yOfXMin = merge->GetEdge(LY);
if (rect.yOfXMin != -1)
{
rect.type |= 0x01;
}
}
// xmax edge
merge = mYMerge[qrect.xOrigin + qrect.xStride];
if (merge->GetNode(LY) != LinearMergeTree::CFG_NONE)
{
rect.yOfXMax = merge->GetEdge(LY);
if (rect.yOfXMax != -1)
{
rect.type |= 0x02;
}
}
// ymin edge
merge = mXMerge[qrect.yOrigin];
if (merge->GetNode(LX) != LinearMergeTree::CFG_NONE)
{
rect.xOfYMin = merge->GetEdge(LX);
if (rect.xOfYMin != -1)
{
rect.type |= 0x04;
}
}
// ymax edge
merge = mXMerge[qrect.yOrigin + qrect.yStride];
if (merge->GetNode(LX) != LinearMergeTree::CFG_NONE)
{
rect.xOfYMax = merge->GetEdge(LX);
if (rect.xOfYMax != -1)
{
rect.type |= 0x08;
}
}
return rect;
}
std::vector<std::shared_ptr<LinearMergeTree>> mXMerge;
std::vector<std::shared_ptr<LinearMergeTree>> mYMerge;
std::vector<QuadNode> mNodes;
};
private:
// Support for extraction of level sets.
Real GetInterp(Real level, int base, int index, int increment)
{
Real f0 = static_cast<Real>(mInputPixels[index]);
index += increment;
Real f1 = static_cast<Real>(mInputPixels[index]);
LogAssert((f0 - level) * (f1 - level) < (Real)0, "Unexpected condition.");
return static_cast<Real>(base) + (level - f0) / (f1 - f0);
}
void AddVertex(std::vector<Vertex>& vertices, Real x, Real y)
{
Vertex vertex = { x, y };
vertices.push_back(vertex);
}
void AddEdge(std::vector<Vertex>& vertices,
std::vector<Edge>& edges, Real x0, Real y0, Real x1, Real y1)
{
int v0 = static_cast<int>(vertices.size());
int v1 = v0 + 1;
Edge edge = { v0, v1 };
edges.push_back(edge);
Vertex vertex0 = { x0, y0 };
Vertex vertex1 = { x1, y1 };
vertices.push_back(vertex0);
vertices.push_back(vertex1);
}
void SetLevel(Real level, int depth)
{
int offset, stride;
for (int y = 0; y < mSize; ++y)
{
offset = mSize * y;
stride = 1;
mXMerge[y]->SetLevel(level, mInputPixels, offset, stride);
}
for (int x = 0; x < mSize; ++x)
{
offset = x;
stride = mSize;
mYMerge[x]->SetLevel(level, mInputPixels, offset, stride);
}
mXYMerge->ConstructMono(0, 0, 0, 0, 0, mTwoPowerN, depth);
}
void GetRectangles(std::vector<Rectangle>& rectangles)
{
mXYMerge->GetRectangles(0, 0, 0, 0, 0, mTwoPowerN, rectangles);
}
void GetComponents(Real level, Rectangle const& rectangle,
std::vector<Vertex>& vertices, std::vector<Edge>& edges)
{
int x, y;
Real x0, y0, x1, y1;
switch (rectangle.type)
{
case 3: // two vertices, on xmin and xmax
LogAssert(rectangle.yOfXMin != -1, "Unexpected condition.");
x = rectangle.xOrigin;
y = rectangle.yOfXMin;
x0 = static_cast<Real>(x);
y0 = GetInterp(level, y, x + mSize * y, mSize);
LogAssert(rectangle.yOfXMax != -1, "Unexpected condition.");
x = rectangle.xOrigin + rectangle.xStride;
y = rectangle.yOfXMax;
x1 = static_cast<Real>(x);
y1 = GetInterp(level, y, x + mSize * y, mSize);
AddEdge(vertices, edges, x0, y0, x1, y1);
break;
case 5: // two vertices, on xmin and ymin
LogAssert(rectangle.yOfXMin != -1, "Unexpected condition.");
x = rectangle.xOrigin;
y = rectangle.yOfXMin;
x0 = static_cast<Real>(x);
y0 = GetInterp(level, y, x + mSize * y, mSize);
LogAssert(rectangle.xOfYMin != -1, "Unexpected condition.");
x = rectangle.xOfYMin;
y = rectangle.yOrigin;
x1 = GetInterp(level, x, x + mSize * y, 1);
y1 = static_cast<Real>(y);
AddEdge(vertices, edges, x0, y0, x1, y1);
break;
case 6: // two vertices, on xmax and ymin
LogAssert(rectangle.yOfXMax != -1, "Unexpected condition.");
x = rectangle.xOrigin + rectangle.xStride;
y = rectangle.yOfXMax;
x0 = static_cast<Real>(x);
y0 = GetInterp(level, y, x + mSize * y, mSize);
LogAssert(rectangle.xOfYMin != -1, "Unexpected condition.");
x = rectangle.xOfYMin;
y = rectangle.yOrigin;
x1 = GetInterp(level, x, x + mSize * y, 1);
y1 = static_cast<Real>(y);
AddEdge(vertices, edges, x0, y0, x1, y1);
break;
case 9: // two vertices, on xmin and ymax
LogAssert(rectangle.yOfXMin != -1, "Unexpected condition.");
x = rectangle.xOrigin;
y = rectangle.yOfXMin;
x0 = static_cast<Real>(x);
y0 = GetInterp(level, y, x + mSize * y, mSize);
LogAssert(rectangle.xOfYMax != -1, "Unexpected condition.");
x = rectangle.xOfYMax;
y = rectangle.yOrigin + rectangle.yStride;
x1 = GetInterp(level, x, x + mSize * y, 1);
y1 = static_cast<Real>(y);
AddEdge(vertices, edges, x0, y0, x1, y1);
break;
case 10: // two vertices, on xmax and ymax
LogAssert(rectangle.yOfXMax != -1, "Unexpected condition.");
x = rectangle.xOrigin + rectangle.xStride;
y = rectangle.yOfXMax;
x0 = static_cast<Real>(x);
y0 = GetInterp(level, y, x + mSize * y, mSize);
LogAssert(rectangle.xOfYMax != -1, "Unexpected condition.");
x = rectangle.xOfYMax;
y = rectangle.yOrigin + rectangle.yStride;
x1 = GetInterp(level, x, x + mSize * y, 1);
y1 = static_cast<Real>(y);
AddEdge(vertices, edges, x0, y0, x1, y1);
break;
case 12: // two vertices, on ymin and ymax
LogAssert(rectangle.xOfYMin != -1, "Unexpected condition.");
x = rectangle.xOfYMin;
y = rectangle.yOrigin;
x0 = GetInterp(level, x, x + mSize * y, 1);
y0 = static_cast<Real>(y);
LogAssert(rectangle.xOfYMax != -1, "Unexpected condition.");
x = rectangle.xOfYMax;
y = rectangle.yOrigin + rectangle.yStride;
x1 = GetInterp(level, x, x + mSize * y, 1);
y1 = static_cast<Real>(y);
AddEdge(vertices, edges, x0, y0, x1, y1);
break;
case 15: // four vertices, one per edge, need to disambiguate
{
LogAssert(rectangle.xStride == 1 && rectangle.yStride == 1,
"Unexpected condition.");
LogAssert(rectangle.yOfXMin != -1, "Unexpected condition.");
x = rectangle.xOrigin;
y = rectangle.yOfXMin;
x0 = static_cast<Real>(x);
y0 = GetInterp(level, y, x + mSize * y, mSize);
LogAssert(rectangle.yOfXMax != -1, "Unexpected condition.");
x = rectangle.xOrigin + rectangle.xStride;
y = rectangle.yOfXMax;
x1 = static_cast<Real>(x);
y1 = GetInterp(level, y, x + mSize * y, mSize);
LogAssert(rectangle.xOfYMin != -1, "Unexpected condition.");
x = rectangle.xOfYMin;
y = rectangle.yOrigin;
Real fx2 = GetInterp(level, x, x + mSize * y, 1);
Real fy2 = static_cast<Real>(y);
LogAssert(rectangle.xOfYMax != -1, "Unexpected condition.");
x = rectangle.xOfYMax;
y = rectangle.yOrigin + rectangle.yStride;
Real fx3 = GetInterp(level, x, x + mSize * y, 1);
Real fy3 = static_cast<Real>(y);
int index = rectangle.xOrigin + mSize * rectangle.yOrigin;
int64_t i00 = static_cast<int64_t>(mInputPixels[index]);
++index;
int64_t i10 = static_cast<int64_t>(mInputPixels[index]);
index += mSize;
int64_t i11 = static_cast<int64_t>(mInputPixels[index]);
--index;
int64_t i01 = static_cast<int64_t>(mInputPixels[index]);
int64_t det = i00 * i11 - i01 * i10;
if (det > 0)
{
// Disjoint hyperbolic segments, pair <P0,P2> and <P1,P3>.
AddEdge(vertices, edges, x0, y0, fx2, fy2);
AddEdge(vertices, edges, x1, y1, fx3, fy3);
}
else if (det < 0)
{
// Disjoint hyperbolic segments, pair <P0,P3> and <P1,P2>.
AddEdge(vertices, edges, x0, y0, fx3, fy3);
AddEdge(vertices, edges, x1, y1, fx2, fy2);
}
else
{
// Plus-sign configuration, add branch point to
// tessellation.
Real fx4 = fx2, fy4 = y0;
AddEdge(vertices, edges, x0, y0, fx4, fy4);
AddEdge(vertices, edges, x1, y1, fx4, fy4);
AddEdge(vertices, edges, fx2, fy2, fx4, fy4);
AddEdge(vertices, edges, fx3, fy3, fx4, fy4);
}
break;
}
default:
LogError("Unexpected condition.");
}
}
// Support for debugging.
void PrintRectangles(std::ostream& output, std::vector<Rectangle> const& rectangles)
{
for (size_t i = 0; i < rectangles.size(); ++i)
{
auto const& rectangle = rectangles[i];
output << "rectangle " << i << std::endl;
output << " x origin = " << rectangle.xOrigin << std::endl;
output << " y origin = " << rectangle.yOrigin << std::endl;
output << " x stride = " << rectangle.xStride << std::endl;
output << " y stride = " << rectangle.yStride << std::endl;
output << " flag = " << rectangle.type << std::endl;
output << " y of xmin = " << rectangle.yOfXMin << std::endl;
output << " y of xmax = " << rectangle.yOfXMax << std::endl;
output << " x of ymin = " << rectangle.xOfYMin << std::endl;
output << " x of ymax = " << rectangle.xOfYMax << std::endl;
output << std::endl;
}
}
// Storage of image data.
int mTwoPowerN, mSize;
T const* mInputPixels;
// Trees for linear merging.
std::vector<std::shared_ptr<LinearMergeTree>> mXMerge, mYMerge;
// Tree for area merging.
std::unique_ptr<AreaMergeTree> mXYMerge;
};
}