Exemplo n.º 1
0
        /// <summary>
        /// Return 1 if ccw -1 if cw
        /// </summary>
        /// <param name="polygon"></param>
        /// <returns></returns>
        public static int GetWindingDirection(this Polygon polygon)
        {
            int    pointCount = polygon.Count;
            double totalTurns = 0;

            for (int pointIndex = 0; pointIndex < pointCount; pointIndex++)
            {
                int      prevIndex    = (pointIndex + pointCount - 1) % pointCount;
                int      nextIndex    = (pointIndex + 1) % pointCount;
                IntPoint prevPoint    = polygon[prevIndex];
                IntPoint currentPoint = polygon[pointIndex];
                IntPoint nextPoint    = polygon[nextIndex];

                double turnAmount = currentPoint.GetTurnAmount(prevPoint, nextPoint);

                totalTurns += turnAmount;
            }

            return(totalTurns > 0 ? 1 : -1);
        }
Exemplo n.º 2
0
        public void CorrectSeamPlacement()
        {
            // coincident points return 0 angle
            {
                var p1 = new IntPoint(10, 0);
                var p2 = new IntPoint(0, 0);
                var p3 = new IntPoint(0, 0);
                Assert.IsTrue(p2.GetTurnAmount(p1, p3) == 0);
            }

            // no turn returns a 0 angle
            {
                var p1 = new IntPoint(10, 0);
                var p2 = new IntPoint(0, 0);
                var p3 = new IntPoint(-10, 0);
                Assert.IsTrue(p2.GetTurnAmount(p1, p3) == 0);
            }

            // 90 turn works
            {
                var p1 = new IntPoint(0, 0);
                var p2 = new IntPoint(10, 0);
                var p3 = new IntPoint(10, 10);
                Assert.AreEqual(p2.GetTurnAmount(p1, p3), Math.PI / 2, .001);

                var p4 = new IntPoint(0, 10);
                var p5 = new IntPoint(0, 0);
                var p6 = new IntPoint(10, 0);
                Assert.AreEqual(p5.GetTurnAmount(p4, p6), Math.PI / 2, .001);
            }

            // -90 turn works
            {
                var p1 = new IntPoint(0, 0);
                var p2 = new IntPoint(10, 0);
                var p3 = new IntPoint(10, -10);
                Assert.AreEqual(p2.GetTurnAmount(p1, p3), -Math.PI / 2, .001);
            }

            // 45 turn works
            {
                var p1 = new IntPoint(0, 0);
                var p2 = new IntPoint(10, 0);
                var p3 = new IntPoint(15, 5);
                Assert.AreEqual(Math.PI / 4, p2.GetTurnAmount(p1, p3), .001);

                var p4 = new IntPoint(0, 0);
                var p5 = new IntPoint(-10, 0);
                var p6 = new IntPoint(-15, -5);
                Assert.AreEqual(Math.PI / 4, p5.GetTurnAmount(p4, p6), .001);
            }

            // -45 turn works
            {
                var p1 = new IntPoint(0, 0);
                var p2 = new IntPoint(10, 0);
                var p3 = new IntPoint(15, -5);
                Assert.AreEqual(-Math.PI / 4, p2.GetTurnAmount(p1, p3), .001);
            }

            // find the right point wound ccw
            {
                // 4________3
                // |       /
                // |      /2
                // |      \
                // |0______\1
                var testPoints = new List <IntPoint> {
                    new IntPoint(0, 0), new IntPoint(100, 0), new IntPoint(70, 50), new IntPoint(100, 100), new IntPoint(0, 100)
                };
                int bestPoint = testPoints.FindGreatestTurnIndex();
                Assert.IsTrue(bestPoint == 2);
            }

            // find the right point wound ccw
            {
                // 3________2
                // |       |
                // |       |
                // |       |
                // |0______|1
                var testPoints = new List <IntPoint> {
                    new IntPoint(0, 0), new IntPoint(100, 0), new IntPoint(100, 100), new IntPoint(0, 100)
                };
                int bestPoint = testPoints.FindGreatestTurnIndex();
                Assert.IsTrue(bestPoint == 3);
            }

            // find closest greatest ccw
            {
                // 3________2
                // |       |
                // |       |
                // |       |
                // |0______|1
                var testPoints = new List <IntPoint> {
                    new IntPoint(0, 0), new IntPoint(100, 0), new IntPoint(100, 100), new IntPoint(0, 100)
                };
                int bestPoint = testPoints.FindGreatestTurnIndex(startPosition: new IntPoint(110, 110));
                Assert.IsTrue(bestPoint == 2);
            }

            // find the right point wound ccw
            {
                // 1________0
                // |       |
                // |       |
                // |       |
                // |2______|3
                var testPoints = new List <IntPoint> {
                    new IntPoint(100, 100), new IntPoint(0, 100), new IntPoint(0, 0), new IntPoint(100, 0)
                };
                int bestPoint = testPoints.FindGreatestTurnIndex();
                Assert.IsTrue(bestPoint == 1);
            }

            // find the right point wound cw
            {
                // 1________2
                // |       |
                // |       |
                // |       |
                // |0______|3
                var testPoints = new List <IntPoint> {
                    new IntPoint(0, 0), new IntPoint(0, 100), new IntPoint(100, 100), new IntPoint(100, 0)
                };
                int bestPoint = testPoints.FindGreatestTurnIndex();
                Assert.IsTrue(bestPoint == 1);                 // this is an inside perimeter so we place the seem to the front
            }

            // find the right point wound cw
            {
                // 0________1
                // |       |
                // |       |
                // |       |
                // |3______|2
                var testPoints = new List <IntPoint> {
                    new IntPoint(0, 100), new IntPoint(100, 100), new IntPoint(100, 0), new IntPoint(0, 0)
                };
                int bestPoint = testPoints.FindGreatestTurnIndex();
                Assert.IsTrue(bestPoint == 0);
            }

            // find the right point wound ccw
            {
                // 4________3
                // |       /
                // |      /2
                // |      \
                // |0______\1
                var testPoints = new List <IntPoint> {
                    new IntPoint(0, 0), new IntPoint(1000, 0), new IntPoint(900, 500), new IntPoint(1000, 1000), new IntPoint(0, 1000)
                };
                int bestPoint = testPoints.FindGreatestTurnIndex();
                // 2 is too shallow to have the seem
                Assert.IsTrue(bestPoint == 2);
            }

            // ccw shallow
            {
                // 2________1
                // |       /
                // |      /0
                // |      \
                // |3______\4
                var testPoints = new List <IntPoint> {
                    new IntPoint(90, 50), new IntPoint(100, 100), new IntPoint(0, 100), new IntPoint(0, 0), new IntPoint(100, 0)
                };
                int bestPoint = testPoints.FindGreatestTurnIndex();
                // 0 is too shallow to have the seem
                Assert.IsTrue(bestPoint == 0);
            }

            // ccw
            {
                // 2________1
                // |       /
                // |      /0
                // |      \
                // |3______\4
                var testPoints = new List <IntPoint> {
                    new IntPoint(90, 50), new IntPoint(200, 100), new IntPoint(0, 100), new IntPoint(0, 0), new IntPoint(200, 0)
                };
                int bestPoint = testPoints.FindGreatestTurnIndex();
                Assert.IsTrue(bestPoint == 0);
            }

            // ccw
            {
                // 2________1
                //  \      /
                //   \3   /0
                //   /    \
                //  /4_____\5
                var testPoints = new List <IntPoint> {
                    new IntPoint(90, 50), new IntPoint(100, 100), new IntPoint(0, 100), new IntPoint(10, 50), new IntPoint(0, 0), new IntPoint(100, 0)
                };
                int bestPoint = testPoints.FindGreatestTurnIndex();
                // 3 is too shallow to have the seem
                Assert.IsTrue(bestPoint == 0);
            }

            // ccw
            {
                // 2________1
                //  \      /
                //   \3   /0
                //   /    \
                //  /4_____\5
                var testPoints = new List <IntPoint> {
                    new IntPoint(90, 50), new IntPoint(100, 100), new IntPoint(0, 100), new IntPoint(10, 50), new IntPoint(0, 0), new IntPoint(100, 0)
                };
                int bestPoint = testPoints.FindGreatestTurnIndex(startPosition: new IntPoint(95, 50));
                // 3 is too shallow to have the seem
                Assert.IsTrue(bestPoint == 0);
            }

            // ccw
            {
                // 2________1
                //  \      /
                //   \3   /0
                //   /    \
                //  /4_____\5
                var testPoints = new List <IntPoint> {
                    new IntPoint(55, 50), new IntPoint(100, 100), new IntPoint(0, 100), new IntPoint(45, 50), new IntPoint(0, 0), new IntPoint(100, 0)
                };
                int bestPoint = testPoints.FindGreatestTurnIndex();
                Assert.IsTrue(bestPoint == 3);
            }

            // ccw
            {
                // 2________1
                //  \      /
                //   \3   /0 less angle
                //   /    \
                //  /4_____\5
                var testPoints = new List <IntPoint> {
                    new IntPoint(950, 500), new IntPoint(1000, 1000), new IntPoint(0, 1000), new IntPoint(100, 500), new IntPoint(0, 0), new IntPoint(1000, 0)
                };
                int bestPoint = testPoints.FindGreatestTurnIndex();
                // 2 is too shallow to have the seem
                Assert.IsTrue(bestPoint == 3);
            }

            // ccw
            {
                // 2________1
                //  \      /
                //   \3   /0 more angle
                //   /    \
                //  /4_____\5
                var testPoints = new List <IntPoint> {
                    new IntPoint(550, 500), new IntPoint(1000, 1000), new IntPoint(0, 1000), new IntPoint(100, 500), new IntPoint(0, 0), new IntPoint(1000, 0)
                };
                int bestPoint = testPoints.FindGreatestTurnIndex();
                Assert.IsTrue(bestPoint == 0);
            }

            // ccw
            {
                // 5________4
                //  \      /
                //   \0   /3
                //   /    \
                //  /1_____\2
                var testPoints = new List <IntPoint> {
                    new IntPoint(10, 50), new IntPoint(0, 0), new IntPoint(100, 0), new IntPoint(90, 50), new IntPoint(100, 100), new IntPoint(0, 100),
                };
                int bestPoint = testPoints.FindGreatestTurnIndex();
                // 0 is too shallow
                Assert.IsTrue(bestPoint == 3);
            }

            // ccw
            {
                // 5________4
                //  \      /
                //   \0   /3
                //   /    \
                //  /1_____\2
                var testPoints = new List <IntPoint> {
                    new IntPoint(45, 50), new IntPoint(0, 0), new IntPoint(100, 0), new IntPoint(55, 50), new IntPoint(100, 100), new IntPoint(0, 100),
                };
                int bestPoint = testPoints.FindGreatestTurnIndex();
                Assert.IsTrue(bestPoint == 3);
            }

            // find the right point wound cw (inside hole loops)
            {
                // 1________2
                // |       /
                // |      /3
                // |      \
                // |0______\4
                var testPoints = new List <IntPoint> {
                    new IntPoint(0, 0), new IntPoint(0, 100), new IntPoint(100, 100), new IntPoint(90, 50), new IntPoint(100, 0)
                };
                int bestPoint = testPoints.FindGreatestTurnIndex();
                Assert.IsTrue(bestPoint == 4);                 // everything over 90 degrees is treated the same so the front left is the best
            }

            // find the right point wound cw
            {
                // 2________3
                // |       /
                // |      /4
                // |      \
                // |1______\0
                var testPoints = new List <IntPoint> {
                    new IntPoint(100, 0), new IntPoint(0, 0), new IntPoint(0, 100), new IntPoint(100, 100), new IntPoint(90, 50)
                };
                int bestPoint = testPoints.FindGreatestTurnIndex();
                Assert.IsTrue(bestPoint == 0);
            }

            // cw
            {
                // 4________5
                //  \      /
                //   \3   /0
                //   /    \
                //  /2_____\1
                var testPoints = new List <IntPoint>
                {
                    new IntPoint(90, 50), new IntPoint(100, 0), new IntPoint(0, 0), new IntPoint(10, 50), new IntPoint(0, 100), new IntPoint(100, 100)
                };
                int bestPoint = testPoints.FindGreatestTurnIndex();
                Assert.IsTrue(bestPoint == 4);
            }

            // cw
            {
                // 1________2
                //  \      /
                //   \0   /3
                //   /    \
                //  /5_____\4
                var testPoints = new List <IntPoint>
                {
                    new IntPoint(10, 50), new IntPoint(0, 100), new IntPoint(100, 100), new IntPoint(90, 50), new IntPoint(100, 0), new IntPoint(0, 0),
                };
                int bestPoint = testPoints.FindGreatestTurnIndex();
                Assert.IsTrue(bestPoint == 4);
            }
        }
