private static void DiscoverAndAddTurns(Polygon inputPolygon, long neighborhood, CandidateGroup candidateGroup, Func <double, bool> validDelta) { var polygon2 = inputPolygon.Select(i => new Vector2(i.X, i.Y)).ToList(); var count = inputPolygon.Count; for (var i = 0; i < count; i++) { var position = polygon2[i]; var prevPosition = polygon2[(count + i - 1) % count]; var nextPosition = polygon2[(count + i + 1) % count]; var angle = position.GetTurnAmount(prevPosition, nextPosition); var lengthToPoint = polygon2.LengthTo(i); var leftPosition = polygon2.GetPositionAt(lengthToPoint - neighborhood); var rightPosition = polygon2.GetPositionAt(lengthToPoint + neighborhood); var nearAngle = position.GetTurnAmount(leftPosition, rightPosition); var directionNormal = (rightPosition - leftPosition).GetNormal().GetPerpendicularRight(); var delta = Vector2.Dot(directionNormal, position - leftPosition); var currentPoint = inputPolygon[i]; if (validDelta(delta)) { candidateGroup.ConditionalAdd(new CandidatePoint(delta, i, currentPoint)); } } }
/// <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 static IntPoint GetBestPosition(Polygon inputPolygon, long lineWidth) { 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, lineWidth / 4); // TODO: other considerations // 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 = GetTurnAmount(prevPoint, currentPoint, 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) < lineWidth) { return(currentFurthestBackActual); } return(positionToReturn); }