// Copyright Epic Games, Inc. All Rights Reserved. #pragma once #include "VectorTypes.h" #include "SegmentTypes.h" #include "LineTypes.h" #include "BoxTypes.h" namespace UE { namespace Geometry { using namespace UE::Math; /* * TPolylinePolicy represents a collection of needed support classes for TPolyline depending on the required dimension */ template class TPolylinePolicy {}; template class TPolylinePolicy { public: typedef TVector VectorType; typedef TSegment3 SegmentType; typedef TAxisAlignedBox3 BoxType; }; template class TPolylinePolicy { public: typedef TVector2 VectorType; typedef TSegment2 SegmentType; typedef TAxisAlignedBox2 BoxType; }; /** * TPolyline represents a dimensional independent polyline stored as a list of Vertices. */ template class TPolyline { protected: typedef typename TPolylinePolicy::VectorType VectorType; typedef typename TPolylinePolicy::SegmentType SegmentType; typedef typename TPolylinePolicy::BoxType BoxType; /** The list of vertices of the polyline */ TArray Vertices; public: TPolyline() { } /** * Construct polyline with given list of vertices */ TPolyline(const TArray& VertexList) : Vertices(VertexList) { } /** * Construct a single-segment polyline */ TPolyline(const VectorType& Point0, const VectorType& Point1) { Vertices.Add(Point0); Vertices.Add(Point1); } /** * Get the vertex at a given index */ const VectorType& operator[](int Index) const { return Vertices[Index]; } /** * Get the vertex at a given index */ VectorType& operator[](int Index) { return Vertices[Index]; } /** * @return first vertex of polyline */ const VectorType& Start() const { return Vertices[0]; } /** * @return last vertex of polyline */ const VectorType& End() const { return Vertices[Vertices.Num()-1]; } /** * @return list of Vertices of polyline */ const TArray& GetVertices() const { return Vertices; } /** * @return number of Vertices in polyline */ int VertexCount() const { return Vertices.Num(); } /** * @return number of segments in polyline */ int SegmentCount() const { return Vertices.Num()-1; } /** Discard all vertices of polyline */ void Clear() { Vertices.Reset(); } /** * Add a vertex to the polyline */ void AppendVertex(const VectorType& Position) { Vertices.Add(Position); } /** * Add a list of Vertices to the polyline */ void AppendVertices(const TArray& NewVertices) { Vertices.Append(NewVertices); } /** * Add a list of Vertices to the polyline */ template void AppendVertices(const TArray& NewVertices) { int32 NumV = NewVertices.Num(); Vertices.Reserve(Vertices.Num() + NumV); for (int32 k = 0; k < NumV; ++k) { Vertices.Append( (VectorType)NewVertices[k] ); } } /** * Set vertex at given index to a new Position */ void Set(int VertexIndex, const VectorType& Position) { Vertices[VertexIndex] = Position; } /** * Remove a vertex of the polyline (existing Vertices are shifted) */ void RemoveVertex(int VertexIndex) { Vertices.RemoveAt(VertexIndex); } /** * Replace the list of Vertices with a new list */ void SetVertices(const TArray& NewVertices) { int NumVerts = NewVertices.Num(); Vertices.SetNum(NumVerts, EAllowShrinking::No); for (int k = 0; k < NumVerts; ++k) { Vertices[k] = NewVertices[k]; } } /** * Reverse the order of the Vertices in the polyline (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]); } } /** * Get the tangent vector at a vertex of the polyline, which is the normalized * vector from the previous vertex to the next vertex */ VectorType GetTangent(int VertexIndex) const { if (VertexIndex == 0) { return (Vertices[1] - Vertices[0]).Normalized(); } int NumVerts = Vertices.Num(); if (VertexIndex == NumVerts - 1) { return (Vertices[NumVerts-1] - Vertices[NumVerts-2]).Normalized(); } return (Vertices[VertexIndex+1] - Vertices[VertexIndex-1]).Normalized(); } /** * @return edge of the polyline starting at vertex SegmentIndex */ SegmentType GetSegment(int SegmentIndex) const { return SegmentType(Vertices[SegmentIndex], Vertices[SegmentIndex+1]); } /** * @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 */ VectorType GetSegmentPoint(int SegmentIndex, T SegmentParam) const { SegmentType seg(Vertices[SegmentIndex], Vertices[SegmentIndex + 1]); 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 */ VectorType GetSegmentPointUnitParam(int SegmentIndex, T SegmentParam) const { SegmentType seg(Vertices[SegmentIndex], Vertices[SegmentIndex + 1]); return seg.PointBetween(SegmentParam); } /** * @return the bounding box of the polyline Vertices */ BoxType GetBounds() const { BoxType box = BoxType::Empty(); int NumVertices = Vertices.Num(); for (int k = 0; k < NumVertices; ++k) { box.Contain(Vertices[k]); } return box; } /** * @return the total perimeter length of the Polygon */ T Length() const { T length = 0; int N = SegmentCount(); for (int i = 0; i < N; ++i) { length += Distance(Vertices[i], Vertices[i+1]); } return length; } /** * SegmentIterator is used to iterate over the segments of the polyline */ class SegmentIterator { public: inline bool operator!() { return i < Polyline->SegmentCount(); } inline SegmentType operator*() const { check(Polyline != nullptr && i < Polyline->SegmentCount()); return SegmentType(Polyline->Vertices[i], Polyline->Vertices[i+1]); } inline SegmentIterator & operator++() // prefix { i++; return *this; } inline SegmentIterator operator++(int) // postfix { SegmentIterator copy(*this); i++; return copy; } inline bool operator==(const SegmentIterator & i3) { return i3.Polyline == Polyline && i3.i == i; } inline bool operator!=(const SegmentIterator & i3) { return i3.Polyline != Polyline || i3.i != i; } protected: const TPolyline* Polyline; int i; inline SegmentIterator(const TPolyline* p, int iCur) : Polyline(p), i(iCur) {} friend class TPolyline; }; 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 TPolyline* Polyline; SegmentEnumerable() : Polyline(nullptr) {} SegmentEnumerable(const TPolyline * p) : Polyline(p) {} SegmentIterator begin() { return Polyline->SegmentItr(); } SegmentIterator end() { return SegmentIterator(Polyline, Polyline->SegmentCount()); } }; /** * @return an object that can be used in a range-based for loop to iterate over the Segments of the Polyline */ SegmentEnumerable Segments() const { return SegmentEnumerable(this); } /** * Calculate the squared distance from a point to the polyline * @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 polyline */ T DistanceSquared(const VectorType& QueryPoint, int& NearestSegIndexOut, T& NearestSegParamOut) const { NearestSegIndexOut = -1; NearestSegParamOut = TNumericLimits::Max(); T dist = TNumericLimits::Max(); int N = SegmentCount(); for (int vi = 0; vi < N; ++vi) { // @todo can't we just use segment function here now? SegmentType seg = SegmentType(Vertices[vi], Vertices[vi+1]); T t = (QueryPoint - seg.Center).Dot(seg.Direction); T d = TNumericLimits::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 = UE::Geometry::DistanceSquared(seg.PointAt(t), QueryPoint); } if (d < dist) { dist = d; NearestSegIndexOut = vi; NearestSegParamOut = TMathUtil::Clamp(t, -seg.Extent, seg.Extent); } } return dist; } /** * Calculate the squared distance from a point to the polyline * @param QueryPoint the query point * @return squared distance to the polyline */ T DistanceSquared(const VectorType& 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]); } return avg / (T)(N-1); } /** * Compute a point on the polyline based on its distance from the first vertex */ VectorType GetPointFromFirstVertex(const T InDistance) const { if (InDistance <= 0) { return Vertices[0]; } T RemainingDistance = InDistance; const int N = Vertices.Num(); for (int i = 1; i < N; ++i) { const T SegmentLength = Distance(Vertices[i], Vertices[i - 1]); if (SegmentLength > 0 && SegmentLength > RemainingDistance) { return Lerp(Vertices[i - 1], Vertices[i], RemainingDistance / SegmentLength); } RemainingDistance -= SegmentLength; } return Vertices.Last(); } /** * Compute a point on the polyline based on its distance from the last vertex */ VectorType GetPointFromLastVertex(const T InDistance) const { if (InDistance <= 0) { return Vertices.Last(); } T RemainingDistance = InDistance; const int N = Vertices.Num(); for (int i = N-1; i > 0; --i) { const T SegmentLength = Distance(Vertices[i], Vertices[i - 1]); if (SegmentLength > 0 && SegmentLength > RemainingDistance) { return Lerp(Vertices[i], Vertices[i-1], RemainingDistance / SegmentLength); } RemainingDistance -= SegmentLength; } return Vertices[0]; } /** * Produce a new polyline that is smoother than this one */ void SmoothSubdivide(TPolyline& NewPolyline) const { const T Alpha = (T)1 / (T)3; const T OneMinusAlpha = (T)2 / (T)3; int N = Vertices.Num() - 1; NewPolyline.Vertices.SetNum(2*N); NewPolyline.Vertices[0] = Vertices[0]; int k = 1; for (int i = 1; i < N; ++i) { const VectorType& Prev = Vertices[i-1]; const VectorType& Cur = Vertices[i]; const VectorType& Next = Vertices[i+1]; NewPolyline.Vertices[k++] = Alpha * Prev + OneMinusAlpha * Cur; NewPolyline.Vertices[k++] = OneMinusAlpha * Cur + Alpha * Next; } NewPolyline.Vertices[k] = Vertices[N]; } /** * Simplify the Polyline 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 polylines */ void Simplify(T ClusterTolerance, T LineDeviationTolerance) { const int32 VertexCount = Vertices.Num(); if (VertexCount < 3) { // we need at least 3 vertices to be able to simplify a line return; } TArray NewVertices; NewVertices.Reserve(VertexCount); // STAGE 1. Vertex Reduction within tolerance of prior vertex cluster if (ClusterTolerance > 0) { T ClusterToleranceSquared = ClusterTolerance * ClusterTolerance; NewVertices.Add(Vertices[0]); // keep the first vertex for (int32 Index = 1; Index < VertexCount-1; Index++) { if (Geometry::DistanceSquared(Vertices[Index], NewVertices.Last()) < ClusterToleranceSquared) { continue; } NewVertices.Add(Vertices[Index]); } NewVertices.Add(Vertices.Last()); // keep the last vertex } else { NewVertices = Vertices; } // STAGE 2. Douglas-Peucker polyline simplification TArray Marked; if (LineDeviationTolerance > 0 && NewVertices.Num() >= 3) { Marked.SetNumZeroed(NewVertices.Num()); // mark the first and last vertices to make sure we keep them Marked[0] = true; Marked.Last() = true; SimplifyDouglasPeucker(LineDeviationTolerance, NewVertices, 0, NewVertices.Num()-1, Marked); } // STAGE 3. Copy back values in Vertices if (Marked.IsEmpty()) { // we have not run Douglas-Pecker so we can just copy the list Vertices = MoveTemp(NewVertices); } else { // only keep the marked ones Vertices.Reset(); const int32 MarkedCount = Marked.Num(); for (int32 Index=0; Index& Vertices, int32 j, int32 k, TArray& 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 SegmentType S = SegmentType(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; } }; } // end namespace UE::Geometry } // end namespace UE