/// <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">The polygon to analyze</param> /// <param name="considerAsSameY">Range to treat y positions as the same value.</param> /// <param name="startPosition">If two or more angles are similar, choose the one close to the start</param> /// <returns>The position that has the largest turn angle</returns> public static int FindGreatestTurnIndex(this Polygon inputPolygon, long extrusionWidth_um = 3, IntPoint?startPosition = null) { var count = inputPolygon.Count; var positiveGroup = new CandidateGroup(extrusionWidth_um); var negativeGroup = new CandidateGroup(extrusionWidth_um / 4); // look for relatively big concave turns DiscoverAndAddTurns(inputPolygon, extrusionWidth_um * 4, negativeGroup, delta => delta < -extrusionWidth_um / 2); if (negativeGroup.Count == 0) { // look for relatively big convex turns DiscoverAndAddTurns(inputPolygon, extrusionWidth_um * 4, positiveGroup, delta => delta > extrusionWidth_um * 2); if (positiveGroup.Count == 0) { negativeGroup.SameDelta = extrusionWidth_um / 16; // look for small concave turns DiscoverAndAddTurns(inputPolygon, extrusionWidth_um, negativeGroup, delta => delta < -extrusionWidth_um / 8); } } if (negativeGroup.Count > 0) { return(negativeGroup.GetBestIndex(startPosition)); } else if (positiveGroup.Count > 0) { return(positiveGroup.GetBestIndex(startPosition)); } else { var currentFurthestBack = new IntPoint(long.MaxValue, long.MinValue); int furthestBackIndex = 0; // get the furthest back index for (var i = 0; i < count; i++) { var currentPoint = inputPolygon[i]; if (currentPoint.Y >= currentFurthestBack.Y) { if (currentPoint.Y > currentFurthestBack.Y || currentPoint.X < currentFurthestBack.X) { furthestBackIndex = i; currentFurthestBack = currentPoint; } } } // If can't find good candidate go with vertex most in a single direction return(furthestBackIndex); } }
/// <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]); } }
/// <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">The polygon to analyze</param> /// <param name="considerAsSameY">Range to treat y positions as the same value.</param> /// <param name="startPosition">If two or more angles are similar, choose the one close to the start</param> /// <returns>The position that has the largest turn angle</returns> public static int FindGreatestTurnIndex(this Polygon inputPolygon, int layerIndex = 0, long extrusionWidth_um = 3, SEAM_PLACEMENT seamPlacement = SEAM_PLACEMENT.FURTHEST_BACK, IntPoint?startPosition = null) { var count = inputPolygon.Count; if (seamPlacement == SEAM_PLACEMENT.ALWAYS_CENTERED_IN_BACK) { return(inputPolygon.GetCenteredInBackIndex(out _)); } var positiveGroup = new CandidateGroup(extrusionWidth_um); var negativeGroup = new CandidateGroup(extrusionWidth_um / 4); // look for relatively big concave turns DiscoverAndAddTurns(inputPolygon, extrusionWidth_um * 4, negativeGroup, delta => delta < -extrusionWidth_um / 2); if (negativeGroup.Count == 0) { // look for relatively big convex turns DiscoverAndAddTurns(inputPolygon, extrusionWidth_um * 4, positiveGroup, delta => delta > extrusionWidth_um * 2); if (positiveGroup.Count == 0) { negativeGroup.SameDelta = extrusionWidth_um / 16; // look for small concave turns DiscoverAndAddTurns(inputPolygon, extrusionWidth_um, negativeGroup, delta => delta < -extrusionWidth_um / 8); if (seamPlacement == SEAM_PLACEMENT.RANDOMIZED) { // look for very small concave turns DiscoverAndAddTurns(inputPolygon, extrusionWidth_um, negativeGroup, delta => delta < -extrusionWidth_um / 32); // choose at random which point to pick if (negativeGroup.Count > 1) { var selectedPoint = (int)(inputPolygon.GetLongHashCode(layerIndex.GetLongHashCode()) % (ulong)negativeGroup.Count); var singlePoint = negativeGroup[selectedPoint]; // remove every point except the random one we want negativeGroup.Clear(); negativeGroup.Add(singlePoint); } } } } if (negativeGroup.Count > 0) { return(negativeGroup.GetBestIndex(startPosition)); } else if (positiveGroup.Count > 0) { return(positiveGroup.GetBestIndex(startPosition)); } else // there is no really good candidate { switch (seamPlacement) { case SEAM_PLACEMENT.CENTERED_IN_BACK: return(inputPolygon.GetCenteredInBackIndex(out _)); case SEAM_PLACEMENT.RANDOMIZED: return((int)(inputPolygon.GetLongHashCode(layerIndex.GetLongHashCode()) % (ulong)inputPolygon.Count)); case SEAM_PLACEMENT.FURTHEST_BACK: default: var currentFurthestBack = new IntPoint(long.MaxValue, long.MinValue); int furthestBackIndex = 0; // get the furthest back index for (var i = 0; i < count; i++) { var currentPoint = inputPolygon[i]; if (currentPoint.Y >= currentFurthestBack.Y) { if (currentPoint.Y > currentFurthestBack.Y || currentPoint.X < currentFurthestBack.X) { furthestBackIndex = i; currentFurthestBack = currentPoint; } } } // If can't find good candidate go with vertex most in a single direction return(furthestBackIndex); case SEAM_PLACEMENT.FASTEST: if (startPosition != null) { return(inputPolygon.FindClosestIndex(startPosition.Value)); } return(0); } } }