/// <inheritdoc/> public GameObject GetPlayAreaVisualization() { if (!Application.isPlaying) { return(null); } if (currentPlayAreaObject != null) { return(currentPlayAreaObject); } MixedRealityBoundaryVisualizationProfile profile = ConfigurationProfile as MixedRealityBoundaryVisualizationProfile; if (profile == null) { return(null); } // Get the rectangular bounds. Vector2 center; float angle; float width; float height; if (!TryGetRectangularBoundsParams(out center, out angle, out width, out height)) { // No rectangular bounds, therefore cannot create the play area. return(null); } // Render the rectangular bounds. if (!EdgeUtilities.IsValidPoint(center)) { // Invalid rectangle / play area not found return(null); } currentPlayAreaObject = GameObject.CreatePrimitive(PrimitiveType.Quad); currentPlayAreaObject.name = "Play Area"; currentPlayAreaObject.layer = PlayAreaPhysicsLayer; currentPlayAreaObject.transform.Translate(new Vector3(center.x, boundaryObjectRenderOffset, center.y)); currentPlayAreaObject.transform.Rotate(new Vector3(90, -angle, 0)); currentPlayAreaObject.transform.localScale = new Vector3(width, height, 1.0f); currentPlayAreaObject.GetComponent <Renderer>().sharedMaterial = profile.PlayAreaMaterial; currentPlayAreaObject.transform.parent = BoundaryVisualizationParent.transform; return(currentPlayAreaObject); }
/// <inheritdoc/> public bool Contains(Vector3 location, UnityBoundary.Type boundaryType = UnityBoundary.Type.TrackedArea) { if (!EdgeUtilities.IsValidPoint(location)) { // Invalid location. return(false); } if (!FloorHeight.HasValue) { // No floor. return(false); } // Handle the user teleporting (boundary moves with them). location = MixedRealityPlayspace.InverseTransformPoint(location); if (FloorHeight.Value > location.y || BoundaryHeight < location.y) { // Location below the floor or above the boundary height. return(false); } // Boundary coordinates are always "on the floor" Vector2 point = new Vector2(location.x, location.z); if (boundaryType == UnityBoundary.Type.PlayArea) { // Check the inscribed rectangle. if (rectangularBounds != null) { return(rectangularBounds.IsInsideBoundary(point)); } } else if (boundaryType == UnityBoundary.Type.TrackedArea) { // Check the geometry return(EdgeUtilities.IsInsideBoundary(Bounds, point)); } // Not in either boundary type. return(false); }
/// <summary> /// Check to see if a rectangle centered at the specified point and oriented at /// the specified angle will fit within the geometry. /// </summary> /// <param name="geometryEdges">The boundary geometry.</param> /// <param name="centerPoint">The center point of the rectangle.</param> /// <param name="angleRadians">The orientation, in radians, of the rectangle.</param> /// <param name="width">The width of the rectangle.</param> /// <param name="height">The height of the rectangle.</param> private bool CheckRectangleFit( Edge[] geometryEdges, Vector2 centerPoint, float angleRadians, float width, float height) { float halfWidth = width * 0.5f; float halfHeight = height * 0.5f; // Calculate the rectangle corners. Vector2 topLeft = new Vector2(centerPoint.x - halfWidth, centerPoint.y + halfHeight); Vector2 topRight = new Vector2(centerPoint.x + halfWidth, centerPoint.y + halfHeight); Vector2 bottomLeft = new Vector2(centerPoint.x - halfWidth, centerPoint.y - halfHeight); Vector2 bottomRight = new Vector2(centerPoint.x + halfWidth, centerPoint.y - halfHeight); // Rotate the rectangle. topLeft = RotatePoint(topLeft, centerPoint, angleRadians); topRight = RotatePoint(topRight, centerPoint, angleRadians); bottomLeft = RotatePoint(bottomLeft, centerPoint, angleRadians); bottomRight = RotatePoint(bottomRight, centerPoint, angleRadians); // Get the rectangle edges. Edge topEdge = new Edge(topLeft, topRight); Edge rightEdge = new Edge(topRight, bottomRight); Edge bottomEdge = new Edge(bottomLeft, bottomRight); Edge leftEdge = new Edge(topLeft, bottomLeft); // Check for collisions with the boundary geometry. If any of our edges collide, // the rectangle will not fit within the playspace. for (int i = 0; i < geometryEdges.Length; i++) { if (EdgeUtilities.IsValidPoint(EdgeUtilities.GetIntersectionPoint(geometryEdges[i], topEdge)) || EdgeUtilities.IsValidPoint(EdgeUtilities.GetIntersectionPoint(geometryEdges[i], rightEdge)) || EdgeUtilities.IsValidPoint(EdgeUtilities.GetIntersectionPoint(geometryEdges[i], bottomEdge)) || EdgeUtilities.IsValidPoint(EdgeUtilities.GetIntersectionPoint(geometryEdges[i], leftEdge))) { return(false); } } // No collisions found with the rectangle. Success! return(true); }
/// <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 static 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 var topEndpoint = new Vector2(point.x, largeValue); var bottomEndpoint = new Vector2(point.x, smallValue); topEndpoint = topEndpoint.RotateAroundPoint(point, angleRadians); bottomEndpoint = bottomEndpoint.RotateAroundPoint(point, angleRadians); 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 = rightEndpoint.RotateAroundPoint(point, angleRadians); leftEndpoint = leftEndpoint.RotateAroundPoint(point, angleRadians); var horizontalLine = new Edge(rightEndpoint, leftEndpoint); for (int i = 0; i < geometryEdges.Length; i++) { // Look for a vertical collision var verticalIntersectionPoint = EdgeUtilities.GetIntersectionPoint(geometryEdges[i], verticalLine); if (EdgeUtilities.IsValidPoint(verticalIntersectionPoint)) { // Is the intersection above or below the point? if (verticalIntersectionPoint.RotateAroundPoint(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 var horizontalIntersection = EdgeUtilities.GetIntersectionPoint(geometryEdges[i], horizontalLine); if (!EdgeUtilities.IsValidPoint(horizontalIntersection)) { continue; } // Is this intersection to the left or the right of the point? if (horizontalIntersection.RotateAroundPoint(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. return(EdgeUtilities.IsValidPoint(topCollisionPoint) && EdgeUtilities.IsValidPoint(leftCollisionPoint) && EdgeUtilities.IsValidPoint(rightCollisionPoint) && EdgeUtilities.IsValidPoint(bottomCollisionPoint)); }
/// <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) { if (geometryEdges == null || geometryEdges.Length == 0) { Debug.LogError("InscribedRectangle requires an array of Edges. You passed in a null or empty array."); return; } Edges = geometryEdges; // 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++) { var 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 var startingPoints = new Vector2[RANDOM_POINT_COUNT]; var random = new 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 var topCollisionPoint, out var bottomCollisionPoint, out var leftCollisionPoint, out var 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. var 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; } } } } }