diff --git a/Submodules/Examples b/Submodules/Examples index 737e55647..ee58d6124 160000 --- a/Submodules/Examples +++ b/Submodules/Examples @@ -1 +1 @@ -Subproject commit 737e556476ca7a4e037bc8f852c852f478f258cb +Subproject commit ee58d612407dd64dbe174776f7fd9513f4e63558 diff --git a/Submodules/WindowsMixedReality b/Submodules/WindowsMixedReality index b22ebf919..0205b89ae 160000 --- a/Submodules/WindowsMixedReality +++ b/Submodules/WindowsMixedReality @@ -1 +1 @@ -Subproject commit b22ebf919587513e6a144cc6be7c7f2fcb85cbe0 +Subproject commit 0205b89aecd034342c7e08bc805b1caa26c324d8 diff --git a/XRTK-Core/Packages/com.xrtk.core/Editor/Profiles/MixedRealityBoundaryVisualizationProfileInspector.cs b/XRTK-Core/Packages/com.xrtk.core/Editor/Profiles/MixedRealityBoundaryVisualizationProfileInspector.cs index cb6061bf1..034a4bba9 100644 --- a/XRTK-Core/Packages/com.xrtk.core/Editor/Profiles/MixedRealityBoundaryVisualizationProfileInspector.cs +++ b/XRTK-Core/Packages/com.xrtk.core/Editor/Profiles/MixedRealityBoundaryVisualizationProfileInspector.cs @@ -9,66 +9,46 @@ namespace XRTK.Editor.Profiles { [CustomEditor(typeof(MixedRealityBoundaryVisualizationProfile))] - public class MixedRealityBoundaryVisualizationProfileInspector : BaseMixedRealityProfileInspector + public class MixedRealityBoundaryVisualizationProfileInspector : MixedRealityServiceProfileInspector { + private SerializedProperty showBoundary; private SerializedProperty boundaryHeight; + private SerializedProperty physicsLayer; + private SerializedProperty boundaryMaterial; + private SerializedProperty showFloor; private SerializedProperty floorMaterial; - private SerializedProperty floorScale; - private SerializedProperty floorPhysicsLayer; - - private SerializedProperty showPlayArea; - private SerializedProperty playAreaMaterial; - private SerializedProperty playAreaPhysicsLayer; - private SerializedProperty showTrackedArea; - private SerializedProperty trackedAreaMaterial; - private SerializedProperty trackedAreaPhysicsLayer; + private SerializedProperty showWalls; + private SerializedProperty wallMaterial; - private SerializedProperty showBoundaryWalls; - private SerializedProperty boundaryWallMaterial; - private SerializedProperty boundaryWallsPhysicsLayer; + private SerializedProperty showCeiling; + private SerializedProperty ceilingMaterial; - private SerializedProperty showBoundaryCeiling; - private SerializedProperty boundaryCeilingMaterial; - private SerializedProperty ceilingPhysicsLayer; - - private readonly GUIContent showContent = new GUIContent("Show"); - private readonly GUIContent scaleContent = new GUIContent("Scale"); - private readonly GUIContent materialContent = new GUIContent("Material"); private readonly GUIContent generalSettingsFoldoutHeader = new GUIContent("General Settings"); private readonly GUIContent floorSettingsFoldoutHeader = new GUIContent("Floor Settings"); - private readonly GUIContent playAreaSettingsFoldoutHeader = new GUIContent("Play Area Settings"); - private readonly GUIContent trackedAreaSettingsFoldoutHeader = new GUIContent("Tracked Area Settings"); - private readonly GUIContent boundaryWallSettingsFoldoutHeader = new GUIContent("Boundary Wall Settings"); - private readonly GUIContent boundaryCeilingSettingsFoldoutHeader = new GUIContent("Boundary Ceiling Settings"); + private readonly GUIContent wallSettingsFoldoutHeader = new GUIContent("Wall Settings"); + private readonly GUIContent ceilingSettingsFoldoutHeader = new GUIContent("Ceiling Settings"); + private readonly GUIContent materialContent = new GUIContent("Material Override", "Use a different material than the global boundary material?"); + private readonly GUIContent showContent = new GUIContent("Force Show", "Force the boundary component to be displayed.\n\nNote: This can be toggled on/off at runtime."); protected override void OnEnable() { base.OnEnable(); + showBoundary = serializedObject.FindProperty(nameof(showBoundary)); boundaryHeight = serializedObject.FindProperty(nameof(boundaryHeight)); + boundaryMaterial = serializedObject.FindProperty(nameof(boundaryMaterial)); + physicsLayer = serializedObject.FindProperty(nameof(physicsLayer)); showFloor = serializedObject.FindProperty(nameof(showFloor)); floorMaterial = serializedObject.FindProperty(nameof(floorMaterial)); - floorScale = serializedObject.FindProperty(nameof(floorScale)); - floorPhysicsLayer = serializedObject.FindProperty(nameof(floorPhysicsLayer)); - - showPlayArea = serializedObject.FindProperty(nameof(showPlayArea)); - playAreaMaterial = serializedObject.FindProperty(nameof(playAreaMaterial)); - playAreaPhysicsLayer = serializedObject.FindProperty(nameof(playAreaPhysicsLayer)); - showTrackedArea = serializedObject.FindProperty(nameof(showTrackedArea)); - trackedAreaMaterial = serializedObject.FindProperty(nameof(trackedAreaMaterial)); - trackedAreaPhysicsLayer = serializedObject.FindProperty(nameof(trackedAreaPhysicsLayer)); + showWalls = serializedObject.FindProperty(nameof(showWalls)); + wallMaterial = serializedObject.FindProperty(nameof(wallMaterial)); - showBoundaryWalls = serializedObject.FindProperty(nameof(showBoundaryWalls)); - boundaryWallMaterial = serializedObject.FindProperty(nameof(boundaryWallMaterial)); - boundaryWallsPhysicsLayer = serializedObject.FindProperty(nameof(boundaryWallsPhysicsLayer)); - - showBoundaryCeiling = serializedObject.FindProperty(nameof(showBoundaryCeiling)); - boundaryCeilingMaterial = serializedObject.FindProperty(nameof(boundaryCeilingMaterial)); - ceilingPhysicsLayer = serializedObject.FindProperty(nameof(ceilingPhysicsLayer)); + showCeiling = serializedObject.FindProperty(nameof(showCeiling)); + ceilingMaterial = serializedObject.FindProperty(nameof(ceilingMaterial)); } public override void OnInspectorGUI() @@ -77,65 +57,50 @@ public override void OnInspectorGUI() serializedObject.Update(); - boundaryHeight.FoldoutWithBoldLabelPropertyField(generalSettingsFoldoutHeader); - - EditorGUILayout.Space(); - - if (showFloor.FoldoutWithBoldLabelPropertyField(floorSettingsFoldoutHeader, showContent)) + if (showBoundary.FoldoutWithBoldLabelPropertyField(generalSettingsFoldoutHeader, showContent)) { EditorGUI.indentLevel++; - EditorGUILayout.PropertyField(floorMaterial, materialContent); - var prevWideMode = EditorGUIUtility.wideMode; - EditorGUIUtility.wideMode = true; - EditorGUILayout.PropertyField(floorScale, scaleContent, GUILayout.ExpandWidth(true)); - EditorGUIUtility.wideMode = prevWideMode; - EditorGUILayout.PropertyField(floorPhysicsLayer); - EditorGUI.indentLevel--; - } + EditorGUILayout.PropertyField(boundaryHeight); + EditorGUILayout.PropertyField(boundaryMaterial, materialContent); + EditorGUILayout.PropertyField(physicsLayer); - EditorGUILayout.Space(); + EditorGUILayout.Space(); - if (showPlayArea.FoldoutWithBoldLabelPropertyField(playAreaSettingsFoldoutHeader, showContent)) - { - EditorGUI.indentLevel++; - EditorGUILayout.PropertyField(playAreaMaterial, materialContent); - EditorGUILayout.PropertyField(playAreaPhysicsLayer); - EditorGUI.indentLevel--; - } + if (showFloor.FoldoutWithBoldLabelPropertyField(floorSettingsFoldoutHeader, showContent)) + { + EditorGUI.indentLevel++; + EditorGUILayout.PropertyField(floorMaterial, materialContent); + EditorGUI.indentLevel--; + } - EditorGUILayout.Space(); + EditorGUILayout.Space(); - if (showTrackedArea.FoldoutWithBoldLabelPropertyField(trackedAreaSettingsFoldoutHeader, showContent)) - { - EditorGUI.indentLevel++; - EditorGUILayout.PropertyField(trackedAreaMaterial, materialContent); - EditorGUILayout.PropertyField(trackedAreaPhysicsLayer); - EditorGUI.indentLevel--; - } + EditorGUILayout.Space(); - EditorGUILayout.Space(); + if (showWalls.FoldoutWithBoldLabelPropertyField(wallSettingsFoldoutHeader, showContent)) + { + EditorGUI.indentLevel++; + EditorGUILayout.PropertyField(wallMaterial, materialContent); + EditorGUI.indentLevel--; + } - if (showBoundaryWalls.FoldoutWithBoldLabelPropertyField(boundaryWallSettingsFoldoutHeader, showContent)) - { - EditorGUI.indentLevel++; - EditorGUILayout.PropertyField(boundaryWallMaterial, materialContent); - EditorGUILayout.PropertyField(boundaryWallsPhysicsLayer); - EditorGUI.indentLevel--; - } + EditorGUILayout.Space(); - EditorGUILayout.Space(); + if (showCeiling.FoldoutWithBoldLabelPropertyField(ceilingSettingsFoldoutHeader, showContent)) + { + EditorGUI.indentLevel++; + EditorGUILayout.PropertyField(ceilingMaterial, materialContent); + EditorGUI.indentLevel--; + } - if (showBoundaryCeiling.FoldoutWithBoldLabelPropertyField(boundaryCeilingSettingsFoldoutHeader, showContent)) - { - EditorGUI.indentLevel++; - EditorGUILayout.PropertyField(boundaryCeilingMaterial, materialContent); - EditorGUILayout.PropertyField(ceilingPhysicsLayer); EditorGUI.indentLevel--; } EditorGUILayout.Space(); serializedObject.ApplyModifiedProperties(); + + base.OnInspectorGUI(); } } } diff --git a/XRTK-Core/Packages/com.xrtk.core/Runtime/Definitions/BoundarySystem/Edge.cs b/XRTK-Core/Packages/com.xrtk.core/Runtime/Definitions/BoundarySystem/Edge.cs index 3de094827..afbcc96bc 100644 --- a/XRTK-Core/Packages/com.xrtk.core/Runtime/Definitions/BoundarySystem/Edge.cs +++ b/XRTK-Core/Packages/com.xrtk.core/Runtime/Definitions/BoundarySystem/Edge.cs @@ -40,6 +40,7 @@ public Edge(Vector2 pointA, Vector2 pointB) public Edge(Vector3 pointA, Vector3 pointB) : // Use the X and Z parameters as our edges are height agnostic. this(new Vector2(pointA.x, pointA.z), new Vector2(pointB.x, pointB.z)) - { } + { + } } } diff --git a/XRTK-Core/Packages/com.xrtk.core/Runtime/Definitions/BoundarySystem/InscribedRectangle.cs b/XRTK-Core/Packages/com.xrtk.core/Runtime/Definitions/BoundarySystem/InscribedRectangle.cs index 5eea05a9d..7241f546c 100644 --- a/XRTK-Core/Packages/com.xrtk.core/Runtime/Definitions/BoundarySystem/InscribedRectangle.cs +++ b/XRTK-Core/Packages/com.xrtk.core/Runtime/Definitions/BoundarySystem/InscribedRectangle.cs @@ -1,4 +1,4 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. +// Copyright (c) XRTK. All rights reserved. // Licensed under the MIT License. See LICENSE in the project root for license information. using System; @@ -18,23 +18,23 @@ public class InscribedRectangle /// /// Total number of starting points randomly generated within the boundary. /// - private const int randomPointCount = 30; + private const int RANDOM_POINT_COUNT = 30; /// /// The total amount of height, in meters, we want to gain with each binary search /// change before we decide that it's good enough. /// - private const float minimumHeightGain = 0.01f; + private const float MINIMUM_HEIGHT_GAIN = 0.01f; /// /// Angles to use for fitting the rectangle within the boundary. /// - private static readonly float[] fitAngles = { 0, 15, 30, 45, 60, 75, 90, 105, 120, 135, 150, 165 }; + private static readonly float[] FitAngles = { 0, 15, 30, 45, 60, 75, 90, 105, 120, 135, 150, 165 }; /// /// Aspect ratios used when fitting rectangles within the boundary. /// - private static readonly float[] aspectRatios = + private static readonly float[] AspectRatios = { 1.0f, 1.5f, 2.0f, 2.5f, 3.0f, 3.5f, 4.0f, 4.5f, 5.0f, 5.5f, 6, 6.5f, 7, 7.5f, 8.0f, 8.5f, 9.0f, @@ -70,6 +70,11 @@ public class InscribedRectangle /// public bool IsValid => EdgeUtilities.IsValidPoint(Center); + /// + /// The defined edges of the described rectangle. + /// + public Edge[] Edges { get; } + /// /// Finds a large inscribed rectangle. Tries to be maximal but this is /// best effort. The algorithm used was inspired by the blog post @@ -95,16 +100,18 @@ public InscribedRectangle(Edge[] geometryEdges, int randomSeed) 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; + 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++) @@ -134,7 +141,7 @@ public InscribedRectangle(Edge[] geometryEdges, int randomSeed) } // Generate random points until we have randomPointCount starting points - var startingPoints = new Vector2[randomPointCount]; + var startingPoints = new Vector2[RANDOM_POINT_COUNT]; var random = new Random(randomSeed); for (int i = 0; i < startingPoints.Length; i++) @@ -151,11 +158,11 @@ public InscribedRectangle(Edge[] geometryEdges, int randomSeed) startingPoints[i] = candidatePoint; } - for (int angleIndex = 0; angleIndex < fitAngles.Length; angleIndex++) + for (int angleIndex = 0; angleIndex < FitAngles.Length; angleIndex++) { for (int pointIndex = 0; pointIndex < startingPoints.Length; pointIndex++) { - float angleRadians = MathUtilities.DegreesToRadians(fitAngles[angleIndex]); + 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 @@ -190,7 +197,7 @@ public InscribedRectangle(Edge[] geometryEdges, int randomSeed) out newHeight)) { Center = verticalMidpoint; - Angle = fitAngles[angleIndex]; + Angle = FitAngles[angleIndex]; Width = newWidth; Height = newHeight; } @@ -215,7 +222,7 @@ public InscribedRectangle(Edge[] geometryEdges, int randomSeed) out newHeight)) { Center = horizontalMidpoint; - Angle = fitAngles[angleIndex]; + Angle = FitAngles[angleIndex]; Width = newWidth; Height = newHeight; } @@ -238,7 +245,7 @@ public InscribedRectangle(Edge[] geometryEdges, int randomSeed) /// 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 . /// - private bool FindSurroundingCollisionPoints( + private static bool FindSurroundingCollisionPoints( Edge[] geometryEdges, Vector2 point, float angleRadians, @@ -260,7 +267,7 @@ private bool FindSurroundingCollisionPoints( } // Define values that are outside of the maximum boundary size. - float largeValue = EdgeUtilities.maxWidth; + 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 @@ -353,17 +360,11 @@ public bool IsInsideBoundary(Vector2 point) throw new InvalidOperationException("A point cannot be within an invalid rectangle."); } - point -= Center; - point = point.RotatePoint(MathUtilities.DegreesToRadians(-Angle)); - - var inWidth = Mathf.Abs(point.x) <= (Width * 0.5f); - var inHeight = Mathf.Abs(point.y) <= (Height * 0.5f); - - return (inWidth && inHeight); + return EdgeUtilities.IsInsideBoundary(Edges, point); } /// - /// Check to see if a rectangle centered at the specified point and oriented at + /// Check to see if a rectangle centered at the specified point and oriented at /// the specified angle will fit within the geometry. /// /// The boundary geometry. @@ -372,7 +373,7 @@ public bool IsInsideBoundary(Vector2 point) /// The width of the rectangle. /// The height of the rectangle. /// - private bool CheckRectangleFit(Edge[] geometryEdges, Vector2 centerPoint, float angleRadians, float width, float height) + private static bool CheckRectangleFit(Edge[] geometryEdges, Vector2 centerPoint, float angleRadians, float width, float height) { float halfWidth = width * 0.5f; float halfHeight = height * 0.5f; @@ -425,7 +426,7 @@ private bool CheckRectangleFit(Edge[] geometryEdges, Vector2 centerPoint, float /// True if a rectangle with an area greater than or equal to minArea was able to be fit /// within the geometry at centerPoint. /// - private bool TryFixMaximumRectangle( + private static bool TryFixMaximumRectangle( Edge[] geometryEdges, Vector2 centerPoint, float angleRadians, @@ -467,19 +468,19 @@ private bool TryFixMaximumRectangle( // For each aspect ratio we do a binary search to find the maximum rectangle that fits, // though once we start increasing our area by minimumHeightGain we call it good enough. - for (int i = 0; i < aspectRatios.Length; i++) + for (int i = 0; i < AspectRatios.Length; i++) { // The height is limited by the width. If a height would make our width exceed maxWidth, it can't be used - float searchHeightUpperBound = Mathf.Max(maxHeight, maxWidth / aspectRatios[i]); + float searchHeightUpperBound = Mathf.Max(maxHeight, maxWidth / AspectRatios[i]); // Set to the min height that will out perform our previous area at the given aspect ratio. This is 0 the first time. // Derived from biggestAreaSoFar=height*(height*aspectRatio) - float searchHeightLowerBound = Mathf.Sqrt(Mathf.Max((width * height), minArea) / aspectRatios[i]); + float searchHeightLowerBound = Mathf.Sqrt(Mathf.Max((width * height), minArea) / AspectRatios[i]); // If the lowest value needed to outperform the previous best is greater than our max, // this aspect ratio can't outperform what we've already calculated. if (searchHeightLowerBound > searchHeightUpperBound || - searchHeightLowerBound * aspectRatios[i] > maxWidth) + searchHeightLowerBound * AspectRatios[i] > maxWidth) { continue; } @@ -492,16 +493,16 @@ private bool TryFixMaximumRectangle( if (CheckRectangleFit(geometryEdges, centerPoint, angleRadians, - aspectRatios[i] * currentTestingHeight, + AspectRatios[i] * currentTestingHeight, currentTestingHeight)) { // Binary search up-ward // If the rectangle will fit, increase the lower bounds of our binary search searchHeightLowerBound = currentTestingHeight; - width = currentTestingHeight * aspectRatios[i]; + width = currentTestingHeight * AspectRatios[i]; height = currentTestingHeight; - aspectRatio = aspectRatios[i]; + aspectRatio = AspectRatios[i]; currentTestingHeight = (searchHeightUpperBound + currentTestingHeight) * 0.5f; } else @@ -511,7 +512,7 @@ private bool TryFixMaximumRectangle( currentTestingHeight = (currentTestingHeight + searchHeightLowerBound) * 0.5f; } } - while ((searchHeightUpperBound - searchHeightLowerBound) > minimumHeightGain); + while ((searchHeightUpperBound - searchHeightLowerBound) > MINIMUM_HEIGHT_GAIN); } return (aspectRatio > 0.0f); diff --git a/XRTK-Core/Packages/com.xrtk.core/Runtime/Definitions/BoundarySystem/MixedRealityBoundaryVisualizationProfile.cs b/XRTK-Core/Packages/com.xrtk.core/Runtime/Definitions/BoundarySystem/MixedRealityBoundaryVisualizationProfile.cs index bb04072f9..e9aafe60c 100644 --- a/XRTK-Core/Packages/com.xrtk.core/Runtime/Definitions/BoundarySystem/MixedRealityBoundaryVisualizationProfile.cs +++ b/XRTK-Core/Packages/com.xrtk.core/Runtime/Definitions/BoundarySystem/MixedRealityBoundaryVisualizationProfile.cs @@ -1,9 +1,10 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. +// Copyright (c) XRTK. All rights reserved. // Licensed under the MIT License. See LICENSE in the project root for license information. using UnityEngine; using XRTK.Attributes; using XRTK.Definitions.Utilities; +using XRTK.Interfaces.BoundarySystem; namespace XRTK.Definitions.BoundarySystem { @@ -11,188 +12,116 @@ namespace XRTK.Definitions.BoundarySystem /// Configuration profile settings for setting up boundary visualizations. /// [CreateAssetMenu(menuName = "Mixed Reality Toolkit/Boundary Visualization Profile", fileName = "MixedRealityBoundaryVisualizationProfile", order = (int)CreateProfileMenuItemIndices.BoundaryVisualization)] - public class MixedRealityBoundaryVisualizationProfile : BaseMixedRealityProfile + public class MixedRealityBoundaryVisualizationProfile : BaseMixedRealityServiceProfile { - [SerializeField] - [Tooltip("The approximate height of the play space, in meters.")] - private float boundaryHeight = 3.0f; - - /// - /// The developer defined height of the boundary, in meters. - /// - /// - /// The BoundaryHeight property is used to create a three dimensional volume for the play space. - /// - public float BoundaryHeight => boundaryHeight; - - #region Floor settings + #region General Settings [SerializeField] - [Tooltip("Should the floor be displayed in the scene?")] - private bool showFloor = true; + [Tooltip("Force the boundary to always be displayed in the scene?")] + private bool showBoundary = false; /// - /// Should the boundary system display the floor? - /// - public bool ShowFloor => showFloor; - - // todo: consider allowing optional custom prefab - - [SerializeField] - [Tooltip("The material to use when displaying the floor.")] - private Material floorMaterial = null; - - /// - /// The material to use for the floor when created by the boundary system. - /// - public Material FloorMaterial => floorMaterial; - - [PhysicsLayer] - [SerializeField] - [Tooltip("The physics layer to assign to the generated floor.")] - private int floorPhysicsLayer = 0; - - /// - /// The physics layer to assign to the generated floor. - /// - public int FloorPhysicsLayer => floorPhysicsLayer; - - [SerializeField] - [Tooltip("The dimensions of the floor, in meters.")] - private Vector2 floorScale = new Vector2(10f, 10f); - - /// - /// The size at which to display the rectangular floor plane . + /// Should the boundary system display the play area? /// - public Vector2 FloorScale => floorScale; - - #endregion Floor settings - - #region Play area settings + public bool ShowBoundary => showBoundary; + [Min(1f)] [SerializeField] - [Tooltip("Should the play area be displayed in the scene?")] - private bool showPlayArea = true; + [Tooltip("The height of the boundary, in meters.")] + private float boundaryHeight = 3.0f; /// - /// Should the boundary system display the play area? + /// The developer defined height of the boundary, in meters. /// - public bool ShowPlayArea => showPlayArea; + /// + /// The BoundaryHeight property is used to create a three dimensional volume for the play space. + /// + public float BoundaryHeight => boundaryHeight; [SerializeField] - [Tooltip("The material to use when displaying the play area.")] - private Material playAreaMaterial = null; + [Tooltip("The material to use when displaying the boundary.")] + private Material boundaryMaterial = null; /// /// The material to use for the rectangular play area . /// - public Material PlayAreaMaterial => playAreaMaterial; + public Material BoundaryMaterial => boundaryMaterial; [PhysicsLayer] [SerializeField] - [Tooltip("The physics layer to assign to the generated play area.")] - private int playAreaPhysicsLayer = 2; + [Tooltip("The physics layer to assign to all the generated boundary GameObjects.")] + private int physicsLayer = 2; /// - /// The physics layer to assign to the generated play area. + /// The physics layer to assign to all the generated boundary s. /// - public int PlayAreaPhysicsLayer => playAreaPhysicsLayer; + public int PhysicsLayer => physicsLayer; - #endregion Play area settings + #endregion General Settings - #region Tracked area settings + #region Floor settings [SerializeField] - [Tooltip("Should the tracked area be displayed in the scene?")] - private bool showTrackedArea = true; + [Tooltip("Force the boundary system display the floor?")] + private bool showFloor = true; /// - /// Should the boundary system display the tracked area? + /// Force the boundary system display the floor? /// - public bool ShowTrackedArea => showTrackedArea; + public bool ShowFloor => showFloor; [SerializeField] - [Tooltip("The material to use when displaying the tracked area.")] - private Material trackedAreaMaterial = null; - - /// - /// The material to use for the boundary geometry . - /// - public Material TrackedAreaMaterial => trackedAreaMaterial; - - [PhysicsLayer] - [SerializeField] - [Tooltip("The physics layer to assign to the generated tracked area.")] - private int trackedAreaPhysicsLayer = 2; + [Tooltip("The material to use when displaying the floor.\n\nNote: if none is set, boundary material is used.")] + private Material floorMaterial = null; /// - /// The physics layer to assign to the generated tracked area. + /// The material to use for the floor when created by the boundary system. /// - public int TrackedAreaPhysicsLayer => trackedAreaPhysicsLayer; + public Material FloorMaterial => floorMaterial; - #endregion Tracked area settings + #endregion Floor settings #region Boundary wall settings [SerializeField] - [Tooltip("Should the boundary walls be displayed in the scene?")] - private bool showBoundaryWalls = false; + [Tooltip("Force the boundary walls be displayed?")] + private bool showWalls = false; /// - /// Should the boundary system display the boundary geometry walls? + /// Force the boundary walls be displayed? /// - public bool ShowBoundaryWalls => showBoundaryWalls; + public bool ShowWalls => showWalls; [SerializeField] - [Tooltip("The material to use when displaying the boundary walls.")] - private Material boundaryWallMaterial = null; + [Tooltip("The material to use when displaying the boundary walls.\n\nNote: if none is set, boundary material is used.")] + private Material wallMaterial = null; /// /// The material to use for displaying the boundary geometry walls. /// - public Material BoundaryWallMaterial => boundaryWallMaterial; - - [PhysicsLayer] - [SerializeField] - [Tooltip("The physics layer to assign to the generated boundary walls.")] - private int boundaryWallsPhysicsLayer = 2; - - /// - /// The physics layer to assign to the generated boundary walls. - /// - public int BoundaryWallsPhysicsLayer => boundaryWallsPhysicsLayer; + public Material WallMaterial => wallMaterial; #endregion Boundary wall settings #region Boundary ceiling settings [SerializeField] - [Tooltip("Should the boundary ceiling be displayed in the scene?")] - private bool showBoundaryCeiling = false; + [Tooltip("Force the boundary ceiling be displayed?")] + private bool showCeiling = false; /// - /// Should the boundary system display the boundary ceiling? + /// Force the boundary ceiling be displayed? /// - public bool ShowBoundaryCeiling => showBoundaryCeiling; + public bool ShowCeiling => showCeiling; [SerializeField] - [Tooltip("The material to use when displaying the boundary ceiling.")] - private Material boundaryCeilingMaterial = null; + [Tooltip("The material to use when displaying the boundary ceiling.\n\nNote: if none is set, boundary material is used.")] + private Material ceilingMaterial = null; /// /// The material to use for displaying the boundary ceiling. /// - public Material BoundaryCeilingMaterial => boundaryCeilingMaterial; - - [PhysicsLayer] - [SerializeField] - [Tooltip("The physics layer to assign to the generated boundary ceiling.")] - private int ceilingPhysicsLayer = 2; - - /// - /// The physics layer to assign to the generated boundary ceiling. - /// - public int CeilingPhysicsLayer => ceilingPhysicsLayer; + public Material CeilingMaterial => ceilingMaterial; #endregion Boundary ceiling settings } diff --git a/XRTK-Core/Packages/com.xrtk.core/Runtime/Definitions/BoundarySystem/ProximityAlert.cs b/XRTK-Core/Packages/com.xrtk.core/Runtime/Definitions/BoundarySystem/ProximityAlert.cs new file mode 100644 index 000000000..83f352081 --- /dev/null +++ b/XRTK-Core/Packages/com.xrtk.core/Runtime/Definitions/BoundarySystem/ProximityAlert.cs @@ -0,0 +1,25 @@ +// Copyright (c) XRTK. All rights reserved. +// Licensed under the MIT License. See LICENSE in the project root for license information. + +namespace XRTK.Definitions.BoundarySystem +{ + public enum ProximityAlert + { + /// + /// The tracked object is safely within the boundary. + /// + Clear = 0, + /// + /// The tracked object's bounds have touching the boundary. + /// + Touch, + /// + /// The tracked object has crossed outside the boundary. + /// + Exit, + /// + /// The tracked object has crossed inside the boundary. + /// + Enter, + } +} \ No newline at end of file diff --git a/XRTK-Core/Packages/com.xrtk.core/Runtime/Interfaces/BoundarySystem/IMixedRealityBoundaryHandler.cs.meta b/XRTK-Core/Packages/com.xrtk.core/Runtime/Definitions/BoundarySystem/ProximityAlert.cs.meta similarity index 86% rename from XRTK-Core/Packages/com.xrtk.core/Runtime/Interfaces/BoundarySystem/IMixedRealityBoundaryHandler.cs.meta rename to XRTK-Core/Packages/com.xrtk.core/Runtime/Definitions/BoundarySystem/ProximityAlert.cs.meta index 7202ebdb1..f5ff9ec27 100644 --- a/XRTK-Core/Packages/com.xrtk.core/Runtime/Interfaces/BoundarySystem/IMixedRealityBoundaryHandler.cs.meta +++ b/XRTK-Core/Packages/com.xrtk.core/Runtime/Definitions/BoundarySystem/ProximityAlert.cs.meta @@ -1,5 +1,5 @@ fileFormatVersion: 2 -guid: 09b8ecda83744cb4bbde06155ec420fb +guid: d8300f51e51c4b5fba9a37db81bc54ea MonoImporter: externalObjects: {} serializedVersion: 2 diff --git a/XRTK-Core/Packages/com.xrtk.core/Runtime/EventDatum/Boundary.meta b/XRTK-Core/Packages/com.xrtk.core/Runtime/EventDatum/Boundary.meta deleted file mode 100644 index 4c39f8367..000000000 --- a/XRTK-Core/Packages/com.xrtk.core/Runtime/EventDatum/Boundary.meta +++ /dev/null @@ -1,8 +0,0 @@ -fileFormatVersion: 2 -guid: 11a169014c2b414f97e09b0748e63437 -folderAsset: yes -DefaultImporter: - externalObjects: {} - userData: - assetBundleName: - assetBundleVariant: diff --git a/XRTK-Core/Packages/com.xrtk.core/Runtime/EventDatum/Boundary/BoundaryEventData.cs b/XRTK-Core/Packages/com.xrtk.core/Runtime/EventDatum/Boundary/BoundaryEventData.cs deleted file mode 100644 index 47cf0e252..000000000 --- a/XRTK-Core/Packages/com.xrtk.core/Runtime/EventDatum/Boundary/BoundaryEventData.cs +++ /dev/null @@ -1,64 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. See LICENSE in the project root for license information. - -using UnityEngine.EventSystems; -using XRTK.Interfaces.BoundarySystem; - -namespace XRTK.EventDatum.Boundary -{ - /// - /// The data describing the boundary system event. - /// - public class BoundaryEventData : GenericBaseEventData - { - /// - /// Is the floor being visualized by the boundary system. - /// - public bool IsFloorVisualized { get; private set; } - - /// - /// Is the play area being visualized by the boundary system. - /// - public bool IsPlayAreaVisualized { get; private set; } - - /// - /// Is the tracked area being visualized by the boundary system. - /// - public bool IsTrackedAreaVisualized { get; private set; } - - /// - /// Are the boundary walls being visualized by the boundary system. - /// - public bool AreBoundaryWallsVisualized { get; private set; } - - /// - /// Is the ceiling being visualized by the boundary system. - /// - /// - /// The boundary system defines the ceiling as a plane set at above the floor. - /// - public bool IsCeilingVisualized { get; private set; } - - /// - /// Constructor. - /// - /// - public BoundaryEventData(EventSystem eventSystem) : base(eventSystem) { } - - public void Initialize( - IMixedRealityBoundarySystem boundarySystem, - bool isFloorVisualized, - bool isPlayAreaVisualized, - bool isTrackedAreaVisualized, - bool areBoundaryWallsVisualized, - bool isCeilingVisualized) - { - base.BaseInitialize(boundarySystem); - IsFloorVisualized = isFloorVisualized; - IsPlayAreaVisualized = isPlayAreaVisualized; - IsTrackedAreaVisualized = isTrackedAreaVisualized; - AreBoundaryWallsVisualized = areBoundaryWallsVisualized; - IsCeilingVisualized = isCeilingVisualized; - } - } -} diff --git a/XRTK-Core/Packages/com.xrtk.core/Runtime/Extensions/BoundsExtensions.cs b/XRTK-Core/Packages/com.xrtk.core/Runtime/Extensions/BoundsExtensions.cs index 6e3d000c2..c9b1f3c96 100644 --- a/XRTK-Core/Packages/com.xrtk.core/Runtime/Extensions/BoundsExtensions.cs +++ b/XRTK-Core/Packages/com.xrtk.core/Runtime/Extensions/BoundsExtensions.cs @@ -65,6 +65,7 @@ public static class BoundsExtensions private static readonly Vector3[] rectTransformCorners = new Vector3[4]; #region Public Static Functions + /// /// Returns an instance of the 'Bounds' class which is invalid. An invalid 'Bounds' instance /// is one which has its size vector set to 'float.MaxValue' for all 3 components. The center @@ -208,7 +209,7 @@ public static void GetFacePositions(this Bounds bounds, Transform transform, ref } /// - /// Gets all the corner points and mid points from Renderer's Bounds + /// Gets all the corner points and mid points from Bounds /// /// /// @@ -259,7 +260,7 @@ public static void GetCornerAndMidPointPositions(this Bounds bounds, Transform t } /// - /// Gets all the corner points and mid points from Renderer's Bounds, ignoring the z axis + /// Gets all the corner points and mid points from Bounds, ignoring the z axis /// /// /// @@ -335,7 +336,7 @@ public static void GetCornerAndMidPointPositions2D(this Bounds bounds, Transform /// gameObject that boundingBox bounds. /// array reference that gets filled with points /// layerMask to simplify search - /// The colliders for this gameObject and it's children + /// The colliders to use for calculating the bounds of this gameObject public static void GetColliderBoundsPoints(GameObject target, ref List boundsPoints, LayerMask ignoreLayers, Collider[] colliders = null) { if (colliders == null) @@ -402,7 +403,7 @@ public static void GetColliderBoundsPoints(GameObject target, ref List /// gameObject that bounding box bounds /// array reference that gets filled with points /// layerMask to simplify search - /// The renderers for this gameObject and it's children + /// The renderers to use for calculating the bounds of this gameObject public static void GetRenderBoundsPoints(GameObject target, ref List boundsPoints, LayerMask ignoreLayers, Renderer[] renderers = null) { if (renderers == null) @@ -419,7 +420,7 @@ public static void GetRenderBoundsPoints(GameObject target, ref List bo continue; } - var bounds = rendererObj.transform.GetRenderBounds(); + var bounds = rendererObj.transform.GetRenderBounds(ref renderers); bounds.GetCornerPositions(ref corners); boundsPoints.AddRange(corners); @@ -432,9 +433,13 @@ public static void GetRenderBoundsPoints(GameObject target, ref List bo /// gameObject that bounding box bounds /// array reference that gets filled with points /// layerMask to simplify search - public static void GetMeshFilterBoundsPoints(GameObject target, ref List boundsPoints, LayerMask ignoreLayers) + /// The mesh filters to use for calculating the bounds of this gameObject + public static void GetMeshFilterBoundsPoints(GameObject target, ref List boundsPoints, LayerMask ignoreLayers, MeshFilter[] meshFilters = null) { - var meshFilters = target.GetComponentsInChildren(); + if (meshFilters == null) + { + meshFilters = target.GetComponentsInChildren(); + } for (int i = 0; i < meshFilters.Length; i++) { @@ -501,9 +506,11 @@ public static Bounds Transform(this Bounds bounds, Matrix4x4 transformMatrix) var newSizeZ = (Mathf.Abs(rotatedExtentsRight.z) + Mathf.Abs(rotatedExtentsUp.z) + Mathf.Abs(rotatedExtentsLook.z)) * 2.0f; // Construct the transformed 'Bounds' instance - var transformedBounds = new Bounds(); - transformedBounds.center = transformMatrix.MultiplyPoint(bounds.center); - transformedBounds.size = new Vector3(newSizeX, newSizeY, newSizeZ); + var transformedBounds = new Bounds + { + center = transformMatrix.MultiplyPoint(bounds.center), + size = new Vector3(newSizeX, newSizeY, newSizeZ) + }; // Return the instance to the caller return transformedBounds; diff --git a/XRTK-Core/Packages/com.xrtk.core/Runtime/Extensions/CameraExtensions.cs b/XRTK-Core/Packages/com.xrtk.core/Runtime/Extensions/CameraExtensions.cs index 67d24d17d..3c1aa8f75 100644 --- a/XRTK-Core/Packages/com.xrtk.core/Runtime/Extensions/CameraExtensions.cs +++ b/XRTK-Core/Packages/com.xrtk.core/Runtime/Extensions/CameraExtensions.cs @@ -66,22 +66,12 @@ public static float GetDistanceForFrustumHeight(this Camera camera, float frustu /// Is the inside the s frustum? /// /// - /// + /// /// - public static bool IsInFrustum(this Camera camera, Transform transform) + public static bool IsInFrustum(this Camera camera, Bounds bounds) { var planes = GeometryUtility.CalculateFrustumPlanes(camera); - return GeometryUtility.TestPlanesAABB(planes, transform.GetRenderBounds()); - } - - /// - /// Is the inside the s frustum? - /// - /// - /// - public static bool IsInFrustum(this Camera camera, GameObject gameObject) - { - return IsInFrustum(camera, gameObject.transform); + return GeometryUtility.TestPlanesAABB(planes, bounds); } } } \ No newline at end of file diff --git a/XRTK-Core/Packages/com.xrtk.core/Runtime/Extensions/TransformExtensions.cs b/XRTK-Core/Packages/com.xrtk.core/Runtime/Extensions/TransformExtensions.cs index 2168d51f1..acc06a122 100644 --- a/XRTK-Core/Packages/com.xrtk.core/Runtime/Extensions/TransformExtensions.cs +++ b/XRTK-Core/Packages/com.xrtk.core/Runtime/Extensions/TransformExtensions.cs @@ -148,12 +148,12 @@ public static Bounds GetColliderBounds(this Transform transform, ref Collider[] /// Transform of root GameObject the renderers are attached to. /// /// - /// True, by default, this will sync the rotation to calculate the axis aligned orientation. + /// True, by default, this will sync the rotation to calculate the axis aligned orientation. /// /// Optional cached renderer collection. /// The total bounds of all renderers attached to this GameObject. /// If no renderers attached, returns a bounds of center and extents 0 - public static Bounds GetRenderBounds(this Transform transform, bool syncTransform = true, Renderer[] renderers = null) + public static Bounds GetRenderBounds(this Transform transform, ref Renderer[] renderers, bool syncTransform = true) { // Store current rotation then zero out the rotation so that the bounds // are computed when the object is in its 'axis aligned orientation'. diff --git a/XRTK-Core/Packages/com.xrtk.core/Runtime/Interfaces/BoundarySystem/IMixedRealityBoundaryDataProvider.cs b/XRTK-Core/Packages/com.xrtk.core/Runtime/Interfaces/BoundarySystem/IMixedRealityBoundaryDataProvider.cs new file mode 100644 index 000000000..71e7cd531 --- /dev/null +++ b/XRTK-Core/Packages/com.xrtk.core/Runtime/Interfaces/BoundarySystem/IMixedRealityBoundaryDataProvider.cs @@ -0,0 +1,31 @@ +// Copyright (c) XRTK. All rights reserved. +// Licensed under the MIT License. See LICENSE in the project root for license information. + +using System.Collections.Generic; +using UnityEngine; + +namespace XRTK.Interfaces.BoundarySystem +{ + /// + /// Mixed Reality Toolkit boundary data provider definition, used to instantiate and manage low level api access specific devices, SDKs, and libraries. + /// + public interface IMixedRealityBoundaryDataProvider : IMixedRealityDataProvider + { + /// + /// Is the platform's boundary visible? + /// + bool IsPlatformBoundaryVisible { get; set; } + + /// + /// The the platform's boundary configured? + /// + bool IsPlatformConfigured { get; } + + /// + /// Try to get the boundary geometry from the library api. + /// + /// The list of points associated with the boundary geometry. + /// True, if valid geometry was successfully returned, otherwise false. + bool TryGetBoundaryGeometry(ref List geometry); + } +} \ No newline at end of file diff --git a/XRTK-Core/Packages/com.xrtk.core/Runtime/EventDatum/Boundary/BoundaryEventData.cs.meta b/XRTK-Core/Packages/com.xrtk.core/Runtime/Interfaces/BoundarySystem/IMixedRealityBoundaryDataProvider.cs.meta similarity index 86% rename from XRTK-Core/Packages/com.xrtk.core/Runtime/EventDatum/Boundary/BoundaryEventData.cs.meta rename to XRTK-Core/Packages/com.xrtk.core/Runtime/Interfaces/BoundarySystem/IMixedRealityBoundaryDataProvider.cs.meta index 5c38e07c2..52abb161c 100644 --- a/XRTK-Core/Packages/com.xrtk.core/Runtime/EventDatum/Boundary/BoundaryEventData.cs.meta +++ b/XRTK-Core/Packages/com.xrtk.core/Runtime/Interfaces/BoundarySystem/IMixedRealityBoundaryDataProvider.cs.meta @@ -1,5 +1,5 @@ fileFormatVersion: 2 -guid: 7a5b295dec324d82bee9bd51d54b793e +guid: abe61e338f3c45b79517a9fe2eb81155 MonoImporter: externalObjects: {} serializedVersion: 2 diff --git a/XRTK-Core/Packages/com.xrtk.core/Runtime/Interfaces/BoundarySystem/IMixedRealityBoundaryHandler.cs b/XRTK-Core/Packages/com.xrtk.core/Runtime/Interfaces/BoundarySystem/IMixedRealityBoundaryHandler.cs deleted file mode 100644 index 16973b57a..000000000 --- a/XRTK-Core/Packages/com.xrtk.core/Runtime/Interfaces/BoundarySystem/IMixedRealityBoundaryHandler.cs +++ /dev/null @@ -1,17 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. See LICENSE in the project root for license information. - -using UnityEngine.EventSystems; -using XRTK.EventDatum.Boundary; - -namespace XRTK.Interfaces.BoundarySystem -{ - public interface IMixedRealityBoundaryHandler : IEventSystemHandler - { - /// - /// Raised when the boundary visualization has changed. - /// - /// - void OnBoundaryVisualizationChanged(BoundaryEventData eventData); - } -} diff --git a/XRTK-Core/Packages/com.xrtk.core/Runtime/Interfaces/BoundarySystem/IMixedRealityBoundarySystem.cs b/XRTK-Core/Packages/com.xrtk.core/Runtime/Interfaces/BoundarySystem/IMixedRealityBoundarySystem.cs index 76e2687be..a16a3d754 100644 --- a/XRTK-Core/Packages/com.xrtk.core/Runtime/Interfaces/BoundarySystem/IMixedRealityBoundarySystem.cs +++ b/XRTK-Core/Packages/com.xrtk.core/Runtime/Interfaces/BoundarySystem/IMixedRealityBoundarySystem.cs @@ -1,140 +1,99 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. +// Copyright (c) XRTK. All rights reserved. // Licensed under the MIT License. See LICENSE in the project root for license information. +using System; +using System.Collections.Generic; using UnityEngine; -using UnityEngine.Experimental.XR; using XRTK.Definitions.BoundarySystem; -using XRTK.Interfaces.Events; namespace XRTK.Interfaces.BoundarySystem { /// - /// Manager interface for a Boundary system in the Mixed Reality Toolkit - /// All replacement systems for providing Boundary functionality should derive from this interface + /// The interface for a Boundary system in the Mixed Reality Toolkit + /// All systems for providing Boundary functionality should derive from this interface /// - public interface IMixedRealityBoundarySystem : IMixedRealityEventSystem, IMixedRealityEventSource + public interface IMixedRealityBoundarySystem : IMixedRealitySystem { /// - /// The height of the play space, in meters. - /// - /// - /// This is used to create a three dimensional boundary volume. - /// - float BoundaryHeight { get; set; } - - /// - /// Enable / disable floor rendering. - /// - bool ShowFloor { get; set; } - - /// - /// The size at which to display the rectangular floor plane . - /// - Vector2 FloorScale { get; } - - /// - /// The material to use for the floor when created by the boundary system. - /// - Material FloorMaterial { get; } - - /// - /// The physics layer that the generated floor is assigned to. - /// - int FloorPhysicsLayer { get; set; } - - /// - /// Enable / disable play area rendering. - /// - bool ShowPlayArea { get; set; } - - /// - /// The material to use for the rectangular play area . - /// - Material PlayAreaMaterial { get; } - - /// - /// The physics layer that the generated play area is assigned to. + /// Event raised when a tracked object nears, or crosses the boundary. /// - int PlayAreaPhysicsLayer { get; set; } + event Action BoundaryProximityAlert; /// - /// Enable / disable tracked area rendering. + /// The list of currently tracked objects that will raise s. /// - bool ShowTrackedArea { get; set; } + IReadOnlyList TrackedObjects { get; } /// - /// The material to use for the boundary geometry . + /// The assigned to this system. /// - Material TrackedAreaMaterial { get; } - - /// - /// The physics layer that the generated tracked area is assigned to. - /// - int TrackedAreaPhysicsLayer { get; set; } + /// + /// Typically with systems, there can be multiple data providers, but in this case there should only ever be one. + /// + IMixedRealityBoundaryDataProvider BoundaryDataProvider { get; } /// - /// Enable / disable boundary wall rendering. + /// Is the platform or the boundary system's rendered boundary geometry visible? /// - bool ShowBoundaryWalls { get; set; } + /// + /// This can enables or disables both the platform's and boundary systems rendered geometry, but the platform can always override their rendering state. + /// + bool IsVisible { get; set; } /// - /// The material to use for displaying the boundary geometry walls. + /// Has the boundary been properly configured by the user? /// - Material BoundaryWallMaterial { get; } + bool IsConfigured { get; } /// - /// The physics layer that the generated boundary walls are assigned to. + /// Enable or disable the generated boundary geometry. /// - int BoundaryWallsPhysicsLayer { get; set; } + /// + /// This cannot or ever will disable or override the platform's built-in boundary systems. + /// + bool ShowBoundary { get; set; } /// - /// Enable / disable ceiling rendering. + /// The height of the play space, in meters. /// /// - /// The ceiling is defined as a positioned above the floor. + /// This is used to create a three dimensional boundary volume. /// - bool ShowBoundaryCeiling { get; set; } + float BoundaryHeight { get; set; } /// - /// The material to use for displaying the boundary ceiling. + /// Enable or disable floor geometry. /// - Material BoundaryCeilingMaterial { get; } + bool ShowFloor { get; set; } /// - /// The physics layer that the generated boundary ceiling is assigned to. + /// Enable or disable boundary wall geometry. /// - int CeilingPhysicsLayer { get; set; } + bool ShowWalls { get; set; } /// - /// Two dimensional representation of the geometry of the boundary, as provided - /// by the platform. + /// Enable or disable boundary ceiling geometry. /// /// - /// BoundaryGeometry should be treated as the outline of the player's space, placed - /// on the floor. + /// The ceiling is defined as a positioned above the floor. /// - Edge[] Bounds { get; } + bool ShowCeiling { get; set; } /// - /// Indicates the height of the floor, in relation to the coordinate system origin. + /// Two dimensional representation of the geometry of the boundary, as provided by the platform. /// /// - /// If a floor has been located, FloorHeight.HasValue will be true, otherwise it will be false. + /// BoundaryGeometry should be treated as the outline of the player's space, placed on the floor. /// - float? FloorHeight { get; } + Edge[] BoundaryBounds { get; } /// - /// Determines if a location is within the specified area of the boundary space. + /// Determines if a is within the area of the boundary space. /// - /// The location to be checked. - /// The type of boundary space being checked. - /// True if the location is within the specified area of the boundary space. - /// - /// Use: - /// Boundary.Type.PlayArea for the inscribed volume - /// Boundary.Type.TrackedArea for the area defined by the boundary edges. - /// - bool Contains(Vector3 location, Boundary.Type boundaryType = Boundary.Type.TrackedArea); + /// The position to be checked. + /// Is this position using local or worldspace coordinate space? + /// True if the location is within the area of the boundary space, otherwise false. + bool IsInsideBoundary(Vector3 position, Space referenceSpace = Space.World); /// /// Returns the description of the inscribed rectangular bounds. @@ -147,33 +106,13 @@ public interface IMixedRealityBoundarySystem : IMixedRealityEventSystem, IMixedR bool TryGetRectangularBoundsParams(out Vector2 center, out float angle, out float width, out float height); /// - /// Gets the that represents the user's floor. - /// - /// The floor visualization object or null if one does not exist. - GameObject GetFloorVisualization(); - - /// - /// Gets the that represents the user's play area. - /// - /// The play area visualization object or null if one does not exist. - GameObject GetPlayAreaVisualization(); - - /// - /// Gets the that represents the user's tracked area. - /// - /// The tracked area visualization object or null if one does not exist. - GameObject GetTrackedAreaVisualization(); - - /// - /// Gets the that represents the user's boundary walls. + /// Registers the of a to be tracked for s. /// - /// The boundary wall visualization object or null if one does not exist. - GameObject GetBoundaryWallVisualization(); + void RegisterTrackedObject(GameObject gameObject); /// - /// Gets the that represents the upper surface of the user's boundary. + /// Unregisters the of a from s. /// - /// The boundary ceiling visualization object or null if one does not exist. - GameObject GetBoundaryCeilingVisualization(); + void UnregisterTrackedObject(GameObject gameObject); } } \ No newline at end of file diff --git a/XRTK-Core/Packages/com.xrtk.core/Runtime/Services/BoundarySystem/MixedRealityBoundarySystem.cs b/XRTK-Core/Packages/com.xrtk.core/Runtime/Services/BoundarySystem/MixedRealityBoundarySystem.cs index 78b8900f7..4680c6b90 100644 --- a/XRTK-Core/Packages/com.xrtk.core/Runtime/Services/BoundarySystem/MixedRealityBoundarySystem.cs +++ b/XRTK-Core/Packages/com.xrtk.core/Runtime/Services/BoundarySystem/MixedRealityBoundarySystem.cs @@ -1,14 +1,12 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. +// Copyright (c) XRTK. All rights reserved. // Licensed under the MIT License. See LICENSE in the project root for license information. -using System.Collections; +using System; using System.Collections.Generic; +using System.Linq; +using System.Runtime.InteropServices; using UnityEngine; -using UnityEngine.EventSystems; -using UnityEngine.Experimental.XR; -using UnityEngine.XR; using XRTK.Definitions.BoundarySystem; -using XRTK.EventDatum.Boundary; using XRTK.Extensions; using XRTK.Interfaces.BoundarySystem; using XRTK.Utilities; @@ -18,8 +16,8 @@ namespace XRTK.Services.BoundarySystem /// /// The Boundary system controls the presentation and display of the users boundary in a scene. /// - [System.Runtime.InteropServices.Guid("FE458876-CC0F-4B6F-9459-544DDF6A9263")] - public class MixedRealityBoundarySystem : BaseEventSystem, IMixedRealityBoundarySystem + [Guid("FE458876-CC0F-4B6F-9459-544DDF6A9263")] + public class MixedRealityBoundarySystem : BaseSystem, IMixedRealityBoundarySystem { /// /// Constructor. @@ -28,194 +26,36 @@ public class MixedRealityBoundarySystem : BaseEventSystem, IMixedRealityBoundary public MixedRealityBoundarySystem(MixedRealityBoundaryVisualizationProfile profile) : base(profile) { - showFloor = profile.ShowFloor; - floorPhysicsLayer = profile.FloorPhysicsLayer; - FloorScale = profile.FloorScale; - FloorMaterial = profile.FloorMaterial; - showPlayArea = profile.ShowPlayArea; - PlayAreaMaterial = profile.PlayAreaMaterial; - playAreaPhysicsLayer = profile.PlayAreaPhysicsLayer; + showBoundary = profile.ShowBoundary; BoundaryHeight = profile.BoundaryHeight; - showTrackedArea = profile.ShowTrackedArea; - TrackedAreaMaterial = profile.TrackedAreaMaterial; - trackedAreaPhysicsLayer = profile.TrackedAreaPhysicsLayer; - showBoundaryWalls = profile.ShowBoundaryWalls; - BoundaryWallMaterial = profile.BoundaryWallMaterial; - boundaryWallsPhysicsLayer = profile.BoundaryWallsPhysicsLayer; - showCeiling = profile.ShowBoundaryCeiling; - BoundaryCeilingMaterial = profile.BoundaryCeilingMaterial; - ceilingPhysicsLayer = profile.CeilingPhysicsLayer; - } - - #region IMixedRealityService Implementation - - private BoundaryEventData boundaryEventData = null; - - /// - public override void Initialize() - { - base.Initialize(); - - if (!Application.isPlaying) - { - return; - } - - boundaryEventData = new BoundaryEventData(EventSystem.current); - } - - /// - public override void Enable() - { - base.Enable(); - - if (!Application.isPlaying) - { - return; - } - - CalculateBoundaryBounds(); - Boundary.visible = true; - - if (ShowFloor) - { - GetFloorVisualization(); - } + BoundaryMaterial = profile.BoundaryMaterial; + boundaryPhysicsLayer = profile.PhysicsLayer; - if (ShowPlayArea) - { - GetPlayAreaVisualization(); - } - - if (ShowTrackedArea) - { - GetTrackedAreaVisualization(); - } - - if (ShowBoundaryWalls) - { - GetBoundaryWallVisualization(); - } - - if (ShowBoundaryWalls) - { - GetBoundaryCeilingVisualization(); - } - - RaiseBoundaryVisualizationChanged(); - } - - /// - public override void Disable() - { - base.Disable(); - - if (!Application.isPlaying) - { - return; - } - - showFloor = false; - showPlayArea = false; - showTrackedArea = false; - showBoundaryWalls = false; - showCeiling = false; - Boundary.visible = false; - } - - /// - public override void Destroy() - { - base.Destroy(); - // Destroys the parent and all the child objects - boundaryVisualizationParent.Destroy(); - RaiseBoundaryVisualizationChanged(); - } - - /// - /// Raises an event to indicate that the visualization of the boundary has been changed by the boundary system. - /// - private void RaiseBoundaryVisualizationChanged() - { - if (!Application.isPlaying) { return; } - boundaryEventData.Initialize(this, ShowFloor, ShowPlayArea, ShowTrackedArea, ShowBoundaryWalls, ShowBoundaryCeiling); - HandleEvent(boundaryEventData, OnVisualizationChanged); - } - - /// - /// Event sent whenever the boundary visualization changes. - /// - private static readonly ExecuteEvents.EventFunction OnVisualizationChanged = - delegate (IMixedRealityBoundaryHandler handler, BaseEventData eventData) - { - var boundaryEventData = ExecuteEvents.ValidateEventData(eventData); - handler.OnBoundaryVisualizationChanged(boundaryEventData); - }; - - #endregion IMixedRealityService Implementation - - #region IMixedRealtyEventSystem Implementation - - /// - public override void HandleEvent(BaseEventData eventData, ExecuteEvents.EventFunction eventHandler) - { - base.HandleEvent(eventData, eventHandler); - } - - /// - /// Registers the to listen for boundary events. - /// - /// - public override void Register(GameObject listener) - { - base.Register(listener); - } - - /// - /// UnRegisters the to listen for boundary events. - /// /// - /// - public override void Unregister(GameObject listener) - { - base.Unregister(listener); - } - - #endregion - - #region IMixedRealityEventSource Implementation - - /// - bool IEqualityComparer.Equals(object x, object y) - { - // There shouldn't be other Boundary Managers to compare to. - return false; - } - - /// - public int GetHashCode(object obj) - { - return Mathf.Abs(SourceName.GetHashCode()); + showFloor = profile.ShowFloor; + FloorMaterial = profile.FloorMaterial.IsNull() + ? profile.BoundaryMaterial + : profile.FloorMaterial; + + showWalls = profile.ShowWalls; + WallMaterial = profile.WallMaterial.IsNull() + ? profile.BoundaryMaterial + : profile.WallMaterial; + + showCeiling = profile.ShowCeiling; + CeilingMaterial = profile.CeilingMaterial.IsNull() + ? profile.BoundaryMaterial + : profile.CeilingMaterial; } - /// - public uint SourceId { get; } = 0; - - /// - public string SourceName { get; } = "Mixed Reality Boundary System"; - - #endregion IMixedRealityEventSource Implementation - - #region IMixedRealityBoundarySystem Implementation - /// /// Layer used to tell the (non-floor) boundary objects to not accept raycasts /// - private const int DefaultIgnoreRaycastLayer = 2; + private const int DEFAULT_IGNORE_RAYCAST_LAYER = 2; /// /// The thickness of three dimensional generated boundary objects. /// - private const float BoundaryObjectThickness = 0.005f; + private const float BOUNDARY_OBJECT_THICKNESS = 0.005f; /// /// A small offset to avoid render conflicts, primarily with the floor. @@ -223,358 +63,515 @@ public int GetHashCode(object obj) /// /// This offset is used to avoid consuming multiple physics layers. /// - private const float BoundaryObjectRenderOffset = 0.001f; + private const float BOUNDARY_OBJECT_RENDER_OFFSET = 0.001f; - private GameObject boundaryVisualizationParent; + private readonly Dictionary trackedObjects = new Dictionary(3); - /// - /// Parent which will encapsulate all of the teleportable boundary visualizations. - /// - private GameObject BoundaryVisualizationParent + private InscribedRectangle rectangularBounds; + + private GameObject boundaryVisualizationRoot = null; + + private GameObject BoundarySystemVisualizationRoot { get { - if (!boundaryVisualizationParent.IsNull()) + if (!boundaryVisualizationRoot.IsNull()) { - return boundaryVisualizationParent; + return boundaryVisualizationRoot; } - var visualizationParent = new GameObject("Boundary System Visualizations"); + boundaryVisualizationRoot = new GameObject(nameof(BoundarySystemVisualizationRoot)); var playspaceTransform = MixedRealityToolkit.CameraSystem != null ? MixedRealityToolkit.CameraSystem.MainCameraRig.PlayspaceTransform : CameraCache.Main.transform.parent; - visualizationParent.transform.SetParent(playspaceTransform, false); - return boundaryVisualizationParent = visualizationParent; + boundaryVisualizationRoot.transform.SetParent(playspaceTransform, false); + + return boundaryVisualizationRoot; } } - /// - public float BoundaryHeight { get; set; } - - private bool showFloor; + private GameObject boundaryVisualization = null; - /// - public bool ShowFloor + private GameObject BoundaryVisualization { - get => showFloor; - set + get { - if (showFloor == value) { return; } - - showFloor = value; - - if (value && (currentFloorObject.IsNull())) + if (!boundaryVisualization.IsNull()) { - GetFloorVisualization(); + return boundaryVisualization; } - if (!currentFloorObject.IsNull()) + // If we do not have boundary edges, we cannot render them. + if (BoundaryBounds.Length == 0) { - currentFloorObject.SetActive(value); + return null; } - RaiseBoundaryVisualizationChanged(); - } - } + // Get the rectangular bounds. + if (!TryGetRectangularBoundsParams(out var center, out var angle, out var width, out var height)) + { + // No rectangular bounds, therefore cannot create the play area. + return null; + } - /// - public Vector2 FloorScale { get; } + // Render the rectangular bounds. + if (!EdgeUtilities.IsValidPoint(center)) + { + // Invalid rectangle / play area not found + return null; + } - /// - public Material FloorMaterial { get; } + boundaryVisualization = GameObject.CreatePrimitive(PrimitiveType.Quad); + boundaryVisualization.name = nameof(BoundaryVisualization); + boundaryVisualization.layer = boundaryPhysicsLayer; + boundaryVisualization.transform.Translate(new Vector3(center.x, BOUNDARY_OBJECT_RENDER_OFFSET, center.y)); + boundaryVisualization.transform.Rotate(new Vector3(90f, -angle, 0.0f)); + boundaryVisualization.transform.localScale = new Vector3(width, height, 1.0f); + boundaryVisualization.GetComponent().sharedMaterial = BoundaryMaterial; + boundaryVisualization.transform.SetParent(BoundarySystemVisualizationRoot.transform, false); - private bool showPlayArea; + boundaryVisualization.AddComponent(); - private int floorPhysicsLayer; + // Get the line vertices + var lineVertices = new List(); - /// - public int FloorPhysicsLayer - { - get - { - if (!currentFloorObject.IsNull()) + for (int i = 0; i < BoundaryBounds.Length; i++) { - floorPhysicsLayer = currentFloorObject.layer; + lineVertices.Add(new Vector3(BoundaryBounds[i].PointA.x, 0f, BoundaryBounds[i].PointA.y)); } - return floorPhysicsLayer; - } - set - { - floorPhysicsLayer = value; + // Add the first vertex again to ensure the loop closes. + lineVertices.Add(lineVertices[0]); - if (!currentFloorObject.IsNull()) - { - currentFloorObject.layer = floorPhysicsLayer; - } + // Configure the renderer properties. + const float lineWidth = 0.01f; + var lineRenderer = boundaryVisualization.GetComponent(); + lineRenderer.sharedMaterial = BoundaryMaterial; + lineRenderer.useWorldSpace = false; + lineRenderer.startWidth = lineWidth; + lineRenderer.endWidth = lineWidth; + lineRenderer.positionCount = lineVertices.Count; + lineRenderer.SetPositions(lineVertices.ToArray()); + + boundaryVisualization.SetActive(showBoundary); + + return boundaryVisualization; } } - /// - public bool ShowPlayArea + private GameObject floorVisualization = null; + + private GameObject FloorVisualization { - get => showPlayArea; - set + get { - if (showPlayArea == value) { return; } - - showPlayArea = value; - - if (value && currentPlayAreaObject.IsNull()) + if (!floorVisualization.IsNull()) { - GetPlayAreaVisualization(); + return floorVisualization; } - if (!currentPlayAreaObject.IsNull()) - { - currentPlayAreaObject.SetActive(value); - } + var position = MixedRealityToolkit.CameraSystem != null + ? MixedRealityToolkit.CameraSystem.MainCameraRig.PlayspaceTransform.position + : CameraCache.Main.transform.parent.position; - RaiseBoundaryVisualizationChanged(); + // Render the floor. + floorVisualization = GameObject.CreatePrimitive(PrimitiveType.Cube); + floorVisualization.name = nameof(FloorVisualization); + floorVisualization.transform.Translate(new Vector3( + position.x, + position.y - (floorVisualization.transform.localScale.y * 0.5f), + position.z)); + floorVisualization.layer = boundaryPhysicsLayer; + floorVisualization.GetComponent().sharedMaterial = FloorMaterial; + floorVisualization.transform.SetParent(BoundarySystemVisualizationRoot.transform, false); + floorVisualization.SetActive(showFloor); + + return floorVisualization; } } - private bool showTrackedArea; + private GameObject wallVisualization = null; - /// - public int PlayAreaPhysicsLayer + private GameObject WallVisualization { get { - if (!currentPlayAreaObject.IsNull()) + if (!wallVisualization.IsNull()) { - playAreaPhysicsLayer = currentPlayAreaObject.layer; + return wallVisualization; } - return playAreaPhysicsLayer; - } - set - { - playAreaPhysicsLayer = value; + if (BoundaryBounds.Length == 0) + { + // If we do not have boundary edges, we cannot render walls. + return null; + } + + wallVisualization = new GameObject(nameof(WallVisualization)) + { + layer = boundaryPhysicsLayer + }; - if (!currentPlayAreaObject.IsNull()) + for (int i = 0; i < BoundaryBounds.Length; i++) { - currentPlayAreaObject.layer = playAreaPhysicsLayer; + var wall = GameObject.CreatePrimitive(PrimitiveType.Cube); + wall.name = $"Wall_{i}"; + wall.GetComponent().sharedMaterial = WallMaterial; + wall.transform.localScale = new Vector3((BoundaryBounds[i].PointB - BoundaryBounds[i].PointA).magnitude, BoundaryHeight, BOUNDARY_OBJECT_THICKNESS); + wall.layer = boundaryPhysicsLayer; + + // Position and rotate the wall. + var midpoint = Vector2.Lerp(BoundaryBounds[i].PointA, BoundaryBounds[i].PointB, 0.5f); + wall.transform.SetParent(wallVisualization.transform, false); + wall.transform.position = new Vector3(midpoint.x, (BoundaryHeight * 0.5f), midpoint.y); + var rotationAngle = MathUtilities.GetAngleBetween(BoundaryBounds[i].PointB, BoundaryBounds[i].PointA); + wall.transform.rotation = Quaternion.Euler(0.0f, -rotationAngle, 0.0f); } + + wallVisualization.transform.SetParent(BoundarySystemVisualizationRoot.transform, false); + + return wallVisualization; } } - private int playAreaPhysicsLayer; - - /// - public Material PlayAreaMaterial { get; } + private GameObject ceilingVisualization = null; - /// - public bool ShowTrackedArea + private GameObject CeilingVisualization { - get => showTrackedArea; - set + get { - if (showTrackedArea == value) { return; } - showTrackedArea = value; + if (!ceilingVisualization.IsNull()) + { + return ceilingVisualization; + } - if (value && currentTrackedAreaObject.IsNull()) + if (BoundaryBounds.Length == 0) { - GetTrackedAreaVisualization(); + // If we do not have boundary edges, we cannot render a ceiling. + return null; } - if (!currentTrackedAreaObject.IsNull()) + // Get the smallest rectangle that contains the entire boundary. + var boundaryBoundingBox = new Bounds(); + + for (int i = 0; i < BoundaryBounds.Length; i++) { - currentTrackedAreaObject.SetActive(value); + // The boundary geometry is a closed loop. As such, we can encapsulate only PointA of each Edge. + boundaryBoundingBox.Encapsulate(new Vector3(BoundaryBounds[i].PointA.x, BoundaryHeight * 0.5f, BoundaryBounds[i].PointA.y)); } - RaiseBoundaryVisualizationChanged(); + ceilingVisualization = GameObject.CreatePrimitive(PrimitiveType.Cube); + ceilingVisualization.name = nameof(CeilingVisualization); + ceilingVisualization.layer = DEFAULT_IGNORE_RAYCAST_LAYER; + ceilingVisualization.transform.localScale = new Vector3(boundaryBoundingBox.size.x, BOUNDARY_OBJECT_THICKNESS, boundaryBoundingBox.size.z); + ceilingVisualization.transform.Translate(new Vector3( + boundaryBoundingBox.center.x, + BoundaryHeight + (ceilingVisualization.transform.localScale.y * 0.5f), + boundaryBoundingBox.center.z)); + ceilingVisualization.GetComponent().sharedMaterial = CeilingMaterial; + ceilingVisualization.layer = boundaryPhysicsLayer; + ceilingVisualization.transform.SetParent(BoundarySystemVisualizationRoot.transform, false); + + return ceilingVisualization; } } - private bool showBoundaryWalls = false; - - private int trackedAreaPhysicsLayer; + #region IMixedRealityService Implementation /// - public int TrackedAreaPhysicsLayer + public override void Enable() { - get + base.Enable(); + + BoundaryDataProvider = MixedRealityToolkit.GetService(); + + if (!Application.isPlaying || BoundaryDataProvider == null) { return; } + + // Reset the bounds + BoundaryBounds = new Edge[0]; + rectangularBounds = null; + + // Get the boundary geometry. + var boundaryGeometry = new List(0); + var boundaryEdges = new List(0); + + if (BoundaryDataProvider.TryGetBoundaryGeometry(ref boundaryGeometry) && boundaryGeometry.Count > 0) { - if (!currentTrackedAreaObject.IsNull()) + for (int i = 0; i < boundaryGeometry.Count; i++) { - trackedAreaPhysicsLayer = currentTrackedAreaObject.layer; + var pointA = boundaryGeometry[i]; + var pointB = boundaryGeometry[(i + 1) % boundaryGeometry.Count]; + boundaryEdges.Add(new Edge(pointA, pointB)); } - return trackedAreaPhysicsLayer; + BoundaryBounds = boundaryEdges.ToArray(); + // We always use the same seed so that from run to run, the inscribed bounds are consistent. + rectangularBounds = new InscribedRectangle(BoundaryBounds, Mathf.Abs("Mixed Reality Toolkit".GetHashCode())); } - set + else { - trackedAreaPhysicsLayer = value; - - if (!currentTrackedAreaObject.IsNull()) - { - currentTrackedAreaObject.layer = trackedAreaPhysicsLayer; - } + Debug.LogWarning("No Boundary Geometry found"); } - } - /// - public Material TrackedAreaMaterial { get; } + BoundarySystemVisualizationRoot.SetActive(true); + } /// - public bool ShowBoundaryWalls + public override void Update() { - get => showBoundaryWalls; - set - { - if (showBoundaryWalls == value) { return; } + base.Update(); + + if (!Application.isPlaying || BoundaryDataProvider == null) { return; } - showBoundaryWalls = value; + foreach (var trackedObjectStatus in trackedObjects) + { + var trackedBounds = trackedObjectStatus.Key; + var status = trackedObjectStatus.Value; + var isAnyCornerInsideBoundary = false; + var isCornersFullyInsideBoundary = true; - if (value && currentBoundaryWallObject.IsNull()) + for (int i = 0; i < trackedBounds.BoundsCornerPoints.Length; i++) { - GetBoundaryWallVisualization(); + var result = IsInsideBoundary(trackedBounds.BoundsCornerPoints[i]); + isAnyCornerInsideBoundary |= result; + isCornersFullyInsideBoundary &= result; } - if (!currentBoundaryWallObject.IsNull()) + switch (status) { - currentBoundaryWallObject.SetActive(value); + case ProximityAlert.Clear: + if (isAnyCornerInsideBoundary && !isCornersFullyInsideBoundary) + { + status = ProximityAlert.Touch; + } + + // In case object moves faster than updates can track it's ejection or + // if the object was already outside of the boundary. + if (!isCornersFullyInsideBoundary) + { + status = ProximityAlert.Exit; + } + + break; + case ProximityAlert.Touch: + if (isCornersFullyInsideBoundary) + { + status = ProximityAlert.Clear; + } + + if (!isAnyCornerInsideBoundary) + { + status = ProximityAlert.Exit; + } + + break; + case ProximityAlert.Exit: + // Left and re-entered the boundary + if (isAnyCornerInsideBoundary) + { + status = ProximityAlert.Enter; + } + + break; + case ProximityAlert.Enter: + if (isAnyCornerInsideBoundary) + { + status = isCornersFullyInsideBoundary + ? ProximityAlert.Clear + : ProximityAlert.Touch; + } + else + { + status = ProximityAlert.Exit; + } + + break; } - RaiseBoundaryVisualizationChanged(); + if (status != trackedObjectStatus.Value) + { + trackedObjects[trackedBounds] = status; + BoundaryProximityAlert?.Invoke(trackedBounds.gameObject, status); + } } } - private bool showCeiling = false; + /// + public override void Disable() + { + base.Disable(); - private int boundaryWallsPhysicsLayer; + if (!Application.isPlaying || BoundaryDataProvider == null) { return; } + + if (!boundaryVisualizationRoot.IsNull()) + { + boundaryVisualizationRoot.SetActive(false); + } + } /// - public int BoundaryWallsPhysicsLayer + public override void Destroy() { - get + base.Destroy(); + + if (!Application.isPlaying || BoundaryDataProvider == null) { return; } + + // Destroys the parent and all the child objects + boundaryVisualizationRoot.Destroy(); + } + + #endregion IMixedRealityService Implementation + + #region Implementation of IMixedRealityBoundarySystem + + /// + public event Action BoundaryProximityAlert; + + /// + public IReadOnlyList TrackedObjects => trackedObjects.Keys.Select(cache => cache.gameObject).ToList(); + + /// + public IMixedRealityBoundaryDataProvider BoundaryDataProvider { get; private set; } + + /// + public bool IsVisible + { + get => BoundaryDataProvider != null && BoundaryDataProvider.IsPlatformBoundaryVisible || + BoundarySystemVisualizationRoot.activeInHierarchy && + (ShowBoundary || + ShowFloor || + ShowWalls || + ShowCeiling); + set { - if (!currentBoundaryWallObject.IsNull()) + if (BoundaryDataProvider != null) { - boundaryWallsPhysicsLayer = currentBoundaryWallObject.layer; + BoundaryDataProvider.IsPlatformBoundaryVisible = value; } - return boundaryWallsPhysicsLayer; + BoundarySystemVisualizationRoot.SetActive(value); } + } + + private bool showBoundary; + + /// + public bool ShowBoundary + { + get => showBoundary; set { - boundaryWallsPhysicsLayer = value; + showBoundary = value; - if (!currentBoundaryWallObject.IsNull()) + if (!BoundaryVisualization.IsNull()) { - currentBoundaryWallObject.layer = boundaryWallsPhysicsLayer; + BoundaryVisualization.SetActive(value); } } } - /// - public Material BoundaryWallMaterial { get; } + /// + public bool IsConfigured => BoundaryDataProvider?.IsPlatformConfigured ?? false; - /// - public bool ShowBoundaryCeiling + /// + public float BoundaryHeight { get; set; } + + private readonly int boundaryPhysicsLayer; + + private bool showFloor; + + /// + public bool ShowFloor { - get => showCeiling; + get => showFloor; set { - if (showCeiling == value) { return; } - - showCeiling = value; - - if (value && currentCeilingObject.IsNull()) - { - GetBoundaryCeilingVisualization(); - } + showFloor = value; - if (!currentCeilingObject.IsNull()) + if (!FloorVisualization.IsNull()) { - currentCeilingObject.SetActive(value); + FloorVisualization.SetActive(value); } - - RaiseBoundaryVisualizationChanged(); } } - private int ceilingPhysicsLayer; + private Material FloorMaterial { get; } - /// - public int CeilingPhysicsLayer + private Material BoundaryMaterial { get; } + + private bool showWalls = false; + + /// + public bool ShowWalls { - get + get => showWalls; + set { - if (!currentCeilingObject.IsNull()) + showWalls = value; + + if (!WallVisualization.IsNull()) { - ceilingPhysicsLayer = currentCeilingObject.layer; + WallVisualization.SetActive(value); } - - return ceilingPhysicsLayer; } + } + + private Material WallMaterial { get; } + + private bool showCeiling = false; + + /// + public bool ShowCeiling + { + get => showCeiling; set { - ceilingPhysicsLayer = value; + showCeiling = value; - if (!currentCeilingObject.IsNull()) + if (!CeilingVisualization.IsNull()) { - currentFloorObject.layer = ceilingPhysicsLayer; + CeilingVisualization.SetActive(value); } } } - /// - public Material BoundaryCeilingMaterial { get; } - - /// - public Edge[] Bounds { get; private set; } = new Edge[0]; + private Material CeilingMaterial { get; } - /// - public float? FloorHeight { get; private set; } + /// + public Edge[] BoundaryBounds { get; private set; } = new Edge[0]; - /// - public bool Contains(Vector3 location, Boundary.Type boundaryType = Boundary.Type.TrackedArea) + /// + public bool IsInsideBoundary(Vector3 position, Space referenceSpace = Space.World) { - if (!EdgeUtilities.IsValidPoint(location)) + if (!EdgeUtilities.IsValidPoint(position)) { // Invalid location. return false; } - if (!FloorHeight.HasValue) - { - // No floor. - return false; - } - // Handle the user teleporting (boundary moves with them). var playspaceTransform = MixedRealityToolkit.CameraSystem != null ? MixedRealityToolkit.CameraSystem.MainCameraRig.PlayspaceTransform : CameraCache.Main.transform.parent; - location = playspaceTransform.InverseTransformPoint(location); - if (FloorHeight.Value > location.y || - BoundaryHeight < location.y) + if (referenceSpace == Space.World) + { + position = playspaceTransform.InverseTransformPoint(position); + } + + if (position.y < 0 || + position.y > BoundaryHeight) { // Location below the floor or above the boundary height. return false; } // Boundary coordinates are always "on the floor" - var point = new Vector2(location.x, location.z); - - switch (boundaryType) - { - case Boundary.Type.PlayArea when rectangularBounds != null: - // Check the inscribed rectangle. - return rectangularBounds.IsInsideBoundary(point); - case Boundary.Type.TrackedArea: - // Check the geometry - return EdgeUtilities.IsInsideBoundary(Bounds, point); - default: - // Not in either boundary type. - return false; - } + var point = new Vector2(position.x, position.z); + return EdgeUtilities.IsInsideBoundary(BoundaryBounds, point); } - /// + /// public bool TryGetRectangularBoundsParams(out Vector2 center, out float angle, out float width, out float height) { if (rectangularBounds == null || !rectangularBounds.IsValid) @@ -600,298 +597,29 @@ public bool TryGetRectangularBoundsParams(out Vector2 center, out float angle, o return true; } - /// - public GameObject GetFloorVisualization() - { - if (!Application.isPlaying) { return null; } - - if (!currentFloorObject.IsNull()) - { - return currentFloorObject; - } - - if (!FloorHeight.HasValue) - { - // We were unable to locate the floor. - return null; - } - - var floorScale = FloorScale; - var position = MixedRealityToolkit.CameraSystem != null - ? MixedRealityToolkit.CameraSystem.MainCameraRig.PlayspaceTransform.position - : CameraCache.Main.transform.parent.position; - - // Render the floor. - currentFloorObject = GameObject.CreatePrimitive(PrimitiveType.Cube); - currentFloorObject.name = "Boundary System Floor"; - currentFloorObject.transform.localScale = new Vector3(floorScale.x, BoundaryObjectThickness, floorScale.y); - currentFloorObject.transform.Translate(new Vector3( - position.x, - FloorHeight.Value - (currentFloorObject.transform.localScale.y * 0.5f), - position.z)); - currentFloorObject.layer = FloorPhysicsLayer; - currentFloorObject.GetComponent().sharedMaterial = FloorMaterial; - - currentFloorObject.transform.parent = BoundaryVisualizationParent.transform; - - return currentFloorObject; - } - - /// - public GameObject GetPlayAreaVisualization() - { - if (!Application.isPlaying) { return null; } - - if (!currentPlayAreaObject.IsNull()) - { - return currentPlayAreaObject; - } - - // Get the rectangular bounds. - - if (!TryGetRectangularBoundsParams(out var center, out var angle, out var width, out var 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(90f, -angle, 0.0f)); - currentPlayAreaObject.transform.localScale = new Vector3(width, height, 1.0f); - currentPlayAreaObject.GetComponent().sharedMaterial = PlayAreaMaterial; - - currentPlayAreaObject.transform.parent = BoundaryVisualizationParent.transform; - - return currentPlayAreaObject; - } - - /// - public GameObject GetTrackedAreaVisualization() - { - if (!Application.isPlaying) { return null; } - - if (!currentTrackedAreaObject.IsNull()) - { - return currentTrackedAreaObject; - } - - if (Bounds.Length == 0) - { - // If we do not have boundary edges, we cannot render them. - return null; - } - - // Get the line vertices - var lineVertices = new List(); - - for (int i = 0; i < Bounds.Length; i++) - { - lineVertices.Add(new Vector3(Bounds[i].PointA.x, 0f, Bounds[i].PointA.y)); - } - - // Add the first vertex again to ensure the loop closes. - lineVertices.Add(lineVertices[0]); - - // We use an empty object and attach a line renderer. - currentTrackedAreaObject = new GameObject("Tracked Area") - { - layer = DefaultIgnoreRaycastLayer - }; - - var position = MixedRealityToolkit.CameraSystem != null - ? MixedRealityToolkit.CameraSystem.MainCameraRig.PlayspaceTransform.position - : CameraCache.Main.transform.parent.position; - - currentTrackedAreaObject.AddComponent(); - currentTrackedAreaObject.transform.Translate( - new Vector3(position.x, BoundaryObjectRenderOffset, position.z)); - currentPlayAreaObject.layer = TrackedAreaPhysicsLayer; - - // Configure the renderer properties. - const float lineWidth = 0.01f; - var lineRenderer = currentTrackedAreaObject.GetComponent(); - lineRenderer.sharedMaterial = TrackedAreaMaterial; - lineRenderer.useWorldSpace = false; - lineRenderer.startWidth = lineWidth; - lineRenderer.endWidth = lineWidth; - lineRenderer.positionCount = lineVertices.Count; - lineRenderer.SetPositions(lineVertices.ToArray()); - - currentTrackedAreaObject.transform.parent = BoundaryVisualizationParent.transform; - - return currentTrackedAreaObject; - } - - /// - public GameObject GetBoundaryWallVisualization() - { - if (!Application.isPlaying) { return null; } - - if (!currentBoundaryWallObject.IsNull()) - { - return currentBoundaryWallObject; - } - - if (!FloorHeight.HasValue) - { - // We need a floor on which to place the walls. - return null; - } - - if (Bounds.Length == 0) - { - // If we do not have boundary edges, we cannot render walls. - return null; - } - - currentBoundaryWallObject = new GameObject("Tracked Area Walls") - { - layer = BoundaryWallsPhysicsLayer - }; - - // Create and parent the child objects - const float wallDepth = BoundaryObjectThickness; - - for (int i = 0; i < Bounds.Length; i++) - { - var wall = GameObject.CreatePrimitive(PrimitiveType.Cube); - wall.name = $"Wall_{i}"; - wall.GetComponent().sharedMaterial = BoundaryWallMaterial; - wall.transform.localScale = new Vector3((Bounds[i].PointB - Bounds[i].PointA).magnitude, BoundaryHeight, wallDepth); - wall.layer = DefaultIgnoreRaycastLayer; - - // Position and rotate the wall. - var mid = Vector2.Lerp(Bounds[i].PointA, Bounds[i].PointB, 0.5f); - wall.transform.position = new Vector3(mid.x, (BoundaryHeight * 0.5f), mid.y); - var rotationAngle = MathUtilities.GetAngleBetween(Bounds[i].PointB, Bounds[i].PointA); - wall.transform.rotation = Quaternion.Euler(0.0f, -rotationAngle, 0.0f); - - wall.transform.parent = currentBoundaryWallObject.transform; - } - - currentBoundaryWallObject.transform.parent = BoundaryVisualizationParent.transform; - - return currentBoundaryWallObject; - } - - /// - public GameObject GetBoundaryCeilingVisualization() + /// + public void RegisterTrackedObject(GameObject gameObject) { - if (!Application.isPlaying) { return null; } - - if (!currentCeilingObject.IsNull()) - { - return currentCeilingObject; - } + var boundsCached = gameObject.EnsureComponent(); - if (Bounds.Length == 0) + if (!trackedObjects.TryGetValue(boundsCached, out _)) { - // If we do not have boundary edges, we cannot render a ceiling. - return null; + trackedObjects.Add(boundsCached, ProximityAlert.Clear); } - - // Get the smallest rectangle that contains the entire boundary. - var boundaryBoundingBox = new Bounds(); - - for (int i = 0; i < Bounds.Length; i++) - { - // The boundary geometry is a closed loop. As such, we can encapsulate only PointA of each Edge. - boundaryBoundingBox.Encapsulate(new Vector3(Bounds[i].PointA.x, BoundaryHeight * 0.5f, Bounds[i].PointA.y)); - } - - // Render the ceiling. - const float ceilingDepth = BoundaryObjectThickness; - currentCeilingObject = GameObject.CreatePrimitive(PrimitiveType.Cube); - currentCeilingObject.name = "Ceiling"; - currentCeilingObject.layer = DefaultIgnoreRaycastLayer; - currentCeilingObject.transform.localScale = new Vector3(boundaryBoundingBox.size.x, ceilingDepth, boundaryBoundingBox.size.z); - currentCeilingObject.transform.Translate(new Vector3( - boundaryBoundingBox.center.x, - BoundaryHeight + (currentCeilingObject.transform.localScale.y * 0.5f), - boundaryBoundingBox.center.z)); - currentCeilingObject.GetComponent().sharedMaterial = BoundaryCeilingMaterial; - currentCeilingObject.layer = CeilingPhysicsLayer; - currentCeilingObject.transform.parent = BoundaryVisualizationParent.transform; - - return currentCeilingObject; } - #endregion IMixedRealityBoundarySystem Implementation - - /// - /// The largest rectangle that is contained withing the play space geometry. - /// - private InscribedRectangle rectangularBounds; - - private GameObject currentFloorObject; - private GameObject currentPlayAreaObject; - private GameObject currentTrackedAreaObject; - private GameObject currentBoundaryWallObject; - private GameObject currentCeilingObject; - - /// - /// Retrieves the boundary geometry and creates the boundary and inscribed play space volumes. - /// - private void CalculateBoundaryBounds() + /// + public void UnregisterTrackedObject(GameObject gameObject) { - // Reset the bounds - Bounds = new Edge[0]; - FloorHeight = null; - rectangularBounds = null; - - // Boundaries are supported for Room Scale experiences only. - if (XRDevice.GetTrackingSpaceType() != TrackingSpaceType.RoomScale) - { - return; - } - - // Get the boundary geometry. - var boundaryGeometry = new List(0); - var boundaryEdges = new List(0); - - if (Boundary.TryGetGeometry(boundaryGeometry, Boundary.Type.TrackedArea) && boundaryGeometry.Count > 0) - { - // FloorHeight starts out as null. Use a suitably high value for the floor to ensure - // that we do not accidentally set it too low. - var floorHeight = float.MaxValue; - - for (int i = 0; i < boundaryGeometry.Count; i++) - { - var pointA = boundaryGeometry[i]; - var pointB = boundaryGeometry[(i + 1) % boundaryGeometry.Count]; - boundaryEdges.Add(new Edge(pointA, pointB)); - - floorHeight = Mathf.Min(floorHeight, boundaryGeometry[i].y); - } + var boundsCache = gameObject.GetComponent(); + if (boundsCache == null) { return; } - FloorHeight = floorHeight; - Bounds = boundaryEdges.ToArray(); - CreateInscribedBounds(); - } - else if (XRSettings.enabled && Boundary.configured) + if (trackedObjects.TryGetValue(boundsCache, out _)) { - Debug.LogWarning("Failed to calculate boundary bounds."); + trackedObjects.Remove(boundsCache); } } - /// - /// Creates the two dimensional volume described by the largest rectangle that - /// is contained withing the play space geometry and the configured height. - /// - private void CreateInscribedBounds() - { - // We always use the same seed so that from run to run, the inscribed bounds are consistent. - rectangularBounds = new InscribedRectangle(Bounds, Mathf.Abs("Mixed Reality Toolkit".GetHashCode())); - } + #endregion Implementation of IMixedRealityBoundarySystem } } diff --git a/XRTK-Core/Packages/com.xrtk.core/Runtime/Services/MixedRealityToolkit.cs b/XRTK-Core/Packages/com.xrtk.core/Runtime/Services/MixedRealityToolkit.cs index c029f2000..2d7f02012 100644 --- a/XRTK-Core/Packages/com.xrtk.core/Runtime/Services/MixedRealityToolkit.cs +++ b/XRTK-Core/Packages/com.xrtk.core/Runtime/Services/MixedRealityToolkit.cs @@ -432,7 +432,11 @@ private void InitializeServiceLocator() if (ActiveProfile.IsBoundarySystemEnabled) { - if (!TryCreateAndRegisterService(ActiveProfile.BoundarySystemSystemType, out _, ActiveProfile.BoundaryVisualizationProfile) || BoundarySystem == null) + if (TryCreateAndRegisterService(ActiveProfile.BoundarySystemSystemType, out var service, ActiveProfile.BoundaryVisualizationProfile) && BoundarySystem != null) + { + TryRegisterDataProviderConfigurations(ActiveProfile.BoundaryVisualizationProfile.RegisteredServiceConfigurations, service); + } + else { Debug.LogError("Failed to start the Boundary System!"); } diff --git a/XRTK-Core/Packages/com.xrtk.core/Runtime/Utilities/BoundsCache.cs b/XRTK-Core/Packages/com.xrtk.core/Runtime/Utilities/BoundsCache.cs new file mode 100644 index 000000000..6fd01e1ba --- /dev/null +++ b/XRTK-Core/Packages/com.xrtk.core/Runtime/Utilities/BoundsCache.cs @@ -0,0 +1,80 @@ +// Copyright (c) XRTK. All rights reserved. +// Licensed under the MIT License. See LICENSE in the project root for license information. + +using UnityEngine; +using XRTK.Extensions; + +namespace XRTK.Utilities +{ + /// + /// used to cache s information about this + /// + public class BoundsCache : MonoBehaviour + { + private Bounds bounds = default; + + /// + /// The bounds of the object + /// + public Bounds Bounds => bounds; + + [SerializeField] + private bool useColliderData = true; + + [SerializeField] + private Collider[] colliders = null; + + /// + /// The collection of s for this object. + /// + public Collider[] Colliders => colliders; + + [SerializeField] + private bool useRendererData = true; + + [SerializeField] + private Renderer[] renderers = null; + + /// + /// The collection of s for this object. + /// + public Renderer[] Renderers => renderers; + + private Vector3[] boundsCornerPoints = new Vector3[0]; + + /// + /// The bounds corner points in world space. + /// + public Vector3[] BoundsCornerPoints => boundsCornerPoints; + + private void Start() + { + if (useColliderData) + { + bounds.Encapsulate(transform.GetColliderBounds(ref colliders)); + } + + if (useRendererData) + { + bounds.Encapsulate(transform.GetRenderBounds(ref renderers)); + } + + if (!bounds.IsValid()) + { + Debug.LogError($"Bounds calculation was invalid for {gameObject.name}!"); + return; + } + + bounds.GetCornerPositionsWorldSpace(transform, ref boundsCornerPoints); + } + + private void Update() + { + if (bounds.IsValid()) + { + // update the bounds corner points. + bounds.GetCornerPositionsWorldSpace(transform, ref boundsCornerPoints); + } + } + } +} \ No newline at end of file diff --git a/XRTK-Core/Packages/com.xrtk.core/Runtime/Utilities/BoundsCache.cs.meta b/XRTK-Core/Packages/com.xrtk.core/Runtime/Utilities/BoundsCache.cs.meta new file mode 100644 index 000000000..140618d98 --- /dev/null +++ b/XRTK-Core/Packages/com.xrtk.core/Runtime/Utilities/BoundsCache.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 5dfa998c390f351408f9c9378d74bd44 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {fileID: 2800000, guid: 8ac5213854cf4dbabd140decf8df1946, type: 3} + userData: + assetBundleName: + assetBundleVariant: diff --git a/XRTK-Core/Packages/com.xrtk.core/Runtime/Utilities/EdgeUtilities.cs b/XRTK-Core/Packages/com.xrtk.core/Runtime/Utilities/EdgeUtilities.cs index ea2062466..5368472a3 100644 --- a/XRTK-Core/Packages/com.xrtk.core/Runtime/Utilities/EdgeUtilities.cs +++ b/XRTK-Core/Packages/com.xrtk.core/Runtime/Utilities/EdgeUtilities.cs @@ -15,10 +15,10 @@ public static class EdgeUtilities /// A value that should be larger than the maximum boundary width. /// /// - /// This value is used to ensure that line segments are created + /// This value is used to ensure that line segments are created /// that will intersect with a piece of the room boundary. /// - internal static readonly float maxWidth = 10000f; + internal static readonly float MaxWidth = 10000f; /// /// A value representing an invalid point. @@ -26,12 +26,12 @@ public static class EdgeUtilities public static readonly Vector2 InvalidPoint = new Vector2(float.NegativeInfinity, float.NegativeInfinity); /// - /// Determines if the specified point is within the provided geometry. + /// Determines if the specified point is within the provided . /// /// The geometry for which we are checking the point. /// The point being checked. /// - /// True if the point falls within the geometry, false otherwise. + /// True if the point falls within the geometry, otherwise false. /// public static bool IsInsideBoundary(Edge[] geometryEdges, Vector2 point) { @@ -40,9 +40,9 @@ public static bool IsInsideBoundary(Edge[] geometryEdges, Vector2 point) return false; } - // Check if a ray to the right (X+) intersects with an + // Check if a ray to the right (X+) intersects with an // odd number of edges (inside) or an even number of edges (outside) - var rightEdge = new Edge(point, new Vector2(maxWidth, point.y)); + var rightEdge = new Edge(point, new Vector2(MaxWidth, point.y)); int intersections = 0; for (int i = 0; i < geometryEdges.Length; i++)