/// <summary> /// Pass in the game object's position to check if it's within /// the specified boundary space. /// </summary> /// <param name="gameObjectPosition">The position of the GameObject to check.</param> /// <param name="boundaryType">The type of the boundary. Use PlayArea for the inscribed rectangle or TrackedArea for the bounds containing the whole space.</param> /// <returns>True if the point is in the boundary type's bounds.</returns> public bool ContainsObject(Vector3 gameObjectPosition, UnityEngine.Experimental.XR.Boundary.Type boundaryType) { gameObjectPosition = CameraCache.Main.transform.parent.InverseTransformPoint(gameObjectPosition); if (gameObjectPosition.y < boundaryFloor || gameObjectPosition.y > boundaryHeight) { return(false); } if (boundaryType == UnityEngine.Experimental.XR.Boundary.Type.PlayArea) { if (inscribedRectangle == null || !inscribedRectangle.IsRectangleValid) { return(false); } return(inscribedRectangle.IsPointInRectangleBounds(new Vector2(gameObjectPosition.x, gameObjectPosition.z))); } else if (boundaryType == UnityEngine.Experimental.XR.Boundary.Type.TrackedArea) { // Check if the supplied game object's position is within the bounds volume. return(EdgeHelpers.IsInside(boundaryGeometryEdges, new Vector2(gameObjectPosition.x, gameObjectPosition.z))); } return(false); }
/// <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> private void FindInscribedRectangle(Edge[] edges, int randomSeed, out Vector2 center, out float angle, out float width, out float height) { center = EdgeHelpers.InvalidPoint; angle = width = height = 0; // Find min x, min y, max x, max y and generate random // points in this range until we have randomPointCount // random starting points float minX = largeValue; float minY = largeValue; float maxX = smallValue; float maxY = smallValue; foreach (var edge in edges) { if (edge.Ax < minX || edge.Bx < minX) { minX = Math.Min(edge.Ax, edge.Bx); } if (edge.Ay < minY || edge.By < minY) { minY = Math.Min(edge.Ay, edge.By); } if (edge.Ax > maxX || edge.Bx > maxX) { maxX = Math.Max(edge.Ax, edge.Bx); } if (edge.Ay > maxY || edge.By > maxY) { maxY = Math.Max(edge.Ay, edge.By); } } // Generate random points Vector2[] startingPoints = new Vector2[randomPointCount]; { var random = new System.Random(randomSeed); for (int pointIndex = 0; pointIndex < randomPointCount; ++pointIndex) { Vector2 candidatePoint; do { candidatePoint.x = ((float)random.NextDouble() * (maxX - minX)) + minX; candidatePoint.y = ((float)random.NextDouble() * (maxY - minY)) + minY; }while (!EdgeHelpers.IsInside(edges, candidatePoint)); startingPoints[pointIndex] = candidatePoint; } } float[] angles = { 0, 15, 30, 45, 60, 75, 90, 105, 120, 135, 150, 165 }; foreach (var candidateAngle in angles) { // For each randomly generated starting point foreach (var startingPoint in startingPoints) { // Find the collision point of a cross through the given point at the given angle // Ignore the return value. A return value of false indicates one of the points is bad. // We will check each point's validity ourselves anyway. Vector2 topCollisionPoint; Vector2 bottomCollisionPoint; Vector2 leftCollisionPoint; Vector2 rightCollisionPoint; FindSurroundingCollisionPoints(edges, startingPoint, DegreesToRadians(candidateAngle), out topCollisionPoint, out bottomCollisionPoint, out leftCollisionPoint, out rightCollisionPoint); // Now calculate the midpoint between top and bottom (the "vertical midpoint") and left and right (the "horizontal midpoint") if (EdgeHelpers.IsValidPoint(topCollisionPoint) && EdgeHelpers.IsValidPoint(bottomCollisionPoint)) { var verticalMidpoint = new Vector2((topCollisionPoint.x + bottomCollisionPoint.x) / 2, (topCollisionPoint.y + bottomCollisionPoint.y) / 2); float aspectRatio; float w; float h; if (TryFitMaximumRectangleAtAngle(edges, verticalMidpoint, DegreesToRadians(candidateAngle), width * height, out aspectRatio, out w, out h)) { center = verticalMidpoint; angle = candidateAngle; width = w; height = h; } } if (EdgeHelpers.IsValidPoint(leftCollisionPoint) && EdgeHelpers.IsValidPoint(rightCollisionPoint)) { var horizontalMidpoint = new Vector2((leftCollisionPoint.x + rightCollisionPoint.x) / 2, (leftCollisionPoint.y + rightCollisionPoint.y) / 2); float aspectRatio; float w; float h; if (TryFitMaximumRectangleAtAngle(edges, horizontalMidpoint, DegreesToRadians(candidateAngle), width * height, out aspectRatio, out w, out h)) { center = horizontalMidpoint; angle = candidateAngle; width = w; height = h; } } } } }
/// <summary> /// Given a point inside of the boundary, finds the points on the /// boundary directly above, below, left and right of the point, /// with respect to the given angle. /// </summary> private bool FindSurroundingCollisionPoints( Edge[] edges, Vector2 point, float angleRad, out Vector2 topCollisionPoint, out Vector2 bottomCollisionPoint, out Vector2 leftCollisionPoint, out Vector2 rightCollisionPoint) { topCollisionPoint = EdgeHelpers.InvalidPoint; bottomCollisionPoint = EdgeHelpers.InvalidPoint; leftCollisionPoint = EdgeHelpers.InvalidPoint; rightCollisionPoint = EdgeHelpers.InvalidPoint; bool isInside = EdgeHelpers.IsInside(edges, point); if (!isInside) { return(false); } // 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 var topEndpoint = new Vector2(point.x, largeValue); var bottomEndpoint = new Vector2(point.x, smallValue); topEndpoint = RotatePoint(point, angleRad, topEndpoint); bottomEndpoint = RotatePoint(point, angleRad, bottomEndpoint); var 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 var rightEndpoint = new Vector2(largeValue, point.y); var leftEndpoint = new Vector2(smallValue, point.y); rightEndpoint = RotatePoint(point, angleRad, rightEndpoint); leftEndpoint = RotatePoint(point, angleRad, leftEndpoint); var horizontalLine = new Edge(rightEndpoint, leftEndpoint); // Loop the edges and find the nearest intersection point foreach (var edge in edges) { Vector2 verticalIntersection = EdgeHelpers.GetIntersection(edge, verticalLine); if (EdgeHelpers.IsValidPoint(verticalIntersection)) { // Is this intersection above or below the point? bool isAbove = RotatePoint(point, -angleRad, verticalIntersection).y > point.y; if (isAbove) { // If this collision point is closer than the previous one if (!EdgeHelpers.IsValidPoint(topCollisionPoint) || Vector2.SqrMagnitude(point - verticalIntersection) < Vector2.SqrMagnitude(point - topCollisionPoint)) { topCollisionPoint = verticalIntersection; } } else { if (!EdgeHelpers.IsValidPoint(bottomCollisionPoint) || Vector2.SqrMagnitude(point - verticalIntersection) < Vector2.SqrMagnitude(point - bottomCollisionPoint)) { bottomCollisionPoint = verticalIntersection; } } } // If vertical intersection found Vector2 horizontalIntersection = EdgeHelpers.GetIntersection(edge, horizontalLine); if (EdgeHelpers.IsValidPoint(horizontalIntersection)) { // Is this intersection left or right of the point? bool isLeft = RotatePoint(point, -angleRad, horizontalIntersection).x < point.x; if (isLeft) { // Is it closer than the previous intersection? if (!EdgeHelpers.IsValidPoint(leftCollisionPoint) || Vector2.SqrMagnitude(point - horizontalIntersection) < Vector2.SqrMagnitude(point - leftCollisionPoint)) { leftCollisionPoint = horizontalIntersection; } } else { // Is it closer than the previous intersection? if (!EdgeHelpers.IsValidPoint(rightCollisionPoint) || Vector2.SqrMagnitude(point - horizontalIntersection) < Vector2.SqrMagnitude(point - rightCollisionPoint)) { rightCollisionPoint = horizontalIntersection; } } } } // Assert that any point inside should have intersection points on all sides with the polygon if (!EdgeHelpers.IsValidPoint(topCollisionPoint) || !EdgeHelpers.IsValidPoint(bottomCollisionPoint) || !EdgeHelpers.IsValidPoint(leftCollisionPoint) || !EdgeHelpers.IsValidPoint(rightCollisionPoint)) { return(false); } return(true); }