Example #1
0
        /// <summary>
        /// Finds a large inscribed rectangle. Tries to be maximal but this is
        /// best effort. The algorithm used was inspired by the blog post
        /// https://d3plus.org/blog/behind-the-scenes/2014/07/08/largest-rect/
        /// Random points within the polygon are chosen, and then 2 lines are
        /// drawn through those points. The midpoints of those lines are
        /// used as the center of various rectangles, using a binary search to
        /// vary the size, until the largest fit-able rectangle is found.
        /// This is then repeated for predefined angles (0-180 in steps of 15)
        /// and aspect ratios (1 to 15 in steps of 0.5).
        /// </summary>
        /// <param name="geometryEdges">The boundary geometry.</param>
        /// <param name="randomSeed">Random number generator seed.</param>
        /// <remarks>
        /// For the most reproducible results, use the same randomSeed value
        /// each time this method is called.
        /// </remarks>
        public InscribedRectangle(Edge[] geometryEdges, int randomSeed)
        {
            // Clear previous rectangle
            Center = EdgeUtilities.InvalidPoint;
            Width  = 0;
            Height = 0;
            Angle  = 0;

            float minX = EdgeUtilities.maxWidth;
            float minY = EdgeUtilities.maxWidth;
            float maxX = -EdgeUtilities.maxWidth;
            float maxY = -EdgeUtilities.maxWidth;

            // Find min x, min y, max x, max y
            for (int i = 0; i < geometryEdges.Length; i++)
            {
                Edge edge = geometryEdges[i];

                if ((edge.PointA.x < minX) || (edge.PointB.x < minX))
                {
                    minX = Mathf.Min(edge.PointA.x, edge.PointB.x);
                }

                if ((edge.PointA.y < minY) || (edge.PointB.y < minY))
                {
                    minY = Mathf.Min(edge.PointA.y, edge.PointB.y);
                }

                if ((edge.PointA.x > maxX) || (edge.PointB.x > maxX))
                {
                    maxX = Mathf.Max(edge.PointA.x, edge.PointB.x);
                }

                if ((edge.PointA.y > maxY) || (edge.PointB.y > maxY))
                {
                    maxY = Mathf.Min(edge.PointA.y, edge.PointB.y);
                }
            }

            // Generate random points until we have randomPointCount starting points
            Vector2[] startingPoints = new Vector2[randomPointCount];
            {
                System.Random random = new System.Random(randomSeed);
                for (int i = 0; i < startingPoints.Length; i++)
                {
                    Vector2 candidatePoint;

                    do
                    {
                        candidatePoint.x = ((float)random.NextDouble() * (maxX - minX)) + minX;
                        candidatePoint.y = ((float)random.NextDouble() * (maxY - minY)) + minY;
                    }while (!EdgeUtilities.IsInsideBoundary(geometryEdges, candidatePoint));

                    startingPoints[i] = candidatePoint;
                }
            }

            for (int angleIndex = 0; angleIndex < fitAngles.Length; angleIndex++)
            {
                for (int pointIndex = 0; pointIndex < startingPoints.Length; pointIndex++)
                {
                    float angleRadians = MathUtilities.DegreesToRadians(fitAngles[angleIndex]);

                    // Find the collision point of a cross through the given point at the given angle.
                    // Note, we are ignoring the return value as we are checking each point's validity
                    // individually.
                    FindSurroundingCollisionPoints(
                        geometryEdges,
                        startingPoints[pointIndex],
                        angleRadians,
                        out Vector2 topCollisionPoint,
                        out Vector2 bottomCollisionPoint,
                        out Vector2 leftCollisionPoint,
                        out Vector2 rightCollisionPoint);

                    float newWidth;
                    float newHeight;

                    if (EdgeUtilities.IsValidPoint(topCollisionPoint) && EdgeUtilities.IsValidPoint(bottomCollisionPoint))
                    {
                        float aX = topCollisionPoint.x;
                        float aY = topCollisionPoint.y;
                        float bX = bottomCollisionPoint.x;
                        float bY = bottomCollisionPoint.y;

                        // Calculate the midpoint between the top and bottom collision points.
                        Vector2 verticalMidpoint = new Vector2((aX + bX) * 0.5f, (aY + bY) * 0.5f);
                        if (TryFixMaximumRectangle(
                                geometryEdges,
                                verticalMidpoint,
                                angleRadians,
                                Width * Height,
                                out newWidth,
                                out newHeight))
                        {
                            Center = verticalMidpoint;
                            Angle  = fitAngles[angleIndex];
                            Width  = newWidth;
                            Height = newHeight;
                        }
                    }

                    if (EdgeUtilities.IsValidPoint(leftCollisionPoint) && EdgeUtilities.IsValidPoint(rightCollisionPoint))
                    {
                        float aX = leftCollisionPoint.x;
                        float aY = leftCollisionPoint.y;
                        float bX = rightCollisionPoint.x;
                        float bY = rightCollisionPoint.y;

                        // Calculate the midpoint between the left and right collision points.
                        Vector2 horizontalMidpoint = new Vector2((aX + bX) * 0.5f, (aY + bY) * 0.5f);
                        if (TryFixMaximumRectangle(
                                geometryEdges,
                                horizontalMidpoint,
                                angleRadians,
                                Width * Height,
                                out newWidth,
                                out newHeight))
                        {
                            Center = horizontalMidpoint;
                            Angle  = fitAngles[angleIndex];
                            Width  = newWidth;
                            Height = newHeight;
                        }
                    }
                }
            }
        }