Exemplo n.º 3
0
        /// <summary>
        /// This will find the largest turn in a given models. It prefers concave turns to convex turns.
        /// If turn amount is the same bias towards the smallest y position.
        /// </summary>
        /// <param name="inputPolygon"></param>
        /// <param name="considerAsSameY">Range to treat y positions as the same value.</param>
        /// <returns></returns>
        public static IntPoint FindGreatestTurnPosition(this Polygon inputPolygon, long considerAsSameY, int layerIndex, IntPoint?startPosition = null)
        {
            Polygon currentPolygon = Clipper.CleanPolygon(inputPolygon, considerAsSameY / 8);

            // collect & bucket options and then choose the closest
            if (currentPolygon.Count == 0)
            {
                return(inputPolygon[0]);
            }

            double         totalTurns    = 0;
            CandidateGroup positiveGroup = new CandidateGroup(DegreesToRadians(35));
            CandidateGroup negativeGroup = new CandidateGroup(DegreesToRadians(10));

            IntPoint currentFurthestBack = new IntPoint(long.MaxValue, long.MinValue);
            int      furthestBackIndex   = 0;

            double minTurnToChoose = DegreesToRadians(1);
            long   minSegmentLengthToConsiderSquared = 50 * 50;

            int pointCount = currentPolygon.Count;

            for (int pointIndex = 0; pointIndex < pointCount; pointIndex++)
            {
                int      prevIndex    = ((pointIndex + pointCount - 1) % pointCount);
                int      nextIndex    = ((pointIndex + 1) % pointCount);
                IntPoint prevPoint    = currentPolygon[prevIndex];
                IntPoint currentPoint = currentPolygon[pointIndex];
                IntPoint nextPoint    = currentPolygon[nextIndex];

                if (currentPoint.Y >= currentFurthestBack.Y)
                {
                    if (currentPoint.Y > currentFurthestBack.Y ||
                        currentPoint.X < currentFurthestBack.X)
                    {
                        furthestBackIndex   = pointIndex;
                        currentFurthestBack = currentPoint;
                    }
                }

                long lengthPrevToCurSquared = (prevPoint - currentPoint).LengthSquared();
                long lengthCurToNextSquared = (nextPoint - currentPoint).LengthSquared();
                bool distanceLongeEnough    = lengthCurToNextSquared > minSegmentLengthToConsiderSquared && lengthPrevToCurSquared > minSegmentLengthToConsiderSquared;

                double turnAmount = currentPoint.GetTurnAmount(prevPoint, nextPoint);

                totalTurns += turnAmount;

                if (turnAmount < 0)
                {
                    // threshold angles, don't pick angles that are too shallow
                    // threshold line lengths, don't pick big angles hiding in TINY lines
                    if (Math.Abs(turnAmount) > minTurnToChoose &&
                        distanceLongeEnough)
                    {
                        negativeGroup.ConditionalAdd(new CandidatePoint(turnAmount, pointIndex, currentPoint));
                    }
                }
                else
                {
                    if (Math.Abs(turnAmount) > minTurnToChoose &&
                        distanceLongeEnough)
                    {
                        positiveGroup.ConditionalAdd(new CandidatePoint(turnAmount, pointIndex, currentPoint));
                    }
                }
            }

            if (negativeGroup.Count > 0)
            {
                if (positiveGroup.Count > 0
                    // the negative group is a small turn and the positive group is a big turn
                    && ((Math.Abs(negativeGroup[0].turnAmount) < Math.PI / 4 &&
                         Math.Abs(positiveGroup[0].turnAmount) > Math.PI / 4)
                        // the negative turn amount is very small
                        || Math.Abs(negativeGroup[0].turnAmount) < Math.PI / 8))
                {
                    // return the positive rather than the negative turn
                    return(currentPolygon[positiveGroup.GetBestIndex(layerIndex, startPosition)]);
                }

                return(currentPolygon[negativeGroup.GetBestIndex(layerIndex, startPosition)]);
            }
            else if (positiveGroup.Count > 0)
            {
                return(currentPolygon[positiveGroup.GetBestIndex(layerIndex, startPosition)]);
            }
            else
            {
                // If can't find good candidate go with vertex most in a single direction
                return(currentPolygon[furthestBackIndex]);
            }
        }
