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));
                }
            }
        }
Beispiel #2
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]);
            }
        }
        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);
        }
        /// <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);
            }
        }
Beispiel #5
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">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);
                }
            }
        }