Example #2
0
        /// <summary>
        /// Find points at which there are collisions with the geometry around a given point.
        /// </summary>
        /// <param name="geometryEdges">The boundary geometry.</param>
        /// <param name="point">The point around which collisions will be identified.</param>
        /// <param name="angleRadians">The angle, in radians, at which the collision points will be oriented.</param>
        /// <param name="topCollisionPoint">Receives the coordinates of the upper collision point.</param>
        /// <param name="bottomCollisionPoint">Receives the coordinates of the lower collision point.</param>
        /// <param name="leftCollisionPoint">Receives the coordinates of the left collision point.</param>
        /// <param name="rightCollisionPoint">Receives the coordinates of the right collision point.</param>
        /// <returns>
        /// True if all of the required collision points are located, false otherwise.
        /// If a point is unable to be found, the appropriate out parameter will be set to <see cref="EdgeUtilities.InvalidPoint"/>.
        /// </returns>
        private bool FindSurroundingCollisionPoints(
            Edge[] geometryEdges,
            Vector2 point,
            float angleRadians,
            out Vector2 topCollisionPoint,
            out Vector2 bottomCollisionPoint,
            out Vector2 leftCollisionPoint,
            out Vector2 rightCollisionPoint)
        {
            // Initialize out parameters.
            topCollisionPoint    = EdgeUtilities.InvalidPoint;
            bottomCollisionPoint = EdgeUtilities.InvalidPoint;
            leftCollisionPoint   = EdgeUtilities.InvalidPoint;
            rightCollisionPoint  = EdgeUtilities.InvalidPoint;

            // Check to see if the point is inside the geometry.
            if (!EdgeUtilities.IsInsideBoundary(geometryEdges, point))
            {
                return(false);
            }

            // Define values that are outside of the maximum boundary size.
            float largeValue = EdgeUtilities.maxWidth;
            float smallValue = -largeValue;

            // Find the top and bottom collision points by creating a large line segment that goes through the point to MAX and MIN values on Y
            Vector2 topEndpoint    = new Vector2(point.x, largeValue);
            Vector2 bottomEndpoint = new Vector2(point.x, smallValue);

            topEndpoint    = RotatePoint(topEndpoint, point, angleRadians);
            bottomEndpoint = RotatePoint(bottomEndpoint, point, angleRadians);
            Edge verticalLine = new Edge(topEndpoint, bottomEndpoint);

            // Find the left and right collision points by creating a large line segment that goes through the point to MAX and Min values on X
            Vector2 rightEndpoint = new Vector2(largeValue, point.y);
            Vector2 leftEndpoint  = new Vector2(smallValue, point.y);

            rightEndpoint = RotatePoint(rightEndpoint, point, angleRadians);
            leftEndpoint  = RotatePoint(leftEndpoint, point, angleRadians);
            Edge horizontalLine = new Edge(rightEndpoint, leftEndpoint);

            for (int i = 0; i < geometryEdges.Length; i++)
            {
                // Look for a vertical collision
                Vector2 verticalIntersectionPoint = EdgeUtilities.GetIntersectionPoint(geometryEdges[i], verticalLine);
                if (EdgeUtilities.IsValidPoint(verticalIntersectionPoint))
                {
                    // Is the intersection above or below the point?
                    if (RotatePoint(verticalIntersectionPoint, point, -angleRadians).y > point.y)
                    {
                        // Update the top collision point
                        if (!EdgeUtilities.IsValidPoint(topCollisionPoint) ||
                            (Vector2.SqrMagnitude(point - verticalIntersectionPoint) < Vector2.SqrMagnitude(point - topCollisionPoint)))
                        {
                            topCollisionPoint = verticalIntersectionPoint;
                        }
                    }
                    else
                    {
                        // Update the bottom collision point
                        if (!EdgeUtilities.IsValidPoint(bottomCollisionPoint) ||
                            (Vector2.SqrMagnitude(point - verticalIntersectionPoint) < Vector2.SqrMagnitude(point - bottomCollisionPoint)))
                        {
                            bottomCollisionPoint = verticalIntersectionPoint;
                        }
                    }
                }

                // Look for a horizontal collision
                Vector2 horizontalIntersection = EdgeUtilities.GetIntersectionPoint(geometryEdges[i], horizontalLine);
                if (EdgeUtilities.IsValidPoint(horizontalIntersection))
                {
                    // Is this intersection to the left or the right of the point?
                    if (RotatePoint(horizontalIntersection, point, -angleRadians).x < point.x)
                    {
                        // Update the left collision point
                        if (!EdgeUtilities.IsValidPoint(leftCollisionPoint) ||
                            (Vector2.SqrMagnitude(point - horizontalIntersection) < Vector2.SqrMagnitude(point - leftCollisionPoint)))
                        {
                            leftCollisionPoint = horizontalIntersection;
                        }
                    }
                    else
                    {
                        // Update the right collision point
                        if (!EdgeUtilities.IsValidPoint(rightCollisionPoint) ||
                            (Vector2.SqrMagnitude(point - horizontalIntersection) < Vector2.SqrMagnitude(point - rightCollisionPoint)))
                        {
                            rightCollisionPoint = horizontalIntersection;
                        }
                    }
                }
            }

            // Each corner of the rectangle must intersect with the geometry.
            if (!EdgeUtilities.IsValidPoint(topCollisionPoint) ||
                !EdgeUtilities.IsValidPoint(bottomCollisionPoint) ||
                !EdgeUtilities.IsValidPoint(leftCollisionPoint) ||
                !EdgeUtilities.IsValidPoint(rightCollisionPoint))
            {
                return(false);
            }

            return(true);
        }