/// <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); }
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); } }
/// <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]); } }
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); }
/// <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); }
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); } }