Files
UnrealEngineUWP/Engine/Source/Runtime/GeometryCore/Public/Polygon2.h

1165 lines
28 KiB
C++

// Copyright Epic Games, Inc. All Rights Reserved.
// port of geometry3Sharp Polygon
#pragma once
#include "Templates/UnrealTemplate.h"
#include "Math/UnrealMath.h"
#include "VectorTypes.h"
#include "BoxTypes.h"
#include "SegmentTypes.h"
#include "LineTypes.h"
#include "MathUtil.h"
#include "Intersection/IntrSegment2Segment2.h"
#include "Util/DynamicVector.h"
namespace UE
{
namespace Geometry
{
using namespace UE::Math;
/**
* TPolygon2 is a 2D polygon represented as a list of Vertices.
*
* @todo move operators
*/
template<typename T>
class TPolygon2
{
protected:
/** The list of vertices/corners of the polygon */
TArray<TVector2<T>> Vertices;
/** A counter that is incremented every time the polygon vertices are modified */
int Timestamp;
public:
TPolygon2() : Timestamp(0)
{
}
/**
* Construct polygon that is a copy of another polygon
*/
TPolygon2(const TPolygon2& Copy) : Vertices(Copy.Vertices), Timestamp(Copy.Timestamp)
{
}
/**
* Construct polygon with given list of vertices
*/
TPolygon2(const TArray<TVector2<T>>& VertexList) : Vertices(VertexList), Timestamp(0)
{
}
template<typename OtherVertexType>
TPolygon2(const TArray<OtherVertexType>& VertexList) : Timestamp(0)
{
Vertices.Reserve(VertexList.Num());
for (const OtherVertexType& OtherVtx : VertexList)
{
Vertices.Add( TVector2<T>(OtherVtx.X, OtherVtx.Y) );
}
}
/**
* Construct polygon with given indices into a vertex array
*/
TPolygon2(TArrayView<const TVector2<T>> VertexArray, TArrayView<const int32> VertexIndices) : Timestamp(0)
{
Vertices.SetNum(VertexIndices.Num());
for (int32 Idx = 0; Idx < VertexIndices.Num(); Idx++)
{
Vertices[Idx] = VertexArray[VertexIndices[Idx]];
}
}
/** @return the Timestamp for the polygon, which is updated every time the polygon is modified */
int GetTimestamp() const
{
return Timestamp;
}
/**
* Get the vertex at a given index
*/
const TVector2<T>& operator[](int Index) const
{
return Vertices[Index];
}
/**
* Get the vertex at a given index
* @warning changing the vertex via this operator does not update Timestamp!
*/
TVector2<T>& operator[](int Index)
{
return Vertices[Index];
}
/**
* @return first vertex of Polygon
*/
const TVector2<T>& Start() const
{
return Vertices[0];
}
/**
* @return list of Vertices of Polygon
*/
const TArray<TVector2<T>>& GetVertices() const
{
return Vertices;
}
/**
* @return number of Vertices in Polygon
*/
int VertexCount() const
{
return Vertices.Num();
}
/**
* Add a vertex to the Polygon
*/
void AppendVertex(const TVector2<T>& Position)
{
Vertices.Add(Position);
Timestamp++;
}
/**
* Add a list of Vertices to the Polygon
*/
void AppendVertices(const TArray<TVector2<T>>& NewVertices)
{
Vertices.Append(NewVertices);
Timestamp++;
}
/**
* Set vertex at given index to a new Position
*/
void Set(int VertexIndex, const TVector2<T>& Position)
{
Vertices[VertexIndex] = Position;
Timestamp++;
}
/**
* Remove a vertex of the Polygon (existing Vertices are shifted)
*/
void RemoveVertex(int VertexIndex)
{
Vertices.RemoveAt(VertexIndex);
Timestamp++;
}
/**
* Replace the list of Vertices with a new list
*/
void SetVertices(const TArray<TVector2<T>>& NewVertices)
{
Vertices = NewVertices;
Timestamp++;
}
/**
* Reverse the order of the Vertices in the Polygon (ie switch between Clockwise and CounterClockwise)
*/
void Reverse()
{
int32 j = Vertices.Num()-1;
for (int32 VertexIndex = 0; VertexIndex < j; VertexIndex++, j--)
{
Swap(Vertices[VertexIndex], Vertices[j]);
}
Timestamp++;
}
/**
* Get the tangent vector at a vertex of the polygon, which is the normalized
* vector from the previous vertex to the next vertex
*/
TVector2<T> GetTangent(int VertexIndex) const
{
TVector2<T> next = Vertices[(VertexIndex + 1) % Vertices.Num()];
TVector2<T> prev = Vertices[VertexIndex == 0 ? Vertices.Num() - 1 : VertexIndex - 1];
return Normalized(next - prev);
}
/**
* Get the normal vector at a vertex of the polygon, which is perpendicular to GetTangent()
* Points "inward" for a Clockwise Polygon, and outward for CounterClockwise
*/
TVector2<T> GetNormal(int VertexIndex) const
{
return PerpCW(GetTangent(VertexIndex));
}
/**
* Construct a normal at a vertex of the Polygon by averaging the adjacent face normals.
* This vector is independent of the lengths of the adjacent segments.
* Points "inward" for a Clockwise Polygon, and outward for CounterClockwise
*/
TVector2<T> GetNormal_FaceAvg(int VertexIndex) const
{
TVector2<T> next = Vertices[(VertexIndex + 1) % Vertices.Num()];
TVector2<T> prev = Vertices[VertexIndex == 0 ? Vertices.Num() - 1 : VertexIndex - 1];
next -= Vertices[VertexIndex]; Normalize(next);
prev -= Vertices[VertexIndex]; Normalize(prev);
TVector2<T> n = (PerpCW(next) - PerpCW(prev));
T len = Normalize(n);
if (len == 0)
{
return Normalized(next + prev); // this gives right direction for degenerate angle
}
else
{
return n;
}
}
/**
* @return the bounding box of the Polygon Vertices
*/
TAxisAlignedBox2<T> Bounds() const
{
TAxisAlignedBox2<T> box = TAxisAlignedBox2<T>::Empty();
box.Contain(Vertices);
return box;
}
/**
* SegmentIterator is used to iterate over the TSegment2<T> segments of the polygon
*/
class SegmentIterator
{
public:
inline bool operator!()
{
return i < polygon->VertexCount();
}
inline TSegment2<T> operator*() const
{
check(polygon != nullptr && i < polygon->VertexCount());
return TSegment2<T>(polygon->Vertices[i], polygon->Vertices[(i+1) % polygon->VertexCount()]);
}
//inline TSegment2<T> & operator*();
inline SegmentIterator & operator++() // prefix
{
i++;
return *this;
}
inline SegmentIterator operator++(int) // postfix
{
SegmentIterator copy(*this);
i++;
return copy;
}
inline bool operator==(const SegmentIterator & i2) { return i2.polygon == polygon && i2.i == i; }
inline bool operator!=(const SegmentIterator & i2) { return i2.polygon != polygon || i2.i != i; }
protected:
const TPolygon2 * polygon;
int i;
inline SegmentIterator(const TPolygon2 * p, int iCur) : polygon(p), i(iCur) {}
friend class TPolygon2;
};
friend class SegmentIterator;
SegmentIterator SegmentItr() const
{
return SegmentIterator(this, 0);
}
/**
* Wrapper around SegmentIterator that has begin() and end() suitable for range-based for loop
*/
class SegmentEnumerable
{
public:
const TPolygon2<T>* polygon;
SegmentEnumerable() : polygon(nullptr) {}
SegmentEnumerable(const TPolygon2<T> * p) : polygon(p) {}
SegmentIterator begin() { return polygon->SegmentItr(); }
SegmentIterator end() { return SegmentIterator(polygon, polygon->VertexCount()); }
};
/**
* @return an object that can be used in a range-based for loop to iterate over the Segments of the Polygon
*/
SegmentEnumerable Segments() const
{
return SegmentEnumerable(this);
}
/**
* @return true if the Polygon Vertices have Clockwise winding order / orientation (signed area is negative)
*/
bool IsClockwise() const
{
return SignedArea() < 0;
}
/**
* @return the signed area of the Polygon
*/
T SignedArea() const
{
T fArea = 0;
int N = Vertices.Num();
if (N == 0)
{
return 0;
}
for (int i = 0; i < N; ++i)
{
const TVector2<T>& v1 = Vertices[i];
const TVector2<T>& v2 = Vertices[(i + 1) % N];
fArea += v1.X * v2.Y - v1.Y * v2.X;
}
return fArea * 0.5;
}
/**
* @return the unsigned area of the Polygon
*/
T Area() const
{
return TMathUtil<T>::Abs(SignedArea());
}
/**
* @return the total perimeter length of the Polygon
*/
T Perimeter() const
{
T fPerim = 0;
int N = Vertices.Num();
for (int i = 0; i < N; ++i)
{
fPerim += Distance(Vertices[i], Vertices[(i + 1) % N]);
}
return fPerim;
}
/**
* Get the previous and next vertex positions for a given vertex of the Polygon
*/
void NeighbourPoints(int iVertex, TVector2<T> &PrevNbrOut, TVector2<T> &NextNbrOut) const
{
int N = Vertices.Num();
PrevNbrOut = Vertices[(iVertex == 0) ? N - 1 : iVertex - 1];
NextNbrOut = Vertices[(iVertex + 1) % N];
}
/**
* Get the vectors from a given vertex to the previous and next Vertices, optionally normalized
*/
void NeighbourVectors(int iVertex, TVector2<T> &ToPrevOut, TVector2<T> &ToNextOut, bool bNormalize = false) const
{
int N = Vertices.Num();
ToPrevOut = Vertices[(iVertex == 0) ? N - 1 : iVertex - 1] - Vertices[iVertex];
ToNextOut = Vertices[(iVertex + 1) % N] - Vertices[iVertex];
if (bNormalize)
{
Normalize(ToPrevOut);
Normalize(ToNextOut);
}
}
/**
* @return the opening angle in degrees at a vertex of the Polygon
*/
T OpeningAngleDeg(int iVertex) const
{
TVector2<T> e0, e1;
NeighbourVectors(iVertex, e0, e1, true);
return AngleD(e0, e1);
}
/**
* @return analytic winding integral for this Polygon at an arbitrary point
*/
T WindingIntegral(const TVector2<T>& QueryPoint) const
{
T sum = 0;
int N = Vertices.Num();
TVector2<T> a = Vertices[0] - QueryPoint, b = TVector2<T>::Zero();
for (int i = 0; i < N; ++i)
{
b = Vertices[(i + 1) % N] - QueryPoint;
sum += TMathUtil<T>::Atan2(a.X * b.Y - a.Y * b.X, a.X * b.X + a.Y * b.Y);
a = b;
}
return sum / FMathd::TwoPi;
}
/**
* @return true if the given query point is inside the Polygon, based on the winding integral
*/
bool Contains(const TVector2<T>& QueryPoint) const
{
int nWindingNumber = 0;
int N = Vertices.Num();
if (N == 0)
{
return false;
}
TVector2<T> a = Vertices[0], b = TVector2<T>::Zero();
for (int i = 0; i < N; ++i)
{
b = Vertices[(i + 1) % N];
if (a.Y <= QueryPoint.Y) // y <= P.Y (below)
{
if (b.Y > QueryPoint.Y) // an upward crossing
{
if (Orient(a, b, QueryPoint) > 0) // P left of edge
++nWindingNumber; // have a valid up intersect
}
}
else // y > P.Y (above)
{
if (b.Y <= QueryPoint.Y) // a downward crossing
{
if (Orient(a, b, QueryPoint) < 0) // P right of edge
{
--nWindingNumber; // have a valid down intersect
}
}
}
a = b;
}
return nWindingNumber != 0;
}
/**
* Check for polygon overlap, aka solid intersection. (In contrast, note that the "Intersects" method checks for edge intersection)
*
* @return true if the Polygon overlaps the OtherPolygon.
*/
bool Overlaps(const TPolygon2<T>& OtherPoly) const
{
if (!Bounds().Intersects(OtherPoly.Bounds()))
{
return false;
}
for (int i = 0, N = OtherPoly.VertexCount(); i < N; ++i)
{
if (Contains(OtherPoly[i]))
{
return true;
}
}
for (int i = 0, N = VertexCount(); i < N; ++i)
{
if (OtherPoly.Contains(Vertices[i]))
{
return true;
}
}
for (TSegment2<T> seg : Segments())
{
for (TSegment2<T> oseg : OtherPoly.Segments())
{
if (seg.Intersects(oseg))
{
return true;
}
}
}
return false;
}
/**
* @return true if the Polygon fully contains the OtherPolygon
*/
bool Contains(const TPolygon2<T>& OtherPoly) const
{
// @todo fast bbox check?
int N = OtherPoly.VertexCount();
for (int i = 0; i < N; ++i)
{
if (Contains(OtherPoly[i]) == false)
{
return false;
}
}
if (Intersects(OtherPoly))
{
return false;
}
return true;
}
/**
* @return true if the Segment is fully contained inside the Polygon
*/
bool Contains(const TSegment2<T>& Segment) const
{
// [TODO] Add bbox check
if (Contains(Segment.StartPoint()) == false || Contains(Segment.EndPoint()) == false)
{
return false;
}
for (TSegment2<T> seg : Segments())
{
if (seg.Intersects(Segment))
{
return false;
}
}
return true;
}
/**
* @return true if at least one edge of the OtherPolygon intersects the Polygon
*/
bool Intersects(const TPolygon2<T>& OtherPoly) const
{
if (!Bounds().Intersects(OtherPoly.Bounds()))
{
return false;
}
for (TSegment2<T> seg : Segments())
{
for (TSegment2<T> oseg : OtherPoly.Segments())
{
if (seg.Intersects(oseg))
{
return true;
}
}
}
return false;
}
/**
* @return true if the Segment intersects an edge of the Polygon
*/
bool Intersects(const TSegment2<T>& Segment) const
{
// [TODO] Add bbox check
if (Contains(Segment.StartPoint()) == true || Contains(Segment.EndPoint()) == true)
{
return true;
}
// [TODO] Add bbox check
for (TSegment2<T> seg : Segments())
{
if (seg.Intersects(Segment))
{
return true;
}
}
return false;
}
/**
* Find all the points where an edge of the Polygon intersects an edge of the OtherPolygon
* @param OtherPoly polygon to test against
* @param OutArray intersection points are stored here
* @return true if any intersections were found
*/
bool FindIntersections(const TPolygon2<T>& OtherPoly, TArray<TVector2<T>>& OutArray) const
{
if (!Bounds().Intersects(OtherPoly.Bounds()))
{
return false;
}
bool bFoundIntersections = false;
for (TSegment2<T> seg : Segments())
{
for (TSegment2<T> oseg : OtherPoly.Segments())
{
// this computes test twice for intersections, but seg.intersects doesn't
// create any new objects so it should be much faster for majority of segments (should profile!)
if (seg.Intersects(oseg))
{
//@todo can we replace with something like seg.intersects?
TIntrSegment2Segment2<T> intr(seg, oseg);
if (intr.Find())
{
bFoundIntersections = true;
OutArray.Add(intr.Point0);
if (intr.Quantity == 2)
{
OutArray.Add(intr.Point1);
}
}
}
}
}
return bFoundIntersections;
}
/**
* @return edge of the polygon starting at vertex SegmentIndex
*/
TSegment2<T> Segment(int SegmentIndex) const
{
return TSegment2<T>(Vertices[SegmentIndex], Vertices[(SegmentIndex + 1) % Vertices.Num()]);
}
/**
* @param SegmentIndex index of first vertex of the edge
* @param SegmentParam parameter in range [-Extent,Extent] along segment
* @return point on the segment at the given parameter value
*/
TVector2<T> GetSegmentPoint(int SegmentIndex, T SegmentParam) const
{
TSegment2<T> seg(Vertices[SegmentIndex], Vertices[(SegmentIndex + 1) % Vertices.Num()]);
return seg.PointAt(SegmentParam);
}
/**
* @param SegmentIndex index of first vertex of the edge
* @param SegmentParam parameter in range [0,1] along segment
* @return point on the segment at the given parameter value
*/
TVector2<T> GetSegmentPointUnitParam(int SegmentIndex, T SegmentParam) const
{
TSegment2<T> seg(Vertices[SegmentIndex], Vertices[(SegmentIndex + 1) % Vertices.Num()]);
return seg.PointBetween(SegmentParam);
}
/**
* @param SegmentIndex index of first vertex of the edge
* @param SegmentParam parameter in range [0,1] along segment
* @return interpolated normal to the segment at the given parameter value
*/
TVector2<T> GetNormal(int iSeg, T SegmentParam) const
{
TSegment2<T> seg(Vertices[iSeg], Vertices[(iSeg + 1) % Vertices.Num()]);
T t = ((SegmentParam / seg.Extent) + 1.0) / 2.0;
TVector2<T> n0 = GetNormal(iSeg);
TVector2<T> n1 = GetNormal((iSeg + 1) % Vertices.Num());
return Normalized((T(1) - t) * n0 + t * n1);
}
/**
* Calculate the squared distance from a point to the polygon
* @param QueryPoint the query point
* @param NearestSegIndexOut The index of the nearest segment
* @param NearestSegParamOut the parameter value of the nearest point on the segment
* @return squared distance to the polygon
*/
T DistanceSquared(const TVector2<T>& QueryPoint, int& NearestSegIndexOut, T& NearestSegParamOut) const
{
NearestSegIndexOut = -1;
NearestSegParamOut = TNumericLimits<T>::Max();
T dist = TNumericLimits<T>::Max();
int N = Vertices.Num();
for (int vi = 0; vi < N; ++vi)
{
// @todo can't we just use segment function here now?
TSegment2<T> seg = TSegment2<T>(Vertices[vi], Vertices[(vi + 1) % N]);
T t = (QueryPoint - seg.Center).Dot(seg.Direction);
T d = TNumericLimits<T>::Max();
if (t >= seg.Extent)
{
d = UE::Geometry::DistanceSquared(seg.EndPoint(), QueryPoint);
}
else if (t <= -seg.Extent)
{
d = UE::Geometry::DistanceSquared(seg.StartPoint(), QueryPoint);
}
else
{
d = (seg.PointAt(t) - QueryPoint).SquaredLength();
}
if (d < dist)
{
dist = d;
NearestSegIndexOut = vi;
NearestSegParamOut = TMathUtil<T>::Clamp(t, -seg.Extent, seg.Extent);
}
}
return dist;
}
/**
* Calculate the squared distance from a point to the polygon
* @param QueryPoint the query point
* @return squared distance to the polygon
*/
T DistanceSquared(const TVector2<T>& QueryPoint) const
{
int seg; T segt;
return DistanceSquared(QueryPoint, seg, segt);
}
/**
* @return average edge length of all the edges of the Polygon
*/
T AverageEdgeLength() const
{
T avg = 0; int N = Vertices.Num();
for (int i = 1; i < N; ++i) {
avg += Distance(Vertices[i], Vertices[i - 1]);
}
avg += Distance(Vertices[N - 1], Vertices[0]);
return avg / N;
}
/**
* Translate the polygon
* @returns the Polygon, so that you can chain calls like Translate().Scale()
*/
TPolygon2<T>& Translate(const TVector2<T>& Translate)
{
int N = Vertices.Num();
for (int i = 0; i < N; ++i)
{
Vertices[i] += Translate;
}
Timestamp++;
return *this;
}
/**
* Scale the Polygon relative to a given point
* @returns the Polygon, so that you can chain calls like Translate().Scale()
*/
TPolygon2<T>& Scale(const TVector2<T>& Scale, const TVector2<T>& Origin)
{
int N = Vertices.Num();
for (int i = 0; i < N; ++i)
{
Vertices[i] = Scale * (Vertices[i] - Origin) + Origin;
}
Timestamp++;
return *this;
}
/**
* Apply an arbitrary transformation to the Polygon
* @returns the Polygon, so that you can chain calls like Translate().Scale()
*/
TPolygon2<T>& Transform(const TFunction<TVector2<T> (const TVector2<T>&)>& TransformFunc)
{
int N = Vertices.Num();
for (int i = 0; i < N; ++i)
{
Vertices[i] = TransformFunc(Vertices[i]);
}
Timestamp++;
return *this;
}
/**
* Offset each point by the given Distance along vertex "normal" direction
* @param OffsetDistance the distance to offset
* @param bUseFaceAvg if true, we offset by the average-face normal instead of the perpendicular-tangent normal
*/
void VtxNormalOffset(T OffsetDistance, bool bUseFaceAvg = false)
{
TArray<TVector2<T>> NewVertices;
NewVertices.SetNumUninitialized(Vertices.Num());
if (bUseFaceAvg)
{
for (int k = 0; k < Vertices.Num(); ++k)
{
NewVertices[k] = Vertices[k] + OffsetDistance * GetNormal_FaceAvg(k);
}
}
else
{
for (int k = 0; k < Vertices.Num(); ++k)
{
NewVertices[k] = Vertices[k] + OffsetDistance * GetNormal(k);
}
}
for (int k = 0; k < Vertices.Num(); ++k)
{
Vertices[k] = NewVertices[k];
}
Timestamp++;
}
/**
* Offset polygon by fixed distance, by offsetting and intersecting edges.
* CounterClockWise Polygon offsets "outwards", ClockWise "inwards".
*/
void PolyOffset(T OffsetDistance)
{
// [TODO] possibly can do with half as many normalizes if we do w/ sequential edges,
// rather than centering on each v?
TArray<TVector2<T>> NewVertices;
NewVertices.SetNumUninitialized(Vertices.Num());
for (int k = 0; k < Vertices.Num(); ++k)
{
TVector2<T> v = Vertices[k];
TVector2<T> next = Vertices[(k + 1) % Vertices.Num()];
TVector2<T> prev = Vertices[k == 0 ? Vertices.Num() - 1 : k - 1];
TVector2<T> dn = Normalized(next - v);
TVector2<T> dp = Normalized(prev - v);
TLine2<T> ln(v + OffsetDistance * PerpCW(dn), dn);
TLine2<T> lp(v - OffsetDistance * PerpCW(dp), dp);
bool bIntersectsAtPoint = ln.IntersectionPoint(lp, NewVertices[k]);
if (!bIntersectsAtPoint) // lines were parallel
{
NewVertices[k] = Vertices[k] + OffsetDistance * GetNormal_FaceAvg(k);
}
}
for (int k = 0; k < Vertices.Num(); ++k)
{
Vertices[k] = NewVertices[k];
}
Timestamp++;
}
private:
// Polygon simplification
// code adapted from: http://softsurfer.com/Archive/algorithm_0205/algorithm_0205.htm
// simplifyDP():
// This is the Douglas-Peucker recursive simplification routine
// It just marks Vertices that are part of the simplified polyline
// for approximating the polyline subchain v[j] to v[k].
// Input: tol = approximation tolerance
// v[] = polyline array of vertex points
// j,k = indices for the subchain v[j] to v[k]
// Output: mk[] = array of markers matching vertex array v[]
static void SimplifyDouglasPeucker(T Tolerance, const TArray<TVector2<T>>& Vertices, int j, int k, TArray<bool>& Marked)
{
Marked.SetNum(Vertices.Num());
if (k <= j + 1) // there is nothing to simplify
return;
// check for adequate approximation by segment S from v[j] to v[k]
int maxi = j; // index of vertex farthest from S
T maxd2 = 0; // distance squared of farthest vertex
T tol2 = Tolerance * Tolerance; // tolerance squared
TSegment2<T> S = TSegment2<T>(Vertices[j], Vertices[k]); // segment from v[j] to v[k]
// test each vertex v[i] for max distance from S
// Note: this works in any dimension (2D, 3D, ...)
for (int i = j + 1; i < k; i++)
{
T dv2 = S.DistanceSquared(Vertices[i]);
if (dv2 <= maxd2)
continue;
// v[i] is a max vertex
maxi = i;
maxd2 = dv2;
}
if (maxd2 > tol2) // error is worse than the tolerance
{
// split the polyline at the farthest vertex from S
Marked[maxi] = true; // mark v[maxi] for the simplified polyline
// recursively simplify the two subpolylines at v[maxi]
SimplifyDouglasPeucker(Tolerance, Vertices, j, maxi, Marked); // polyline v[j] to v[maxi]
SimplifyDouglasPeucker(Tolerance, Vertices, maxi, k, Marked); // polyline v[maxi] to v[k]
}
// else the approximation is OK, so ignore intermediate Vertices
return;
}
public:
/**
* Simplify the Polygon to reduce the vertex count
* @param ClusterTolerance Vertices closer than this distance will be merged into a single vertex
* @param LineDeviationTolerance Vertices are allowed to deviate this much from the input polygon lines
*/
void Simplify(T ClusterTolerance = 0.0001, T LineDeviationTolerance = 0.01)
{
int n = Vertices.Num();
if (n < 3)
{
return;
}
int i, k, pv; // misc counters
TArray<TVector2<T>> NewVertices;
NewVertices.SetNumUninitialized(n + 1); // vertex buffer
TArray<bool> Marked;
Marked.SetNumUninitialized(n + 1);
for (i = 0; i < n + 1; ++i) // marker buffer
{
Marked[i] = false;
}
// STAGE 1. Vertex Reduction within tolerance of prior vertex cluster
T clusterTol2 = ClusterTolerance * ClusterTolerance;
NewVertices[0] = Vertices[0]; // start at the beginning
for (i = 1, k = 1, pv = 0; i < n; i++)
{
if ( UE::Geometry::DistanceSquared(Vertices[i], Vertices[pv]) < clusterTol2)
{
continue;
}
NewVertices[k++] = Vertices[i];
pv = i;
}
bool skip_dp = false;
if (k == 1)
{
NewVertices[k++] = Vertices[1];
NewVertices[k++] = Vertices[2];
skip_dp = true;
}
else if (k == 2)
{
NewVertices[k++] = Vertices[0];
skip_dp = true;
}
// push on start vertex again, because simplifyDP is for polylines, not polygons
NewVertices[k++] = Vertices[0];
// STAGE 2. Douglas-Peucker polyline simplification
int nv = 0;
if (skip_dp == false && LineDeviationTolerance > 0)
{
Marked[0] = Marked[k - 1] = true; // mark the first and last Vertices
SimplifyDouglasPeucker(LineDeviationTolerance, NewVertices, 0, k - 1, Marked);
for (i = 0; i < k - 1; ++i)
{
if (Marked[i])
{
nv++;
}
}
}
else
{
for (i = 0; i < k; ++i)
{
Marked[i] = true;
}
nv = k - 1;
}
// polygon requires at least 3 Vertices
if (nv == 2)
{
for (i = 1; i < k - 1; ++i)
{
if (Marked[1] == false)
{
Marked[1] = true;
}
else if (Marked[k - 2] == false)
{
Marked[k - 2] = true;
}
}
nv++;
}
else if (nv == 1)
{
Marked[1] = true;
Marked[2] = true;
nv += 2;
}
// copy marked Vertices back to this polygon
Vertices.Reset();
for (i = 0; i < k - 1; ++i) // last vtx is copy of first, and definitely marked
{
if (Marked[i])
{
Vertices.Add(NewVertices[i]);
}
}
Timestamp++;
return;
}
/**
* Chamfer each vertex corner of the Polygon
* @param ChamferDist offset distance from corner that we cut at
*/
void Chamfer(T ChamferDist, T MinConvexAngleDeg = 30, T MinConcaveAngleDeg = 30)
{
check(IsClockwise());
TArray<TVector2<T>> OldV = Vertices;
int N = OldV.Num();
Vertices.Reset();
int iCur = 0;
do {
TVector2<T> center = OldV[iCur];
int iPrev = (iCur == 0) ? N - 1 : iCur - 1;
TVector2<T> prev = OldV[iPrev];
int iNext = (iCur + 1) % N;
TVector2<T> next = OldV[iNext];
TVector2<T> cp = prev - center;
T cpdist = Normalize(cp);
TVector2<T> cn = next - center;
T cndist = Normalize(cn);
// if degenerate, skip this vert
if (cpdist < TMathUtil<T>::ZeroTolerance || cndist < TMathUtil<T>::ZeroTolerance)
{
iCur = iNext;
continue;
}
T angle = AngleD(cp, cn);
// TODO document what this means sign-wise
// TODO re-test post Unreal port that this DotPerp is doing the right thing
T sign = DotPerp(cn, cp);
bool bConcave = (sign > 0);
T thresh = (bConcave) ? MinConcaveAngleDeg : MinConvexAngleDeg;
// ok not too sharp
if (angle > thresh)
{
Vertices.Add(center);
iCur = iNext;
continue;
}
T prev_cut_dist = TMathUtil<T>::Min(ChamferDist, cpdist*0.5);
TVector2<T> prev_cut = center + cp * prev_cut_dist;
T next_cut_dist = TMathUtil<T>::Min(ChamferDist, cndist * 0.5);
TVector2<T> next_cut = center + cn * next_cut_dist;
Vertices.Add(prev_cut);
Vertices.Add(next_cut);
iCur = iNext;
} while (iCur != 0);
Timestamp++;
}
/**
* Construct a four-vertex rectangle polygon
*/
static TPolygon2<T> MakeRectangle(const TVector2<T>& Center, T Width, T Height)
{
TPolygon2<T> Rectangle;
Rectangle.Vertices.SetNumUninitialized(4);
Rectangle.Set(0, TVector2<T>(Center.X - Width / 2, Center.Y - Height / 2));
Rectangle.Set(1, TVector2<T>(Center.X + Width / 2, Center.Y - Height / 2));
Rectangle.Set(2, TVector2<T>(Center.X + Width / 2, Center.Y + Height / 2));
Rectangle.Set(3, TVector2<T>(Center.X - Width / 2, Center.Y + Height / 2));
return Rectangle;
}
/**
* Construct a rounded rectangle polygon
*/
static TPolygon2<T> MakeRoundedRectangle(const TVector2<T>& Center, T Width, T Height, T Corner, int CornerSteps)
{
TPolygon2<T> RRectangle;
CornerSteps = FMath::Max(2, CornerSteps);
RRectangle.Vertices.SetNumUninitialized(CornerSteps * 4);
TVector2<T> InnerExtents(Width*.5 - Corner, Height*.5 - Corner);
TVector2<T> InnerCorners[4]
{
Center - InnerExtents,
TVector2<T>(Center.X + InnerExtents.X, Center.Y - InnerExtents.Y),
Center + InnerExtents,
TVector2<T>(Center.X - InnerExtents.X, Center.Y + InnerExtents.Y),
};
T IdxToAng = TMathUtil<T>::HalfPi / T(CornerSteps - 1);
for (int StepIdx = 0; StepIdx < CornerSteps; StepIdx++)
{
T Angle = StepIdx * IdxToAng;
T OffC = TMathUtil<T>::Cos(Angle) * Corner;
T OffS = TMathUtil<T>::Sin(Angle) * Corner;
RRectangle.Set(StepIdx + CornerSteps * 0, InnerCorners[0] + TVector2<T>(-OffC, -OffS));
RRectangle.Set(StepIdx + CornerSteps * 1, InnerCorners[1] + TVector2<T>(OffS, -OffC));
RRectangle.Set(StepIdx + CornerSteps * 2, InnerCorners[2] + TVector2<T>(OffC, OffS));
RRectangle.Set(StepIdx + CornerSteps * 3, InnerCorners[3] + TVector2<T>(-OffS, OffC));
}
return RRectangle;
}
/**
* Construct a circular polygon
*/
static TPolygon2<T> MakeCircle(T Radius, int Steps, T AngleShiftRadians = 0)
{
TPolygon2<T> Circle;
Circle.Vertices.SetNumUninitialized(Steps);
for (int i = 0; i < Steps; ++i)
{
T t = (T)i / (T)Steps;
T a = TMathUtil<T>::TwoPi * t + AngleShiftRadians;
Circle.Set(i, TVector2<T>(Radius * TMathUtil<T>::Cos(a), Radius * TMathUtil<T>::Sin(a)));
}
return Circle;
}
};
typedef TPolygon2<double> FPolygon2d;
typedef TPolygon2<float> FPolygon2f;
} // end namespace UE::Geometry
} // end namespace UE