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