|
|
|
|
|
|
|
|
|
|
|
#include "encode_util.h" |
|
|
|
|
|
#include <algorithm> |
|
|
#include <numeric> |
|
|
#include <sstream> |
|
|
|
|
|
#include "../third_party/clipper/clipper.hpp" |
|
|
|
|
|
using namespace std; |
|
|
|
|
|
namespace graph_detection { |
|
|
|
|
|
template<typename T> |
|
|
struct Candidate : Edge { |
|
|
T C; |
|
|
|
|
|
Candidate() = default; |
|
|
Candidate(int32_t a, int32_t b, T c) : Edge(a, b), C(c) {} |
|
|
}; |
|
|
|
|
|
struct DistStruct { |
|
|
Candidate<Pointf> A; |
|
|
Candidate<Pointf> B; |
|
|
float Dist; |
|
|
|
|
|
DistStruct() = default; |
|
|
DistStruct(Candidate<Pointf> a, Candidate<Pointf> b, float dist) : A(a), B(b), Dist(dist) {} |
|
|
}; |
|
|
|
|
|
template<typename T> |
|
|
float vec_cos(const Point_<T> &a, const Point_<T> &b) |
|
|
{ |
|
|
return dot(a, b) / (length(a) * length(b) + 1e-8); |
|
|
} |
|
|
|
|
|
template<typename T, typename Fn = std::less<T>> |
|
|
vector<size_t> arg_sort(const vector<T> &vec, Fn comp = Fn()) |
|
|
{ |
|
|
vector<size_t> ret; |
|
|
ret.reserve(vec.size()); |
|
|
for (size_t i = 0; i < vec.size(); ++i) { |
|
|
ret.push_back(i); |
|
|
} |
|
|
|
|
|
sort(begin(ret), end(ret), |
|
|
[&vec, &comp] (size_t idxA, size_t idxB) { |
|
|
return comp(vec[idxA], vec[idxB]); |
|
|
} |
|
|
); |
|
|
|
|
|
return ret; |
|
|
} |
|
|
|
|
|
|
|
|
float edge_length(const Polygon_<float> &poly, const vector<Edge> &edges); |
|
|
|
|
|
vector<Edge> find_bottom(const Polygon_<float> &poly, bool useVertexOrder) |
|
|
{ |
|
|
if (poly.Count < 4) { |
|
|
throw runtime_error("Invalid polygon. Fewer than 4 vertices!"); |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
if (useVertexOrder) { |
|
|
if ((poly.Count % 2) == 1) { |
|
|
throw runtime_error("Can't use trusted vertex order when the vertex count is odd!"); |
|
|
} |
|
|
int32_t halfCt = poly.Count / 2; |
|
|
return { { halfCt - 1, halfCt }, |
|
|
{ static_cast<int32_t>(poly.Count) - 1, 0 } }; |
|
|
} |
|
|
|
|
|
if (poly.Count == 4) { |
|
|
float d1 = length(poly[1] - poly[0]) + length(poly[2] - poly[3]); |
|
|
float d2 = length(poly[2] - poly[1]) + length(poly[0] - poly[3]); |
|
|
|
|
|
if (4 * d1 < d2) { |
|
|
return { { 0, 1 }, { 2, 3 } }; |
|
|
} else { |
|
|
return { { 1, 2 }, { 3, 0 } }; |
|
|
} |
|
|
} |
|
|
|
|
|
auto idx_wrap = [&poly] (size_t idx) { |
|
|
return poly[idx % poly.Count]; |
|
|
}; |
|
|
|
|
|
vector<Candidate<float>> candidates; |
|
|
for (size_t i = 1; i < (poly.Count + 1); ++i) { |
|
|
auto vPrev = idx_wrap(i) - idx_wrap(i - 1); |
|
|
auto vNext = idx_wrap(i + 2) - idx_wrap(i + 1); |
|
|
|
|
|
|
|
|
|
|
|
if (vec_cos(vPrev, vNext) < -0.875f) { |
|
|
auto currSeg = idx_wrap(i) - idx_wrap(i + 1); |
|
|
candidates.emplace_back(i % poly.Count, (i + 1) % poly.Count, length(currSeg)); |
|
|
} |
|
|
} |
|
|
|
|
|
if (candidates.size() != 2 || candidates[0].A == candidates[1].B || candidates[0].B == candidates[1].A) { |
|
|
|
|
|
vector<Candidate<Pointf>> midList; |
|
|
for (size_t i = 0; i < poly.Count; ++i) { |
|
|
Pointf midPoint = (idx_wrap(i) + idx_wrap(i + 1)) / 2.0f; |
|
|
midList.emplace_back(i, (i + 1) % poly.Count, midPoint); |
|
|
} |
|
|
|
|
|
vector<DistStruct> distList; |
|
|
|
|
|
|
|
|
if (candidates.size() == 1) { |
|
|
auto idx1a = candidates.back().A; |
|
|
auto idx1b = candidates.back().B; |
|
|
Candidate<Pointf> cand1{ idx1a, idx1b, (idx_wrap(idx1a) + idx_wrap(idx1b)) / 2.0f }; |
|
|
for (size_t j = 0; j < poly.Count; ++j) { |
|
|
auto &cand2 = midList[j]; |
|
|
|
|
|
if (cand1.Touches(cand2)) continue; |
|
|
|
|
|
float dist = length(cand1.C - cand2.C); |
|
|
distList.emplace_back(cand1, cand2, dist); |
|
|
} |
|
|
} else { |
|
|
for (size_t i = 0; i < poly.Count; ++i) { |
|
|
for (size_t j = i + 1; j < poly.Count; ++j) { |
|
|
auto &cand1 = midList[i]; |
|
|
auto &cand2 = midList[j]; |
|
|
|
|
|
if (cand1.Touches(cand2)) continue; |
|
|
|
|
|
float dist = length(cand1.C - cand2.C); |
|
|
distList.emplace_back(cand1, cand2, dist); |
|
|
} |
|
|
} |
|
|
} |
|
|
sort(begin(distList), end(distList), [] (auto a, auto b) { return a.Dist < b.Dist; }); |
|
|
|
|
|
if (distList.empty()) { |
|
|
throw runtime_error("No valid bottom candidates found for this polygon!"); |
|
|
} |
|
|
|
|
|
auto &bEdge = distList.back(); |
|
|
return vector<Edge>{ bEdge.A, bEdge.B }; |
|
|
|
|
|
} else { |
|
|
return vector<Edge>{ candidates[0], candidates[1] }; |
|
|
} |
|
|
} |
|
|
|
|
|
void find_long_edges(const Polygon_<float> &poly, Edge *bottoms, vector<Edge> &outLongEdge1, vector<Edge> &outLongEdge2) |
|
|
{ |
|
|
int32_t b1End = bottoms[0].B; |
|
|
int32_t b2End = bottoms[1].B; |
|
|
|
|
|
int32_t nPoints = poly.Count; |
|
|
|
|
|
auto accum_into = [nPoints] (int32_t end1, int32_t end2, vector<Edge> &outEdge) { |
|
|
int32_t i = (end1 + 1) % nPoints; |
|
|
while ((i % nPoints) != end2) { |
|
|
int32_t start = i > 0 ? i - 1 : nPoints - 1; |
|
|
int32_t end = i % nPoints; |
|
|
outEdge.emplace_back(start, end); |
|
|
i = (i + 1) % nPoints; |
|
|
} |
|
|
}; |
|
|
|
|
|
accum_into(b1End, b2End, outLongEdge1); |
|
|
accum_into(b2End, b1End, outLongEdge2); |
|
|
} |
|
|
|
|
|
float edge_length(const Polygon_<float> &poly, const vector<Edge> &edges) |
|
|
{ |
|
|
float ret = 0.0f; |
|
|
for (const Edge &e : edges) { |
|
|
ret += length(poly[e.B] - poly[e.A]); |
|
|
} |
|
|
return ret; |
|
|
} |
|
|
|
|
|
vector<float> edge_lengths(const Polygon_<float> &poly, const vector<Edge> &edges) |
|
|
{ |
|
|
if (edges.empty()) { |
|
|
throw runtime_error("Found an empty edge!"); |
|
|
} |
|
|
|
|
|
vector<float> ret; |
|
|
ret.reserve(edges.size()); |
|
|
|
|
|
for (const Edge &e : edges) { |
|
|
ret.push_back(length(poly[e.B] - poly[e.A])); |
|
|
} |
|
|
|
|
|
return ret; |
|
|
} |
|
|
|
|
|
void split_edge_sequence(const Polygon_<float> &poly, const vector<Edge> &edges, |
|
|
const vector<float> &edgeLengths, float nParts, |
|
|
vector<Pointf> &outPts); |
|
|
|
|
|
void split_edge_sequence_by_step(const Polygon_<float> &poly, const vector<Edge> &longEdge1, const vector<Edge> &longEdge2, |
|
|
float step, vector<Pointf> &outInnerPoints1, vector<Pointf> &outInnerPoints2) |
|
|
{ |
|
|
auto edgeLengths1 = edge_lengths(poly, longEdge1); |
|
|
auto edgeLengths2 = edge_lengths(poly, longEdge2); |
|
|
|
|
|
float totalLength = (accumulate(begin(edgeLengths1), end(edgeLengths1), 0.0f) + accumulate(begin(edgeLengths2), end(edgeLengths2), 0.0f)) / 2; |
|
|
|
|
|
float nParts = max<float>(ceil(totalLength / step), 2); |
|
|
|
|
|
split_edge_sequence(poly, longEdge1, edgeLengths1, nParts, outInnerPoints1); |
|
|
split_edge_sequence(poly, longEdge2, edgeLengths2, nParts, outInnerPoints2); |
|
|
} |
|
|
|
|
|
void split_edge_sequence(const Polygon_<float> &poly, const vector<Edge> &edges, |
|
|
const vector<float> &edgeLengths, float nParts, |
|
|
vector<Pointf> &outPts) |
|
|
{ |
|
|
vector<float> elCumSum = vec_cumsum(edgeLengths); |
|
|
|
|
|
float totalLength = elCumSum.back(); |
|
|
float lengthPerPart = totalLength / (nParts - 1); |
|
|
|
|
|
size_t iNumParts = nParts; |
|
|
size_t currNode = 0; |
|
|
size_t ctr = 0; |
|
|
for (float i = 0.0f; ctr < iNumParts; i += 1.0f, ++ctr) { |
|
|
float t = min(i * lengthPerPart, totalLength); |
|
|
|
|
|
while (t > elCumSum[currNode + 1]) { |
|
|
++currNode; |
|
|
} |
|
|
|
|
|
Edge currEdge = edges[currNode]; |
|
|
Pointf e1 = poly[currEdge.A]; |
|
|
Pointf e2 = poly[currEdge.B]; |
|
|
|
|
|
float currLen = edgeLengths[currNode]; |
|
|
|
|
|
Pointf sampledPt; |
|
|
|
|
|
if (currLen > 0) { |
|
|
float deltaT = t - elCumSum[currNode]; |
|
|
float ratio = deltaT / currLen; |
|
|
sampledPt = e1 + ratio * (e2 - e1); |
|
|
} else { |
|
|
sampledPt = e1; |
|
|
} |
|
|
|
|
|
outPts.push_back(sampledPt); |
|
|
} |
|
|
} |
|
|
|
|
|
string print_poly(const Polyf &poly) { |
|
|
ostringstream oss; |
|
|
oss << "["; |
|
|
for (size_t i = 0; i < poly.Count; ++i) { |
|
|
if (i > 0) { |
|
|
oss << ", "; |
|
|
} |
|
|
oss << "(" << poly[i].X << ", " << poly[i].Y << ")"; |
|
|
} |
|
|
oss << "]"; |
|
|
return oss.str(); |
|
|
} |
|
|
|
|
|
} |
|
|
|