Exemplo n.º 4
0
        public PathFinder(Polygons outlinePolygons,
                          long avoidInset,
                          IntRect?stayInsideBounds = null,
                          bool useInsideCache      = true,
                          string name = "")
        {
            this.Name = name;
            if (outlinePolygons.Count == 0)
            {
                return;
            }

            // Check if the outline is convex and no holes, if it is, don't create pathing data we can move anywhere in this object
            if (outlinePolygons.Count == 1)
            {
                var    currentPolygon = outlinePolygons[0];
                int    pointCount     = currentPolygon.Count;
                double negativeTurns  = 0;
                double positiveTurns  = 0;
                for (int pointIndex = 0; pointIndex < pointCount; pointIndex++)
                {
                    int      prevIndex    = (pointIndex + pointCount - 1) % pointCount;
                    int      nextIndex    = (pointIndex + 1) % pointCount;
                    IntPoint prevPoint    = currentPolygon[prevIndex];
                    IntPoint currentPoint = currentPolygon[pointIndex];
                    IntPoint nextPoint    = currentPolygon[nextIndex];

                    double turnAmount = currentPoint.GetTurnAmount(prevPoint, nextPoint);

                    if (turnAmount < 0)
                    {
                        negativeTurns += turnAmount;
                    }
                    else
                    {
                        positiveTurns += turnAmount;
                    }
                }

                if (positiveTurns == 0 || negativeTurns == 0)
                {
                    // all the turns are the same way this thing is convex
                    IsSimpleConvex = true;
                }
            }

            InsetAmount = avoidInset;

            var outsidePolygons = FixWinding(outlinePolygons);

            outsidePolygons = Clipper.CleanPolygons(outsidePolygons, InsetAmount / 60);
            if (stayInsideBounds != null)
            {
                var boundary = stayInsideBounds.Value;
                outsidePolygons.Add(new Polygon()
                {
                    new IntPoint(boundary.minX, boundary.minY),
                    new IntPoint(boundary.maxX, boundary.minY),
                    new IntPoint(boundary.maxX, boundary.maxY),
                    new IntPoint(boundary.minX, boundary.maxY),
                });

                outsidePolygons = FixWinding(outsidePolygons);
            }

            // set it to 1/4 the inset amount
            int devisor = 4;

            OutlineData = new PathingData(outsidePolygons, Math.Abs(avoidInset / devisor), useInsideCache);
        }
