![Negin Soltani](/assets/img/avatar_default.png)
- Globe Asset - Spatial Anchors - Photon Implementation - Scripts for Globe Control and Initial Country Colorizing - Script for Reading csv file
1623 lines
58 KiB
C#
1623 lines
58 KiB
C#
/* Poly2Tri
|
|
* Copyright (c) 2009-2010, Poly2Tri Contributors
|
|
* http://code.google.com/p/poly2tri/
|
|
*
|
|
* All rights reserved.
|
|
*
|
|
* Redistribution and use in source and binary forms, with or without modification,
|
|
* are permitted provided that the following conditions are met:
|
|
*
|
|
* * Redistributions of source code must retain the above copyright notice,
|
|
* this list of conditions and the following disclaimer.
|
|
* * Redistributions in binary form must reproduce the above copyright notice,
|
|
* this list of conditions and the following disclaimer in the documentation
|
|
* and/or other materials provided with the distribution.
|
|
* * Neither the name of Poly2Tri nor the names of its contributors may be
|
|
* used to endorse or promote products derived from this software without specific
|
|
* prior written permission.
|
|
*
|
|
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
|
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
|
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
|
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
|
|
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
|
|
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
|
|
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
|
|
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
|
|
* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
|
|
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
|
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
*/
|
|
|
|
|
|
/*
|
|
* The Following notice applies to the Method SplitComplexPolygon and the
|
|
* class SplitComplexPolygonNode. Both are altered only enough to convert to C#
|
|
* and take advantage of some of C#'s language features. Any errors
|
|
* are thus mine from the conversion and not Eric's.
|
|
*
|
|
* Copyright (c) 2007 Eric Jordan
|
|
*
|
|
* This software is provided 'as-is', without any express or implied
|
|
* warranty. In no event will the authors be held liable for any damages
|
|
* arising from the use of this software.
|
|
* Permission is granted to anyone to use this software for any purpose,
|
|
* including commercial applications, and to alter it and redistribute it
|
|
* freely, subject to the following restrictions:
|
|
* 1. The origin of this software must not be misrepresented; you must not
|
|
* claim that you wrote the original software. If you use this software
|
|
* in a product, an acknowledgment in the product documentation would be
|
|
* appreciated but is not required.
|
|
* 2. Altered source versions must be plainly marked as such, and must not be
|
|
* misrepresented as being the original software.
|
|
* 3. This notice may not be removed or altered from any source distribution.
|
|
* */
|
|
|
|
|
|
/*
|
|
* Portions of the following code (notably: the methods PolygonUnion,
|
|
* PolygonSubtract, PolygonIntersect, PolygonOperationContext.Init,
|
|
* PolygonOperationContext.VerticesIntersect,
|
|
* PolygonOperationContext.PointInPolygonAngle, and
|
|
* PolygonOperationContext.VectorAngle are from the Farseer Physics Engine 3.0
|
|
* and are covered under the Microsoft Permissive License V1.1
|
|
* (http://farseerphysics.codeplex.com/license)
|
|
*
|
|
* Microsoft Permissive License (Ms-PL)
|
|
*
|
|
* This license governs use of the accompanying software. If you use the
|
|
* software, you accept this license. If you do not accept the license, do not
|
|
* use the software.
|
|
*
|
|
* 1. Definitions
|
|
*
|
|
* The terms "reproduce," "reproduction," "derivative works," and
|
|
* "distribution" have the same meaning here as under U.S. copyright law.
|
|
*
|
|
* A "contribution" is the original software, or any additions or changes to
|
|
* the software.
|
|
*
|
|
* A "contributor" is any person that distributes its contribution under this
|
|
* license.
|
|
*
|
|
* "Licensed patents" are a contributor's patent claims that read directly on
|
|
* its contribution.
|
|
*
|
|
* 2. Grant of Rights
|
|
*
|
|
* (A) Copyright Grant- Subject to the terms of this license, including the
|
|
* license conditions and limitations in section 3, each contributor grants
|
|
* you a non-exclusive, worldwide, royalty-free copyright license to reproduce
|
|
* its contribution, prepare derivative works of its contribution, and
|
|
* distribute its contribution or any derivative works that you create.
|
|
*
|
|
* (B) Patent Grant- Subject to the terms of this license, including the
|
|
* license conditions and limitations in section 3, each contributor grants
|
|
* you a non-exclusive, worldwide, royalty-free license under its licensed
|
|
* patents to make, have made, use, sell, offer for sale, import, and/or
|
|
* otherwise dispose of its contribution in the software or derivative works
|
|
* of the contribution in the software.
|
|
*
|
|
* 3. Conditions and Limitations
|
|
*
|
|
* (A) No Trademark License- This license does not grant you rights to use
|
|
* any contributors' name, logo, or trademarks.
|
|
*
|
|
* (B) If you bring a patent claim against any contributor over patents that
|
|
* you claim are infringed by the software, your patent license from such
|
|
* contributor to the software ends automatically.
|
|
*
|
|
* (C) If you distribute any portion of the software, you must retain all
|
|
* copyright, patent, trademark, and attribution notices that are present
|
|
* in the software.
|
|
*
|
|
* (D) If you distribute any portion of the software in source code form, you
|
|
* may do so only under this license by including a complete copy of this
|
|
* license with your distribution. If you distribute any portion of the
|
|
* software in compiled or object code form, you may only do so under a
|
|
* license that complies with this license.
|
|
*
|
|
* (E) The software is licensed "as-is." You bear the risk of using it. The
|
|
* contributors give no express warranties, guarantees or conditions. You may
|
|
* have additional consumer rights under your local laws which this license
|
|
* cannot change. To the extent permitted under your local laws, the
|
|
* contributors exclude the implied warranties of merchantability, fitness for
|
|
* a particular purpose and non-infringement.
|
|
*/
|
|
using System;
|
|
using System.Collections.Generic;
|
|
using System.Text;
|
|
|
|
namespace WPM.Poly2Tri {
|
|
public class PolygonUtil {
|
|
public enum PolyUnionError {
|
|
None,
|
|
NoIntersections,
|
|
Poly1InsidePoly2,
|
|
InfiniteLoop
|
|
}
|
|
|
|
[Flags]
|
|
public enum PolyOperation : uint {
|
|
None = 0,
|
|
Union = 1 << 0,
|
|
Intersect = 1 << 1,
|
|
Subtract = 1 << 2,
|
|
}
|
|
|
|
|
|
public static Point2DList.WindingOrderType CalculateWindingOrder (IList<Point2D> l) {
|
|
double area = 0.0;
|
|
for (int i = 0; i < l.Count; i++) {
|
|
int j = (i + 1) % l.Count;
|
|
area += l [i].X * l [j].Y;
|
|
area -= l [i].Y * l [j].X;
|
|
}
|
|
area /= 2.0f;
|
|
|
|
// the sign of the 'area' of the polygon is all we are interested in.
|
|
if (area < 0.0) {
|
|
return Point2DList.WindingOrderType.CW;
|
|
} else if (area > 0.0) {
|
|
return Point2DList.WindingOrderType.CCW;
|
|
}
|
|
|
|
// error condition - not even verts to calculate, non-simple poly, etc.
|
|
return Point2DList.WindingOrderType.Unknown;
|
|
}
|
|
|
|
|
|
/// <summary>
|
|
/// Check if the polys are similar to within a tolerance (Doesn't include reflections,
|
|
/// but allows for the points to be numbered differently, but not reversed).
|
|
/// </summary>
|
|
/// <param name="poly1"></param>
|
|
/// <param name="poly2"></param>
|
|
/// <returns></returns>
|
|
public static bool PolygonsAreSame2D (IList<Point2D> poly1, IList<Point2D> poly2) {
|
|
int numVerts1 = poly1.Count;
|
|
int numVerts2 = poly2.Count;
|
|
|
|
if (numVerts1 != numVerts2) {
|
|
return false;
|
|
}
|
|
const double kEpsilon = 0.01;
|
|
const double kEpsilonSq = kEpsilon * kEpsilon;
|
|
|
|
// Bounds the same to within tolerance, are there polys the same?
|
|
Point2D vdelta = new Point2D (0.0, 0.0);
|
|
for (int k = 0; k < numVerts2; ++k) {
|
|
// Look for a match in verts2 to the first vertex in verts1
|
|
vdelta.Set (poly1 [0]);
|
|
vdelta.Subtract (poly2 [k]);
|
|
|
|
if (vdelta.MagnitudeSquared () < kEpsilonSq) {
|
|
// Found match to the first point, now check the other points continuing round
|
|
// if the points don't match in the first direction we check, then it's possible
|
|
// that the polygons have a different winding order, so we check going round
|
|
// the opposite way as well
|
|
int matchedVertIndex = k;
|
|
bool bReverseSearch = false;
|
|
while (true) {
|
|
bool bMatchFound = true;
|
|
for (int i = 1; i < numVerts1; ++i) {
|
|
if (!bReverseSearch) {
|
|
++k;
|
|
} else {
|
|
--k;
|
|
if (k < 0) {
|
|
k = numVerts2 - 1;
|
|
}
|
|
}
|
|
|
|
vdelta.Set (poly1 [i]);
|
|
vdelta.Subtract (poly2 [k % numVerts2]);
|
|
if (vdelta.MagnitudeSquared () >= kEpsilonSq) {
|
|
if (bReverseSearch) {
|
|
// didn't find a match going in either direction, so the polygons are not the same
|
|
return false;
|
|
} else {
|
|
// mismatch in the first direction checked, so check the other direction.
|
|
k = matchedVertIndex;
|
|
bReverseSearch = true;
|
|
bMatchFound = false;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (bMatchFound) {
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
public static bool PointInPolygon2D (IList<Point2D> polygon, Point2D p) {
|
|
if (polygon == null || polygon.Count < 3) {
|
|
return false;
|
|
}
|
|
|
|
int numVerts = polygon.Count;
|
|
Point2D p0 = polygon [numVerts - 1];
|
|
bool bYFlag0 = (p0.Y >= p.Y) ? true : false;
|
|
Point2D p1 = null;
|
|
|
|
bool bInside = false;
|
|
for (int j = 0; j < numVerts; ++j) {
|
|
p1 = polygon [j];
|
|
bool bYFlag1 = (p1.Y >= p.Y) ? true : false;
|
|
if (bYFlag0 != bYFlag1) {
|
|
if (((p1.Y - p.Y) * (p0.X - p1.X) >= (p1.X - p.X) * (p0.Y - p1.Y)) == bYFlag1) {
|
|
bInside = !bInside;
|
|
}
|
|
}
|
|
|
|
// Move to the next pair of vertices, retaining info as possible.
|
|
bYFlag0 = bYFlag1;
|
|
p0 = p1;
|
|
}
|
|
|
|
return bInside;
|
|
}
|
|
|
|
|
|
// Given two polygons and their bounding rects, returns true if the two polygons intersect.
|
|
// This test will NOT determine if one of the two polygons is contained within the other or if the
|
|
// two polygons are similar - it will return false in all those cases. The only case it will return
|
|
// true for is if the two polygons actually intersect.
|
|
public static bool PolygonsIntersect2D (IList<Point2D> poly1, Rect2D boundRect1,
|
|
IList<Point2D> poly2, Rect2D boundRect2) {
|
|
// do some quick tests first before doing any real work
|
|
if (poly1 == null || poly1.Count < 3 || boundRect1 == null || poly2 == null || poly2.Count < 3 || boundRect2 == null) {
|
|
return false;
|
|
}
|
|
|
|
if (!boundRect1.Intersects (boundRect2)) {
|
|
return false;
|
|
}
|
|
|
|
// We first check whether any edge of one poly intersects any edge of the other poly. If they do,
|
|
// then the two polys intersect.
|
|
|
|
// Make the epsilon a function of the size of the polys. We could take the heights of the rects
|
|
// also into consideration here if needed; but, that should not usually be necessary.
|
|
double epsilon = Math.Max (Math.Min (boundRect1.Width, boundRect2.Width) * 0.001f, MathUtil.EPSILON);
|
|
|
|
int numVerts1 = poly1.Count;
|
|
int numVerts2 = poly2.Count;
|
|
for (int i = 0; i < numVerts1; ++i) {
|
|
int lineEndVert1 = i + 1;
|
|
if (lineEndVert1 == numVerts1) {
|
|
lineEndVert1 = 0;
|
|
}
|
|
for (int j = 0; j < numVerts2; ++j) {
|
|
int lineEndVert2 = j + 1;
|
|
if (lineEndVert2 == numVerts2) {
|
|
lineEndVert2 = 0;
|
|
}
|
|
Point2D tmp = null;
|
|
if (TriangulationUtil.LinesIntersect2D (poly1 [i], poly1 [lineEndVert1], poly2 [j], poly2 [lineEndVert2], ref tmp, epsilon)) {
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
public bool PolygonContainsPolygon (IList<Point2D> poly1, Rect2D boundRect1,
|
|
IList<Point2D> poly2, Rect2D boundRect2) {
|
|
return PolygonContainsPolygon (poly1, boundRect1, poly2, boundRect2, true);
|
|
}
|
|
|
|
|
|
/// <summary>
|
|
/// Checks to see if poly1 contains poly2. return true if so, false otherwise.
|
|
///
|
|
/// If the polygons intersect, then poly1 cannot contain poly2 (or vice-versa for that matter)
|
|
/// Since the poly intersection test can be somewhat expensive, we'll only run it if the user
|
|
/// requests it. If runIntersectionTest is false, then it is assumed that the user has already
|
|
/// verified that the polygons do not intersect. If the polygons DO intersect and runIntersectionTest
|
|
/// is false, then the return value is meaningless. Caveat emptor.
|
|
///
|
|
/// As an added bonus, just to cause more user-carnage, if runIntersectionTest is false, then the
|
|
/// boundRects are not used and can safely be passed in as nulls. However, if runIntersectionTest
|
|
/// is true and you pass nulls for boundRect1 or boundRect2, you will cause a program crash.
|
|
///
|
|
/// Finally, the polygon points are assumed to be passed in Clockwise winding order. It is possible
|
|
/// that CounterClockwise ordering would work, but I have not verified the behavior in that case.
|
|
///
|
|
/// </summary>
|
|
/// <param name="poly1">points of polygon1</param>
|
|
/// <param name="boundRect1">bounding rect of polygon1. Only used if runIntersectionTest is true</param>
|
|
/// <param name="poly2">points of polygon2</param>
|
|
/// <param name="boundRect2">bounding rect of polygon2. Only used if runIntersectionTest is true</param>
|
|
/// <param name="runIntersectionTest">see summary above</param>
|
|
/// <returns>true if poly1 fully contains poly2</returns>
|
|
public static bool PolygonContainsPolygon (IList<Point2D> poly1, Rect2D boundRect1,
|
|
IList<Point2D> poly2, Rect2D boundRect2,
|
|
bool runIntersectionTest) {
|
|
// quick early-out tests
|
|
if (poly1 == null || poly1.Count < 3 || poly2 == null || poly2.Count < 3) {
|
|
return false;
|
|
}
|
|
|
|
if (runIntersectionTest) {
|
|
// make sure the polygons are not actually the same...
|
|
if (poly1.Count == poly2.Count) {
|
|
// Check if the polys are similar to within a tolerance (Doesn't include reflections,
|
|
// but allows for the points to be numbered differently)
|
|
if (PolygonUtil.PolygonsAreSame2D (poly1, poly2)) {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
bool bIntersect = PolygonUtil.PolygonsIntersect2D (poly1, boundRect1, poly2, boundRect2);
|
|
if (bIntersect) {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
// Since we (now) know that the polygons don't intersect and they are not the same, we can just do a
|
|
// single check to see if ANY point in poly2 is inside poly1. If so, then all points of poly2
|
|
// are inside poly1. If not, then ALL points of poly2 are outside poly1.
|
|
if (PolygonUtil.PointInPolygon2D (poly1, poly2 [0])) {
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
|
|
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
// ClipPolygonToEdge2D
|
|
//
|
|
// This function clips a polygon against an edge. The portion of the polygon
|
|
// which is to the left of the edge (while going from edgeBegin to edgeEnd)
|
|
// is returned in "outPoly". Note that the clipped polygon may have more vertices
|
|
// than the input polygon. Make sure that outPolyArraySize is sufficiently large.
|
|
// Otherwise, you may get incorrect results and may be an assert (hopefully, no crash).
|
|
// Pass in the actual size of the array in "outPolyArraySize".
|
|
//
|
|
// Read Sutherland-Hidgman algorithm description in Foley & van Dam book for
|
|
// details about this.
|
|
//
|
|
///////////////////////////////////////////////////////////////////////////
|
|
public static void ClipPolygonToEdge2D (Point2D edgeBegin,
|
|
Point2D edgeEnd,
|
|
IList<Point2D> poly,
|
|
out List<Point2D> outPoly) {
|
|
outPoly = null;
|
|
if (edgeBegin == null ||
|
|
edgeEnd == null ||
|
|
poly == null ||
|
|
poly.Count < 3) {
|
|
return;
|
|
}
|
|
|
|
outPoly = new List<Point2D> ();
|
|
int lastVertex = poly.Count - 1;
|
|
Point2D edgeRayVector = new Point2D (edgeEnd.X - edgeBegin.X, edgeEnd.Y - edgeBegin.Y);
|
|
// Note: >= 0 as opposed to <= 0 is intentional. We are
|
|
// dealing with x and z here. And in our case, z axis goes
|
|
// downward while the x axis goes rightward.
|
|
//bool bLastVertexIsToRight = TriangulationUtil.PointRelativeToLine2D(poly[lastVertex], edgeBegin, edgeEnd) >= 0;
|
|
bool bLastVertexIsToRight = TriangulationUtil.Orient2d (edgeBegin, edgeEnd, poly [lastVertex]) == Orientation.CW ? true : false;
|
|
Point2D tempRay = new Point2D (0.0, 0.0);
|
|
|
|
for (int curVertex = 0; curVertex < poly.Count; curVertex++) {
|
|
//bool bCurVertexIsToRight = TriangulationUtil.PointRelativeToLine2D(poly[curVertex], edgeBegin, edgeEnd) >= 0;
|
|
bool bCurVertexIsToRight = TriangulationUtil.Orient2d (edgeBegin, edgeEnd, poly [curVertex]) == Orientation.CW ? true : false;
|
|
if (bCurVertexIsToRight) {
|
|
if (bLastVertexIsToRight) {
|
|
outPoly.Add (poly [curVertex]);
|
|
} else {
|
|
tempRay.Set (poly [curVertex].X - poly [lastVertex].X, poly [curVertex].Y - poly [lastVertex].Y);
|
|
Point2D ptIntersection = new Point2D (0.0, 0.0);
|
|
bool bIntersect = TriangulationUtil.RaysIntersect2D (poly [lastVertex], tempRay, edgeBegin, edgeRayVector, ref ptIntersection);
|
|
if (bIntersect) {
|
|
outPoly.Add (ptIntersection);
|
|
outPoly.Add (poly [curVertex]);
|
|
}
|
|
}
|
|
} else if (bLastVertexIsToRight) {
|
|
tempRay.Set (poly [curVertex].X - poly [lastVertex].X, poly [curVertex].Y - poly [lastVertex].Y);
|
|
Point2D ptIntersection = new Point2D (0.0, 0.0);
|
|
bool bIntersect = TriangulationUtil.RaysIntersect2D (poly [lastVertex], tempRay, edgeBegin, edgeRayVector, ref ptIntersection);
|
|
if (bIntersect) {
|
|
outPoly.Add (ptIntersection);
|
|
}
|
|
}
|
|
|
|
lastVertex = curVertex;
|
|
bLastVertexIsToRight = bCurVertexIsToRight;
|
|
}
|
|
}
|
|
|
|
public static void ClipPolygonToPolygon (IList<Point2D> poly, IList<Point2D> clipPoly, out List<Point2D> outPoly) {
|
|
outPoly = null;
|
|
if (poly == null || poly.Count < 3 || clipPoly == null || clipPoly.Count < 3) {
|
|
return;
|
|
}
|
|
|
|
outPoly = new List<Point2D> (poly);
|
|
int numClipVertices = clipPoly.Count;
|
|
int lastVertex = numClipVertices - 1;
|
|
|
|
// The algorithm keeps clipping the polygon against each edge of "clipPoly".
|
|
for (int curVertex = 0; curVertex < numClipVertices; curVertex++) {
|
|
List<Point2D> clippedPoly = null;
|
|
Point2D edgeBegin = clipPoly [lastVertex];
|
|
Point2D edgeEnd = clipPoly [curVertex];
|
|
PolygonUtil.ClipPolygonToEdge2D (edgeBegin, edgeEnd, outPoly, out clippedPoly);
|
|
outPoly.Clear ();
|
|
outPoly.AddRange (clippedPoly);
|
|
|
|
lastVertex = curVertex;
|
|
}
|
|
}
|
|
|
|
|
|
/// Merges two polygons, given that they intersect.
|
|
/// </summary>
|
|
/// <param name="polygon1">The first polygon.</param>
|
|
/// <param name="polygon2">The second polygon.</param>
|
|
/// <param name="union">The union of the two polygons</param>
|
|
/// <returns>The error returned from union</returns>
|
|
public static PolygonUtil.PolyUnionError PolygonUnion (Point2DList polygon1, Point2DList polygon2, out Point2DList union) {
|
|
PolygonOperationContext ctx = new PolygonOperationContext ();
|
|
ctx.Init (PolygonUtil.PolyOperation.Union, polygon1, polygon2);
|
|
PolygonUnionInternal (ctx);
|
|
union = ctx.Union;
|
|
return ctx.mError;
|
|
}
|
|
|
|
protected static void PolygonUnionInternal (PolygonOperationContext ctx) {
|
|
Point2DList union = ctx.Union;
|
|
if (ctx.mStartingIndex == -1) {
|
|
switch (ctx.mError) {
|
|
case PolygonUtil.PolyUnionError.NoIntersections:
|
|
case PolygonUtil.PolyUnionError.InfiniteLoop:
|
|
return;
|
|
case PolygonUtil.PolyUnionError.Poly1InsidePoly2:
|
|
union.AddRange (ctx.mOriginalPolygon2);
|
|
return;
|
|
}
|
|
}
|
|
|
|
Point2DList currentPoly = ctx.mPoly1;
|
|
Point2DList otherPoly = ctx.mPoly2;
|
|
List<int> currentPolyVectorAngles = ctx.mPoly1VectorAngles;
|
|
|
|
// Store the starting vertex so we can refer to it later.
|
|
Point2D startingVertex = ctx.mPoly1 [ctx.mStartingIndex];
|
|
int currentIndex = ctx.mStartingIndex;
|
|
int firstPoly2Index = -1;
|
|
union.Clear ();
|
|
|
|
do {
|
|
// Add the current vertex to the final union
|
|
union.Add (currentPoly [currentIndex]);
|
|
|
|
foreach (EdgeIntersectInfo intersect in ctx.mIntersections) {
|
|
// If the current point is an intersection point
|
|
if (currentPoly [currentIndex].Equals (intersect.IntersectionPoint, currentPoly.Epsilon)) {
|
|
// Make sure we want to swap polygons here.
|
|
int otherIndex = otherPoly.IndexOf (intersect.IntersectionPoint);
|
|
|
|
// If the next vertex, if we do swap, is not inside the current polygon,
|
|
// then its safe to swap, otherwise, just carry on with the current poly.
|
|
int comparePointIndex = otherPoly.NextIndex (otherIndex);
|
|
Point2D comparePoint = otherPoly [comparePointIndex];
|
|
bool bPointInPolygonAngle = false;
|
|
if (currentPolyVectorAngles [comparePointIndex] == -1) {
|
|
bPointInPolygonAngle = ctx.PointInPolygonAngle (comparePoint, currentPoly);
|
|
currentPolyVectorAngles [comparePointIndex] = bPointInPolygonAngle ? 1 : 0;
|
|
} else {
|
|
bPointInPolygonAngle = (currentPolyVectorAngles [comparePointIndex] == 1) ? true : false;
|
|
}
|
|
|
|
if (!bPointInPolygonAngle) {
|
|
// switch polygons
|
|
if (currentPoly == ctx.mPoly1) {
|
|
currentPoly = ctx.mPoly2;
|
|
currentPolyVectorAngles = ctx.mPoly2VectorAngles;
|
|
otherPoly = ctx.mPoly1;
|
|
if (firstPoly2Index < 0) {
|
|
firstPoly2Index = otherIndex;
|
|
}
|
|
} else {
|
|
currentPoly = ctx.mPoly1;
|
|
currentPolyVectorAngles = ctx.mPoly1VectorAngles;
|
|
otherPoly = ctx.mPoly2;
|
|
}
|
|
|
|
// set currentIndex
|
|
currentIndex = otherIndex;
|
|
|
|
// Stop checking intersections for this point.
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Move to next index
|
|
currentIndex = currentPoly.NextIndex (currentIndex);
|
|
|
|
if (currentPoly == ctx.mPoly1) {
|
|
if (currentIndex == 0) {
|
|
break;
|
|
}
|
|
} else {
|
|
if (firstPoly2Index >= 0 && currentIndex == firstPoly2Index) {
|
|
break;
|
|
}
|
|
}
|
|
} while ((currentPoly[currentIndex] != startingVertex) && (union.Count <= (ctx.mPoly1.Count + ctx.mPoly2.Count)));
|
|
|
|
// If the number of vertices in the union is more than the combined vertices
|
|
// of the input polygons, then something is wrong and the algorithm will
|
|
// loop forever. Luckily, we check for that.
|
|
if (union.Count > (ctx.mPoly1.Count + ctx.mPoly2.Count)) {
|
|
ctx.mError = PolygonUtil.PolyUnionError.InfiniteLoop;
|
|
}
|
|
|
|
return;
|
|
}
|
|
|
|
|
|
/// <summary>
|
|
/// Finds the intersection between two polygons.
|
|
/// </summary>
|
|
/// <param name="polygon1">The first polygon.</param>
|
|
/// <param name="polygon2">The second polygon.</param>
|
|
/// <param name="intersectOut">The intersection of the two polygons</param>
|
|
/// <returns>error code</returns>
|
|
public static PolygonUtil.PolyUnionError PolygonIntersect (Point2DList polygon1, Point2DList polygon2, out Point2DList intersectOut) {
|
|
PolygonOperationContext ctx = new PolygonOperationContext ();
|
|
ctx.Init (PolygonUtil.PolyOperation.Intersect, polygon1, polygon2);
|
|
PolygonIntersectInternal (ctx);
|
|
intersectOut = ctx.Intersect;
|
|
return ctx.mError;
|
|
}
|
|
|
|
protected static void PolygonIntersectInternal (PolygonOperationContext ctx) {
|
|
Point2DList intersectOut = ctx.Intersect;
|
|
if (ctx.mStartingIndex == -1) {
|
|
switch (ctx.mError) {
|
|
case PolygonUtil.PolyUnionError.NoIntersections:
|
|
case PolygonUtil.PolyUnionError.InfiniteLoop:
|
|
return;
|
|
case PolygonUtil.PolyUnionError.Poly1InsidePoly2:
|
|
intersectOut.AddRange (ctx.mOriginalPolygon2);
|
|
return;
|
|
}
|
|
}
|
|
|
|
Point2DList currentPoly = ctx.mPoly1;
|
|
Point2DList otherPoly = ctx.mPoly2;
|
|
List<int> currentPolyVectorAngles = ctx.mPoly1VectorAngles;
|
|
|
|
// Store the starting vertex so we can refer to it later.
|
|
int currentIndex = ctx.mPoly1.IndexOf (ctx.mIntersections [0].IntersectionPoint);
|
|
Point2D startingVertex = ctx.mPoly1 [currentIndex];
|
|
int firstPoly1Index = currentIndex;
|
|
int firstPoly2Index = -1;
|
|
intersectOut.Clear ();
|
|
|
|
do {
|
|
// Add the current vertex to the final intersection
|
|
if (intersectOut.Contains (currentPoly [currentIndex])) {
|
|
// This can happen when the two polygons only share a single edge, and neither is inside the other
|
|
break;
|
|
}
|
|
intersectOut.Add (currentPoly [currentIndex]);
|
|
|
|
foreach (EdgeIntersectInfo intersect in ctx.mIntersections) {
|
|
// If the current point is an intersection point
|
|
if (currentPoly [currentIndex].Equals (intersect.IntersectionPoint, currentPoly.Epsilon)) {
|
|
// Make sure we want to swap polygons here.
|
|
int otherIndex = otherPoly.IndexOf (intersect.IntersectionPoint);
|
|
|
|
// If the next vertex, if we do swap, is inside the current polygon,
|
|
// then its safe to swap, otherwise, just carry on with the current poly.
|
|
int comparePointIndex = otherPoly.NextIndex (otherIndex);
|
|
Point2D comparePoint = otherPoly [comparePointIndex];
|
|
bool bPointInPolygonAngle = false;
|
|
if (currentPolyVectorAngles [comparePointIndex] == -1) {
|
|
bPointInPolygonAngle = ctx.PointInPolygonAngle (comparePoint, currentPoly);
|
|
currentPolyVectorAngles [comparePointIndex] = bPointInPolygonAngle ? 1 : 0;
|
|
} else {
|
|
bPointInPolygonAngle = (currentPolyVectorAngles [comparePointIndex] == 1) ? true : false;
|
|
}
|
|
|
|
if (bPointInPolygonAngle) {
|
|
// switch polygons
|
|
if (currentPoly == ctx.mPoly1) {
|
|
currentPoly = ctx.mPoly2;
|
|
currentPolyVectorAngles = ctx.mPoly2VectorAngles;
|
|
otherPoly = ctx.mPoly1;
|
|
if (firstPoly2Index < 0) {
|
|
firstPoly2Index = otherIndex;
|
|
}
|
|
} else {
|
|
currentPoly = ctx.mPoly1;
|
|
currentPolyVectorAngles = ctx.mPoly1VectorAngles;
|
|
otherPoly = ctx.mPoly2;
|
|
}
|
|
|
|
// set currentIndex
|
|
currentIndex = otherIndex;
|
|
|
|
// Stop checking intersections for this point.
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Move to next index
|
|
currentIndex = currentPoly.NextIndex (currentIndex);
|
|
|
|
if (currentPoly == ctx.mPoly1) {
|
|
if (currentIndex == firstPoly1Index) {
|
|
break;
|
|
}
|
|
} else {
|
|
if (firstPoly2Index >= 0 && currentIndex == firstPoly2Index) {
|
|
break;
|
|
}
|
|
}
|
|
} while ((currentPoly[currentIndex] != startingVertex) && (intersectOut.Count <= (ctx.mPoly1.Count + ctx.mPoly2.Count)));
|
|
|
|
// If the number of vertices in the union is more than the combined vertices
|
|
// of the input polygons, then something is wrong and the algorithm will
|
|
// loop forever. Luckily, we check for that.
|
|
if (intersectOut.Count > (ctx.mPoly1.Count + ctx.mPoly2.Count)) {
|
|
ctx.mError = PolygonUtil.PolyUnionError.InfiniteLoop;
|
|
}
|
|
|
|
return;
|
|
}
|
|
|
|
|
|
/// <summary>
|
|
/// Subtracts one polygon from another.
|
|
/// </summary>
|
|
/// <param name="polygon1">The base polygon.</param>
|
|
/// <param name="polygon2">The polygon to subtract from the base.</param>
|
|
/// <param name="subtract">The result of the polygon subtraction</param>
|
|
/// <returns>error code</returns>
|
|
public static PolygonUtil.PolyUnionError PolygonSubtract (Point2DList polygon1, Point2DList polygon2, out Point2DList subtract) {
|
|
PolygonOperationContext ctx = new PolygonOperationContext ();
|
|
ctx.Init (PolygonUtil.PolyOperation.Subtract, polygon1, polygon2);
|
|
PolygonSubtractInternal (ctx);
|
|
subtract = ctx.Subtract;
|
|
return ctx.mError;
|
|
}
|
|
|
|
public static void PolygonSubtractInternal (PolygonOperationContext ctx) {
|
|
Point2DList subtract = ctx.Subtract;
|
|
if (ctx.mStartingIndex == -1) {
|
|
switch (ctx.mError) {
|
|
case PolygonUtil.PolyUnionError.NoIntersections:
|
|
case PolygonUtil.PolyUnionError.InfiniteLoop:
|
|
case PolygonUtil.PolyUnionError.Poly1InsidePoly2:
|
|
return;
|
|
}
|
|
}
|
|
|
|
Point2DList currentPoly = ctx.mPoly1;
|
|
Point2DList otherPoly = ctx.mPoly2;
|
|
List<int> currentPolyVectorAngles = ctx.mPoly1VectorAngles;
|
|
|
|
// Store the starting vertex so we can refer to it later.
|
|
Point2D startingVertex = ctx.mPoly1 [ctx.mStartingIndex];
|
|
int currentIndex = ctx.mStartingIndex;
|
|
subtract.Clear ();
|
|
|
|
// Trace direction
|
|
bool forward = true;
|
|
|
|
do {
|
|
// Add the current vertex to the final union
|
|
subtract.Add (currentPoly [currentIndex]);
|
|
|
|
foreach (EdgeIntersectInfo intersect in ctx.mIntersections) {
|
|
// If the current point is an intersection point
|
|
if (currentPoly [currentIndex].Equals (intersect.IntersectionPoint, currentPoly.Epsilon)) {
|
|
// Make sure we want to swap polygons here.
|
|
int otherIndex = otherPoly.IndexOf (intersect.IntersectionPoint);
|
|
|
|
//Point2D otherVertex;
|
|
if (forward) {
|
|
// If the next vertex, if we do swap, is inside the current polygon,
|
|
// then its safe to swap, otherwise, just carry on with the current poly.
|
|
int compareIndex = otherPoly.PreviousIndex (otherIndex);
|
|
Point2D compareVertex = otherPoly [compareIndex];
|
|
bool bPointInPolygonAngle = false;
|
|
if (currentPolyVectorAngles [compareIndex] == -1) {
|
|
bPointInPolygonAngle = ctx.PointInPolygonAngle (compareVertex, currentPoly);
|
|
currentPolyVectorAngles [compareIndex] = bPointInPolygonAngle ? 1 : 0;
|
|
} else {
|
|
bPointInPolygonAngle = (currentPolyVectorAngles [compareIndex] == 1) ? true : false;
|
|
}
|
|
|
|
if (bPointInPolygonAngle) {
|
|
// switch polygons
|
|
if (currentPoly == ctx.mPoly1) {
|
|
currentPoly = ctx.mPoly2;
|
|
currentPolyVectorAngles = ctx.mPoly2VectorAngles;
|
|
otherPoly = ctx.mPoly1;
|
|
} else {
|
|
currentPoly = ctx.mPoly1;
|
|
currentPolyVectorAngles = ctx.mPoly1VectorAngles;
|
|
otherPoly = ctx.mPoly2;
|
|
}
|
|
|
|
// set currentIndex
|
|
currentIndex = otherIndex;
|
|
|
|
// Reverse direction
|
|
forward = !forward;
|
|
|
|
// Stop checking ctx.mIntersections for this point.
|
|
break;
|
|
}
|
|
} else {
|
|
// If the next vertex, if we do swap, is outside the current polygon,
|
|
// then its safe to swap, otherwise, just carry on with the current poly.
|
|
int compareIndex = otherPoly.NextIndex (otherIndex);
|
|
Point2D compareVertex = otherPoly [compareIndex];
|
|
bool bPointInPolygonAngle = false;
|
|
if (currentPolyVectorAngles [compareIndex] == -1) {
|
|
bPointInPolygonAngle = ctx.PointInPolygonAngle (compareVertex, currentPoly);
|
|
currentPolyVectorAngles [compareIndex] = bPointInPolygonAngle ? 1 : 0;
|
|
} else {
|
|
bPointInPolygonAngle = (currentPolyVectorAngles [compareIndex] == 1) ? true : false;
|
|
}
|
|
|
|
if (!bPointInPolygonAngle) {
|
|
// switch polygons
|
|
if (currentPoly == ctx.mPoly1) {
|
|
currentPoly = ctx.mPoly2;
|
|
currentPolyVectorAngles = ctx.mPoly2VectorAngles;
|
|
otherPoly = ctx.mPoly1;
|
|
} else {
|
|
currentPoly = ctx.mPoly1;
|
|
currentPolyVectorAngles = ctx.mPoly1VectorAngles;
|
|
otherPoly = ctx.mPoly2;
|
|
}
|
|
|
|
// set currentIndex
|
|
currentIndex = otherIndex;
|
|
|
|
// Reverse direction
|
|
forward = !forward;
|
|
|
|
// Stop checking intersections for this point.
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if (forward) {
|
|
// Move to next index
|
|
currentIndex = currentPoly.NextIndex (currentIndex);
|
|
} else {
|
|
currentIndex = currentPoly.PreviousIndex (currentIndex);
|
|
}
|
|
} while ((currentPoly[currentIndex] != startingVertex) && (subtract.Count <= (ctx.mPoly1.Count + ctx.mPoly2.Count)));
|
|
|
|
|
|
// If the number of vertices in the union is more than the combined vertices
|
|
// of the input polygons, then something is wrong and the algorithm will
|
|
// loop forever. Luckily, we check for that.
|
|
if (subtract.Count > (ctx.mPoly1.Count + ctx.mPoly2.Count)) {
|
|
ctx.mError = PolygonUtil.PolyUnionError.InfiniteLoop;
|
|
}
|
|
|
|
return;
|
|
}
|
|
|
|
|
|
/// <summary>
|
|
/// Performs one or more polygon operations on the 2 provided polygons
|
|
/// </summary>
|
|
/// <param name="polygon1">The first polygon.</param>
|
|
/// <param name="polygon2">The second polygon</param>
|
|
/// <param name="subtract">The result of the polygon subtraction</param>
|
|
/// <returns>error code</returns>
|
|
public static PolygonUtil.PolyUnionError PolygonOperation (PolygonUtil.PolyOperation operations, Point2DList polygon1, Point2DList polygon2, out Dictionary<uint, Point2DList> results) {
|
|
PolygonOperationContext ctx = new PolygonOperationContext ();
|
|
ctx.Init (operations, polygon1, polygon2);
|
|
results = ctx.mOutput;
|
|
return PolygonUtil.PolygonOperation (ctx);
|
|
}
|
|
|
|
public static PolygonUtil.PolyUnionError PolygonOperation (PolygonOperationContext ctx) {
|
|
if ((ctx.mOperations & PolygonUtil.PolyOperation.Union) == PolygonUtil.PolyOperation.Union) {
|
|
PolygonUtil.PolygonUnionInternal (ctx);
|
|
}
|
|
if ((ctx.mOperations & PolygonUtil.PolyOperation.Intersect) == PolygonUtil.PolyOperation.Intersect) {
|
|
PolygonUtil.PolygonIntersectInternal (ctx);
|
|
}
|
|
if ((ctx.mOperations & PolygonUtil.PolyOperation.Subtract) == PolygonUtil.PolyOperation.Subtract) {
|
|
PolygonUtil.PolygonSubtractInternal (ctx);
|
|
}
|
|
|
|
return ctx.mError;
|
|
}
|
|
|
|
|
|
/// <summary>
|
|
/// Trace the edge of a non-simple polygon and return a simple polygon.
|
|
///
|
|
///Method:
|
|
///Start at vertex with minimum y (pick maximum x one if there are two).
|
|
///We aim our "lastDir" vector at (1.0, 0)
|
|
///We look at the two rays going off from our start vertex, and follow whichever
|
|
///has the smallest angle (in -Pi . Pi) wrt lastDir ("rightest" turn)
|
|
///
|
|
///Loop until we hit starting vertex:
|
|
///
|
|
///We add our current vertex to the list.
|
|
///We check the seg from current vertex to next vertex for intersections
|
|
/// - if no intersections, follow to next vertex and continue
|
|
/// - if intersections, pick one with minimum distance
|
|
/// - if more than one, pick one with "rightest" next point (two possibilities for each)
|
|
///
|
|
/// </summary>
|
|
/// <param name="verts"></param>
|
|
/// <returns></returns>
|
|
public static List<Point2DList> SplitComplexPolygon (Point2DList verts, double epsilon) {
|
|
int numVerts = verts.Count;
|
|
int nNodes = 0;
|
|
List<SplitComplexPolygonNode> nodes = new List<SplitComplexPolygonNode> ();
|
|
|
|
//Add base nodes (raw outline)
|
|
for (int i = 0; i < verts.Count; ++i) {
|
|
SplitComplexPolygonNode newNode = new SplitComplexPolygonNode (new Point2D (verts [i].X, verts [i].Y));
|
|
nodes.Add (newNode);
|
|
}
|
|
for (int i = 0; i < verts.Count; ++i) {
|
|
int iplus = (i == numVerts - 1) ? 0 : i + 1;
|
|
int iminus = (i == 0) ? numVerts - 1 : i - 1;
|
|
nodes [i].AddConnection (nodes [iplus]);
|
|
nodes [i].AddConnection (nodes [iminus]);
|
|
}
|
|
nNodes = nodes.Count;
|
|
|
|
//Process intersection nodes - horribly inefficient
|
|
bool dirty = true;
|
|
int counter = 0;
|
|
while (dirty) {
|
|
dirty = false;
|
|
for (int i = 0; !dirty && i < nNodes; ++i) {
|
|
for (int j = 0; !dirty && j < nodes[i].NumConnected; ++j) {
|
|
for (int k = 0; !dirty && k < nNodes; ++k) {
|
|
if (k == i || nodes [k] == nodes [i] [j]) {
|
|
continue;
|
|
}
|
|
for (int l = 0; !dirty && l < nodes[k].NumConnected; ++l) {
|
|
if (nodes [k] [l] == nodes [i] [j] || nodes [k] [l] == nodes [i]) {
|
|
continue;
|
|
}
|
|
//Check intersection
|
|
Point2D intersectPt = new Point2D ();
|
|
//if (counter > 100) printf("checking intersection: %d, %d, %d, %d\n",i,j,k,l);
|
|
bool crosses = TriangulationUtil.LinesIntersect2D (nodes [i].Position,
|
|
nodes [i] [j].Position,
|
|
nodes [k].Position,
|
|
nodes [k] [l].Position,
|
|
true, true, true,
|
|
ref intersectPt,
|
|
epsilon);
|
|
if (crosses) {
|
|
/*if (counter > 100) {
|
|
printf("Found crossing at %f, %f\n",intersectPt.x, intersectPt.y);
|
|
printf("Locations: %f,%f - %f,%f | %f,%f - %f,%f\n",
|
|
nodes[i].position.x, nodes[i].position.y,
|
|
nodes[i].connected[j].position.x, nodes[i].connected[j].position.y,
|
|
nodes[k].position.x,nodes[k].position.y,
|
|
nodes[k].connected[l].position.x,nodes[k].connected[l].position.y);
|
|
printf("Memory addresses: %d, %d, %d, %d\n",(int)&nodes[i],(int)nodes[i].connected[j],(int)&nodes[k],(int)nodes[k].connected[l]);
|
|
}*/
|
|
dirty = true;
|
|
//Destroy and re-hook connections at crossing point
|
|
SplitComplexPolygonNode intersectionNode = new SplitComplexPolygonNode (intersectPt);
|
|
int idx = nodes.IndexOf (intersectionNode);
|
|
if (idx >= 0 && idx < nodes.Count) {
|
|
intersectionNode = nodes [idx];
|
|
} else {
|
|
nodes.Add (intersectionNode);
|
|
nNodes = nodes.Count;
|
|
}
|
|
|
|
SplitComplexPolygonNode nodei = nodes [i];
|
|
SplitComplexPolygonNode connij = nodes [i] [j];
|
|
SplitComplexPolygonNode nodek = nodes [k];
|
|
SplitComplexPolygonNode connkl = nodes [k] [l];
|
|
connij.RemoveConnection (nodei);
|
|
nodei.RemoveConnection (connij);
|
|
connkl.RemoveConnection (nodek);
|
|
nodek.RemoveConnection (connkl);
|
|
if (!intersectionNode.Position.Equals (nodei.Position, epsilon)) {
|
|
intersectionNode.AddConnection (nodei);
|
|
nodei.AddConnection (intersectionNode);
|
|
}
|
|
if (!intersectionNode.Position.Equals (nodek.Position, epsilon)) {
|
|
intersectionNode.AddConnection (nodek);
|
|
nodek.AddConnection (intersectionNode);
|
|
}
|
|
if (!intersectionNode.Position.Equals (connij.Position, epsilon)) {
|
|
intersectionNode.AddConnection (connij);
|
|
connij.AddConnection (intersectionNode);
|
|
}
|
|
if (!intersectionNode.Position.Equals (connkl.Position, epsilon)) {
|
|
intersectionNode.AddConnection (connkl);
|
|
connkl.AddConnection (intersectionNode);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
++counter;
|
|
//if (counter > 100) printf("Counter: %d\n",counter);
|
|
}
|
|
|
|
// /*
|
|
// // Debugging: check for connection consistency
|
|
// for (int i=0; i<nNodes; ++i) {
|
|
// int nConn = nodes[i].nConnected;
|
|
// for (int j=0; j<nConn; ++j) {
|
|
// if (nodes[i].connected[j].nConnected == 0) Assert(false);
|
|
// SplitComplexPolygonNode* connect = nodes[i].connected[j];
|
|
// bool found = false;
|
|
// for (int k=0; k<connect.nConnected; ++k) {
|
|
// if (connect.connected[k] == &nodes[i]) found = true;
|
|
// }
|
|
// Assert(found);
|
|
// }
|
|
// }*/
|
|
|
|
//Collapse duplicate points
|
|
bool foundDupe = true;
|
|
int nActive = nNodes;
|
|
double epsilonSquared = epsilon * epsilon;
|
|
while (foundDupe) {
|
|
foundDupe = false;
|
|
for (int i = 0; i < nNodes; ++i) {
|
|
if (nodes [i].NumConnected == 0) {
|
|
continue;
|
|
}
|
|
for (int j = i + 1; j < nNodes; ++j) {
|
|
if (nodes [j].NumConnected == 0) {
|
|
continue;
|
|
}
|
|
Point2D diff = nodes [i].Position - nodes [j].Position;
|
|
if (diff.MagnitudeSquared () <= epsilonSquared) {
|
|
if (nActive <= 3) {
|
|
throw new Exception ("Eliminated so many duplicate points that resulting polygon has < 3 vertices!");
|
|
}
|
|
|
|
//printf("Found dupe, %d left\n",nActive);
|
|
--nActive;
|
|
foundDupe = true;
|
|
SplitComplexPolygonNode inode = nodes [i];
|
|
SplitComplexPolygonNode jnode = nodes [j];
|
|
//Move all of j's connections to i, and remove j
|
|
int njConn = jnode.NumConnected;
|
|
for (int k = 0; k < njConn; ++k) {
|
|
SplitComplexPolygonNode knode = jnode [k];
|
|
//Debug.Assert(knode != jnode);
|
|
if (knode != inode) {
|
|
inode.AddConnection (knode);
|
|
knode.AddConnection (inode);
|
|
}
|
|
knode.RemoveConnection (jnode);
|
|
//printf("knode %d on node %d now has %d connections\n",k,j,knode.nConnected);
|
|
//printf("Found duplicate point.\n");
|
|
}
|
|
jnode.ClearConnections (); // to help with garbage collection
|
|
nodes.RemoveAt (j);
|
|
--nNodes;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// /*
|
|
// // Debugging: check for connection consistency
|
|
// for (int i=0; i<nNodes; ++i) {
|
|
// int nConn = nodes[i].nConnected;
|
|
// printf("Node %d has %d connections\n",i,nConn);
|
|
// for (int j=0; j<nConn; ++j) {
|
|
// if (nodes[i].connected[j].nConnected == 0) {
|
|
// printf("Problem with node %d connection at address %d\n",i,(int)(nodes[i].connected[j]));
|
|
// Assert(false);
|
|
// }
|
|
// SplitComplexPolygonNode* connect = nodes[i].connected[j];
|
|
// bool found = false;
|
|
// for (int k=0; k<connect.nConnected; ++k) {
|
|
// if (connect.connected[k] == &nodes[i]) found = true;
|
|
// }
|
|
// if (!found) printf("Connection %d (of %d) on node %d (of %d) did not have reciprocal connection.\n",j,nConn,i,nNodes);
|
|
// Assert(found);
|
|
// }
|
|
// }//*/
|
|
|
|
//Now walk the edge of the list
|
|
|
|
//Find node with minimum y value (max x if equal)
|
|
double minY = double.MaxValue;
|
|
double maxX = -double.MaxValue;
|
|
int minYIndex = -1;
|
|
for (int i = 0; i < nNodes; ++i) {
|
|
if (nodes [i].Position.Y < minY && nodes [i].NumConnected > 1) {
|
|
minY = nodes [i].Position.Y;
|
|
minYIndex = i;
|
|
maxX = nodes [i].Position.X;
|
|
} else if (nodes [i].Position.Y == minY && nodes [i].Position.X > maxX && nodes [i].NumConnected > 1) {
|
|
minYIndex = i;
|
|
maxX = nodes [i].Position.X;
|
|
}
|
|
}
|
|
|
|
Point2D origDir = new Point2D (1.0f, 0.0f);
|
|
List<Point2D> resultVecs = new List<Point2D> ();
|
|
SplitComplexPolygonNode currentNode = nodes [minYIndex];
|
|
SplitComplexPolygonNode startNode = currentNode;
|
|
//Debug.Assert(currentNode.nConnected > 0);
|
|
SplitComplexPolygonNode nextNode = currentNode.GetRightestConnection (origDir);
|
|
if (nextNode == null) {
|
|
// Borked, clean up our mess and return
|
|
return PolygonUtil.SplitComplexPolygonCleanup (verts);
|
|
}
|
|
|
|
resultVecs.Add (startNode.Position);
|
|
while (nextNode != startNode) {
|
|
if (resultVecs.Count > (4 * nNodes)) {
|
|
//printf("%d, %d, %d\n",(int)startNode,(int)currentNode,(int)nextNode);
|
|
//printf("%f, %f . %f, %f\n",currentNode.position.x,currentNode.position.y, nextNode.position.x, nextNode.position.y);
|
|
//verts.printFormatted();
|
|
//printf("Dumping connection graph: \n");
|
|
//for (int i=0; i<nNodes; ++i)
|
|
//{
|
|
// printf("nodex[%d] = %f; nodey[%d] = %f;\n",i,nodes[i].position.x,i,nodes[i].position.y);
|
|
// printf("//connected to\n");
|
|
// for (int j=0; j<nodes[i].nConnected; ++j)
|
|
// {
|
|
// printf("connx[%d][%d] = %f; conny[%d][%d] = %f;\n",i,j,nodes[i].connected[j].position.x, i,j,nodes[i].connected[j].position.y);
|
|
// }
|
|
//}
|
|
//printf("Dumping results thus far: \n");
|
|
//for (int i=0; i<nResultVecs; ++i)
|
|
//{
|
|
// printf("x[%d]=map(%f,-3,3,0,width); y[%d] = map(%f,-3,3,height,0);\n",i,resultVecs[i].x,i,resultVecs[i].y);
|
|
//}
|
|
//Debug.Assert(false);
|
|
//nodes should never be visited four times apiece (proof?), so we've probably hit a loop...crap
|
|
throw new Exception ("nodes should never be visited four times apiece (proof?), so we've probably hit a loop...crap");
|
|
}
|
|
resultVecs.Add (nextNode.Position);
|
|
SplitComplexPolygonNode oldNode = currentNode;
|
|
currentNode = nextNode;
|
|
//printf("Old node connections = %d; address %d\n",oldNode.nConnected, (int)oldNode);
|
|
//printf("Current node connections = %d; address %d\n",currentNode.nConnected, (int)currentNode);
|
|
nextNode = currentNode.GetRightestConnection (oldNode);
|
|
if (nextNode == null) {
|
|
return PolygonUtil.SplitComplexPolygonCleanup (resultVecs);
|
|
}
|
|
// There was a problem, so jump out of the loop and use whatever garbage we've generated so far
|
|
//printf("nextNode address: %d\n",(int)nextNode);
|
|
}
|
|
|
|
if (resultVecs.Count < 1) {
|
|
// Borked, clean up our mess and return
|
|
return PolygonUtil.SplitComplexPolygonCleanup (verts);
|
|
} else {
|
|
return PolygonUtil.SplitComplexPolygonCleanup (resultVecs);
|
|
}
|
|
}
|
|
|
|
private static List<Point2DList> SplitComplexPolygonCleanup (IList<Point2D> orig) {
|
|
List<Point2DList> l = new List<Point2DList> ();
|
|
Point2DList origP2DL = new Point2DList (orig);
|
|
l.Add (origP2DL);
|
|
int listIdx = 0;
|
|
int numLists = l.Count;
|
|
while (listIdx < numLists) {
|
|
int numPoints = l [listIdx].Count;
|
|
for (int i = 0; i < numPoints; ++i) {
|
|
for (int j = i + 1; j < numPoints; ++j) {
|
|
if (l [listIdx] [i].Equals (l [listIdx] [j], origP2DL.Epsilon)) {
|
|
// found a self-intersection loop - split it off into it's own list
|
|
int numToRemove = j - i;
|
|
Point2DList newList = new Point2DList ();
|
|
for (int k = i + 1; k <= j; ++k) {
|
|
newList.Add (l [listIdx] [k]);
|
|
}
|
|
l [listIdx].RemoveRange (i + 1, numToRemove);
|
|
l.Add (newList);
|
|
++numLists;
|
|
numPoints -= numToRemove;
|
|
j = i + 1;
|
|
}
|
|
}
|
|
}
|
|
l [listIdx].Simplify ();
|
|
++listIdx;
|
|
}
|
|
|
|
return l;
|
|
}
|
|
|
|
}
|
|
|
|
public class EdgeIntersectInfo {
|
|
public EdgeIntersectInfo (Edge edgeOne, Edge edgeTwo, Point2D intersectionPoint) {
|
|
EdgeOne = edgeOne;
|
|
EdgeTwo = edgeTwo;
|
|
IntersectionPoint = intersectionPoint;
|
|
}
|
|
|
|
public Edge EdgeOne { get; private set; }
|
|
|
|
public Edge EdgeTwo { get; private set; }
|
|
|
|
public Point2D IntersectionPoint { get; private set; }
|
|
}
|
|
|
|
public class SplitComplexPolygonNode {
|
|
/*
|
|
* Given sines and cosines, tells if A's angle is less than B's on -Pi, Pi
|
|
* (in other words, is A "righter" than B)
|
|
*/
|
|
private List<SplitComplexPolygonNode> mConnected = new List<SplitComplexPolygonNode> ();
|
|
private Point2D mPosition = null;
|
|
|
|
public int NumConnected { get { return mConnected.Count; } }
|
|
|
|
public Point2D Position { get { return mPosition; } set { mPosition = value; } }
|
|
|
|
public SplitComplexPolygonNode this [int index] {
|
|
get { return mConnected [index]; }
|
|
}
|
|
|
|
public SplitComplexPolygonNode () {
|
|
}
|
|
|
|
public SplitComplexPolygonNode (Point2D pos) {
|
|
mPosition = pos;
|
|
}
|
|
|
|
public override bool Equals (Object obj) {
|
|
SplitComplexPolygonNode pn = obj as SplitComplexPolygonNode;
|
|
if (pn == null) {
|
|
return base.Equals (obj);
|
|
}
|
|
|
|
return Equals (pn);
|
|
}
|
|
|
|
public bool Equals (SplitComplexPolygonNode pn) {
|
|
if ((Object)pn == null) {
|
|
return false;
|
|
}
|
|
if (mPosition == null || pn.Position == null) {
|
|
return false;
|
|
}
|
|
|
|
return mPosition.Equals (pn.Position);
|
|
}
|
|
|
|
public override int GetHashCode () {
|
|
return base.GetHashCode ();
|
|
}
|
|
|
|
public static bool operator == (SplitComplexPolygonNode lhs, SplitComplexPolygonNode rhs) {
|
|
if ((object)lhs != null) {
|
|
return lhs.Equals (rhs);
|
|
}
|
|
if ((Object)rhs == null) {
|
|
return true;
|
|
} else {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
public static bool operator != (SplitComplexPolygonNode lhs, SplitComplexPolygonNode rhs) {
|
|
if ((object)lhs != null) {
|
|
return !lhs.Equals (rhs);
|
|
}
|
|
if ((Object)rhs == null) {
|
|
return false;
|
|
} else {
|
|
return true;
|
|
}
|
|
}
|
|
|
|
public override string ToString () {
|
|
StringBuilder sb = new StringBuilder (256);
|
|
sb.Append (mPosition.ToString ());
|
|
sb.Append (" -> ");
|
|
for (int i = 0; i < NumConnected; ++i) {
|
|
if (i != 0) {
|
|
sb.Append (", ");
|
|
}
|
|
sb.Append (mConnected [i].Position.ToString ());
|
|
}
|
|
|
|
return sb.ToString ();
|
|
}
|
|
|
|
private bool IsRighter (double sinA, double cosA, double sinB, double cosB) {
|
|
if (sinA < 0) {
|
|
if (sinB > 0 || cosA <= cosB) {
|
|
return true;
|
|
} else {
|
|
return false;
|
|
}
|
|
} else {
|
|
if (sinB < 0 || cosA <= cosB) {
|
|
return false;
|
|
} else {
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
|
|
//Fix for obnoxious behavior for the % operator for negative numbers...
|
|
private int remainder (int x, int modulus) {
|
|
int rem = x % modulus;
|
|
while (rem < 0) {
|
|
rem += modulus;
|
|
}
|
|
return rem;
|
|
}
|
|
|
|
public void AddConnection (SplitComplexPolygonNode toMe) {
|
|
// Ignore duplicate additions
|
|
if (!mConnected.Contains (toMe) && toMe != this) {
|
|
mConnected.Add (toMe);
|
|
}
|
|
}
|
|
|
|
public void RemoveConnection (SplitComplexPolygonNode fromMe) {
|
|
mConnected.Remove (fromMe);
|
|
}
|
|
|
|
private void RemoveConnectionByIndex (int index) {
|
|
if (index < 0 || index >= mConnected.Count) {
|
|
return;
|
|
}
|
|
mConnected.RemoveAt (index);
|
|
}
|
|
|
|
public void ClearConnections () {
|
|
mConnected.Clear ();
|
|
}
|
|
|
|
private bool IsConnectedTo (SplitComplexPolygonNode me) {
|
|
return mConnected.Contains (me);
|
|
}
|
|
|
|
public SplitComplexPolygonNode GetRightestConnection (SplitComplexPolygonNode incoming) {
|
|
if (NumConnected == 0) {
|
|
throw new Exception ("the connection graph is inconsistent");
|
|
}
|
|
if (NumConnected == 1) {
|
|
//b2Assert(false);
|
|
// Because of the possibility of collapsing nearby points,
|
|
// we may end up with "spider legs" dangling off of a region.
|
|
// The correct behavior here is to turn around.
|
|
return incoming;
|
|
}
|
|
Point2D inDir = mPosition - incoming.mPosition;
|
|
|
|
double inLength = inDir.Magnitude ();
|
|
inDir.Normalize ();
|
|
|
|
if (inLength <= MathUtil.EPSILON) {
|
|
throw new Exception ("Length too small");
|
|
}
|
|
|
|
SplitComplexPolygonNode result = null;
|
|
for (int i = 0; i < NumConnected; ++i) {
|
|
if (mConnected [i] == incoming) {
|
|
continue;
|
|
}
|
|
Point2D testDir = mConnected [i].mPosition - mPosition;
|
|
double testLengthSqr = testDir.MagnitudeSquared ();
|
|
testDir.Normalize ();
|
|
/*
|
|
if (testLengthSqr < COLLAPSE_DIST_SQR) {
|
|
printf("Problem with connection %d\n",i);
|
|
printf("This node has %d connections\n",nConnected);
|
|
printf("That one has %d\n",connected[i].nConnected);
|
|
if (this == connected[i]) printf("This points at itself.\n");
|
|
}*/
|
|
if (testLengthSqr <= (MathUtil.EPSILON * MathUtil.EPSILON)) {
|
|
throw new Exception ("Length too small");
|
|
}
|
|
|
|
double myCos = Point2D.Dot (inDir, testDir);
|
|
double mySin = Point2D.Cross (inDir, testDir);
|
|
if (result != null) {
|
|
Point2D resultDir = result.mPosition - mPosition;
|
|
resultDir.Normalize ();
|
|
double resCos = Point2D.Dot (inDir, resultDir);
|
|
double resSin = Point2D.Cross (inDir, resultDir);
|
|
if (IsRighter (mySin, myCos, resSin, resCos)) {
|
|
result = mConnected [i];
|
|
}
|
|
} else {
|
|
result = mConnected [i];
|
|
}
|
|
}
|
|
|
|
//if (B2_POLYGON_REPORT_ERRORS && result != null)
|
|
//{
|
|
// printf("nConnected = %d\n", nConnected);
|
|
// for (int i = 0; i < nConnected; ++i)
|
|
// {
|
|
// printf("connected[%d] @ %d\n", i, (int)connected[i]);
|
|
// }
|
|
//}
|
|
//Debug.Assert(result != null);
|
|
|
|
return result;
|
|
}
|
|
|
|
public SplitComplexPolygonNode GetRightestConnection (Point2D incomingDir) {
|
|
Point2D diff = mPosition - incomingDir;
|
|
SplitComplexPolygonNode temp = new SplitComplexPolygonNode (diff);
|
|
SplitComplexPolygonNode res = GetRightestConnection (temp);
|
|
//Debug.Assert(res != null);
|
|
return res;
|
|
}
|
|
}
|
|
|
|
public class PolygonOperationContext {
|
|
public PolygonUtil.PolyOperation mOperations;
|
|
public Point2DList mOriginalPolygon1;
|
|
public Point2DList mOriginalPolygon2;
|
|
public Point2DList mPoly1;
|
|
public Point2DList mPoly2;
|
|
public List<EdgeIntersectInfo> mIntersections;
|
|
public int mStartingIndex;
|
|
public PolygonUtil.PolyUnionError mError;
|
|
public List<int> mPoly1VectorAngles;
|
|
public List<int> mPoly2VectorAngles;
|
|
public Dictionary<uint, Point2DList> mOutput = new Dictionary<uint, Point2DList> ();
|
|
|
|
public Point2DList Union {
|
|
get {
|
|
Point2DList l = null;
|
|
if (!mOutput.TryGetValue ((uint)PolygonUtil.PolyOperation.Union, out l)) {
|
|
l = new Point2DList ();
|
|
mOutput.Add ((uint)PolygonUtil.PolyOperation.Union, l);
|
|
}
|
|
|
|
return l;
|
|
}
|
|
}
|
|
|
|
public Point2DList Intersect {
|
|
get {
|
|
Point2DList l = null;
|
|
if (!mOutput.TryGetValue ((uint)PolygonUtil.PolyOperation.Intersect, out l)) {
|
|
l = new Point2DList ();
|
|
mOutput.Add ((uint)PolygonUtil.PolyOperation.Intersect, l);
|
|
}
|
|
|
|
return l;
|
|
}
|
|
}
|
|
|
|
public Point2DList Subtract {
|
|
get {
|
|
Point2DList l = null;
|
|
if (!mOutput.TryGetValue ((uint)PolygonUtil.PolyOperation.Subtract, out l)) {
|
|
l = new Point2DList ();
|
|
mOutput.Add ((uint)PolygonUtil.PolyOperation.Subtract, l);
|
|
}
|
|
|
|
return l;
|
|
}
|
|
}
|
|
|
|
public PolygonOperationContext () {
|
|
}
|
|
|
|
public void Clear () {
|
|
mOperations = PolygonUtil.PolyOperation.None;
|
|
mOriginalPolygon1 = null;
|
|
mOriginalPolygon2 = null;
|
|
mPoly1 = null;
|
|
mPoly2 = null;
|
|
mIntersections = null;
|
|
mStartingIndex = -1;
|
|
mError = PolygonUtil.PolyUnionError.None;
|
|
mPoly1VectorAngles = null;
|
|
mPoly2VectorAngles = null;
|
|
mOutput = new Dictionary<uint, Point2DList> ();
|
|
}
|
|
|
|
public bool Init (PolygonUtil.PolyOperation operations, Point2DList polygon1, Point2DList polygon2) {
|
|
Clear ();
|
|
|
|
mOperations = operations;
|
|
mOriginalPolygon1 = polygon1;
|
|
mOriginalPolygon2 = polygon2;
|
|
|
|
// Make a copy of the polygons so that we dont modify the originals, and
|
|
// force vertices to integer (pixel) values.
|
|
mPoly1 = new Point2DList (polygon1);
|
|
mPoly1.WindingOrder = Point2DList.WindingOrderType.Default;
|
|
mPoly2 = new Point2DList (polygon2);
|
|
mPoly2.WindingOrder = Point2DList.WindingOrderType.Default;
|
|
|
|
// Find intersection points
|
|
if (!VerticesIntersect (mPoly1, mPoly2, out mIntersections)) {
|
|
// No intersections found - polygons do not overlap.
|
|
mError = PolygonUtil.PolyUnionError.NoIntersections;
|
|
return false;
|
|
}
|
|
|
|
// make sure edges that intersect more than once are updated to have correct start points
|
|
int numIntersections = mIntersections.Count;
|
|
for (int i = 0; i < numIntersections; ++i) {
|
|
for (int j = i + 1; j < numIntersections; ++j) {
|
|
if (mIntersections [i].EdgeOne.EdgeStart.Equals (mIntersections [j].EdgeOne.EdgeStart) &&
|
|
mIntersections [i].EdgeOne.EdgeEnd.Equals (mIntersections [j].EdgeOne.EdgeEnd)) {
|
|
mIntersections [j].EdgeOne.EdgeStart = mIntersections [i].IntersectionPoint;
|
|
}
|
|
if (mIntersections [i].EdgeTwo.EdgeStart.Equals (mIntersections [j].EdgeTwo.EdgeStart) &&
|
|
mIntersections [i].EdgeTwo.EdgeEnd.Equals (mIntersections [j].EdgeTwo.EdgeEnd)) {
|
|
mIntersections [j].EdgeTwo.EdgeStart = mIntersections [i].IntersectionPoint;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Add intersection points to original polygons, ignoring existing points.
|
|
foreach (EdgeIntersectInfo intersect in mIntersections) {
|
|
if (!mPoly1.Contains (intersect.IntersectionPoint)) {
|
|
mPoly1.Insert (mPoly1.IndexOf (intersect.EdgeOne.EdgeStart) + 1, intersect.IntersectionPoint);
|
|
}
|
|
|
|
if (!mPoly2.Contains (intersect.IntersectionPoint)) {
|
|
mPoly2.Insert (mPoly2.IndexOf (intersect.EdgeTwo.EdgeStart) + 1, intersect.IntersectionPoint);
|
|
}
|
|
}
|
|
|
|
mPoly1VectorAngles = new List<int> ();
|
|
for (int i = 0; i < mPoly2.Count; ++i) {
|
|
mPoly1VectorAngles.Add (-1);
|
|
}
|
|
mPoly2VectorAngles = new List<int> ();
|
|
for (int i = 0; i < mPoly1.Count; ++i) {
|
|
mPoly2VectorAngles.Add (-1);
|
|
}
|
|
|
|
// Find starting point on the edge of polygon1 that is outside of
|
|
// the intersected area to begin polygon trace.
|
|
int currentIndex = 0;
|
|
do {
|
|
bool bPointInPolygonAngle = PointInPolygonAngle (mPoly1 [currentIndex], mPoly2);
|
|
mPoly2VectorAngles [currentIndex] = bPointInPolygonAngle ? 1 : 0;
|
|
if (bPointInPolygonAngle) {
|
|
mStartingIndex = currentIndex;
|
|
break;
|
|
}
|
|
currentIndex = mPoly1.NextIndex (currentIndex);
|
|
} while (currentIndex != 0);
|
|
|
|
// If we don't find a point on polygon1 thats outside of the
|
|
// intersect area, the polygon1 must be inside of polygon2,
|
|
// in which case, polygon2 IS the union of the two.
|
|
if (mStartingIndex == -1) {
|
|
mError = PolygonUtil.PolyUnionError.Poly1InsidePoly2;
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
|
|
/// <summary>
|
|
/// Check and return polygon intersections
|
|
/// </summary>
|
|
/// <param name="polygon1"></param>
|
|
/// <param name="polygon2"></param>
|
|
/// <param name="intersections"></param>
|
|
/// <returns></returns>
|
|
private bool VerticesIntersect (Point2DList polygon1, Point2DList polygon2, out List<EdgeIntersectInfo> intersections) {
|
|
intersections = new List<EdgeIntersectInfo> ();
|
|
double epsilon = Math.Min (polygon1.Epsilon, polygon2.Epsilon);
|
|
|
|
// Iterate through polygon1's edges
|
|
for (int i = 0; i < polygon1.Count; i++) {
|
|
// Get edge vertices
|
|
Point2D p1 = polygon1 [i];
|
|
Point2D p2 = polygon1 [polygon1.NextIndex (i)];
|
|
|
|
// Get intersections between this edge and polygon2
|
|
for (int j = 0; j < polygon2.Count; j++) {
|
|
Point2D point = new Point2D ();
|
|
|
|
Point2D p3 = polygon2 [j];
|
|
Point2D p4 = polygon2 [polygon2.NextIndex (j)];
|
|
|
|
// Check if the edges intersect
|
|
if (TriangulationUtil.LinesIntersect2D (p1, p2, p3, p4, ref point, epsilon)) {
|
|
// Rounding is not needed since we compare using an epsilon.
|
|
//// Here, we round the returned intersection point to its nearest whole number.
|
|
//// This prevents floating point anomolies where 99.9999-> is returned instead of 100.
|
|
//point = new Point2D((float)Math.Round(point.X, 0), (float)Math.Round(point.Y, 0));
|
|
// Record the intersection
|
|
intersections.Add (new EdgeIntersectInfo (new Edge (p1, p2), new Edge (p3, p4), point));
|
|
}
|
|
}
|
|
}
|
|
|
|
// true if any intersections were found.
|
|
return (intersections.Count > 0);
|
|
}
|
|
|
|
|
|
/// <summary>
|
|
/// * ref: http://ozviz.wasp.uwa.edu.au/~pbourke/geometry/insidepoly/ - Solution 2
|
|
/// * Compute the sum of the angles made between the test point and each pair of points making up the polygon.
|
|
/// * If this sum is 2pi then the point is an interior point, if 0 then the point is an exterior point.
|
|
/// </summary>
|
|
public bool PointInPolygonAngle (Point2D point, Point2DList polygon) {
|
|
double angle = 0;
|
|
|
|
// Iterate through polygon's edges
|
|
for (int i = 0; i < polygon.Count; i++) {
|
|
// Get points
|
|
Point2D p1 = polygon [i] - point;
|
|
Point2D p2 = polygon [polygon.NextIndex (i)] - point;
|
|
|
|
angle += VectorAngle (p1, p2);
|
|
}
|
|
|
|
if (Math.Abs (angle) < Math.PI) {
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
|
|
/// <summary>
|
|
/// Return the angle between two vectors on a plane
|
|
/// The angle is from vector 1 to vector 2, positive anticlockwise
|
|
/// The result is between -pi -> pi
|
|
/// </summary>
|
|
public double VectorAngle (Point2D p1, Point2D p2) {
|
|
double theta1 = Math.Atan2 (p1.Y, p1.X);
|
|
double theta2 = Math.Atan2 (p2.Y, p2.X);
|
|
double dtheta = theta2 - theta1;
|
|
while (dtheta > Math.PI) {
|
|
dtheta -= (2.0 * Math.PI);
|
|
}
|
|
while (dtheta < -Math.PI) {
|
|
dtheta += (2.0 * Math.PI);
|
|
}
|
|
|
|
return (dtheta);
|
|
}
|
|
|
|
}
|
|
|
|
}
|