Exemplo n.º 5
0
        /// <summary>
        /// This will find the largest turn in a given models. It preffers concave turns to convex turns.
        /// If turn amount is the same bias towards the smallest y position.
        /// </summary>
        /// <param name="inputPolygon"></param>
        /// <param name="considerAsSameY">Range to treat y positions as the same value.</param>
        /// <returns></returns>
        public static IntPoint FindGreatestTurnPosition(this Polygon inputPolygon, long considerAsSameY)
        {
            IntPoint currentFurthestBackActual = new IntPoint(long.MaxValue, long.MinValue);
            {
                int actualFurthestBack = 0;
                for (int pointIndex = 0; pointIndex < inputPolygon.Count; pointIndex++)
                {
                    IntPoint currentPoint = inputPolygon[pointIndex];

                    if (currentPoint.Y >= currentFurthestBackActual.Y)
                    {
                        if (currentPoint.Y > currentFurthestBackActual.Y ||
                            currentPoint.X < currentFurthestBackActual.X)
                        {
                            actualFurthestBack        = pointIndex;
                            currentFurthestBackActual = currentPoint;
                        }
                    }
                }
            }

            Polygon currentPolygon = Clipper.CleanPolygon(inputPolygon, considerAsSameY / 4);

            // collect & bucket options and then choose the closest
            if (currentPolygon.Count == 0)
            {
                return(inputPolygon[0]);
            }

            double         totalTurns    = 0;
            CandidateGroup positiveGroup = new CandidateGroup(DegreesToRadians(35));
            CandidateGroup negativeGroup = new CandidateGroup(DegreesToRadians(10));

            IntPoint currentFurthestBack = new IntPoint(long.MaxValue, long.MinValue);
            int      furthestBackIndex   = 0;

            double minTurnToChoose = DegreesToRadians(1);
            long   minSegmentLengthToConsiderSquared = 50 * 50;

            int pointCount = currentPolygon.Count;

            for (int pointIndex = 0; pointIndex < pointCount; pointIndex++)
            {
                int      prevIndex    = ((pointIndex + pointCount - 1) % pointCount);
                int      nextIndex    = ((pointIndex + 1) % pointCount);
                IntPoint prevPoint    = currentPolygon[prevIndex];
                IntPoint currentPoint = currentPolygon[pointIndex];
                IntPoint nextPoint    = currentPolygon[nextIndex];

                if (currentPoint.Y >= currentFurthestBack.Y)
                {
                    if (currentPoint.Y > currentFurthestBack.Y ||
                        currentPoint.X < currentFurthestBack.X)
                    {
                        furthestBackIndex   = pointIndex;
                        currentFurthestBack = currentPoint;
                    }
                }

                long lengthPrevToCurSquared = (prevPoint - currentPoint).LengthSquared();
                long lengthCurToNextSquared = (nextPoint - currentPoint).LengthSquared();
                bool distanceLongeEnough    = lengthCurToNextSquared > minSegmentLengthToConsiderSquared && lengthPrevToCurSquared > minSegmentLengthToConsiderSquared;

                double turnAmount = currentPoint.GetTurnAmount(prevPoint, nextPoint);

                totalTurns += turnAmount;

                if (turnAmount < 0)
                {
                    // threshold angles, don't pick angles that are too shallow
                    // threshold line lengths, don't pick big angles hiding in TINY lines
                    if (Math.Abs(turnAmount) > minTurnToChoose &&
                        distanceLongeEnough)
                    {
                        negativeGroup.ConditionalAdd(new CandidatePoint(turnAmount, pointIndex, currentPoint));
                    }
                }
                else
                {
                    if (Math.Abs(turnAmount) > minTurnToChoose &&
                        distanceLongeEnough)
                    {
                        positiveGroup.ConditionalAdd(new CandidatePoint(turnAmount, pointIndex, currentPoint));
                    }
                }
            }

            IntPoint positionToReturn = new IntPoint();

            if (totalTurns > 0)             // ccw
            {
                if (negativeGroup.Count > 0)
                {
                    positionToReturn = currentPolygon[negativeGroup.BestIndex];
                }
                else if (positiveGroup.Count > 0)
                {
                    positionToReturn = currentPolygon[positiveGroup.BestIndex];
                }
                else
                {
                    // If can't find good candidate go with vertex most in a single direction
                    positionToReturn = currentPolygon[furthestBackIndex];
                }
            }
            else             // cw
            {
                if (negativeGroup.Count > 0)
                {
                    positionToReturn = currentPolygon[negativeGroup.BestIndex];
                }
                else if (positiveGroup.Count > 0)
                {
                    positionToReturn = currentPolygon[positiveGroup.BestIndex];
                }
                else
                {
                    // If can't find good candidate go with vertex most in a single direction
                    positionToReturn = currentPolygon[furthestBackIndex];
                }
            }

            if (Math.Abs(currentFurthestBackActual.Y - positionToReturn.Y) < considerAsSameY)
            {
                return(currentFurthestBackActual);
            }

            return(positionToReturn);
        }
Exemplo n.º 6
0
        public void CorrectSeamPlacement()
        {
            // coincident points return 0 angle
            {
                IntPoint p1 = new IntPoint(10, 0);
                IntPoint p2 = new IntPoint(0, 0);
                IntPoint p3 = new IntPoint(0, 0);
                Assert.IsTrue(p2.GetTurnAmount(p1, p3) == 0);
            }

            // no turn returns a 0 angle
            {
                IntPoint p1 = new IntPoint(10, 0);
                IntPoint p2 = new IntPoint(0, 0);
                IntPoint p3 = new IntPoint(-10, 0);
                Assert.IsTrue(p2.GetTurnAmount(p1, p3) == 0);
            }

            // 90 turn works
            {
                IntPoint p1 = new IntPoint(0, 0);
                IntPoint p2 = new IntPoint(10, 0);
                IntPoint p3 = new IntPoint(10, 10);
                Assert.AreEqual(p2.GetTurnAmount(p1, p3), Math.PI / 2, .001);

                IntPoint p4 = new IntPoint(0, 10);
                IntPoint p5 = new IntPoint(0, 0);
                IntPoint p6 = new IntPoint(10, 0);
                Assert.AreEqual(p5.GetTurnAmount(p4, p6), Math.PI / 2, .001);
            }

            // -90 turn works
            {
                IntPoint p1 = new IntPoint(0, 0);
                IntPoint p2 = new IntPoint(10, 0);
                IntPoint p3 = new IntPoint(10, -10);
                Assert.AreEqual(p2.GetTurnAmount(p1, p3), -Math.PI / 2, .001);
            }

            // 45 turn works
            {
                IntPoint p1 = new IntPoint(0, 0);
                IntPoint p2 = new IntPoint(10, 0);
                IntPoint p3 = new IntPoint(15, 5);
                Assert.AreEqual(Math.PI / 4, p2.GetTurnAmount(p1, p3), .001);

                IntPoint p4 = new IntPoint(0, 0);
                IntPoint p5 = new IntPoint(-10, 0);
                IntPoint p6 = new IntPoint(-15, -5);
                Assert.AreEqual(Math.PI / 4, p5.GetTurnAmount(p4, p6), .001);
            }

            // -45 turn works
            {
                IntPoint p1 = new IntPoint(0, 0);
                IntPoint p2 = new IntPoint(10, 0);
                IntPoint p3 = new IntPoint(15, -5);
                Assert.AreEqual(-Math.PI / 4, p2.GetTurnAmount(p1, p3), .001);
            }

            // find the right point wound ccw
            {
                // 4________3
                // |       /
                // |      /2
                // |      \
                // |0______\1
                List <IntPoint> testPoints = new List <IntPoint> {
                    new IntPoint(0, 0), new IntPoint(100, 0), new IntPoint(70, 50), new IntPoint(100, 100), new IntPoint(0, 100)
                };
                int bestPoint = testPoints.FindGreatestTurnIndex();
                Assert.IsTrue(bestPoint == 2);
            }

            // find the right point wound ccw
            {
                // 3________2
                // |       |
                // |       |
                // |       |
                // |0______|1
                List <IntPoint> testPoints = new List <IntPoint> {
                    new IntPoint(0, 0), new IntPoint(100, 0), new IntPoint(100, 100), new IntPoint(0, 100)
                };
                int bestPoint = testPoints.FindGreatestTurnIndex();
                Assert.IsTrue(bestPoint == 3);
            }

            // find the right point wound ccw
            {
                // 1________0
                // |       |
                // |       |
                // |       |
                // |2______|3
                List <IntPoint> testPoints = new List <IntPoint> {
                    new IntPoint(100, 100), new IntPoint(0, 100), new IntPoint(0, 0), new IntPoint(100, 0)
                };
                int bestPoint = testPoints.FindGreatestTurnIndex();
                Assert.IsTrue(bestPoint == 1);
            }

            // find the right point wound cw
            {
                // 1________2
                // |       |
                // |       |
                // |       |
                // |0______|3
                List <IntPoint> testPoints = new List <IntPoint> {
                    new IntPoint(0, 0), new IntPoint(0, 100), new IntPoint(100, 100), new IntPoint(100, 0)
                };
                int bestPoint = testPoints.FindGreatestTurnIndex();
                Assert.IsTrue(bestPoint == 1);
            }

            // find the right point wound cw
            {
                // 0________1
                // |       |
                // |       |
                // |       |
                // |3______|2
                List <IntPoint> testPoints = new List <IntPoint> {
                    new IntPoint(0, 100), new IntPoint(100, 100), new IntPoint(100, 0), new IntPoint(0, 0)
                };
                int bestPoint = testPoints.FindGreatestTurnIndex();
                Assert.IsTrue(bestPoint == 0);
            }

            // find the right point wound ccw
            {
                // 4________3
                // |       /
                // |      /2
                // |      \
                // |0______\1
                List <IntPoint> testPoints = new List <IntPoint> {
                    new IntPoint(0, 0), new IntPoint(1000, 0), new IntPoint(900, 500), new IntPoint(1000, 1000), new IntPoint(0, 1000)
                };
                int bestPoint = testPoints.FindGreatestTurnIndex();
                Assert.IsTrue(bestPoint == 2);
            }

            // ccw
            {
                // 2________1
                // |       /
                // |      /0
                // |      \
                // |3______\4
                List <IntPoint> testPoints = new List <IntPoint> {
                    new IntPoint(90, 50), new IntPoint(100, 100), new IntPoint(0, 100), new IntPoint(0, 0), new IntPoint(100, 0)
                };
                int bestPoint = testPoints.FindGreatestTurnIndex();
                Assert.IsTrue(bestPoint == 0);
            }

            // ccw
            {
                // 2________1
                //  \      /
                //   \3   /0
                //   /    \
                //  /4_____\5
                List <IntPoint> testPoints = new List <IntPoint> {
                    new IntPoint(90, 50), new IntPoint(100, 100), new IntPoint(0, 100), new IntPoint(10, 50), new IntPoint(0, 0), new IntPoint(100, 0)
                };
                int bestPoint = testPoints.FindGreatestTurnIndex();
                Assert.IsTrue(bestPoint == 3);
            }

            // ccw
            {
                // 2________1
                //  \      /
                //   \3   /0 less angle
                //   /    \
                //  /4_____\5
                List <IntPoint> testPoints = new List <IntPoint> {
                    new IntPoint(950, 500), new IntPoint(1000, 1000), new IntPoint(0, 1000), new IntPoint(100, 500), new IntPoint(0, 0), new IntPoint(1000, 0)
                };
                int bestPoint = testPoints.FindGreatestTurnIndex();
                Assert.IsTrue(bestPoint == 3);
            }

            // ccw
            {
                // 2________1
                //  \      /
                //   \3   /0 more angle
                //   /    \
                //  /4_____\5
                List <IntPoint> testPoints = new List <IntPoint> {
                    new IntPoint(550, 500), new IntPoint(1000, 1000), new IntPoint(0, 1000), new IntPoint(100, 500), new IntPoint(0, 0), new IntPoint(1000, 0)
                };
                int bestPoint = testPoints.FindGreatestTurnIndex();
                Assert.IsTrue(bestPoint == 0);
            }

            // ccw
            {
                // 5________4
                //  \      /
                //   \0   /3
                //   /    \
                //  /1_____\2
                List <IntPoint> testPoints = new List <IntPoint> {
                    new IntPoint(10, 50), new IntPoint(0, 0), new IntPoint(100, 0), new IntPoint(90, 50), new IntPoint(100, 100), new IntPoint(0, 100),
                };
                int bestPoint = testPoints.FindGreatestTurnIndex();
                Assert.IsTrue(bestPoint == 0);
            }

            // find the right point wound cw (inside hole loops)
            {
                // 1________2
                // |       /
                // |      /3
                // |      \
                // |0______\4
                List <IntPoint> testPoints = new List <IntPoint> {
                    new IntPoint(0, 0), new IntPoint(0, 100), new IntPoint(100, 100), new IntPoint(90, 50), new IntPoint(100, 0)
                };
                int bestPoint = testPoints.FindGreatestTurnIndex();
                Assert.IsTrue(bestPoint == 1);
            }

            // find the right point wound cw
            {
                // 2________3
                // |       /
                // |      /4
                // |      \
                // |1______\0
                List <IntPoint> testPoints = new List <IntPoint> {
                    new IntPoint(100, 0), new IntPoint(0, 0), new IntPoint(0, 100), new IntPoint(100, 100), new IntPoint(90, 50)
                };
                int bestPoint = testPoints.FindGreatestTurnIndex();
                Assert.IsTrue(bestPoint == 2);
            }

            // cw
            {
                // 4________5
                //  \      /
                //   \3   /0
                //   /    \
                //  /2_____\1
                List <IntPoint> testPoints = new List <IntPoint>
                {
                    new IntPoint(90, 50), new IntPoint(100, 0), new IntPoint(0, 0), new IntPoint(10, 50), new IntPoint(0, 100), new IntPoint(100, 100)
                };
                int bestPoint = testPoints.FindGreatestTurnIndex();
                Assert.IsTrue(bestPoint == 4);
            }

            // cw
            {
                // 1________2
                //  \      /
                //   \0   /3
                //   /    \
                //  /5_____\4
                List <IntPoint> testPoints = new List <IntPoint>
                {
                    new IntPoint(10, 50), new IntPoint(0, 100), new IntPoint(100, 100), new IntPoint(90, 50), new IntPoint(100, 0), new IntPoint(0, 0),
                };
                int bestPoint = testPoints.FindGreatestTurnIndex();
                Assert.IsTrue(bestPoint == 1);
            }
        }