Alles
This commit is contained in:
@@ -0,0 +1,310 @@
|
||||
using UnityEngine.XR.ARFoundation;
|
||||
using UnityEngine.XR.ARSubsystems;
|
||||
|
||||
namespace UnityEngine.XR.Templates.MR
|
||||
{
|
||||
// Copied from AR Foundation Samples project. Commit: 8970b5a
|
||||
|
||||
[RequireComponent(typeof(ARBoundingBox))]
|
||||
[RequireComponent(typeof(DebugInfoDisplayController))]
|
||||
[RequireComponent(typeof(BoundingBoxEdgeVisualizer))]
|
||||
public class ARBoundingBoxDebugVisualizer : MonoBehaviour
|
||||
{
|
||||
static readonly Vector3 k_CanvasVerticalOffset = new(0, 0.15f, 0);
|
||||
|
||||
[Header("References")]
|
||||
[SerializeField, Tooltip("The prefab to visualize the bounding box's orientation.")]
|
||||
GameObject m_OrientationVisualizerPrefab;
|
||||
|
||||
[SerializeField, Tooltip("The mesh renderer of the bounding box visualizer.")]
|
||||
MeshRenderer m_MeshRenderer;
|
||||
|
||||
[SerializeField, Tooltip("The Text Mesh Pro material for drawing overlay text to always render debug text info on top of Geometry.")]
|
||||
Material m_TmpOverlayMaterial;
|
||||
|
||||
[Header("Debug Options")]
|
||||
[SerializeField, Tooltip("Show the trackableId visualizer.")]
|
||||
bool m_ShowTrackableId;
|
||||
|
||||
/// <summary>
|
||||
/// Show the trackableId visualizer.
|
||||
/// </summary>
|
||||
public bool showTrackableId
|
||||
{
|
||||
get => m_ShowTrackableId;
|
||||
set => m_ShowTrackableId = value;
|
||||
}
|
||||
|
||||
[SerializeField, Tooltip("Show the bounding box position and orientation visualizer.")]
|
||||
bool m_ShowOrientation = true;
|
||||
|
||||
/// <summary>
|
||||
/// Show the bounding box position and orientation visualizer.
|
||||
/// </summary>
|
||||
public bool showOrientation
|
||||
{
|
||||
get => m_ShowOrientation;
|
||||
set => m_ShowOrientation = value;
|
||||
}
|
||||
|
||||
[SerializeField, Tooltip("Show the classifications visualizer.")]
|
||||
bool m_ShowClassifications = true;
|
||||
|
||||
/// <summary>
|
||||
/// Show the classifications visualizer.
|
||||
/// </summary>
|
||||
public bool showClassifications
|
||||
{
|
||||
get => m_ShowClassifications;
|
||||
set => m_ShowClassifications = value;
|
||||
}
|
||||
|
||||
[SerializeField, Tooltip("Show the tracking state visualizer.")]
|
||||
bool m_ShowTrackingState = true;
|
||||
|
||||
/// <summary>
|
||||
/// Show the tracking state visualizer.
|
||||
/// </summary>
|
||||
public bool showTrackingState
|
||||
{
|
||||
get => m_ShowTrackingState;
|
||||
set => m_ShowTrackingState = value;
|
||||
}
|
||||
|
||||
[Header("Tracking state visualization settings")]
|
||||
|
||||
[SerializeField, Tooltip("The mesh color when the tracking state is set to tracking")]
|
||||
Color m_TrackingMeshColor;
|
||||
|
||||
[SerializeField, Tooltip("The outline color gradient when the tracking state is set to tracking.")]
|
||||
Gradient m_TrackingOutlineGradient;
|
||||
|
||||
[Space]
|
||||
|
||||
[SerializeField, Tooltip("The outline color gradient when the tracking state is set to limited.")]
|
||||
Gradient m_LimitedTrackingOutlineGradient;
|
||||
|
||||
[Space]
|
||||
|
||||
[SerializeField, Tooltip("The texture the bounding box will have when the tracking state is set to none.")]
|
||||
Texture m_NoneTrackingTexture;
|
||||
|
||||
[SerializeField, Tooltip("The mesh color when the tracking state is set to none.")]
|
||||
Color m_NoneTrackingMeshColor;
|
||||
|
||||
[SerializeField, Tooltip("The mesh texture color when the tracking state is set to none.")]
|
||||
Color m_NoneTrackingMeshTextureColor;
|
||||
|
||||
[SerializeField, Tooltip("The outline color gradient when the tracking state is set to none.")]
|
||||
Gradient m_NoneTrackingOutlineGradient;
|
||||
|
||||
[SerializeField, HideInInspector]
|
||||
BoundingBoxEdgeVisualizer m_BoundingBoxEdgeVisualizer;
|
||||
|
||||
[SerializeField, HideInInspector]
|
||||
DebugInfoDisplayController m_DebugInfoDisplayController;
|
||||
|
||||
ARBoundingBox m_ARBoundingBox;
|
||||
GameObject m_OrientationVisualizer;
|
||||
TrackableId m_TrackableId;
|
||||
BoundingBoxClassifications m_Classifications;
|
||||
TrackingState m_TrackingState;
|
||||
bool m_ShowDebugInfoCanvas;
|
||||
|
||||
void Reset()
|
||||
{
|
||||
m_BoundingBoxEdgeVisualizer = GetComponent<BoundingBoxEdgeVisualizer>();
|
||||
m_DebugInfoDisplayController = GetComponent<DebugInfoDisplayController>();
|
||||
m_ARBoundingBox = GetComponent<ARBoundingBox>();
|
||||
}
|
||||
|
||||
void Awake()
|
||||
{
|
||||
if (m_BoundingBoxEdgeVisualizer == null)
|
||||
m_BoundingBoxEdgeVisualizer = GetComponent<BoundingBoxEdgeVisualizer>();
|
||||
|
||||
if (m_DebugInfoDisplayController == null)
|
||||
m_DebugInfoDisplayController = GetComponent<DebugInfoDisplayController>();
|
||||
|
||||
if (m_DebugInfoDisplayController != null)
|
||||
m_DebugInfoDisplayController.Show(m_ShowDebugInfoCanvas && (m_ShowTrackingState || m_ShowClassifications || m_ShowTrackableId));
|
||||
|
||||
if (m_ARBoundingBox == null)
|
||||
m_ARBoundingBox = GetComponent<ARBoundingBox>();
|
||||
|
||||
if (m_MeshRenderer == null)
|
||||
Debug.LogError($"{nameof(m_MeshRenderer)} is null.");
|
||||
|
||||
if (m_ShowOrientation && m_OrientationVisualizerPrefab == null)
|
||||
{
|
||||
Debug.LogWarning($"{nameof(m_ShowOrientation)} is enabled but {nameof(m_OrientationVisualizerPrefab)} is not assigned. To show the bounding box orientation visualizer assign a prefab to the {nameof(m_OrientationVisualizerPrefab)} in the inspector.", this);
|
||||
}
|
||||
|
||||
if (m_OrientationVisualizerPrefab != null)
|
||||
{
|
||||
m_OrientationVisualizer = Instantiate(m_OrientationVisualizerPrefab, transform);
|
||||
m_OrientationVisualizer.SetActive(false);
|
||||
}
|
||||
|
||||
var controller = FindFirstObjectByType<ARFeatureController>();
|
||||
if (controller != null)
|
||||
{
|
||||
if (!controller.BoundingBoxVisualsEnabled)
|
||||
enabled = false;
|
||||
}
|
||||
}
|
||||
|
||||
void Start()
|
||||
{
|
||||
if (m_ShowOrientation)
|
||||
m_DebugInfoDisplayController.SetBottomPivot();
|
||||
else
|
||||
m_DebugInfoDisplayController.SetCenterPivot();
|
||||
}
|
||||
|
||||
void OnEnable()
|
||||
{
|
||||
if (m_OrientationVisualizer != null && m_ShowOrientation)
|
||||
m_OrientationVisualizer.SetActive(true);
|
||||
|
||||
if (m_DebugInfoDisplayController != null)
|
||||
m_DebugInfoDisplayController.Show(true);
|
||||
|
||||
if (m_BoundingBoxEdgeVisualizer != null)
|
||||
m_BoundingBoxEdgeVisualizer.enabled = true;
|
||||
|
||||
if (m_MeshRenderer != null)
|
||||
m_MeshRenderer.enabled = true;
|
||||
}
|
||||
|
||||
void OnDisable()
|
||||
{
|
||||
if (m_OrientationVisualizer != null)
|
||||
m_OrientationVisualizer.SetActive(false);
|
||||
|
||||
if (m_DebugInfoDisplayController != null)
|
||||
m_DebugInfoDisplayController.Show(false);
|
||||
|
||||
if (m_BoundingBoxEdgeVisualizer != null)
|
||||
m_BoundingBoxEdgeVisualizer.enabled = false;
|
||||
|
||||
if (m_MeshRenderer != null)
|
||||
m_MeshRenderer.enabled = false;
|
||||
}
|
||||
|
||||
void OnDestroy()
|
||||
{
|
||||
if (m_OrientationVisualizer != null)
|
||||
Destroy(m_OrientationVisualizer);
|
||||
}
|
||||
|
||||
void Update()
|
||||
{
|
||||
UpdateDebugInfo();
|
||||
UpdateVisualizers();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Toggles the visibility of the AR Bounding Box debug information.
|
||||
/// </summary>
|
||||
/// <param name="isOn">If <see langword="true"/>, the debug information for AR Bounding Boxes will be visible. Otherwise, the debug information for the AR Bounding Boxes will not be visible.</param>
|
||||
public void ShowDebugInfoCanvas(bool isOn)
|
||||
{
|
||||
if (m_DebugInfoDisplayController == null)
|
||||
return;
|
||||
|
||||
m_ShowDebugInfoCanvas = isOn;
|
||||
UpdateDebugInfo();
|
||||
}
|
||||
|
||||
void UpdateDebugInfo()
|
||||
{
|
||||
if (m_DebugInfoDisplayController == null)
|
||||
return;
|
||||
|
||||
m_DebugInfoDisplayController.Show(m_ShowDebugInfoCanvas && (m_ShowTrackingState || m_ShowClassifications || m_ShowTrackableId));
|
||||
|
||||
var canvasPosition = m_ARBoundingBox.transform.position;
|
||||
canvasPosition += m_ShowOrientation ? k_CanvasVerticalOffset : Vector3.zero;
|
||||
m_DebugInfoDisplayController.SetPosition(canvasPosition);
|
||||
|
||||
if (m_ARBoundingBox.trackableId == m_TrackableId &&
|
||||
m_ARBoundingBox.classifications == m_Classifications &&
|
||||
m_ARBoundingBox.trackingState == m_TrackingState)
|
||||
return;
|
||||
|
||||
m_TrackableId = m_ARBoundingBox.trackableId;
|
||||
m_Classifications = m_ARBoundingBox.classifications;
|
||||
m_TrackingState = m_ARBoundingBox.trackingState;
|
||||
UpdateTrackingStateVisualization(m_TrackingState);
|
||||
|
||||
if (m_ShowTrackableId)
|
||||
m_DebugInfoDisplayController.AppendDebugEntry("TrackableId:", m_TrackableId.ToString());
|
||||
|
||||
if (m_ShowClassifications)
|
||||
m_DebugInfoDisplayController.AppendDebugEntry("Classifications:", m_Classifications.ToString());
|
||||
|
||||
if (m_ShowTrackingState)
|
||||
m_DebugInfoDisplayController.AppendDebugEntry("Tracking State:", m_TrackingState.ToString());
|
||||
|
||||
m_DebugInfoDisplayController.RefreshDisplayInfo();
|
||||
}
|
||||
|
||||
void UpdateVisualizers()
|
||||
{
|
||||
UpdateVisualizersEnabledState();
|
||||
UpdateVisualizerTransforms();
|
||||
}
|
||||
|
||||
void UpdateVisualizersEnabledState()
|
||||
{
|
||||
if (m_OrientationVisualizer != null && m_ShowOrientation != m_OrientationVisualizer.activeSelf)
|
||||
{
|
||||
m_OrientationVisualizer.SetActive(m_ShowOrientation);
|
||||
}
|
||||
}
|
||||
|
||||
void UpdateVisualizerTransforms()
|
||||
{
|
||||
if (m_MeshRenderer != null)
|
||||
{
|
||||
m_MeshRenderer.transform.localScale = m_ARBoundingBox.size;
|
||||
}
|
||||
|
||||
if (m_ShowOrientation && m_OrientationVisualizer != null)
|
||||
{
|
||||
var boundingBoxPose = m_ARBoundingBox.pose;
|
||||
m_OrientationVisualizer.transform.position = boundingBoxPose.position;
|
||||
m_OrientationVisualizer.transform.rotation = boundingBoxPose.rotation;
|
||||
}
|
||||
}
|
||||
|
||||
void UpdateTrackingStateVisualization(TrackingState trackingState)
|
||||
{
|
||||
switch (trackingState)
|
||||
{
|
||||
case TrackingState.Tracking:
|
||||
m_MeshRenderer.material.SetTexture("_MainTex", default);
|
||||
m_MeshRenderer.material.mainTextureScale = new(1, 1);
|
||||
m_MeshRenderer.material.SetColor("_Color", m_TrackingMeshColor);
|
||||
m_MeshRenderer.material.SetColor("_TexColorTint", m_TrackingMeshColor);
|
||||
m_BoundingBoxEdgeVisualizer.SetGradient(m_TrackingOutlineGradient);
|
||||
break;
|
||||
case TrackingState.Limited:
|
||||
m_MeshRenderer.material.SetTexture("_MainTex", default);
|
||||
m_MeshRenderer.material.mainTextureScale = new(1, 1);
|
||||
m_MeshRenderer.material.SetColor("_Color", m_TrackingMeshColor);
|
||||
m_MeshRenderer.material.SetColor("_TexColorTint", m_TrackingMeshColor);
|
||||
m_BoundingBoxEdgeVisualizer.SetGradient(m_TrackingOutlineGradient);
|
||||
break;
|
||||
case TrackingState.None:
|
||||
m_MeshRenderer.material.SetTexture("_MainTex", m_NoneTrackingTexture);
|
||||
m_MeshRenderer.material.mainTextureScale = new(2, 2);
|
||||
m_MeshRenderer.material.SetColor("_Color", m_NoneTrackingMeshColor);
|
||||
m_MeshRenderer.material.SetColor("_TexColorTint", m_NoneTrackingMeshTextureColor);
|
||||
m_BoundingBoxEdgeVisualizer.SetGradient(m_NoneTrackingOutlineGradient);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: e676a4fe85b15f142b5df169fd2423b1
|
||||
@@ -0,0 +1,230 @@
|
||||
using UnityEngine.Events;
|
||||
using UnityEngine.XR.ARFoundation;
|
||||
|
||||
namespace UnityEngine.XR.Templates.MR
|
||||
{
|
||||
/// <summary>
|
||||
/// Utility class used to control various AR features like occlusion, AR bounding boxes, and AR planes.
|
||||
/// </summary>
|
||||
public class ARFeatureController : MonoBehaviour
|
||||
{
|
||||
[SerializeField, Tooltip("AR Occlusion Manager that is in charge of changing MR Occlusion Features.")]
|
||||
OcclusionManager m_OcclusionManager;
|
||||
|
||||
/// <summary>
|
||||
/// AR Occlusion Manager that is in charge of changing MR Occlusion Features.
|
||||
/// </summary>
|
||||
public OcclusionManager occlusionManager
|
||||
{
|
||||
get => m_OcclusionManager;
|
||||
set => m_OcclusionManager = value;
|
||||
}
|
||||
|
||||
[SerializeField, Tooltip("AR Plane Manager that is in charge of spawning new AR Plane prefabs into the scene.")]
|
||||
ARPlaneManager m_PlaneManager;
|
||||
|
||||
/// <summary>
|
||||
/// AR Plane Manager that is in charge of spawning new AR Plane prefabs into the scene.
|
||||
/// </summary>
|
||||
public ARPlaneManager planeManager
|
||||
{
|
||||
get => m_PlaneManager;
|
||||
set => m_PlaneManager = value;
|
||||
}
|
||||
|
||||
[SerializeField, Tooltip("Toggle that dictates whether AR Planes should be visualized at runtime.")]
|
||||
bool m_PlaneVisualsEnabled = true;
|
||||
|
||||
/// <summary>
|
||||
/// Toggle that dictates whether AR Planes should be visualized at runtime.
|
||||
/// </summary>
|
||||
public bool PlaneVisualsEnabled => m_PlaneVisualsEnabled;
|
||||
|
||||
[SerializeField, Tooltip("AR Bounding Box Manager that is in charge of spawning new AR Bounding Box prefabs into the scene")]
|
||||
ARBoundingBoxManager m_BoundingBoxManager;
|
||||
|
||||
/// <summary>
|
||||
/// AR Bounding Box Manager that is in charge of spawning new AR Bounding Box prefabs into the scene.
|
||||
/// </summary>
|
||||
public ARBoundingBoxManager BoundingBoxManager
|
||||
{
|
||||
get => m_BoundingBoxManager;
|
||||
set => m_BoundingBoxManager = value;
|
||||
}
|
||||
|
||||
[SerializeField, Tooltip("Toggle that dictates whether AR Bounding Boxes should be visualized at runtime.")]
|
||||
bool m_BoundingBoxVisualsEnabled = true;
|
||||
|
||||
/// <summary>
|
||||
/// Toggle that dictates whether AR Bounding Boxes should be visualized at runtime.
|
||||
/// </summary>
|
||||
public bool BoundingBoxVisualsEnabled => m_BoundingBoxVisualsEnabled;
|
||||
|
||||
[SerializeField, Tooltip("Toggle that dictates whether AR Bounding Box visualizations should show additional debug information.")]
|
||||
bool m_BoundingBoxDebugInfoEnabled = true;
|
||||
|
||||
/// <summary>
|
||||
/// Toggle that dictates whether AR Bounding Box visualizations should show additional debug information.
|
||||
/// </summary>
|
||||
public bool boundingBoxDebugInfoEnabled => m_BoundingBoxDebugInfoEnabled;
|
||||
|
||||
[Header("Feature Changed Events")]
|
||||
|
||||
[SerializeField]
|
||||
UnityEvent<bool> m_OnARPlaneFeatureChanged = new UnityEvent<bool>();
|
||||
|
||||
public UnityEvent<bool> onARPlaneFeatureChanged => m_OnARPlaneFeatureChanged;
|
||||
|
||||
[SerializeField]
|
||||
UnityEvent<bool> m_OnARPlaneFeatureVisualizationChanged = new UnityEvent<bool>();
|
||||
|
||||
public UnityEvent<bool> onARPlaneFeatureVisualizationChanged => m_OnARPlaneFeatureVisualizationChanged;
|
||||
|
||||
[SerializeField]
|
||||
UnityEvent<bool> m_OnARBoundingBoxFeatureChanged = new UnityEvent<bool>();
|
||||
|
||||
public UnityEvent<bool> onARBoundingBoxFeatureChanged => m_OnARBoundingBoxFeatureChanged;
|
||||
|
||||
[SerializeField]
|
||||
UnityEvent<bool> m_OnARBoundingBoxFeatureVisualizationChanged = new UnityEvent<bool>();
|
||||
|
||||
public UnityEvent<bool> onARBoundingBoxFeatureVisualizationChanged => m_OnARBoundingBoxFeatureVisualizationChanged;
|
||||
|
||||
[SerializeField]
|
||||
UnityEvent<bool> m_OnARBoundingBoxFeatureDebugVisualizationChanged = new UnityEvent<bool>();
|
||||
|
||||
public UnityEvent<bool> onARBoundingBoxFeatureDebugVisualizationChanged => m_OnARBoundingBoxFeatureDebugVisualizationChanged;
|
||||
|
||||
/// <summary>
|
||||
/// Allows access to easily see if the AR Features are enabled and there is at least one bounding box
|
||||
/// </summary>
|
||||
/// <returns>Will return True if there is 1 or more AR Bounding Boxes found in the AR Scene.</returns>
|
||||
public bool HasBoundingBoxes() => m_BoundingBoxManager != null && m_BoundingBoxManager.trackables.count > 0;
|
||||
|
||||
bool m_BoundingBoxManagerEnabled;
|
||||
bool m_PlaneManagerEnabled;
|
||||
|
||||
/// <summary>
|
||||
/// Functionally turns AR Planes on and off in a scene.
|
||||
/// </summary>
|
||||
/// <param name="enabled">Whether to enable or disable the currently detected planes.</param>
|
||||
public void TogglePlanes(bool enabled)
|
||||
{
|
||||
if (m_PlaneManager == null)
|
||||
return;
|
||||
|
||||
m_PlaneManagerEnabled = enabled;
|
||||
m_OnARPlaneFeatureChanged?.Invoke(m_PlaneManagerEnabled);
|
||||
|
||||
if (m_PlaneManagerEnabled)
|
||||
{
|
||||
m_PlaneManager.enabled = m_PlaneManagerEnabled;
|
||||
m_PlaneManager.SetTrackablesActive(m_PlaneManagerEnabled);
|
||||
}
|
||||
else
|
||||
{
|
||||
m_PlaneManager.SetTrackablesActive(m_PlaneManagerEnabled);
|
||||
m_PlaneManager.enabled = m_PlaneManagerEnabled;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Toggles the AR plane visualizations in a scene.
|
||||
/// </summary>
|
||||
/// <param name="enabled">If <see langword="true"/>, AR plane visualizations will be enabled. Otherwise AR plane visualizations be disabled.</param>
|
||||
public void TogglePlaneVisualization(bool enabled)
|
||||
{
|
||||
if (m_PlaneManager == null)
|
||||
return;
|
||||
|
||||
m_PlaneVisualsEnabled = enabled;
|
||||
m_OnARPlaneFeatureVisualizationChanged?.Invoke(m_PlaneVisualsEnabled);
|
||||
|
||||
var trackables = m_PlaneManager.trackables;
|
||||
foreach (var trackable in trackables)
|
||||
{
|
||||
if (trackable.TryGetComponent(out FadePlaneMaterial fader))
|
||||
{
|
||||
fader.FadePlane(m_PlaneVisualsEnabled);
|
||||
}
|
||||
if (trackable.TryGetComponent(out ARPlaneMeshVisualizer visualizer))
|
||||
{
|
||||
visualizer.enabled = m_PlaneVisualsEnabled;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Functionally turns AR Bounding Boxes on and off in a scene.
|
||||
/// </summary>
|
||||
/// <param name="enabled">Whether to enable or disable the currently detected bounding boxes.</param>
|
||||
public void ToggleBoundingBoxes(bool enabled)
|
||||
{
|
||||
if (m_BoundingBoxManager == null)
|
||||
return;
|
||||
|
||||
m_BoundingBoxManagerEnabled = enabled;
|
||||
m_OnARBoundingBoxFeatureChanged?.Invoke(m_BoundingBoxManagerEnabled);
|
||||
|
||||
if (m_BoundingBoxManagerEnabled)
|
||||
{
|
||||
m_BoundingBoxManager.enabled = m_BoundingBoxManagerEnabled;
|
||||
m_BoundingBoxManager.SetTrackablesActive(m_BoundingBoxManagerEnabled);
|
||||
}
|
||||
else
|
||||
{
|
||||
m_BoundingBoxManager.SetTrackablesActive(m_BoundingBoxManagerEnabled);
|
||||
m_BoundingBoxManager.enabled = m_BoundingBoxManagerEnabled;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Toggles the AR Bounding Boxes visualizations in a scene.
|
||||
/// </summary>
|
||||
/// <param name="enabled">If <see langword="true"/>, AR Bounding Boxes visualizations will be enabled. Otherwise AR Bounding Boxes visualizations be disabled.</param>
|
||||
public void ToggleBoundingBoxVisualization(bool enabled)
|
||||
{
|
||||
if (m_BoundingBoxManager == null)
|
||||
return;
|
||||
|
||||
m_BoundingBoxVisualsEnabled = enabled;
|
||||
m_OnARBoundingBoxFeatureVisualizationChanged?.Invoke(m_BoundingBoxVisualsEnabled);
|
||||
|
||||
var trackables = m_BoundingBoxManager.trackables;
|
||||
foreach (var trackable in trackables)
|
||||
{
|
||||
if (trackable.TryGetComponent(out ARBoundingBoxDebugVisualizer visualizer))
|
||||
{
|
||||
visualizer.enabled = m_BoundingBoxVisualsEnabled;
|
||||
visualizer.ShowDebugInfoCanvas(m_BoundingBoxVisualsEnabled && m_BoundingBoxDebugInfoEnabled);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Toggles the visualization of the debug information for AR Bounding Boxes.
|
||||
/// </summary>
|
||||
/// <param name="enabled">If <see langword="true"/>, debug information will be shown for AR Bounding Boxes. Otherwise, debug information will not be shown.</param>
|
||||
public void ToggleDebugInfo(bool enabled)
|
||||
{
|
||||
if (m_BoundingBoxManager == null)
|
||||
return;
|
||||
|
||||
m_BoundingBoxDebugInfoEnabled = enabled;
|
||||
m_OnARBoundingBoxFeatureDebugVisualizationChanged?.Invoke(m_BoundingBoxDebugInfoEnabled);
|
||||
|
||||
// If general bounding box visuals are not enabled, do not enable the debug info.
|
||||
if (!m_BoundingBoxVisualsEnabled)
|
||||
return;
|
||||
|
||||
var trackables = m_BoundingBoxManager.trackables;
|
||||
foreach (var trackable in trackables)
|
||||
{
|
||||
if (trackable.TryGetComponent(out ARBoundingBoxDebugVisualizer visualizer))
|
||||
{
|
||||
visualizer.ShowDebugInfoCanvas(m_BoundingBoxDebugInfoEnabled);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 382a7d029b7cc7a4c985d053963fc9d8
|
||||
@@ -0,0 +1,90 @@
|
||||
using UnityEngine.EventSystems;
|
||||
using UnityEngine.UI;
|
||||
|
||||
namespace UnityEngine.XR.Templates.MR
|
||||
{
|
||||
/// <summary>
|
||||
/// Controls the visual states of a boolean toggle switch UI
|
||||
/// </summary>
|
||||
[RequireComponent(typeof(Toggle))]
|
||||
public class BooleanToggleVisualsController : MonoBehaviour, IPointerEnterHandler, IPointerExitHandler
|
||||
{
|
||||
const float k_TargetPositionX = 17f;
|
||||
|
||||
#pragma warning disable 649
|
||||
[SerializeField, Tooltip("The boolean toggle knob.")]
|
||||
RectTransform m_Knob;
|
||||
|
||||
[SerializeField, Tooltip("How much to translate the button imagery on the z on hover.")]
|
||||
float m_ZTranslation = 5f;
|
||||
#pragma warning restore 649
|
||||
|
||||
Toggle m_Toggle;
|
||||
float m_InitialBackground;
|
||||
Coroutine m_ColorFade;
|
||||
Coroutine m_LocalMove;
|
||||
|
||||
void Awake()
|
||||
{
|
||||
m_Toggle = gameObject.GetComponent<Toggle>();
|
||||
|
||||
//Add listener for when the state of the Toggle changes, to take action
|
||||
m_Toggle.onValueChanged.AddListener(ToggleValueChanged);
|
||||
|
||||
if (m_Knob != null)
|
||||
{
|
||||
m_InitialBackground = m_Knob.localPosition.z;
|
||||
}
|
||||
}
|
||||
|
||||
void OnEnable()
|
||||
{
|
||||
ToggleValueChanged(m_Toggle.isOn);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
void IPointerEnterHandler.OnPointerEnter(PointerEventData eventData)
|
||||
{
|
||||
PerformEntranceActions();
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
void IPointerExitHandler.OnPointerExit(PointerEventData eventData)
|
||||
{
|
||||
PerformExitActions();
|
||||
}
|
||||
|
||||
void ToggleValueChanged(bool value)
|
||||
{
|
||||
if (value)
|
||||
{
|
||||
m_Knob.localPosition = new Vector3(k_TargetPositionX, m_Knob.localPosition.y, m_Knob.localPosition.z);
|
||||
}
|
||||
else
|
||||
{
|
||||
m_Knob.localPosition = new Vector3(-k_TargetPositionX, m_Knob.localPosition.y, m_Knob.localPosition.z);
|
||||
}
|
||||
}
|
||||
|
||||
void PerformEntranceActions()
|
||||
{
|
||||
if (m_Knob != null)
|
||||
{
|
||||
var backgroundLocalPosition = m_Knob.localPosition;
|
||||
backgroundLocalPosition.z = m_InitialBackground - m_ZTranslation;
|
||||
m_Knob.localPosition = backgroundLocalPosition;
|
||||
}
|
||||
}
|
||||
|
||||
void PerformExitActions()
|
||||
{
|
||||
if (m_Knob != null)
|
||||
{
|
||||
var backgroundLocalPosition = m_Knob.localPosition;
|
||||
backgroundLocalPosition.z = m_InitialBackground;
|
||||
m_Knob.localPosition = backgroundLocalPosition;
|
||||
m_Knob.localScale = Vector3.one;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 1f47872a1879dff4c8a322f9226c58f2
|
||||
@@ -0,0 +1,182 @@
|
||||
using UnityEngine.XR.ARFoundation;
|
||||
|
||||
namespace UnityEngine.XR.Templates.MR
|
||||
{
|
||||
// Copied from AR Foundation Samples project. Commit: 8970b5a
|
||||
|
||||
[RequireComponent(typeof(ARBoundingBox))]
|
||||
public class BoundingBoxEdgeVisualizer : MonoBehaviour
|
||||
{
|
||||
const float k_EdgeThickness = 0.01f;
|
||||
|
||||
[SerializeField, HideInInspector]
|
||||
ARBoundingBox m_ARBoundingBox;
|
||||
|
||||
[Header("Top Edges")]
|
||||
[SerializeField]
|
||||
Renderer m_TopEdge1;
|
||||
|
||||
[SerializeField]
|
||||
Renderer m_TopEdge2;
|
||||
|
||||
[SerializeField]
|
||||
Renderer m_TopEdge3;
|
||||
|
||||
[SerializeField]
|
||||
Renderer m_TopEdge4;
|
||||
|
||||
[Header("Vertical Edges")]
|
||||
[SerializeField]
|
||||
Renderer m_VerticalEdge1;
|
||||
|
||||
[SerializeField]
|
||||
Renderer m_VerticalEdge2;
|
||||
|
||||
[SerializeField]
|
||||
Renderer m_VerticalEdge3;
|
||||
|
||||
[SerializeField]
|
||||
Renderer m_VerticalEdge4;
|
||||
|
||||
[Header("BottomEdges")]
|
||||
[SerializeField]
|
||||
Renderer m_BottomEdge1;
|
||||
|
||||
[SerializeField]
|
||||
Renderer m_BottomEdge2;
|
||||
|
||||
[SerializeField]
|
||||
Renderer m_BottomEdge3;
|
||||
|
||||
[SerializeField]
|
||||
Renderer m_BottomEdge4;
|
||||
|
||||
Vector3 m_PreviouseSize;
|
||||
|
||||
public void SetGradient(Gradient gradient)
|
||||
{
|
||||
// top edges
|
||||
m_TopEdge1.material.SetColor("_ColorA", gradient.colorKeys[0].color);
|
||||
m_TopEdge1.material.SetColor("_ColorB", gradient.colorKeys[1].color);
|
||||
|
||||
m_TopEdge2.material.SetColor("_ColorA", gradient.colorKeys[0].color);
|
||||
m_TopEdge2.material.SetColor("_ColorB", gradient.colorKeys[1].color);
|
||||
|
||||
m_TopEdge3.material.SetColor("_ColorA", gradient.colorKeys[0].color);
|
||||
m_TopEdge3.material.SetColor("_ColorB", gradient.colorKeys[1].color);
|
||||
|
||||
m_TopEdge4.material.SetColor("_ColorA", gradient.colorKeys[0].color);
|
||||
m_TopEdge4.material.SetColor("_ColorB", gradient.colorKeys[1].color);
|
||||
|
||||
// vertical edges
|
||||
m_VerticalEdge1.material.SetColor("_ColorA", gradient.colorKeys[0].color);
|
||||
m_VerticalEdge1.material.SetColor("_ColorB", gradient.colorKeys[1].color);
|
||||
|
||||
m_VerticalEdge2.material.SetColor("_ColorA", gradient.colorKeys[0].color);
|
||||
m_VerticalEdge2.material.SetColor("_ColorB", gradient.colorKeys[1].color);
|
||||
|
||||
m_VerticalEdge3.material.SetColor("_ColorA", gradient.colorKeys[0].color);
|
||||
m_VerticalEdge3.material.SetColor("_ColorB", gradient.colorKeys[1].color);
|
||||
|
||||
m_VerticalEdge4.material.SetColor("_ColorA", gradient.colorKeys[0].color);
|
||||
m_VerticalEdge4.material.SetColor("_ColorB", gradient.colorKeys[1].color);
|
||||
|
||||
// bottom edges
|
||||
m_BottomEdge1.material.SetColor("_ColorA", gradient.colorKeys[0].color);
|
||||
m_BottomEdge1.material.SetColor("_ColorB", gradient.colorKeys[1].color);
|
||||
|
||||
m_BottomEdge2.material.SetColor("_ColorA", gradient.colorKeys[0].color);
|
||||
m_BottomEdge2.material.SetColor("_ColorB", gradient.colorKeys[1].color);
|
||||
|
||||
m_BottomEdge3.material.SetColor("_ColorA", gradient.colorKeys[0].color);
|
||||
m_BottomEdge3.material.SetColor("_ColorB", gradient.colorKeys[1].color);
|
||||
|
||||
m_BottomEdge4.material.SetColor("_ColorA", gradient.colorKeys[0].color);
|
||||
m_BottomEdge4.material.SetColor("_ColorB", gradient.colorKeys[1].color);
|
||||
}
|
||||
|
||||
void Reset()
|
||||
{
|
||||
m_ARBoundingBox = GetComponent<ARBoundingBox>();
|
||||
}
|
||||
|
||||
void Awake()
|
||||
{
|
||||
if (m_ARBoundingBox == null)
|
||||
m_ARBoundingBox = GetComponent<ARBoundingBox>();
|
||||
}
|
||||
|
||||
void OnEnable()
|
||||
{
|
||||
m_TopEdge1.enabled = m_TopEdge2.enabled = m_TopEdge3.enabled = m_TopEdge4.enabled = true;
|
||||
m_VerticalEdge1.enabled = m_VerticalEdge2.enabled = m_VerticalEdge3.enabled = m_VerticalEdge4.enabled = true;
|
||||
m_BottomEdge1.enabled = m_BottomEdge2.enabled = m_BottomEdge3.enabled = m_BottomEdge4.enabled = true;
|
||||
}
|
||||
|
||||
void OnDisable()
|
||||
{
|
||||
m_TopEdge1.enabled = m_TopEdge2.enabled = m_TopEdge3.enabled = m_TopEdge4.enabled = false;
|
||||
m_VerticalEdge1.enabled = m_VerticalEdge2.enabled = m_VerticalEdge3.enabled = m_VerticalEdge4.enabled = false;
|
||||
m_BottomEdge1.enabled = m_BottomEdge2.enabled = m_BottomEdge3.enabled = m_BottomEdge4.enabled = false;
|
||||
}
|
||||
|
||||
void Update()
|
||||
{
|
||||
if (m_ARBoundingBox.size == m_PreviouseSize)
|
||||
return;
|
||||
|
||||
m_PreviouseSize = m_ARBoundingBox.size;
|
||||
var size = m_ARBoundingBox.size;
|
||||
var halfSize = size * 0.5f;
|
||||
|
||||
UpdateTopEdges(size, halfSize);
|
||||
UpdateVerticalEdges(size, halfSize);
|
||||
UpdateBottomEdges(size, halfSize);
|
||||
}
|
||||
|
||||
void UpdateTopEdges(Vector3 size, Vector3 halfSize)
|
||||
{
|
||||
// update sizes
|
||||
m_TopEdge1.transform.localScale = new(k_EdgeThickness, k_EdgeThickness, size.z);
|
||||
m_TopEdge2.transform.localScale = new(size.x, k_EdgeThickness, k_EdgeThickness);
|
||||
m_TopEdge3.transform.localScale = new(k_EdgeThickness, k_EdgeThickness, size.z);
|
||||
m_TopEdge4.transform.localScale = new(size.x, k_EdgeThickness, k_EdgeThickness);
|
||||
|
||||
// update positions
|
||||
m_TopEdge1.transform.localPosition = new(halfSize.x, halfSize.y, 0);
|
||||
m_TopEdge2.transform.localPosition = new(0, halfSize.y, halfSize.z);
|
||||
m_TopEdge3.transform.localPosition = new(-halfSize.x, halfSize.y, 0);
|
||||
m_TopEdge4.transform.localPosition = new(0, halfSize.y, -halfSize.z);
|
||||
}
|
||||
|
||||
void UpdateVerticalEdges(Vector3 size, Vector3 halfSize)
|
||||
{
|
||||
// update sizes
|
||||
m_VerticalEdge1.transform.localScale = new(k_EdgeThickness, size.y, k_EdgeThickness);
|
||||
m_VerticalEdge2.transform.localScale = new(k_EdgeThickness, size.y, k_EdgeThickness);
|
||||
m_VerticalEdge3.transform.localScale = new(k_EdgeThickness, size.y, k_EdgeThickness);
|
||||
m_VerticalEdge4.transform.localScale = new(k_EdgeThickness, size.y, k_EdgeThickness);
|
||||
|
||||
// update positions
|
||||
m_VerticalEdge1.transform.localPosition = new(halfSize.x, 0, -halfSize.z);
|
||||
m_VerticalEdge2.transform.localPosition = new(halfSize.x, 0, halfSize.z);
|
||||
m_VerticalEdge3.transform.localPosition = new(-halfSize.x, 0, halfSize.z);
|
||||
m_VerticalEdge4.transform.localPosition = new(-halfSize.x, 0, -halfSize.z);
|
||||
}
|
||||
|
||||
void UpdateBottomEdges(Vector3 size, Vector3 halfSize)
|
||||
{
|
||||
// update sizes
|
||||
m_BottomEdge1.transform.localScale = new(k_EdgeThickness, k_EdgeThickness, size.z);
|
||||
m_BottomEdge2.transform.localScale = new(size.x, k_EdgeThickness, k_EdgeThickness);
|
||||
m_BottomEdge3.transform.localScale = new(k_EdgeThickness, k_EdgeThickness, size.z);
|
||||
m_BottomEdge4.transform.localScale = new(size.x, k_EdgeThickness, k_EdgeThickness);
|
||||
|
||||
// update positions
|
||||
m_BottomEdge1.transform.localPosition = new(halfSize.x, -halfSize.y, 0);
|
||||
m_BottomEdge2.transform.localPosition = new(0, -halfSize.y, halfSize.z);
|
||||
m_BottomEdge3.transform.localPosition = new(-halfSize.x, -halfSize.y, 0);
|
||||
m_BottomEdge4.transform.localPosition = new(0, -halfSize.y, -halfSize.z);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: d30583e31550c4344acca889b5bdc7b1
|
||||
@@ -0,0 +1,157 @@
|
||||
using System;
|
||||
using System.Text;
|
||||
using TMPro;
|
||||
|
||||
namespace UnityEngine.XR.Templates.MR
|
||||
{
|
||||
// Copied from AR Foundation Samples project. Commit: 8970b5a
|
||||
|
||||
public class DebugInfoDisplayController : MonoBehaviour
|
||||
{
|
||||
const float k_DebugInfoBackgroundPadding = 0.04f;
|
||||
|
||||
[SerializeField]
|
||||
Canvas m_Canvas;
|
||||
|
||||
[SerializeField]
|
||||
RectTransform m_BackgroundRT;
|
||||
|
||||
[SerializeField]
|
||||
RectTransform m_DebugLabelOffsetRT;
|
||||
|
||||
[SerializeField]
|
||||
TextMeshProUGUI m_DebugLabelTypes;
|
||||
|
||||
[SerializeField]
|
||||
TextMeshProUGUI m_DebugLabelValues;
|
||||
|
||||
[SerializeField]
|
||||
RectTransform m_Icon;
|
||||
|
||||
Camera m_MainCamera;
|
||||
Transform m_MainCameraTransform;
|
||||
RectTransform m_MainCameraRT;
|
||||
|
||||
StringBuilder m_TypesBuilder = new();
|
||||
StringBuilder m_ValuesBuilder = new();
|
||||
Transform m_CanvasTransform;
|
||||
float m_ColumnWidth;
|
||||
Vector2 m_CanvasSizeInWorld;
|
||||
float m_HalfCanvasHeight;
|
||||
|
||||
/// <summary>
|
||||
/// Sets the canvas pivot to the center when centering the canvas at
|
||||
/// its position is required.
|
||||
/// </summary>
|
||||
public void SetCenterPivot()
|
||||
{
|
||||
m_MainCameraRT.pivot = new(0.5f, m_HalfCanvasHeight);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets the canvas pivot at the bottom when positioning the canvas
|
||||
/// offset above a position is required.
|
||||
/// </summary>
|
||||
public void SetBottomPivot()
|
||||
{
|
||||
if (m_MainCameraRT != null)
|
||||
m_MainCameraRT.pivot = new(0.5f, 0f);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Shows or hides the debug info. Debug info is shown by default.
|
||||
/// </summary>
|
||||
/// <param name="isOn">Whether or not the canvas GameObject will be set active.</param>
|
||||
public void Show(bool isOn)
|
||||
{
|
||||
m_Canvas.gameObject.SetActive(isOn);
|
||||
}
|
||||
|
||||
public void SetPosition(Vector3 position)
|
||||
{
|
||||
if (m_MainCameraRT != null)
|
||||
m_CanvasTransform.position = position;
|
||||
}
|
||||
|
||||
public void AppendDebugEntry(string dataType, string dataValue)
|
||||
{
|
||||
FormatDebugText(dataType, dataValue);
|
||||
}
|
||||
|
||||
public void RefreshDisplayInfo()
|
||||
{
|
||||
m_DebugLabelTypes.text = m_TypesBuilder.ToString();
|
||||
m_TypesBuilder.Clear();
|
||||
m_DebugLabelValues.text = m_ValuesBuilder.ToString();
|
||||
m_ValuesBuilder.Clear();
|
||||
|
||||
// update background size and position
|
||||
var debugLabelTypesSize = m_DebugLabelTypes.GetPreferredValues();
|
||||
var debugLabelValuesSize = m_DebugLabelValues.GetPreferredValues();
|
||||
|
||||
m_DebugLabelTypes.GetComponent<RectTransform>().sizeDelta = debugLabelTypesSize;
|
||||
m_DebugLabelValues.GetComponent<RectTransform>().sizeDelta = debugLabelValuesSize;
|
||||
|
||||
var width = debugLabelTypesSize.x + debugLabelValuesSize.x;
|
||||
var widthPlusSpacingAndPadding = width + m_ColumnWidth + k_DebugInfoBackgroundPadding;
|
||||
var height = Math.Max(debugLabelTypesSize.y, debugLabelValuesSize.y);
|
||||
|
||||
m_BackgroundRT.sizeDelta = new(widthPlusSpacingAndPadding, height + m_Icon.sizeDelta.y + k_DebugInfoBackgroundPadding);
|
||||
|
||||
// shift background to the left to center
|
||||
var halfWidth = width * 0.5f;
|
||||
var positionOffset = Vector2.zero;
|
||||
positionOffset.x -= debugLabelTypesSize.x - halfWidth;
|
||||
positionOffset.y = -k_DebugInfoBackgroundPadding * 0.5f;
|
||||
m_BackgroundRT.anchoredPosition = positionOffset;
|
||||
|
||||
// place icon in center
|
||||
m_Icon.anchoredPosition = new(positionOffset.x, height);
|
||||
|
||||
// shift the label offset to the right to center
|
||||
var anchoredPosition = new Vector2(-positionOffset.x, 0);
|
||||
m_DebugLabelOffsetRT.anchoredPosition = anchoredPosition;
|
||||
}
|
||||
|
||||
void Awake()
|
||||
{
|
||||
m_MainCamera = Camera.main!;
|
||||
m_MainCameraTransform = m_MainCamera.transform;
|
||||
m_Canvas.worldCamera = m_MainCamera;
|
||||
m_BackgroundRT.sizeDelta = Vector2.zero;
|
||||
m_CanvasTransform = m_Canvas.transform;
|
||||
m_MainCameraRT = m_Canvas.GetComponent<RectTransform>();
|
||||
|
||||
var debugLabelValuesRT = m_DebugLabelValues.GetComponent<RectTransform>();
|
||||
m_ColumnWidth = debugLabelValuesRT.anchoredPosition.x * 2f;
|
||||
m_HalfCanvasHeight = m_MainCameraRT.sizeDelta.y * 0.5f;
|
||||
|
||||
m_DebugLabelTypes.text = string.Empty;
|
||||
m_DebugLabelValues.text = string.Empty;
|
||||
}
|
||||
|
||||
void Update()
|
||||
{
|
||||
var cameraToCanvasVector = transform.position - m_MainCameraTransform.position;
|
||||
m_CanvasTransform.LookAt(transform.position + cameraToCanvasVector);
|
||||
}
|
||||
|
||||
void OnDestroy()
|
||||
{
|
||||
Destroy(m_DebugLabelValues.gameObject);
|
||||
}
|
||||
|
||||
void FormatDebugText(string dataType, string value)
|
||||
{
|
||||
if (m_TypesBuilder.Length != 0)
|
||||
m_TypesBuilder.AppendLine();
|
||||
|
||||
m_TypesBuilder.Append($"<b>{dataType}</b>");
|
||||
|
||||
if (m_ValuesBuilder.Length != 0)
|
||||
m_ValuesBuilder.AppendLine();
|
||||
|
||||
m_ValuesBuilder.Append(value);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 2cf09e924d5427b438ec9d08cdf4f7f5
|
||||
@@ -0,0 +1,53 @@
|
||||
using System.Collections;
|
||||
|
||||
namespace UnityEngine.XR.Templates.MR
|
||||
{
|
||||
public class FadeMaterial : MonoBehaviour
|
||||
{
|
||||
// attached game object for fading
|
||||
public GameObject Environment;
|
||||
|
||||
// fade speed length
|
||||
public float fadeSpeed;
|
||||
|
||||
Coroutine m_FadeCoroutine;
|
||||
|
||||
public void FadeSkybox(bool visible)
|
||||
{
|
||||
if (m_FadeCoroutine != null)
|
||||
StopCoroutine(m_FadeCoroutine);
|
||||
|
||||
m_FadeCoroutine = StartCoroutine(Fade(visible));
|
||||
}
|
||||
|
||||
//Fade Coroutine
|
||||
public IEnumerator Fade(bool visible)
|
||||
{
|
||||
Renderer rend = Environment.transform.GetComponent<Renderer>();
|
||||
float alphaValue = rend.material.GetFloat("_Alpha");
|
||||
|
||||
if (visible)
|
||||
{
|
||||
//while loop to deincrement Alpha value until object is invisible
|
||||
while (rend.material.GetFloat("_Alpha") > 0f)
|
||||
{
|
||||
alphaValue -= Time.deltaTime / fadeSpeed;
|
||||
rend.material.SetFloat("_Alpha", alphaValue);
|
||||
yield return null;
|
||||
}
|
||||
rend.material.SetFloat("_Alpha", 0f);
|
||||
}
|
||||
else if (!visible)
|
||||
{
|
||||
//while loop to increment object Alpha value until object is opaque
|
||||
while (rend.material.GetFloat("_Alpha") < 1f)
|
||||
{
|
||||
alphaValue += Time.deltaTime / fadeSpeed;
|
||||
rend.material.SetFloat("_Alpha", alphaValue);
|
||||
yield return null;
|
||||
}
|
||||
rend.material.SetFloat("_Alpha", 1f);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: afa06ae48506e0c43b85c8f4f70ce3e4
|
||||
@@ -0,0 +1,110 @@
|
||||
using System.Collections;
|
||||
|
||||
namespace UnityEngine.XR.Templates.MR
|
||||
{
|
||||
// Original source: AR Mobile Template project
|
||||
|
||||
public class FadePlaneMaterial : MonoBehaviour
|
||||
{
|
||||
// attached game object for fading
|
||||
public GameObject Plane;
|
||||
|
||||
// fade speed length
|
||||
public float fadeSpeed = 1f;
|
||||
|
||||
// fade alpha speed length
|
||||
public float fadeAlphaSpeed = 0.5f;
|
||||
|
||||
// view radius
|
||||
public float viewRadius = 2.5f;
|
||||
|
||||
// alpha
|
||||
public float alpha = 0.5f;
|
||||
|
||||
Coroutine m_FadeDotsCoroutine;
|
||||
Coroutine m_FadeAlphaCoroutine;
|
||||
static readonly int k_DotViewRadius = Shader.PropertyToID("_DotViewRadius");
|
||||
static readonly int k_Alpha = Shader.PropertyToID("_Alpha");
|
||||
|
||||
void Awake()
|
||||
{
|
||||
Renderer rend = Plane.transform.GetComponent<Renderer>();
|
||||
rend.material.SetFloat(k_DotViewRadius, 0f);
|
||||
rend.material.SetFloat(k_Alpha, 0f);
|
||||
FadePlane(true);
|
||||
}
|
||||
|
||||
public void FadePlane(bool visible)
|
||||
{
|
||||
if (m_FadeDotsCoroutine != null)
|
||||
StopCoroutine(m_FadeDotsCoroutine);
|
||||
|
||||
if (m_FadeAlphaCoroutine != null)
|
||||
StopCoroutine(m_FadeAlphaCoroutine);
|
||||
|
||||
m_FadeAlphaCoroutine = StartCoroutine(FadeAlpha(visible));
|
||||
m_FadeDotsCoroutine = StartCoroutine(FadeDots(visible));
|
||||
}
|
||||
|
||||
//Fade Coroutine
|
||||
public IEnumerator FadeDots(bool visible)
|
||||
{
|
||||
yield return new WaitForSeconds(fadeAlphaSpeed);
|
||||
|
||||
Renderer rend = Plane.transform.GetComponent<Renderer>();
|
||||
float viewRadiusValue = rend.material.GetFloat(k_DotViewRadius);
|
||||
|
||||
if (!visible)
|
||||
{
|
||||
//while loop to deincrement Alpha value until object is invisible
|
||||
while (rend.material.GetFloat(k_DotViewRadius) > 0f)
|
||||
{
|
||||
viewRadiusValue -= Time.deltaTime / fadeSpeed;
|
||||
rend.material.SetFloat(k_DotViewRadius, viewRadiusValue);
|
||||
yield return null;
|
||||
}
|
||||
rend.material.SetFloat(k_DotViewRadius, 0f);
|
||||
}
|
||||
else if (visible)
|
||||
{
|
||||
//while loop to increment object Alpha value until object is opaque
|
||||
while (rend.material.GetFloat(k_DotViewRadius) < viewRadius)
|
||||
{
|
||||
viewRadiusValue += Time.deltaTime / fadeSpeed;
|
||||
rend.material.SetFloat(k_DotViewRadius, viewRadiusValue);
|
||||
yield return null;
|
||||
}
|
||||
rend.material.SetFloat(k_DotViewRadius, viewRadius);
|
||||
}
|
||||
}
|
||||
|
||||
public IEnumerator FadeAlpha(bool visible)
|
||||
{
|
||||
Renderer rend = Plane.transform.GetComponent<Renderer>();
|
||||
float alphaValue = rend.material.GetFloat(k_Alpha);
|
||||
|
||||
if (!visible)
|
||||
{
|
||||
//while loop to deincrement Alpha value until object is invisible
|
||||
while (rend.material.GetFloat(k_Alpha) > 0f)
|
||||
{
|
||||
alphaValue -= Time.deltaTime / fadeAlphaSpeed;
|
||||
rend.material.SetFloat(k_DotViewRadius, alphaValue);
|
||||
yield return null;
|
||||
}
|
||||
rend.material.SetFloat(k_Alpha, 0f);
|
||||
}
|
||||
else if (visible)
|
||||
{
|
||||
//while loop to increment object Alpha value until object is opaque
|
||||
while (rend.material.GetFloat(k_Alpha) < alpha)
|
||||
{
|
||||
alphaValue += Time.deltaTime / fadeAlphaSpeed;
|
||||
rend.material.SetFloat(k_Alpha, alphaValue);
|
||||
yield return null;
|
||||
}
|
||||
rend.material.SetFloat(k_Alpha, alpha);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 1e2c97b728a740f4da44def78496027c
|
||||
@@ -0,0 +1,111 @@
|
||||
using System.Collections.Generic;
|
||||
using Unity.XR.CoreUtils;
|
||||
using UnityEngine.XR.Interaction.Toolkit.Samples.ARStarterAssets;
|
||||
|
||||
namespace UnityEngine.XR.Templates.MR
|
||||
{
|
||||
public class GazeTooltips : MonoBehaviour
|
||||
{
|
||||
[SerializeField]
|
||||
XROrigin m_XROrigin;
|
||||
|
||||
[SerializeField]
|
||||
Transform m_Tooltip;
|
||||
|
||||
[SerializeField]
|
||||
bool m_TapTooltip = true;
|
||||
|
||||
Transform m_XRCameraTransform;
|
||||
LayerMask m_PlaneMask;
|
||||
ZoneScale m_ZoneScale;
|
||||
GameObject m_LastPlane = null;
|
||||
readonly List<ARContactSpawnTrigger> m_SpawnerContacts = new();
|
||||
|
||||
const float k_SphereCastRadius = 0.1f;
|
||||
const string k_PlaneLayer = "Placeable Surface";
|
||||
|
||||
void Start()
|
||||
{
|
||||
if (m_XROrigin == null)
|
||||
{
|
||||
#if UNITY_2022_3_OR_NEWER
|
||||
m_XROrigin = FindFirstObjectByType<XROrigin>();
|
||||
#else
|
||||
m_XROrigin = FindObjectOfType<XROrigin>();
|
||||
#endif
|
||||
}
|
||||
|
||||
m_XRCameraTransform = m_XROrigin.Camera.transform;
|
||||
m_XROrigin.GetComponentsInChildren(true, m_SpawnerContacts);
|
||||
m_ZoneScale = GetComponentInChildren<ZoneScale>(true);
|
||||
m_PlaneMask = LayerMask.GetMask(k_PlaneLayer);
|
||||
}
|
||||
|
||||
void OnEnable()
|
||||
{
|
||||
m_Tooltip.gameObject.SetActive(false);
|
||||
}
|
||||
|
||||
void LateUpdate()
|
||||
{
|
||||
PlaceTooltip();
|
||||
}
|
||||
|
||||
void PlaceTooltip()
|
||||
{
|
||||
RaycastHit hitInfo;
|
||||
if (Physics.SphereCast(new Ray(m_XRCameraTransform.position, m_XRCameraTransform.forward), k_SphereCastRadius, out hitInfo, float.MaxValue, m_PlaneMask))
|
||||
{
|
||||
var spawnSurfaceFound = false;
|
||||
Vector3 surfacePosition = default;
|
||||
|
||||
// plane tap tooltip
|
||||
if (m_TapTooltip)
|
||||
{
|
||||
foreach (var spawnerContact in m_SpawnerContacts)
|
||||
{
|
||||
if (spawnerContact.isActiveAndEnabled && spawnerContact.TryGetSpawnSurfaceData(hitInfo.collider, out surfacePosition, out _))
|
||||
{
|
||||
spawnSurfaceFound = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (spawnSurfaceFound)
|
||||
{
|
||||
m_Tooltip.position = surfacePosition;
|
||||
var facePosition = m_XRCameraTransform.position;
|
||||
var forward = facePosition - m_Tooltip.position;
|
||||
var targetRotation = forward.sqrMagnitude > float.Epsilon ? Quaternion.LookRotation(forward, Vector3.up) : Quaternion.identity;
|
||||
targetRotation *= Quaternion.Euler(new Vector3(0f, 180f, 0f));
|
||||
var targetEuler = targetRotation.eulerAngles;
|
||||
var currentEuler = m_Tooltip.rotation.eulerAngles;
|
||||
targetRotation = Quaternion.Euler
|
||||
(
|
||||
currentEuler.x,
|
||||
targetEuler.y,
|
||||
currentEuler.z
|
||||
);
|
||||
|
||||
m_Tooltip.rotation = targetRotation;
|
||||
|
||||
if (!m_Tooltip.gameObject.activeSelf)
|
||||
m_Tooltip.gameObject.SetActive(true);
|
||||
|
||||
if (m_LastPlane != hitInfo.transform.gameObject)
|
||||
m_ZoneScale.Snap();
|
||||
|
||||
m_LastPlane = hitInfo.transform.gameObject;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (m_Tooltip.gameObject.activeSelf)
|
||||
{
|
||||
m_Tooltip.gameObject.SetActive(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 174562462553ba843bd95fe681f15de4
|
||||
@@ -0,0 +1,490 @@
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine.InputSystem;
|
||||
using UnityEngine.UI;
|
||||
using UnityEngine.XR.Interaction.Toolkit.Samples.StarterAssets;
|
||||
using TMPro;
|
||||
using LazyFollow = UnityEngine.XR.Interaction.Toolkit.UI.LazyFollow;
|
||||
|
||||
namespace UnityEngine.XR.Templates.MR
|
||||
{
|
||||
public struct Goal
|
||||
{
|
||||
public GoalManager.OnboardingGoals CurrentGoal;
|
||||
public bool Completed;
|
||||
|
||||
public Goal(GoalManager.OnboardingGoals goal)
|
||||
{
|
||||
CurrentGoal = goal;
|
||||
Completed = false;
|
||||
}
|
||||
}
|
||||
|
||||
public class GoalManager : MonoBehaviour
|
||||
{
|
||||
public enum OnboardingGoals
|
||||
{
|
||||
Empty,
|
||||
FindSurfaces,
|
||||
TapSurface,
|
||||
}
|
||||
|
||||
Queue<Goal> m_OnboardingGoals;
|
||||
Goal m_CurrentGoal;
|
||||
bool m_AllGoalsFinished;
|
||||
int m_SurfacesTapped;
|
||||
int m_CurrentGoalIndex = 0;
|
||||
|
||||
[Serializable]
|
||||
class Step
|
||||
{
|
||||
[SerializeField]
|
||||
public GameObject stepObject;
|
||||
|
||||
[SerializeField]
|
||||
public string buttonText;
|
||||
|
||||
public bool includeSkipButton;
|
||||
}
|
||||
|
||||
[SerializeField]
|
||||
List<Step> m_StepList = new List<Step>();
|
||||
|
||||
[SerializeField]
|
||||
public TextMeshProUGUI m_StepButtonTextField;
|
||||
|
||||
[SerializeField]
|
||||
public GameObject m_SkipButton;
|
||||
|
||||
[SerializeField]
|
||||
GameObject m_LearnButton;
|
||||
|
||||
[SerializeField]
|
||||
GameObject m_LearnModal;
|
||||
|
||||
[SerializeField]
|
||||
Button m_LearnModalButton;
|
||||
|
||||
[SerializeField]
|
||||
GameObject m_CoachingUIParent;
|
||||
|
||||
[SerializeField]
|
||||
FadeMaterial m_FadeMaterial;
|
||||
|
||||
[SerializeField]
|
||||
Toggle m_PassthroughToggle;
|
||||
|
||||
[SerializeField]
|
||||
LazyFollow m_GoalPanelLazyFollow;
|
||||
|
||||
[SerializeField]
|
||||
GameObject m_TapTooltip;
|
||||
|
||||
[SerializeField]
|
||||
GameObject m_VideoPlayer;
|
||||
|
||||
[SerializeField]
|
||||
Toggle m_VideoPlayerToggle;
|
||||
|
||||
[SerializeField]
|
||||
ARFeatureController m_FeatureController;
|
||||
|
||||
[SerializeField]
|
||||
ObjectSpawner m_ObjectSpawner;
|
||||
|
||||
const int k_NumberOfSurfacesTappedToCompleteGoal = 1;
|
||||
Vector3 m_TargetOffset = new Vector3(-.5f, -.25f, 1.5f);
|
||||
|
||||
void Start()
|
||||
{
|
||||
m_OnboardingGoals = new Queue<Goal>();
|
||||
var welcomeGoal = new Goal(OnboardingGoals.Empty);
|
||||
var findSurfaceGoal = new Goal(OnboardingGoals.FindSurfaces);
|
||||
var tapSurfaceGoal = new Goal(OnboardingGoals.TapSurface);
|
||||
var endGoal = new Goal(OnboardingGoals.Empty);
|
||||
|
||||
m_OnboardingGoals.Enqueue(welcomeGoal);
|
||||
m_OnboardingGoals.Enqueue(findSurfaceGoal);
|
||||
m_OnboardingGoals.Enqueue(tapSurfaceGoal);
|
||||
m_OnboardingGoals.Enqueue(endGoal);
|
||||
|
||||
m_CurrentGoal = m_OnboardingGoals.Dequeue();
|
||||
if (m_TapTooltip != null)
|
||||
m_TapTooltip.SetActive(false);
|
||||
|
||||
if (m_VideoPlayer != null)
|
||||
{
|
||||
m_VideoPlayer.SetActive(false);
|
||||
|
||||
if (m_VideoPlayerToggle != null)
|
||||
m_VideoPlayerToggle.isOn = false;
|
||||
}
|
||||
|
||||
if (m_FadeMaterial != null)
|
||||
{
|
||||
m_FadeMaterial.FadeSkybox(false);
|
||||
|
||||
if (m_PassthroughToggle != null)
|
||||
m_PassthroughToggle.isOn = false;
|
||||
}
|
||||
|
||||
if (m_LearnButton != null)
|
||||
{
|
||||
m_LearnButton.GetComponent<Button>().onClick.AddListener(OpenModal); ;
|
||||
m_LearnButton.SetActive(false);
|
||||
}
|
||||
|
||||
if (m_LearnModal != null)
|
||||
{
|
||||
m_LearnModal.transform.localScale = Vector3.zero;
|
||||
}
|
||||
|
||||
if (m_LearnModalButton != null)
|
||||
{
|
||||
m_LearnModalButton.onClick.AddListener(CloseModal);
|
||||
}
|
||||
|
||||
if (m_ObjectSpawner == null)
|
||||
{
|
||||
#if UNITY_2023_1_OR_NEWER
|
||||
m_ObjectSpawner = FindAnyObjectByType<ObjectSpawner>();
|
||||
#else
|
||||
m_ObjectSpawner = FindObjectOfType<ObjectSpawner>();
|
||||
#endif
|
||||
}
|
||||
|
||||
if (m_FeatureController == null)
|
||||
{
|
||||
#if UNITY_2023_1_OR_NEWER
|
||||
m_FeatureController = FindAnyObjectByType<ARFeatureController>();
|
||||
#else
|
||||
m_FeatureController = FindObjectOfType<ARFeatureController>();
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
void OpenModal()
|
||||
{
|
||||
if (m_LearnModal != null)
|
||||
{
|
||||
m_LearnModal.transform.localScale = Vector3.one;
|
||||
}
|
||||
}
|
||||
|
||||
void CloseModal()
|
||||
{
|
||||
if (m_LearnModal != null)
|
||||
{
|
||||
m_LearnModal.transform.localScale = Vector3.zero;
|
||||
}
|
||||
}
|
||||
|
||||
void Update()
|
||||
{
|
||||
if (!m_AllGoalsFinished)
|
||||
{
|
||||
ProcessGoals();
|
||||
}
|
||||
|
||||
// Debug Input
|
||||
#if UNITY_EDITOR
|
||||
if (Keyboard.current.spaceKey.wasPressedThisFrame)
|
||||
{
|
||||
CompleteGoal();
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
void ProcessGoals()
|
||||
{
|
||||
if (!m_CurrentGoal.Completed)
|
||||
{
|
||||
switch (m_CurrentGoal.CurrentGoal)
|
||||
{
|
||||
case OnboardingGoals.Empty:
|
||||
m_GoalPanelLazyFollow.positionFollowMode = LazyFollow.PositionFollowMode.Follow;
|
||||
break;
|
||||
case OnboardingGoals.FindSurfaces:
|
||||
m_GoalPanelLazyFollow.positionFollowMode = LazyFollow.PositionFollowMode.Follow;
|
||||
break;
|
||||
case OnboardingGoals.TapSurface:
|
||||
if (m_TapTooltip != null)
|
||||
{
|
||||
m_TapTooltip.SetActive(true);
|
||||
}
|
||||
m_GoalPanelLazyFollow.positionFollowMode = LazyFollow.PositionFollowMode.None;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void CompleteGoal()
|
||||
{
|
||||
if (m_CurrentGoal.CurrentGoal == OnboardingGoals.TapSurface)
|
||||
m_ObjectSpawner.objectSpawned -= OnObjectSpawned;
|
||||
|
||||
// disable tooltips before setting next goal
|
||||
DisableTooltips();
|
||||
|
||||
m_CurrentGoal.Completed = true;
|
||||
m_CurrentGoalIndex++;
|
||||
if (m_OnboardingGoals.Count > 0)
|
||||
{
|
||||
m_CurrentGoal = m_OnboardingGoals.Dequeue();
|
||||
m_StepList[m_CurrentGoalIndex - 1].stepObject.SetActive(false);
|
||||
m_StepList[m_CurrentGoalIndex].stepObject.SetActive(true);
|
||||
m_StepButtonTextField.text = m_StepList[m_CurrentGoalIndex].buttonText;
|
||||
m_SkipButton.SetActive(m_StepList[m_CurrentGoalIndex].includeSkipButton);
|
||||
}
|
||||
else
|
||||
{
|
||||
m_AllGoalsFinished = true;
|
||||
ForceEndAllGoals();
|
||||
}
|
||||
|
||||
if (m_CurrentGoal.CurrentGoal == OnboardingGoals.FindSurfaces)
|
||||
{
|
||||
if (m_FadeMaterial != null)
|
||||
m_FadeMaterial.FadeSkybox(true);
|
||||
|
||||
if (m_PassthroughToggle != null)
|
||||
m_PassthroughToggle.isOn = true;
|
||||
|
||||
if (m_LearnButton != null)
|
||||
{
|
||||
m_LearnButton.SetActive(true);
|
||||
}
|
||||
|
||||
StartCoroutine(TurnOnPlanes(true));
|
||||
}
|
||||
else if (m_CurrentGoal.CurrentGoal == OnboardingGoals.TapSurface)
|
||||
{
|
||||
if (m_LearnButton != null)
|
||||
{
|
||||
m_LearnButton.SetActive(false);
|
||||
}
|
||||
m_SurfacesTapped = 0;
|
||||
m_ObjectSpawner.objectSpawned += OnObjectSpawned;
|
||||
}
|
||||
}
|
||||
|
||||
public IEnumerator TurnOnPlanes(bool visualize)
|
||||
{
|
||||
yield return new WaitForSeconds(1f);
|
||||
|
||||
if (m_FeatureController != null)
|
||||
{
|
||||
m_FeatureController.TogglePlaneVisualization(visualize);
|
||||
m_FeatureController.TogglePlanes(true);
|
||||
}
|
||||
}
|
||||
|
||||
IEnumerator TurnOnARFeatures()
|
||||
{
|
||||
if (m_FeatureController == null)
|
||||
yield return null;
|
||||
|
||||
yield return new WaitForSeconds(0.5f);
|
||||
|
||||
// We are checking the bounding box count here so that we disable plane visuals so there is no
|
||||
// visual Z fighting. If the user has not defined any furniture in space setup or the platform
|
||||
// does not support bounding boxes, we want to enable plane visuals, but disable bounding box visuals.
|
||||
m_FeatureController.ToggleBoundingBoxes(true);
|
||||
m_FeatureController.TogglePlanes(true);
|
||||
|
||||
// Quick hack for for async await race condition.
|
||||
// TODO: -- Probably better to listen to trackable change events in the ARFeatureController and update accordingly there
|
||||
yield return new WaitForSeconds(0.5f);
|
||||
m_FeatureController.ToggleDebugInfo(false);
|
||||
|
||||
// If there are bounding boxes, we want to hide the planes so they don't cause z-fighting.
|
||||
if (m_FeatureController.HasBoundingBoxes())
|
||||
{
|
||||
m_FeatureController.TogglePlaneVisualization(false);
|
||||
m_FeatureController.ToggleBoundingBoxVisualization(true);
|
||||
}
|
||||
else
|
||||
{
|
||||
m_FeatureController.ToggleBoundingBoxVisualization(true);
|
||||
}
|
||||
|
||||
m_FeatureController.occlusionManager.SetupManager();
|
||||
}
|
||||
|
||||
void TurnOffARFeatureVisualization()
|
||||
{
|
||||
if (m_FeatureController == null)
|
||||
return;
|
||||
|
||||
m_FeatureController.TogglePlaneVisualization(false);
|
||||
m_FeatureController.ToggleBoundingBoxVisualization(false);
|
||||
}
|
||||
|
||||
void DisableTooltips()
|
||||
{
|
||||
if (m_CurrentGoal.CurrentGoal == OnboardingGoals.TapSurface)
|
||||
{
|
||||
if (m_TapTooltip != null)
|
||||
{
|
||||
m_TapTooltip.SetActive(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void ForceCompleteGoal()
|
||||
{
|
||||
CompleteGoal();
|
||||
}
|
||||
|
||||
public void ForceEndAllGoals()
|
||||
{
|
||||
m_CoachingUIParent.transform.localScale = Vector3.zero;
|
||||
|
||||
TurnOnVideoPlayer();
|
||||
|
||||
if (m_VideoPlayerToggle != null)
|
||||
m_VideoPlayerToggle.isOn = true;
|
||||
|
||||
if (m_FadeMaterial != null)
|
||||
{
|
||||
m_FadeMaterial.FadeSkybox(true);
|
||||
|
||||
if (m_PassthroughToggle != null)
|
||||
m_PassthroughToggle.isOn = true;
|
||||
}
|
||||
|
||||
if (m_LearnButton != null)
|
||||
{
|
||||
m_LearnButton.SetActive(false);
|
||||
}
|
||||
|
||||
if (m_LearnModal != null)
|
||||
{
|
||||
m_LearnModal.transform.localScale = Vector3.zero;
|
||||
}
|
||||
|
||||
StartCoroutine(TurnOnARFeatures());
|
||||
}
|
||||
|
||||
public void ResetCoaching()
|
||||
{
|
||||
TurnOffARFeatureVisualization();
|
||||
m_CoachingUIParent.transform.localScale = Vector3.one;
|
||||
|
||||
m_OnboardingGoals.Clear();
|
||||
m_OnboardingGoals = new Queue<Goal>();
|
||||
var welcomeGoal = new Goal(OnboardingGoals.Empty);
|
||||
var findSurfaceGoal = new Goal(OnboardingGoals.FindSurfaces);
|
||||
var tapSurfaceGoal = new Goal(OnboardingGoals.TapSurface);
|
||||
var endGoal = new Goal(OnboardingGoals.Empty);
|
||||
|
||||
m_OnboardingGoals.Enqueue(welcomeGoal);
|
||||
m_OnboardingGoals.Enqueue(findSurfaceGoal);
|
||||
m_OnboardingGoals.Enqueue(tapSurfaceGoal);
|
||||
m_OnboardingGoals.Enqueue(endGoal);
|
||||
|
||||
for (int i = 0; i < m_StepList.Count; i++)
|
||||
{
|
||||
if (i == 0)
|
||||
{
|
||||
m_StepList[i].stepObject.SetActive(true);
|
||||
m_SkipButton.SetActive(m_StepList[i].includeSkipButton);
|
||||
m_StepButtonTextField.text = m_StepList[i].buttonText;
|
||||
}
|
||||
else
|
||||
{
|
||||
m_StepList[i].stepObject.SetActive(false);
|
||||
}
|
||||
}
|
||||
|
||||
m_CurrentGoal = m_OnboardingGoals.Dequeue();
|
||||
m_AllGoalsFinished = false;
|
||||
|
||||
if (m_TapTooltip != null)
|
||||
m_TapTooltip.SetActive(false);
|
||||
|
||||
if (m_LearnButton != null)
|
||||
{
|
||||
m_LearnButton.SetActive(false);
|
||||
}
|
||||
|
||||
if (m_LearnModal != null)
|
||||
{
|
||||
m_LearnModal.transform.localScale = Vector3.zero;
|
||||
}
|
||||
|
||||
m_CurrentGoalIndex = 0;
|
||||
}
|
||||
|
||||
void OnObjectSpawned(GameObject spawnedObject)
|
||||
{
|
||||
m_SurfacesTapped++;
|
||||
if (m_CurrentGoal.CurrentGoal == OnboardingGoals.TapSurface && m_SurfacesTapped >= k_NumberOfSurfacesTappedToCompleteGoal)
|
||||
{
|
||||
CompleteGoal();
|
||||
m_GoalPanelLazyFollow.positionFollowMode = LazyFollow.PositionFollowMode.Follow;
|
||||
}
|
||||
}
|
||||
|
||||
public void TooglePlayer(bool visibility)
|
||||
{
|
||||
if (visibility)
|
||||
{
|
||||
TurnOnVideoPlayer();
|
||||
}
|
||||
else
|
||||
{
|
||||
if (m_VideoPlayer.activeSelf)
|
||||
{
|
||||
m_VideoPlayer.SetActive(false);
|
||||
if (m_VideoPlayerToggle.isOn)
|
||||
m_VideoPlayerToggle.isOn = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void TurnOnVideoPlayer()
|
||||
{
|
||||
if (m_VideoPlayer.activeSelf)
|
||||
return;
|
||||
|
||||
var follow = m_VideoPlayer.GetComponent<LazyFollow>();
|
||||
if (follow != null)
|
||||
follow.rotationFollowMode = LazyFollow.RotationFollowMode.None;
|
||||
|
||||
m_VideoPlayer.SetActive(false);
|
||||
var target = Camera.main.transform;
|
||||
var targetRotation = target.rotation;
|
||||
var newTransform = target;
|
||||
var targetEuler = targetRotation.eulerAngles;
|
||||
targetRotation = Quaternion.Euler
|
||||
(
|
||||
0f,
|
||||
targetEuler.y,
|
||||
targetEuler.z
|
||||
);
|
||||
|
||||
newTransform.rotation = targetRotation;
|
||||
var targetPosition = target.position + newTransform.TransformVector(m_TargetOffset);
|
||||
m_VideoPlayer.transform.position = targetPosition;
|
||||
|
||||
var forward = target.position - m_VideoPlayer.transform.position;
|
||||
var targetPlayerRotation = forward.sqrMagnitude > float.Epsilon ? Quaternion.LookRotation(forward, Vector3.up) : Quaternion.identity;
|
||||
targetPlayerRotation *= Quaternion.Euler(new Vector3(0f, 180f, 0f));
|
||||
var targetPlayerEuler = targetPlayerRotation.eulerAngles;
|
||||
var currentEuler = m_VideoPlayer.transform.rotation.eulerAngles;
|
||||
targetPlayerRotation = Quaternion.Euler
|
||||
(
|
||||
currentEuler.x,
|
||||
targetPlayerEuler.y,
|
||||
currentEuler.z
|
||||
);
|
||||
|
||||
m_VideoPlayer.transform.rotation = targetPlayerRotation;
|
||||
m_VideoPlayer.SetActive(true);
|
||||
if (follow != null)
|
||||
follow.rotationFollowMode = LazyFollow.RotationFollowMode.LookAtWithWorldUp;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: b628c55da6fc2bb4f88b18dff59bfb91
|
||||
@@ -0,0 +1,74 @@
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine.XR.Hands;
|
||||
|
||||
namespace UnityEngine.XR.Templates.MR
|
||||
{
|
||||
/// <summary>
|
||||
/// This class is a convenience wrapper to handle external start/stop
|
||||
/// of a currently running XR Hand Subsystem.
|
||||
/// </summary>
|
||||
/// <seealso cref="XRHandSubsystem"/>
|
||||
public class HandSubsystemManager : MonoBehaviour
|
||||
{
|
||||
static List<XRHandSubsystem> s_HandSubsystems;
|
||||
XRHandSubsystem m_HandSubsystem;
|
||||
|
||||
void OnEnable()
|
||||
{
|
||||
if (m_HandSubsystem == null)
|
||||
{
|
||||
TryGetHandSubsystem(out m_HandSubsystem);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// This function will attempt to find a currently running hand tracking subsystem and stop it.
|
||||
/// </summary>
|
||||
/// <seealso cref="XRHandSubsystem"/>
|
||||
public void DisableHandTracking()
|
||||
{
|
||||
if (m_HandSubsystem != null || TryGetHandSubsystem(out m_HandSubsystem))
|
||||
{
|
||||
m_HandSubsystem.Stop();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// This function will attempt to find a current hand tracking subsystem and start it up.
|
||||
/// </summary>
|
||||
/// <seealso cref="XRHandSubsystem"/>
|
||||
public void EnableHandTracking()
|
||||
{
|
||||
if (m_HandSubsystem != null || TryGetHandSubsystem(out m_HandSubsystem))
|
||||
{
|
||||
m_HandSubsystem.Start();
|
||||
}
|
||||
}
|
||||
|
||||
// This is taken from XRInputTrackingAggregator and should be removed once the internal version
|
||||
// has been made publicly available.
|
||||
static bool TryGetHandSubsystem(out XRHandSubsystem handSubsystem)
|
||||
{
|
||||
s_HandSubsystems ??= new List<XRHandSubsystem>();
|
||||
SubsystemManager.GetSubsystems(s_HandSubsystems);
|
||||
if (s_HandSubsystems.Count == 0)
|
||||
{
|
||||
handSubsystem = default;
|
||||
return false;
|
||||
}
|
||||
|
||||
if (s_HandSubsystems.Count > 1)
|
||||
{
|
||||
for (var i = 0; i < s_HandSubsystems.Count; ++i)
|
||||
{
|
||||
handSubsystem = s_HandSubsystems[i];
|
||||
if (handSubsystem.running)
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
handSubsystem = s_HandSubsystems[0];
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 7d52987a218afbc4c81f20e47e2cb295
|
||||
@@ -0,0 +1,33 @@
|
||||
namespace UnityEngine.XR.Templates.MR
|
||||
{
|
||||
// Copied from XRI Examples. Commit: c40b958
|
||||
|
||||
/// <summary>
|
||||
/// Apply forward force to instantiated prefab
|
||||
/// </summary>
|
||||
public class LaunchProjectile : MonoBehaviour
|
||||
{
|
||||
[SerializeField]
|
||||
[Tooltip("The projectile that's created")]
|
||||
GameObject m_ProjectilePrefab = null;
|
||||
|
||||
[SerializeField]
|
||||
[Tooltip("The point that the project is created")]
|
||||
Transform m_StartPoint = null;
|
||||
|
||||
[SerializeField]
|
||||
[Tooltip("The speed at which the projectile is launched")]
|
||||
float m_LaunchSpeed = 1.0f;
|
||||
|
||||
public void Fire()
|
||||
{
|
||||
GameObject newObject = Instantiate(m_ProjectilePrefab, m_StartPoint.position, m_StartPoint.rotation, null);
|
||||
|
||||
if (newObject.TryGetComponent(out Rigidbody rigidBody))
|
||||
{
|
||||
Vector3 force = m_StartPoint.forward * m_LaunchSpeed;
|
||||
rigidBody.AddForce(force);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: b3f39acb2169f3042ab977cbdbcffeb4
|
||||
@@ -0,0 +1,219 @@
|
||||
#if USING_META_OCCLUSION && (UNITY_ANDROID || UNITY_EDITOR)
|
||||
#define META_OCCLUSION_AVAILABLE
|
||||
using UnityEngine.XR.OpenXR.Features.Meta;
|
||||
using UnityEngine.XR.OpenXR.NativeTypes;
|
||||
#endif
|
||||
|
||||
using UnityEngine.XR.ARFoundation;
|
||||
using UnityEngine.XR.ARSubsystems;
|
||||
|
||||
namespace UnityEngine.XR.Templates.MR
|
||||
{
|
||||
/// <summary>
|
||||
/// Utility class that provides configuration for occlusion support.
|
||||
/// </summary>
|
||||
public class OcclusionManager : MonoBehaviour
|
||||
{
|
||||
[SerializeField, Tooltip("Enable the occlusion manager on start.")]
|
||||
bool m_EnableManagerOnStart = false;
|
||||
|
||||
/// <summary>
|
||||
/// Enable the occlusion manager on start.
|
||||
/// </summary>
|
||||
public bool enableManagerOnStart
|
||||
{
|
||||
get => m_EnableManagerOnStart;
|
||||
set => m_EnableManagerOnStart = value;
|
||||
}
|
||||
|
||||
[SerializeField, Tooltip("The UI Toggle GameObject that will be disabled if occlusion is not supported.")]
|
||||
GameObject m_UIToggleObject;
|
||||
|
||||
/// <summary>
|
||||
/// The UI Toggle GameObject that will be disabled if occlusion is not supported.
|
||||
/// </summary>
|
||||
public GameObject uiToggleObject
|
||||
{
|
||||
get => m_UIToggleObject;
|
||||
set => m_UIToggleObject = value;
|
||||
}
|
||||
|
||||
[SerializeField, Tooltip("The GameObject containing the Quest-specific settings.")]
|
||||
GameObject m_QuestSettings;
|
||||
|
||||
/// <summary>
|
||||
/// The GameObject containing the Quest-specific settings.
|
||||
/// </summary>
|
||||
public GameObject questSettings
|
||||
{
|
||||
get => m_QuestSettings;
|
||||
set => m_QuestSettings = value;
|
||||
}
|
||||
|
||||
[SerializeField, Tooltip("The GameObject containing the Android XR-specific settings.")]
|
||||
GameObject m_AndroidXRSettings;
|
||||
|
||||
/// <summary>
|
||||
/// The GameObject containing the Android XR-specific settings.
|
||||
/// </summary>
|
||||
public GameObject AndroidXRSettings
|
||||
{
|
||||
get => m_AndroidXRSettings;
|
||||
set => m_AndroidXRSettings = value;
|
||||
}
|
||||
|
||||
AROcclusionManager m_AROcclusionManager;
|
||||
ARShaderOcclusion m_ARShaderOcclusion;
|
||||
|
||||
void Start()
|
||||
{
|
||||
if (TrySetupOcclusion() && m_EnableManagerOnStart)
|
||||
{
|
||||
SetupManager();
|
||||
}
|
||||
}
|
||||
|
||||
bool TrySetupOcclusion()
|
||||
{
|
||||
// Check if the platform supports occlusion
|
||||
if (LoaderUtility
|
||||
.GetActiveLoader()?
|
||||
.GetLoadedSubsystem<XROcclusionSubsystem>() != null)
|
||||
{
|
||||
// XROcclusionSubsystem was loaded. The platform supports occlusion.
|
||||
m_AROcclusionManager = FindAnyObjectByType<AROcclusionManager>();
|
||||
if (m_AROcclusionManager == null)
|
||||
{
|
||||
Debug.LogWarning("No AROcclusionManager found, yet Use Occlusion is enabled. Disabling Object.", this);
|
||||
m_UIToggleObject.SetActive(false);
|
||||
return false;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (!m_AROcclusionManager.gameObject.TryGetComponent(out m_ARShaderOcclusion))
|
||||
{
|
||||
Debug.LogWarning($"No {nameof(ARShaderOcclusion)} component found. Adding one manually now.", this);
|
||||
m_ARShaderOcclusion = m_AROcclusionManager.gameObject.AddComponent<ARShaderOcclusion>();
|
||||
}
|
||||
}
|
||||
|
||||
SetupDeviceSpecificSettings();
|
||||
|
||||
return true;
|
||||
}
|
||||
// If the platform does not support occlusion, disable the object
|
||||
else
|
||||
{
|
||||
Debug.LogWarning("Occlusion is not supported on this platform. Disabling Object.", this);
|
||||
m_UIToggleObject.SetActive(false);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets up device-specific settings for the occlusion manager.
|
||||
/// This is called in the Start method to ensure that the correct settings are applied based on the current XR platform.
|
||||
/// This function is important for ensuring that the occlusion manager works correctly on different devices based on the platform specifics.
|
||||
/// </summary>
|
||||
void SetupDeviceSpecificSettings()
|
||||
{
|
||||
Debug.Log($"Current XR Platform: {XRPlatformUnderstanding.CurrentPlatform}");
|
||||
switch (XRPlatformUnderstanding.CurrentPlatform)
|
||||
{
|
||||
case XRPlatformType.Quest:
|
||||
m_QuestSettings.SetActive(true);
|
||||
m_AndroidXRSettings.SetActive(false);
|
||||
break;
|
||||
case XRPlatformType.AndroidXR:
|
||||
m_QuestSettings.SetActive(false);
|
||||
m_AndroidXRSettings.SetActive(true);
|
||||
m_AROcclusionManager.environmentDepthTemporalSmoothingRequested = true;
|
||||
SetShaderMode(AROcclusionShaderMode.HardOcclusion);
|
||||
break;
|
||||
case XRPlatformType.Other:
|
||||
m_QuestSettings.SetActive(false);
|
||||
m_AndroidXRSettings.SetActive(false);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets up the occlusion manager and enables it if necessary.
|
||||
/// This is called from the goal manager to ensure that the occlusion manager is set up correctly and enabled when needed.
|
||||
/// </summary>
|
||||
public void SetupManager()
|
||||
{
|
||||
if (m_AROcclusionManager != null && !m_AROcclusionManager.enabled)
|
||||
m_AROcclusionManager.enabled = true;
|
||||
|
||||
if (m_ARShaderOcclusion != null && !m_ARShaderOcclusion.enabled)
|
||||
m_ARShaderOcclusion.enabled = true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets the occlusion manager to be enabled or disabled.
|
||||
/// This is called from the UI toggle to enable or disable the occlusion manager based on user input.
|
||||
/// </summary>
|
||||
/// <param name="isOn">Enables or disables <see cref="AROcclusionManager"/> based on value.</param>
|
||||
public void SetOcclusionIsOn(bool isOn)
|
||||
{
|
||||
if (m_AROcclusionManager != null)
|
||||
{
|
||||
m_AROcclusionManager.enabled = isOn;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets the shader mode for the occlusion manager.
|
||||
/// This is called from the UI dropdown to set the shader mode based on user input.
|
||||
/// </summary>
|
||||
/// <param name="shaderMode">The shader mode to set for the occlusion manager.</param>
|
||||
/// <remarks>
|
||||
/// The shader mode is an integer value that corresponds to the <see cref="AROcclusionShaderMode"/> enum.
|
||||
/// The value is offset by 1 because the first value in the enum is None, which is not used in this context.
|
||||
/// </remarks>
|
||||
public void SetShaderMode(int shaderMode)
|
||||
{
|
||||
// Offset by 1 because we don't use None
|
||||
SetShaderMode((AROcclusionShaderMode)(++shaderMode));
|
||||
}
|
||||
|
||||
void SetShaderMode(AROcclusionShaderMode mode)
|
||||
{
|
||||
if (m_ARShaderOcclusion != null)
|
||||
{
|
||||
m_ARShaderOcclusion.occlusionShaderMode = mode;
|
||||
Debug.Log("Setting shader mode to " + mode);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets the state of temporal smoothing for the <see cref="OcclusionManager"/>.
|
||||
/// </summary>
|
||||
/// <param name="isEnabled">If true, temporal smoothing will be applied to the environment depth image.</param>
|
||||
public void SetTemporalSmoothingEnabled(bool isEnabled)
|
||||
{
|
||||
if (m_AROcclusionManager != null)
|
||||
{
|
||||
m_AROcclusionManager.environmentDepthTemporalSmoothingRequested = isEnabled;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets the hand removal feature for the Meta OpenXR occlusion subsystem.
|
||||
/// </summary>
|
||||
/// <param name="isEnabled">Sets the Hand Removal Feature value.</param>
|
||||
public void SetHandHandRemovalEnabled(bool isEnabled)
|
||||
{
|
||||
#if META_OCCLUSION_AVAILABLE
|
||||
var subsystem = m_AROcclusionManager.subsystem as MetaOpenXROcclusionSubsystem;
|
||||
var result = subsystem.TrySetHandRemovalEnabled(isEnabled);
|
||||
if (result.IsError())
|
||||
{
|
||||
// Handle error
|
||||
Debug.LogWarning("Error setting hand removal enabled: " + result.ToString());
|
||||
}
|
||||
#endif
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: de26ff91ad797714fb7efe631f55592b
|
||||
@@ -0,0 +1,157 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
#if UNITY_ANDROID
|
||||
using UnityEngine.Android;
|
||||
#endif // UNITY_ANDROID
|
||||
using UnityEngine.Events;
|
||||
|
||||
namespace UnityEngine.XR.Templates.MR
|
||||
{
|
||||
/// <summary>
|
||||
/// Utility class to help define and manage Android device permissions and specify corresponding permission callbacks via <see cref="UnityEvent"/>.
|
||||
/// </summary>
|
||||
[DefaultExecutionOrder(-9999)]
|
||||
public class PermissionsManager : MonoBehaviour
|
||||
{
|
||||
const string k_DefaultPermissionId = "com.oculus.permission.USE_SCENE";
|
||||
|
||||
[SerializeField, Tooltip("Enables or disables the processing of permissions on Start. If disabled, permissions will not be processed until the ProcessPermissions method is called.")]
|
||||
private bool m_ProcessPermissionsOnStart = true;
|
||||
|
||||
[SerializeField, Tooltip("The system permissions to request when this component Starts.")]
|
||||
List<PermissionRequestGroup> m_PermissionGroups = new List<PermissionRequestGroup>();
|
||||
|
||||
/// <summary>
|
||||
/// Current platform permission group to process. This is determined during the <see cref="Awake"/> method using based on <see cref="XRPlatformUnderstanding"/>
|
||||
/// </summary>
|
||||
PermissionRequestGroup m_CurrentPlatformPermissionGroup = new PermissionRequestGroup();
|
||||
|
||||
/// <summary>
|
||||
/// A group of permissions to request based on a specific platform.
|
||||
/// </summary>
|
||||
[Serializable]
|
||||
class PermissionRequestGroup
|
||||
{
|
||||
[Tooltip("The platform type for which these permissions is intended for.")]
|
||||
public XRPlatformType platformType;
|
||||
public List<PermissionRequest> permissions;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// A permission request to be made to the Android operating system.
|
||||
/// </summary>
|
||||
[Serializable]
|
||||
class PermissionRequest
|
||||
{
|
||||
[Tooltip("The Android system permission to request when this component starts.")]
|
||||
public string permissionId = k_DefaultPermissionId;
|
||||
|
||||
[Tooltip("Whether to request permission from the operating system.")]
|
||||
public bool enabled = true;
|
||||
|
||||
[HideInInspector]
|
||||
public bool requested = false;
|
||||
|
||||
[HideInInspector]
|
||||
public bool responseReceived = false;
|
||||
|
||||
[HideInInspector]
|
||||
public bool granted = false;
|
||||
|
||||
public UnityEvent<string> onPermissionGranted;
|
||||
|
||||
public UnityEvent<string> onPermissionDenied;
|
||||
}
|
||||
|
||||
void Awake()
|
||||
{
|
||||
if (m_ProcessPermissionsOnStart)
|
||||
ProcessPermissions();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Process the permissions defined in the <see cref="m_PermissionGroups"/> list.
|
||||
/// </summary>
|
||||
public void ProcessPermissions()
|
||||
{
|
||||
var permissions = new List<string>();
|
||||
|
||||
#if UNITY_ANDROID
|
||||
|
||||
// Grab the current platform permission group based on the current platform in use.
|
||||
m_CurrentPlatformPermissionGroup = m_PermissionGroups.Find(g => g.platformType == XRPlatformUnderstanding.CurrentPlatform);
|
||||
if (m_CurrentPlatformPermissionGroup == null)
|
||||
{
|
||||
Debug.LogWarning($"No permission group found for platform {XRPlatformUnderstanding.CurrentPlatform}. Disabling {nameof(PermissionsManager)} component.", this);
|
||||
enabled = false;
|
||||
return;
|
||||
}
|
||||
|
||||
// Loop through the current platform's permissions and add them to the
|
||||
// list of permissions to request if they are enabled and not already requested.
|
||||
for(int i = 0; i < m_CurrentPlatformPermissionGroup.permissions.Count; i++)
|
||||
{
|
||||
if (!m_CurrentPlatformPermissionGroup.permissions[i].enabled)
|
||||
continue;
|
||||
|
||||
// If permission is not granted and not requested, add it to the list of permissions to request
|
||||
if (!Permission.HasUserAuthorizedPermission(m_CurrentPlatformPermissionGroup.permissions[i].permissionId) && !m_CurrentPlatformPermissionGroup.permissions[i].requested)
|
||||
{
|
||||
permissions.Add(m_CurrentPlatformPermissionGroup.permissions[i].permissionId);
|
||||
m_CurrentPlatformPermissionGroup.permissions[i].requested = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
Debug.Log($"User has permission for: {m_CurrentPlatformPermissionGroup.permissions[i].permissionId}", this);
|
||||
}
|
||||
}
|
||||
|
||||
// Process permissions that were not already granted
|
||||
if (permissions.Count > 0)
|
||||
{
|
||||
var callbacks = new PermissionCallbacks();
|
||||
callbacks.PermissionDenied += OnPermissionDenied;
|
||||
callbacks.PermissionGranted += OnPermissionGranted;
|
||||
|
||||
Permission.RequestUserPermissions(permissions.ToArray(), callbacks);
|
||||
}
|
||||
#endif // UNITY_ANDROID
|
||||
}
|
||||
|
||||
void OnPermissionGranted(string permissionStr)
|
||||
{
|
||||
var permission = m_CurrentPlatformPermissionGroup.permissions.Find(p => p.permissionId == permissionStr);
|
||||
if (permission == null)
|
||||
{
|
||||
Debug.LogWarning($"Permission granted callback received for an unexpected permission request, permission ID {permissionStr}", this);
|
||||
return;
|
||||
}
|
||||
|
||||
// Enable permission
|
||||
permission.granted = true;
|
||||
permission.responseReceived = true;
|
||||
|
||||
Debug.Log($"User granted permission for: {permissionStr}", this);
|
||||
permission.onPermissionGranted.Invoke(permissionStr);
|
||||
}
|
||||
|
||||
void OnPermissionDenied(string permissionStr)
|
||||
{
|
||||
// Find the permission with LINQ
|
||||
var permission = m_CurrentPlatformPermissionGroup.permissions.Find(p => p.permissionId == permissionStr);
|
||||
if (permission == null)
|
||||
{
|
||||
Debug.LogWarning($"Permission denied callback received for an unexpected permission request, permission ID {permissionStr}", this);
|
||||
return;
|
||||
}
|
||||
|
||||
// Disable permission
|
||||
permission.granted = false;
|
||||
permission.responseReceived = true;
|
||||
|
||||
Debug.LogWarning($"User denied permission for: {permissionStr}", this);
|
||||
permission.onPermissionDenied.Invoke(permissionStr);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: a210aa19b985bf849ba74812660e6f87
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,25 @@
|
||||
using UnityEngine.XR.Interaction.Toolkit.Interactors;
|
||||
|
||||
namespace UnityEngine.XR.Templates.MR
|
||||
{
|
||||
public class PinchVizController : MonoBehaviour
|
||||
{
|
||||
[SerializeField]
|
||||
SkinnedMeshRenderer m_Pointer;
|
||||
|
||||
XRRayInteractor m_Interactor;
|
||||
|
||||
// Start is called before the first frame update
|
||||
void Start()
|
||||
{
|
||||
m_Interactor = this.GetComponent<XRRayInteractor>();
|
||||
}
|
||||
|
||||
// Update is called once per frame
|
||||
void Update()
|
||||
{
|
||||
var inputValue = Mathf.Max(m_Interactor.selectInput.ReadValue(), m_Interactor.uiPressInput.ReadValue());
|
||||
m_Pointer.SetBlendShapeWeight(0, inputValue * 100f);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 7989cfc5520236c418f8e58b8a0e19f0
|
||||
@@ -0,0 +1,53 @@
|
||||
using UnityEngine.XR.ARFoundation;
|
||||
using UnityEngine.XR.ARSubsystems;
|
||||
|
||||
namespace UnityEngine.XR.Templates.MR
|
||||
{
|
||||
// Copied from AR Foundation Samples project. Commit: 8970b5a
|
||||
|
||||
/// <summary>
|
||||
/// Manages the plane material color for each recognized plane based on
|
||||
/// the <see cref="UnityEngine.XR.ARSubsystems.TrackingState"/> enumeration defined in ARSubsystems.
|
||||
/// </summary>
|
||||
[RequireComponent(typeof(ARPlane))]
|
||||
[RequireComponent(typeof(MeshRenderer))]
|
||||
public class PlaneTrackingModeVisualizer : MonoBehaviour
|
||||
{
|
||||
ARPlane m_ARPlane;
|
||||
MeshRenderer m_PlaneMeshRenderer;
|
||||
Color m_OriginalColor;
|
||||
|
||||
void Awake()
|
||||
{
|
||||
m_ARPlane = GetComponent<ARPlane>();
|
||||
m_PlaneMeshRenderer = GetComponent<MeshRenderer>();
|
||||
m_OriginalColor = m_PlaneMeshRenderer.material.color;
|
||||
}
|
||||
|
||||
void Update()
|
||||
{
|
||||
UpdatePlaneColor();
|
||||
}
|
||||
|
||||
void UpdatePlaneColor()
|
||||
{
|
||||
Color planeMatColor = Color.cyan;
|
||||
|
||||
switch (m_ARPlane.trackingState)
|
||||
{
|
||||
case TrackingState.None:
|
||||
planeMatColor = Color.grey;
|
||||
break;
|
||||
case TrackingState.Limited:
|
||||
planeMatColor = Color.red;
|
||||
break;
|
||||
case TrackingState.Tracking:
|
||||
planeMatColor = m_OriginalColor;
|
||||
break;
|
||||
}
|
||||
|
||||
planeMatColor.a = m_OriginalColor.a;
|
||||
m_PlaneMeshRenderer.material.color = planeMatColor;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 7b0c26ae29dd55c42bfc4e2f3d1e17fa
|
||||
@@ -0,0 +1,83 @@
|
||||
using UnityEngine.XR.ARSubsystems;
|
||||
using UnityEngine.XR.ARFoundation;
|
||||
|
||||
namespace UnityEngine.XR.Templates.MR
|
||||
{
|
||||
public enum XRPlatformType
|
||||
{
|
||||
Quest,
|
||||
AndroidXR,
|
||||
Other,
|
||||
None
|
||||
}
|
||||
|
||||
public class XRPlatformUnderstanding
|
||||
{
|
||||
// Copied from ARF Samples repo. Commit: 4a7e7b6 - Class: MenuLoader.cs
|
||||
|
||||
/// <summary>
|
||||
/// The current platform based on the active XRSessionSubsystem.
|
||||
/// </summary>
|
||||
public static XRPlatformType CurrentPlatform
|
||||
{
|
||||
get
|
||||
{
|
||||
if (!k_Initialized)
|
||||
{
|
||||
k_CurrentPlatform = GetCurrentXRPlatform();
|
||||
k_Initialized = true;
|
||||
}
|
||||
return k_CurrentPlatform;
|
||||
}
|
||||
}
|
||||
|
||||
static XRPlatformType k_CurrentPlatform = XRPlatformType.None;
|
||||
|
||||
static bool k_Initialized = false;
|
||||
|
||||
/// <summary>
|
||||
/// Returns the current platform based on the active XRSessionSubsystem.
|
||||
/// </summary>
|
||||
/// <returns>The current platform based on the active XRSessionSubsystem.</returns>
|
||||
static XRPlatformType GetCurrentXRPlatform()
|
||||
{
|
||||
// If we have already initialized, just return the current platform
|
||||
if (k_Initialized)
|
||||
return k_CurrentPlatform;
|
||||
|
||||
var loader = LoaderUtility.GetActiveLoader();
|
||||
var sessionSubsystem = loader != null ? loader.GetLoadedSubsystem<XRSessionSubsystem>() : null;
|
||||
|
||||
if (sessionSubsystem == null)
|
||||
{
|
||||
Debug.Log("No active XRSessionSubsystem found. Defaulting to XRPlatformType.None.");
|
||||
k_Initialized = true;
|
||||
k_CurrentPlatform = XRPlatformType.None;
|
||||
return k_CurrentPlatform;
|
||||
}
|
||||
|
||||
// We switch on Session Descriptor id because we can't guarantee with current preprocessor directives whether
|
||||
// a provider package (and its types) will be present. For example, UNITY_ANDROID could signal that either
|
||||
// ARCore or OpenXR loader is present. Because we don't know for sure, we are unable to switch on the loader
|
||||
// type without introducing a build-time error in case that package was stripped.
|
||||
switch (sessionSubsystem.subsystemDescriptor.id)
|
||||
{
|
||||
case "Meta-Session":
|
||||
Debug.Log("Meta-Session detected.");
|
||||
k_CurrentPlatform = XRPlatformType.Quest;
|
||||
break;
|
||||
case "Android-Session":
|
||||
Debug.Log("Android-Session detected.");
|
||||
k_CurrentPlatform = XRPlatformType.AndroidXR;
|
||||
break;
|
||||
default:
|
||||
// Default case includes other third-party providers
|
||||
Debug.Log($"Unknown platform detected, setting platform to Other. Subsystem id: {sessionSubsystem.subsystemDescriptor.id}");
|
||||
k_CurrentPlatform = XRPlatformType.Other;
|
||||
break;
|
||||
}
|
||||
|
||||
return k_CurrentPlatform;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 037903c13b5769e419bf6d16f11e2a62
|
||||
@@ -0,0 +1,39 @@
|
||||
using System.Collections;
|
||||
|
||||
namespace UnityEngine.XR.Templates.MR
|
||||
{
|
||||
public class PositionAtStart : MonoBehaviour
|
||||
{
|
||||
[SerializeField]
|
||||
[Tooltip("The starting target.")]
|
||||
Transform m_Target;
|
||||
|
||||
[SerializeField]
|
||||
[Tooltip("Adjusts the follow point from the target by this amount.")]
|
||||
Vector3 m_TargetOffset = Vector3.forward;
|
||||
|
||||
bool m_IgnoreX = true;
|
||||
Vector3 m_TargetPosition;
|
||||
|
||||
private const float k_StartDelay = 2.0f;
|
||||
|
||||
// Start is called before the first frame update
|
||||
IEnumerator Start()
|
||||
{
|
||||
yield return new WaitForSeconds(k_StartDelay);
|
||||
var targetRotation = m_Target.rotation;
|
||||
var newTransform = m_Target;
|
||||
var targetEuler = targetRotation.eulerAngles;
|
||||
targetRotation = Quaternion.Euler
|
||||
(
|
||||
m_IgnoreX ? 0f : targetEuler.x,
|
||||
targetEuler.y,
|
||||
targetEuler.z
|
||||
);
|
||||
|
||||
newTransform.rotation = targetRotation;
|
||||
m_TargetPosition = m_Target.position + newTransform.TransformVector(m_TargetOffset);
|
||||
transform.position = m_TargetPosition;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: f50756c02c4d2e143aa345b9f9f44834
|
||||
@@ -0,0 +1,118 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using Newtonsoft.Json;
|
||||
using Newtonsoft.Json.Linq;
|
||||
using UnityEngine.XR.ARSubsystems;
|
||||
|
||||
namespace UnityEngine.XR.Templates.MR
|
||||
{
|
||||
// Copied from ARF Samples repo. Commit: 4a7e7b6
|
||||
public class SaveAndLoadAnchorDataToFile
|
||||
{
|
||||
readonly string m_FilePath = Path.Combine(Application.persistentDataPath, "SavedAnchorIds.json");
|
||||
|
||||
public bool initialized => m_Initialized;
|
||||
bool m_Initialized;
|
||||
|
||||
public Awaitable initializeAwaitable => m_InitializeAwaitable;
|
||||
Awaitable m_InitializeAwaitable;
|
||||
|
||||
public Dictionary<SerializableGuid, int> SavedAnchorsData => m_SavedAnchorsData;
|
||||
Dictionary<SerializableGuid, int> m_SavedAnchorsData = new();
|
||||
|
||||
public SaveAndLoadAnchorDataToFile()
|
||||
{
|
||||
m_InitializeAwaitable = PopulateSavedAnchorIdsFromFile();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Saves a `SerializableGuid` to a file asynchronously, appending to the list of ids already saved.
|
||||
/// If no file exists or the file is unreadable, a new file is created.
|
||||
/// </summary>
|
||||
/// <param name="savedAnchorId">The `SerializableGuid` to save.</param>
|
||||
public async Awaitable SaveAnchorIdAsync(SerializableGuid savedAnchorId, int prefabId)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (!m_Initialized)
|
||||
await m_InitializeAwaitable;
|
||||
|
||||
if (!m_SavedAnchorsData.TryAdd(savedAnchorId, prefabId))
|
||||
m_SavedAnchorsData[savedAnchorId] = prefabId;
|
||||
|
||||
await WriteSavedAnchorIdsToFile();
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Debug.LogException(e);
|
||||
}
|
||||
}
|
||||
|
||||
public async Awaitable EraseAnchorIdAsync(SerializableGuid savedAnchorId)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (!m_Initialized)
|
||||
await m_InitializeAwaitable;
|
||||
|
||||
m_SavedAnchorsData.Remove(savedAnchorId);
|
||||
await WriteSavedAnchorIdsToFile();
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Debug.LogException(e);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the set of `SerializableGuid`s from the save file.
|
||||
/// </summary>
|
||||
/// <returns>The set of `SerializableGuid`s that were saved to the file.
|
||||
/// If no file exists or the file is unreadable, an an empty set is returned.</returns>
|
||||
public async Awaitable<Dictionary<SerializableGuid, int>> LoadSavedAnchorsDataAsync()
|
||||
{
|
||||
if (!m_Initialized)
|
||||
await m_InitializeAwaitable;
|
||||
|
||||
return m_SavedAnchorsData;
|
||||
}
|
||||
|
||||
async Awaitable PopulateSavedAnchorIdsFromFile()
|
||||
{
|
||||
try
|
||||
{
|
||||
m_SavedAnchorsData.Clear();
|
||||
if (!File.Exists(m_FilePath))
|
||||
return;
|
||||
|
||||
using var streamReader = File.OpenText(m_FilePath);
|
||||
using var jsonTextReader = new JsonTextReader(streamReader);
|
||||
|
||||
var kvp = (JObject)await JToken.ReadFromAsync(jsonTextReader);
|
||||
foreach (var (idAsString, prefabId) in kvp)
|
||||
{
|
||||
var tokens = idAsString.Split("-");
|
||||
var low = Convert.ToUInt64(tokens[0], 16);
|
||||
var high = Convert.ToUInt64(tokens[1], 16);
|
||||
var serializableGuid = new SerializableGuid(low, high);
|
||||
m_SavedAnchorsData.Add(serializableGuid, (int)prefabId);
|
||||
}
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Debug.LogException(e);
|
||||
}
|
||||
finally
|
||||
{
|
||||
m_Initialized = true;
|
||||
}
|
||||
}
|
||||
|
||||
async Awaitable WriteSavedAnchorIdsToFile()
|
||||
{
|
||||
var jsonString = JsonConvert.SerializeObject(m_SavedAnchorsData, Formatting.Indented);
|
||||
await File.WriteAllTextAsync(m_FilePath, jsonString);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 3c77086553d2fae4da4e63ccc83ef475
|
||||
@@ -0,0 +1,433 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using TMPro;
|
||||
using UnityEngine;
|
||||
using UnityEngine.UI;
|
||||
using UnityEngine.XR.ARFoundation;
|
||||
using UnityEngine.XR.ARSubsystems;
|
||||
using UnityEngine.XR.Interaction.Toolkit.Samples.StarterAssets;
|
||||
|
||||
namespace UnityEngine.XR.Templates.MR
|
||||
{
|
||||
[RequireComponent(typeof(ObjectSpawner))]
|
||||
public class SpawnedObjectsManager : MonoBehaviour
|
||||
{
|
||||
[Tooltip("Spawn objects with a persistent anchor.")]
|
||||
[SerializeField]
|
||||
bool m_SpawnAsPersistentAnchor = true;
|
||||
|
||||
/// <summary>
|
||||
/// Spawn objects with a persistent anchor.
|
||||
/// </summary>
|
||||
public bool spawnAsPersistentAnchor
|
||||
{
|
||||
get => m_SpawnAsPersistentAnchor;
|
||||
set => m_SpawnAsPersistentAnchor = value;
|
||||
}
|
||||
|
||||
[SerializeField]
|
||||
[Tooltip("Load saved anchors on start.")]
|
||||
bool m_LoadSavedAnchorsOnStart = false;
|
||||
|
||||
/// <summary>
|
||||
/// Load saved anchors on start.
|
||||
/// </summary>
|
||||
public bool loadSavedAnchorsOnStart
|
||||
{
|
||||
get => m_LoadSavedAnchorsOnStart;
|
||||
set => m_LoadSavedAnchorsOnStart = value;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// UI drop down representing object spawn selection for the scene.
|
||||
/// </summary>
|
||||
[SerializeField]
|
||||
TMP_Dropdown m_ObjectSelectorDropdown;
|
||||
|
||||
/// <summary>
|
||||
/// UGUI Button clicked to destroy all spawned objects by this spawner.
|
||||
/// </summary>
|
||||
[SerializeField]
|
||||
Button m_DestroyObjectsButton;
|
||||
|
||||
[SerializeField]
|
||||
TMP_Text m_AnchorText;
|
||||
|
||||
[SerializeField]
|
||||
ARAnchorManager m_AnchorManager;
|
||||
|
||||
ObjectSpawner m_Spawner;
|
||||
|
||||
readonly List<SpawnedObjectHelper> m_SpawnedObjects = new();
|
||||
|
||||
SaveAndLoadAnchorDataToFile m_SaveAndLoadAnchorIdsToFile;
|
||||
|
||||
async void Start()
|
||||
{
|
||||
m_SaveAndLoadAnchorIdsToFile = new SaveAndLoadAnchorDataToFile();
|
||||
|
||||
// Wait for the SaveAndLoadAnchorIdsToFile to initialize before continuing.
|
||||
if (!m_SaveAndLoadAnchorIdsToFile.initialized)
|
||||
await m_SaveAndLoadAnchorIdsToFile.initializeAwaitable;
|
||||
|
||||
if (m_LoadSavedAnchorsOnStart)
|
||||
{
|
||||
LoadAnchors();
|
||||
}
|
||||
else
|
||||
{
|
||||
m_AnchorText.text = "<b><u><align=center>- Currently Saved Objects -</b></u></align>\n\n";
|
||||
if (m_SaveAndLoadAnchorIdsToFile.SavedAnchorsData.Count == 0)
|
||||
{
|
||||
m_AnchorText.text += "<align=center>- No Anchors Saved -</align>";
|
||||
}
|
||||
else
|
||||
{
|
||||
foreach (var kvp in m_SaveAndLoadAnchorIdsToFile.SavedAnchorsData)
|
||||
{
|
||||
m_AnchorText.text += $"GUID: [{kvp.Key}] Object: {SpawnedObjectName(kvp.Value)}\n\n";
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void OnEnable()
|
||||
{
|
||||
if (m_Spawner == null)
|
||||
m_Spawner = GetComponent<ObjectSpawner>();
|
||||
|
||||
if (m_SpawnAsPersistentAnchor)
|
||||
{
|
||||
m_Spawner.onlySpawnInView = false;
|
||||
m_Spawner.spawnAsChildren = false;
|
||||
}
|
||||
|
||||
if (m_AnchorManager == null)
|
||||
m_AnchorManager = FindAnyObjectByType<ARAnchorManager>();
|
||||
|
||||
if (m_ObjectSelectorDropdown != null)
|
||||
{
|
||||
OnObjectSelectorDropdownValueChanged(m_ObjectSelectorDropdown.value);
|
||||
m_ObjectSelectorDropdown.onValueChanged.AddListener(OnObjectSelectorDropdownValueChanged);
|
||||
}
|
||||
|
||||
if (m_DestroyObjectsButton != null)
|
||||
m_DestroyObjectsButton.onClick.AddListener(OnDestroyObjectsButtonClicked);
|
||||
|
||||
m_Spawner.objectSpawned += ObjectSpawned;
|
||||
}
|
||||
|
||||
void OnDisable()
|
||||
{
|
||||
if (m_ObjectSelectorDropdown != null)
|
||||
m_ObjectSelectorDropdown.onValueChanged.RemoveListener(OnObjectSelectorDropdownValueChanged);
|
||||
|
||||
if (m_DestroyObjectsButton != null)
|
||||
m_DestroyObjectsButton.onClick.RemoveListener(OnDestroyObjectsButtonClicked);
|
||||
|
||||
m_Spawner.objectSpawned -= ObjectSpawned;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Called from the UI to destroy all spawned objects.
|
||||
/// It will remove non persistent anchors and delete the objects.
|
||||
/// </summary>
|
||||
public void OnDestroyObjectsButtonClicked()
|
||||
{
|
||||
while (m_SpawnedObjects.Count > 0)
|
||||
{
|
||||
SpawnedObjectHelper spawnedObject = m_SpawnedObjects[0];
|
||||
m_SpawnedObjects.RemoveAt(0);
|
||||
|
||||
if (spawnedObject.attachedAnchor != null)
|
||||
{
|
||||
if (!m_AnchorManager.TryRemoveAnchor(spawnedObject.attachedAnchor))
|
||||
{
|
||||
Debug.LogWarning("Failed to remove anchor, manually destroying anchor object.", this);
|
||||
Destroy(spawnedObject.attachedAnchor);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
Destroy(spawnedObject.gameObject);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Deletes all anchors and clears the saved anchor data list.
|
||||
/// This method is called when the Delete Anchors button is clicked from the UI.
|
||||
/// </summary>
|
||||
public async void DeleteAnchors()
|
||||
{
|
||||
m_AnchorText.text = "<b><u><align=center>- Deleted Persistent Anchors -</b></u></align>\n";
|
||||
await EraseAnchorsAsync();
|
||||
|
||||
if (m_SaveAndLoadAnchorIdsToFile.SavedAnchorsData.Count != 0)
|
||||
{
|
||||
m_AnchorText.text += "\n\n<align=center>Failed to delete all anchors.</align>\n\n";
|
||||
foreach (var kvp in m_SaveAndLoadAnchorIdsToFile.SavedAnchorsData)
|
||||
{
|
||||
m_AnchorText.text += $"GUID failed to remove: [{kvp.Key}]\n\n";
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
m_AnchorText.text += "\n\n<align=center>All anchors deleted.</align>";
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Loads all anchors from the saved anchor data list.
|
||||
/// This method is called when the Load Anchors button is clicked from the UI.
|
||||
/// </summary>
|
||||
public async void LoadAnchors()
|
||||
{
|
||||
m_AnchorText.text = "<b><u><align=center>- Loaded Persistent Anchors -</b></u></align>\n";
|
||||
await LoadAnchorsAsync();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Saves all anchors from the spawned objects list.
|
||||
/// This method is called when the Save Anchors button is clicked from the UI.
|
||||
/// </summary>
|
||||
public async void SaveAnchors()
|
||||
{
|
||||
if (!m_AnchorManager.descriptor.supportsSaveAnchor)
|
||||
{
|
||||
Debug.LogWarning("Save anchor is not supported on this device.", this);
|
||||
m_AnchorText.text = "Save anchor is not supported on this device.";
|
||||
return;
|
||||
}
|
||||
// Clear existing anchors
|
||||
await EraseAnchorsAsync();
|
||||
|
||||
// Save the current spawned anchors
|
||||
m_AnchorText.text = "<b><u><align=center>- Saved Persistent Anchors -</b></u></align>\n";
|
||||
await SaveAchorsAsync();
|
||||
}
|
||||
|
||||
// This method is called when the Object Selector Dropdown value changes.
|
||||
void OnObjectSelectorDropdownValueChanged(int value)
|
||||
{
|
||||
if (m_Spawner == null)
|
||||
return;
|
||||
|
||||
if (value == 0)
|
||||
{
|
||||
m_Spawner.RandomizeSpawnOption();
|
||||
return;
|
||||
}
|
||||
|
||||
m_Spawner.spawnOptionIndex = value - 1;
|
||||
}
|
||||
|
||||
// This method is called when an object is spawned by the ObjectSpawner.
|
||||
void ObjectSpawned(GameObject spawnedObject)
|
||||
{
|
||||
SpawnedObjectHelper spawnedObjectHelper = new SpawnedObjectHelper
|
||||
{
|
||||
gameObject = spawnedObject,
|
||||
spawnObjectIdx = m_Spawner.spawnOptionIndex,
|
||||
isPersistent = m_SpawnAsPersistentAnchor,
|
||||
spawnWithAnchor = true
|
||||
};
|
||||
|
||||
m_SpawnedObjects.Add(spawnedObjectHelper);
|
||||
|
||||
CreateAndParentAnchorForObject();
|
||||
}
|
||||
|
||||
// This method Adds the spawned object to an anchor.
|
||||
async void CreateAndParentAnchorForObject()
|
||||
{
|
||||
SpawnedObjectHelper spawnedObjectHelper = m_SpawnedObjects[^1];
|
||||
var result = await m_AnchorManager.TryAddAnchorAsync(new Pose(spawnedObjectHelper.gameObject.transform.position, spawnedObjectHelper.gameObject.transform.rotation));
|
||||
if (result.status.IsSuccess())
|
||||
{
|
||||
var anchor = result.value;
|
||||
spawnedObjectHelper.gameObject.transform.SetParent(anchor.transform);
|
||||
spawnedObjectHelper.attachedAnchor = anchor;
|
||||
m_SpawnedObjects[^1] = spawnedObjectHelper;
|
||||
}
|
||||
}
|
||||
|
||||
// This method deletes all anchors from the saved anchor data list.
|
||||
async Awaitable EraseAnchorsAsync()
|
||||
{
|
||||
try
|
||||
{
|
||||
var deletionList = m_SaveAndLoadAnchorIdsToFile.SavedAnchorsData.Keys.ToArray();
|
||||
foreach (var guid in deletionList)
|
||||
{
|
||||
await EraseAnchorAsync(guid);
|
||||
}
|
||||
}
|
||||
catch (System.Exception e)
|
||||
{
|
||||
Debug.LogError($"Error deleting anchors: {e.Message}", this);
|
||||
}
|
||||
}
|
||||
|
||||
// This method erases the anchor based on the GUID.
|
||||
async Awaitable EraseAnchorAsync(SerializableGuid guid)
|
||||
{
|
||||
var result = await m_AnchorManager.TryEraseAnchorAsync(guid);
|
||||
if (!result.IsError() && result.statusCode != XRResultStatus.StatusCode.UnqualifiedSuccess)
|
||||
{
|
||||
// handle error
|
||||
Debug.LogError($"Error erasing GUID: [{guid}].\n\nStatus Code:{result.statusCode}\n\nError: {result}", this);
|
||||
return;
|
||||
}
|
||||
|
||||
// The anchor was successfully erased.
|
||||
await m_SaveAndLoadAnchorIdsToFile.EraseAnchorIdAsync(guid);
|
||||
}
|
||||
|
||||
// Loads All anchors from the saved anchor data list.
|
||||
async Awaitable LoadAnchorsAsync(bool updateText = true)
|
||||
{
|
||||
foreach (var kvp in m_SaveAndLoadAnchorIdsToFile.SavedAnchorsData)
|
||||
{
|
||||
await TryLoadAnchorAsync(kvp.Key);
|
||||
}
|
||||
if (m_SaveAndLoadAnchorIdsToFile.SavedAnchorsData.Count == 0 && updateText)
|
||||
{
|
||||
m_AnchorText.text += "\n\n<align=center>- No Anchors To Load -</align>";
|
||||
}
|
||||
}
|
||||
|
||||
// Lodas the individual anchor based on the GUID.
|
||||
async Awaitable TryLoadAnchorAsync(SerializableGuid guid)
|
||||
{
|
||||
// Don't respawn objects that are already loaded
|
||||
foreach (var spawnedObjectHelper in m_SpawnedObjects)
|
||||
{
|
||||
if (spawnedObjectHelper.persistentGuid == guid && spawnedObjectHelper.gameObject != null)
|
||||
{
|
||||
m_AnchorText.text += $"Object {SpawnedObjectName(spawnedObjectHelper.spawnObjectIdx)} is already loaded.\nGUID {guid}.\n\n";
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
var result = await m_AnchorManager.TryLoadAnchorAsync(guid);
|
||||
if (result.status.IsError())
|
||||
{
|
||||
// handle error
|
||||
Debug.Log($"Error Loading Anchor - Status Code:{result.status.statusCode}\n\nError: {result.status} GUID: [{guid}].\n\n", this);
|
||||
m_AnchorText.text += $"Error loading GUID: [{guid}].\n{result.status.statusCode}\n";
|
||||
return;
|
||||
}
|
||||
|
||||
var newAnchor = result.value;
|
||||
if (m_SaveAndLoadAnchorIdsToFile.SavedAnchorsData.ContainsKey(guid))
|
||||
{
|
||||
spawnAsPersistentAnchor = true;
|
||||
int spawnId = m_SaveAndLoadAnchorIdsToFile.SavedAnchorsData[guid];
|
||||
CreateObjectForLoadedAnchor(newAnchor, guid, spawnId);
|
||||
m_AnchorText.text += $"Loaded Object{SpawnedObjectName(spawnId)}\nGUID {guid}.\n\n";
|
||||
}
|
||||
else
|
||||
{
|
||||
m_AnchorText.text += $"Persistent Dictionary did not contain GUID: [{guid}].\n\n";
|
||||
m_AnchorManager.TryRemoveAnchor(newAnchor);
|
||||
}
|
||||
}
|
||||
|
||||
// This method creates a new object for the loaded anchor reference.
|
||||
void CreateObjectForLoadedAnchor(ARAnchor newAnchor, SerializableGuid guid, int spawnId)
|
||||
{
|
||||
int nonZeroIndex = spawnId < 0 ? Random.Range(0, m_Spawner.objectPrefabs.Count) : spawnId;
|
||||
GameObject respawnedObject = Instantiate(m_Spawner.objectPrefabs[nonZeroIndex], newAnchor.transform);
|
||||
respawnedObject.transform.SetLocalPositionAndRotation(Vector3.zero, Quaternion.identity);
|
||||
|
||||
// Create new SpawnedObjectHelper object to store the new anchor reference.
|
||||
SpawnedObjectHelper spawnedObjectHelper = new SpawnedObjectHelper
|
||||
{
|
||||
gameObject = respawnedObject,
|
||||
attachedAnchor = newAnchor,
|
||||
spawnObjectIdx = spawnId,
|
||||
isPersistent = true,
|
||||
spawnWithAnchor = true,
|
||||
persistentGuid = guid
|
||||
};
|
||||
|
||||
bool anchorAlreadyExists = false;
|
||||
// Find and replace the SpawnedObjectHelper in the list with the new reference.
|
||||
for (int i = 0; i < m_SpawnedObjects.Count; i++)
|
||||
{
|
||||
if (m_SpawnedObjects[i].persistentGuid == guid)
|
||||
{
|
||||
m_SpawnedObjects[i] = spawnedObjectHelper;
|
||||
anchorAlreadyExists = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!anchorAlreadyExists)
|
||||
m_SpawnedObjects.Add(spawnedObjectHelper);
|
||||
}
|
||||
|
||||
// Saves all anchors from the spawned objects list.
|
||||
async Awaitable SaveAchorsAsync(bool updateText = true)
|
||||
{
|
||||
for (int i = 0; i < m_SpawnedObjects.Count; i++)
|
||||
{
|
||||
if (m_SpawnedObjects[i].isPersistent && m_SpawnedObjects[i].spawnWithAnchor)
|
||||
await TrySaveAnchorAsync(i);
|
||||
}
|
||||
|
||||
if (m_SaveAndLoadAnchorIdsToFile.SavedAnchorsData.Count == 0 && updateText)
|
||||
{
|
||||
m_AnchorText.text += "\n\n<align=center>- No Anchors To Save -</align>";
|
||||
}
|
||||
}
|
||||
|
||||
// Loop through the spawned objects and save the anchors.
|
||||
async Awaitable TrySaveAnchorAsync(int idx)
|
||||
{
|
||||
if (m_SaveAndLoadAnchorIdsToFile.SavedAnchorsData.ContainsKey(m_SpawnedObjects[idx].persistentGuid))
|
||||
{
|
||||
m_AnchorText.text += $"Object {SpawnedObjectName(m_SpawnedObjects[idx].spawnObjectIdx)} already saved.\nGUID: [{m_SpawnedObjects[idx].persistentGuid}].\n\n";
|
||||
return;
|
||||
}
|
||||
|
||||
var result = await m_AnchorManager.TrySaveAnchorAsync(m_SpawnedObjects[idx].attachedAnchor);
|
||||
if (result.status.IsError())
|
||||
{
|
||||
// handle error
|
||||
Debug.Log($"Error Saving Object: [{SpawnedObjectName(m_SpawnedObjects[idx].spawnObjectIdx)}].\n\nStatus Code:{result.status.statusCode}\n\nError: {result.status}", this);
|
||||
m_AnchorText.text += $"Failed to save {SpawnedObjectName(m_SpawnedObjects[idx].spawnObjectIdx)}\n\n";
|
||||
return;
|
||||
}
|
||||
|
||||
// Save this value, then use it as an input parameter
|
||||
SerializableGuid guid = result.value;
|
||||
m_SaveAndLoadAnchorIdsToFile ??= new SaveAndLoadAnchorDataToFile();
|
||||
await m_SaveAndLoadAnchorIdsToFile.SaveAnchorIdAsync(guid, m_SpawnedObjects[idx].spawnObjectIdx);
|
||||
|
||||
var spawnedObjectHelper = m_SpawnedObjects[idx];
|
||||
spawnedObjectHelper.persistentGuid = guid;
|
||||
m_SpawnedObjects[idx] = spawnedObjectHelper;
|
||||
|
||||
m_AnchorText.text += $"Saved Object {SpawnedObjectName(m_SpawnedObjects[idx].spawnObjectIdx)}\nGUID: [{guid}].\n\n";
|
||||
}
|
||||
|
||||
string SpawnedObjectName(int id)
|
||||
{
|
||||
return id < 0 ? "Random" : m_Spawner.objectPrefabs[id].name;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Helper struct to store the spawned object, anchor, and spawn object index, persistence, and persistent GUID.
|
||||
/// </summary>
|
||||
struct SpawnedObjectHelper
|
||||
{
|
||||
public GameObject gameObject;
|
||||
public ARAnchor attachedAnchor;
|
||||
public int spawnObjectIdx;
|
||||
public bool isPersistent;
|
||||
public bool spawnWithAnchor;
|
||||
public SerializableGuid persistentGuid;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: aa5e7045ec6a31048afad1c854a0d6a9
|
||||
@@ -0,0 +1,124 @@
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace UnityEngine.XR.Templates.MR
|
||||
{
|
||||
// Copied from XRI Examples. Commit: c40b958
|
||||
|
||||
/// <summary>
|
||||
/// Makes this object face a target smoothly and along specific axes
|
||||
/// </summary>
|
||||
public class TurnToFace : MonoBehaviour
|
||||
{
|
||||
#pragma warning disable 649
|
||||
[SerializeField, Tooltip("Target to face towards. If not set, this will default to the main camera")]
|
||||
Transform m_FaceTarget;
|
||||
|
||||
[SerializeField, Tooltip("Speed to turn")]
|
||||
float m_TurnToFaceSpeed = 5f;
|
||||
|
||||
[SerializeField, Tooltip("Local rotation offset")]
|
||||
Vector3 m_RotationOffset = Vector3.zero;
|
||||
|
||||
[SerializeField, Tooltip("If enabled, ignore the x axis when rotating")]
|
||||
bool m_IgnoreX;
|
||||
|
||||
[SerializeField, Tooltip("If enabled, ignore the y axis when rotating")]
|
||||
bool m_IgnoreY;
|
||||
|
||||
[SerializeField, Tooltip("If enabled, ignore the z axis when rotating")]
|
||||
bool m_IgnoreZ;
|
||||
#pragma warning restore 649
|
||||
|
||||
static readonly HashSet<TurnToFace> k_EnabledInstances = new HashSet<TurnToFace>();
|
||||
|
||||
void OnEnable()
|
||||
{
|
||||
transform.rotation = GetTargetRotation(transform.position);
|
||||
k_EnabledInstances.Add(this);
|
||||
|
||||
}
|
||||
|
||||
void OnDisable()
|
||||
{
|
||||
k_EnabledInstances.Remove(this);
|
||||
}
|
||||
|
||||
void Update()
|
||||
{
|
||||
LookAtTarget();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Causes all turn to face layout components to snap to the correct rotation immediately.
|
||||
/// </summary>
|
||||
public static void SnapAll()
|
||||
{
|
||||
foreach (var turnToFace in k_EnabledInstances)
|
||||
{
|
||||
var instanceTransform = turnToFace.transform;
|
||||
instanceTransform.rotation = turnToFace.GetTargetRotation(instanceTransform.position);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get the current easing lerp amount that will be used for rotation.
|
||||
/// </summary>
|
||||
/// <returns>The easing value based on current delta time.</returns>
|
||||
public float GetCurrentRotationEase()
|
||||
{
|
||||
return 1f - Mathf.Exp(-m_TurnToFaceSpeed * Time.unscaledDeltaTime);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Ease the rotation towards the target rotation
|
||||
/// </summary>
|
||||
public void LookAtTarget()
|
||||
{
|
||||
var thisTransform = transform;
|
||||
var targetRotation = GetTargetRotation(thisTransform.position);
|
||||
var ease = GetCurrentRotationEase();
|
||||
thisTransform.rotation = Quaternion.Slerp(thisTransform.rotation, targetRotation, ease);
|
||||
}
|
||||
|
||||
void SetDefaultTargetIfNeeded()
|
||||
{
|
||||
// Default to main camera
|
||||
if (m_FaceTarget == null)
|
||||
{
|
||||
var mainCamera = Camera.main;
|
||||
m_FaceTarget = mainCamera == null ? null : mainCamera.transform;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get the target rotation to use for a given position
|
||||
/// </summary>
|
||||
/// <param name="position">The position to compare to the face target when calculating the look direction.</param>
|
||||
/// <returns>The rotation that faces towards the target from the given position.</returns>
|
||||
public Quaternion GetTargetRotation(Vector3 position)
|
||||
{
|
||||
SetDefaultTargetIfNeeded();
|
||||
|
||||
if (m_FaceTarget == null)
|
||||
return Quaternion.identity;
|
||||
|
||||
var facePosition = m_FaceTarget.position;
|
||||
var forward = facePosition - position;
|
||||
var targetRotation = forward.sqrMagnitude > float.Epsilon ? Quaternion.LookRotation(forward, Vector3.up) : Quaternion.identity;
|
||||
targetRotation *= Quaternion.Euler(m_RotationOffset);
|
||||
if (m_IgnoreX || m_IgnoreY || m_IgnoreZ)
|
||||
{
|
||||
var targetEuler = targetRotation.eulerAngles;
|
||||
var currentEuler = transform.rotation.eulerAngles;
|
||||
targetRotation = Quaternion.Euler
|
||||
(
|
||||
m_IgnoreX ? currentEuler.x : targetEuler.x,
|
||||
m_IgnoreY ? currentEuler.y : targetEuler.y,
|
||||
m_IgnoreZ ? currentEuler.z : targetEuler.z
|
||||
);
|
||||
}
|
||||
|
||||
return targetRotation;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 06d2696e115df2c4591657f92bc483e3
|
||||
@@ -0,0 +1,57 @@
|
||||
using UnityEngine.Rendering;
|
||||
using UnityEngine.Video;
|
||||
|
||||
namespace UnityEngine.XR.Templates.MR
|
||||
{
|
||||
/// <summary>
|
||||
/// Create a RenderTexture for rendering video to a target renderer.
|
||||
/// </summary>
|
||||
[RequireComponent(typeof(VideoPlayer))]
|
||||
public class VideoPlayerRenderTexture : MonoBehaviour
|
||||
{
|
||||
private string k_ShaderName;
|
||||
|
||||
string GetShaderName()
|
||||
{
|
||||
if (GraphicsSettings.currentRenderPipeline)
|
||||
{
|
||||
string pipelineName = GraphicsSettings.currentRenderPipeline.name;
|
||||
if (pipelineName.Contains("UniversalRenderPipeline"))
|
||||
{
|
||||
return "Universal Render Pipeline/Unlit";
|
||||
}
|
||||
else if (pipelineName.Contains("HDRenderPipeline"))
|
||||
{
|
||||
return "HDRP/Unlit";
|
||||
}
|
||||
}
|
||||
|
||||
// Fallback for the built-in render pipeline
|
||||
return "Unlit/Texture";
|
||||
}
|
||||
|
||||
[SerializeField, Tooltip("The target Renderer which will display the video.")]
|
||||
Renderer m_Renderer;
|
||||
|
||||
[SerializeField, Tooltip("The width of the RenderTexture which will be created.")]
|
||||
int m_RenderTextureWidth = 1920;
|
||||
|
||||
[SerializeField, Tooltip("The height of the RenderTexture which will be created.")]
|
||||
int m_RenderTextureHeight = 1080;
|
||||
|
||||
[SerializeField, Tooltip("The bit depth of the depth channel for the RenderTexture which will be created.")]
|
||||
int m_RenderTextureDepth;
|
||||
|
||||
void Start()
|
||||
{
|
||||
k_ShaderName = GetShaderName();
|
||||
|
||||
var renderTexture = new RenderTexture(m_RenderTextureWidth, m_RenderTextureHeight, m_RenderTextureDepth);
|
||||
renderTexture.Create();
|
||||
var material = new Material(Shader.Find(k_ShaderName));
|
||||
material.mainTexture = renderTexture;
|
||||
GetComponent<VideoPlayer>().targetTexture = renderTexture;
|
||||
m_Renderer.material = material;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 1b45269ca3a28fe47924e08799d6178f
|
||||
@@ -0,0 +1,191 @@
|
||||
using System;
|
||||
using System.Collections;
|
||||
using TMPro;
|
||||
using UnityEngine.UI;
|
||||
using UnityEngine.Video;
|
||||
|
||||
namespace UnityEngine.XR.Templates.MR
|
||||
{
|
||||
/// <summary>
|
||||
/// Connects a UI slider control to a video player, allowing users to scrub to a particular time in th video.
|
||||
/// </summary>
|
||||
[RequireComponent(typeof(VideoPlayer))]
|
||||
public class VideoTimeScrubControl : MonoBehaviour
|
||||
{
|
||||
[SerializeField]
|
||||
[Tooltip("Video play/pause button GameObject")]
|
||||
GameObject m_ButtonPlayOrPause;
|
||||
|
||||
[SerializeField]
|
||||
[Tooltip("Slider that controls the video")]
|
||||
Slider m_Slider;
|
||||
|
||||
[SerializeField]
|
||||
[Tooltip("Play icon sprite")]
|
||||
Sprite m_IconPlay;
|
||||
|
||||
[SerializeField]
|
||||
[Tooltip("Pause icon sprite")]
|
||||
Sprite m_IconPause;
|
||||
|
||||
[SerializeField]
|
||||
[Tooltip("Play or pause button image.")]
|
||||
Image m_ButtonPlayOrPauseIcon;
|
||||
|
||||
[SerializeField]
|
||||
[Tooltip("Text that displays the current time of the video.")]
|
||||
TextMeshProUGUI m_VideoTimeText;
|
||||
|
||||
[SerializeField]
|
||||
[Tooltip("If checked, the slider will fade off after a few seconds. If unchecked, the slider will remain on.")]
|
||||
bool m_HideSliderAfterFewSeconds;
|
||||
|
||||
bool m_IsDragging;
|
||||
bool m_VideoIsPlaying;
|
||||
bool m_VideoJumpPending;
|
||||
long m_LastFrameBeforeScrub;
|
||||
VideoPlayer m_VideoPlayer;
|
||||
|
||||
void Start()
|
||||
{
|
||||
m_VideoPlayer = GetComponent<VideoPlayer>();
|
||||
if (!m_VideoPlayer.playOnAwake)
|
||||
{
|
||||
m_VideoPlayer.playOnAwake = true; // Set play on awake for next enable.
|
||||
m_VideoPlayer.Play(); // Play video to load first frame.
|
||||
VideoStop(); // Stop the video to set correct state and pause frame.
|
||||
}
|
||||
else
|
||||
{
|
||||
VideoPlay(); // Play to ensure correct state.
|
||||
}
|
||||
|
||||
if (m_ButtonPlayOrPause != null)
|
||||
m_ButtonPlayOrPause.SetActive(false);
|
||||
}
|
||||
|
||||
void OnEnable()
|
||||
{
|
||||
if (m_VideoPlayer != null)
|
||||
{
|
||||
m_VideoPlayer.frame = 0;
|
||||
VideoPlay(); // Ensures correct UI state update if paused.
|
||||
}
|
||||
|
||||
m_Slider.value = 0.0f;
|
||||
m_Slider.onValueChanged.AddListener(OnSliderValueChange);
|
||||
m_Slider.gameObject.SetActive(true);
|
||||
if (m_HideSliderAfterFewSeconds)
|
||||
StartCoroutine(HideSliderAfterSeconds());
|
||||
}
|
||||
|
||||
void Update()
|
||||
{
|
||||
if (m_VideoJumpPending)
|
||||
{
|
||||
// We're trying to jump to a new position, but we're checking to make sure the video player is updated to our new jump frame.
|
||||
if (m_LastFrameBeforeScrub == m_VideoPlayer.frame)
|
||||
return;
|
||||
|
||||
// If the video player has been updated with desired jump frame, reset these values.
|
||||
m_LastFrameBeforeScrub = long.MinValue;
|
||||
m_VideoJumpPending = false;
|
||||
}
|
||||
|
||||
if (!m_IsDragging && !m_VideoJumpPending)
|
||||
{
|
||||
if (m_VideoPlayer.frameCount > 0)
|
||||
{
|
||||
var progress = (float)m_VideoPlayer.frame / m_VideoPlayer.frameCount;
|
||||
m_Slider.value = progress;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void OnPointerDown()
|
||||
{
|
||||
m_VideoJumpPending = true;
|
||||
VideoStop();
|
||||
VideoJump();
|
||||
}
|
||||
|
||||
public void OnRelease()
|
||||
{
|
||||
m_IsDragging = false;
|
||||
VideoPlay();
|
||||
VideoJump();
|
||||
}
|
||||
|
||||
void OnSliderValueChange(float sliderValue)
|
||||
{
|
||||
UpdateVideoTimeText();
|
||||
}
|
||||
|
||||
IEnumerator HideSliderAfterSeconds(float duration = 1f)
|
||||
{
|
||||
yield return new WaitForSeconds(duration);
|
||||
m_Slider.gameObject.SetActive(false);
|
||||
}
|
||||
|
||||
public void OnDrag()
|
||||
{
|
||||
m_IsDragging = true;
|
||||
m_VideoJumpPending = true;
|
||||
}
|
||||
|
||||
void VideoJump()
|
||||
{
|
||||
m_VideoJumpPending = true;
|
||||
var frame = m_VideoPlayer.frameCount * m_Slider.value;
|
||||
m_LastFrameBeforeScrub = m_VideoPlayer.frame;
|
||||
m_VideoPlayer.frame = (long)frame;
|
||||
}
|
||||
|
||||
public void PlayOrPauseVideo()
|
||||
{
|
||||
if (m_VideoIsPlaying)
|
||||
{
|
||||
VideoStop();
|
||||
}
|
||||
else
|
||||
{
|
||||
VideoPlay();
|
||||
}
|
||||
}
|
||||
|
||||
void UpdateVideoTimeText()
|
||||
{
|
||||
if (m_VideoPlayer != null && m_VideoTimeText != null)
|
||||
{
|
||||
var currentTimeTimeSpan = TimeSpan.FromSeconds(m_VideoPlayer.time);
|
||||
var totalTimeTimeSpan = TimeSpan.FromSeconds(m_VideoPlayer.length);
|
||||
var currentTimeString = string.Format("{0:D1}:{1:D2}",
|
||||
currentTimeTimeSpan.Minutes,
|
||||
currentTimeTimeSpan.Seconds
|
||||
);
|
||||
|
||||
var totalTimeString = string.Format("{0:D1}:{1:D2}",
|
||||
totalTimeTimeSpan.Minutes,
|
||||
totalTimeTimeSpan.Seconds
|
||||
);
|
||||
m_VideoTimeText.SetText(currentTimeString + " / " + totalTimeString);
|
||||
}
|
||||
}
|
||||
|
||||
void VideoStop()
|
||||
{
|
||||
m_VideoIsPlaying = false;
|
||||
m_VideoPlayer.Pause();
|
||||
m_ButtonPlayOrPauseIcon.sprite = m_IconPlay;
|
||||
m_ButtonPlayOrPause.SetActive(true);
|
||||
}
|
||||
|
||||
void VideoPlay()
|
||||
{
|
||||
m_VideoIsPlaying = true;
|
||||
m_VideoPlayer.Play();
|
||||
m_ButtonPlayOrPauseIcon.sprite = m_IconPause;
|
||||
m_ButtonPlayOrPause.SetActive(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 6f061770d897d9940b53cbfb589c72f2
|
||||
@@ -0,0 +1,83 @@
|
||||
using System;
|
||||
using UnityEngine.Events;
|
||||
using UnityEngine.XR.Interaction.Toolkit;
|
||||
using UnityEngine.XR.Interaction.Toolkit.Interactables;
|
||||
using UnityEngine.XR.Interaction.Toolkit.Interactors;
|
||||
|
||||
namespace UnityEngine.XR.Templates.MR
|
||||
{
|
||||
/// <summary>
|
||||
/// An interactable that aligns/faces the position of the interactor
|
||||
/// </summary>
|
||||
public class XRBlaster : XRGrabInteractable
|
||||
{
|
||||
[Serializable]
|
||||
public class ValueChangeEvent : UnityEvent<float> { }
|
||||
|
||||
IXRSelectInteractor m_Interactor;
|
||||
bool m_Front;
|
||||
|
||||
protected override void OnEnable()
|
||||
{
|
||||
base.OnEnable();
|
||||
selectEntered.AddListener(StartGrab);
|
||||
selectExited.AddListener(EndGrab);
|
||||
}
|
||||
|
||||
protected override void OnDisable()
|
||||
{
|
||||
selectEntered.RemoveListener(StartGrab);
|
||||
selectExited.RemoveListener(EndGrab);
|
||||
base.OnDisable();
|
||||
}
|
||||
|
||||
void StartGrab(SelectEnterEventArgs args)
|
||||
{
|
||||
m_Interactor = args.interactorObject;
|
||||
|
||||
var interactorTransform = m_Interactor.GetAttachTransform(this);
|
||||
|
||||
Vector3 posRelative = transform.InverseTransformPoint(interactorTransform.position);
|
||||
|
||||
if (posRelative.z > 0)
|
||||
m_Front = true;
|
||||
else
|
||||
m_Front = false;
|
||||
|
||||
UpdateRotation(true);
|
||||
}
|
||||
|
||||
void EndGrab(SelectExitEventArgs args)
|
||||
{
|
||||
m_Interactor = null;
|
||||
}
|
||||
|
||||
public override void ProcessInteractable(XRInteractionUpdateOrder.UpdatePhase updatePhase)
|
||||
{
|
||||
base.ProcessInteractable(updatePhase);
|
||||
|
||||
if (updatePhase == XRInteractionUpdateOrder.UpdatePhase.Dynamic)
|
||||
{
|
||||
if (isSelected)
|
||||
{
|
||||
UpdateRotation();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void UpdateRotation(bool freshCheck = false)
|
||||
{
|
||||
var interactorTransform = m_Interactor.GetAttachTransform(this);
|
||||
|
||||
if (m_Front)
|
||||
{
|
||||
transform.LookAt(interactorTransform.position);
|
||||
}
|
||||
else
|
||||
{
|
||||
transform.LookAt(interactorTransform.position);
|
||||
transform.rotation *= Quaternion.Euler(new Vector3(0, 180f, 0f));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 566a0ffd7d209ab4dbfd2fbb8cdb4b45
|
||||
@@ -0,0 +1,234 @@
|
||||
using Unity.Mathematics;
|
||||
using Unity.XR.CoreUtils.Bindings;
|
||||
using UnityEngine.XR.Interaction.Toolkit.Filtering;
|
||||
using UnityEngine.XR.Interaction.Toolkit.Utilities.Tweenables.Primitives;
|
||||
|
||||
namespace UnityEngine.XR.Templates.MR
|
||||
{
|
||||
/// <summary>
|
||||
/// Follow animation affordance for <see cref="IPokeStateDataProvider"/>, such as <see cref="XRPokeFilter"/>.
|
||||
/// Used to animate a pressed transform, such as a button to follow the poke position.
|
||||
/// </summary>
|
||||
[AddComponentMenu("XR/XR Poke Follow Affordance Fill", 22)]
|
||||
public class XRPokeFollowAffordanceFill : MonoBehaviour
|
||||
{
|
||||
[SerializeField]
|
||||
[Tooltip("Transform that will move in the poke direction when this or a parent GameObject is poked." +
|
||||
"\nNote: Should be a direct child GameObject.")]
|
||||
Transform m_PokeFollowTransform;
|
||||
|
||||
[SerializeField]
|
||||
[Tooltip("Transform that will scale the mask when this interactable is poked.")]
|
||||
RectTransform m_PokeFill;
|
||||
|
||||
/// <summary>
|
||||
/// Transform that will animate along the axis of interaction when this interactable is poked.
|
||||
/// Note: Must be a direct child GameObject as it moves in local space relative to the poke target's transform.
|
||||
/// </summary>
|
||||
public Transform pokeFollowTransform
|
||||
{
|
||||
get => m_PokeFollowTransform;
|
||||
set => m_PokeFollowTransform = value;
|
||||
}
|
||||
|
||||
[SerializeField]
|
||||
[Range(0f, 20f)]
|
||||
[Tooltip("Multiplies transform position interpolation as a factor of Time.deltaTime. If 0, no smoothing will be applied.")]
|
||||
float m_SmoothingSpeed = 8f;
|
||||
|
||||
/// <summary>
|
||||
/// Multiplies transform position interpolation as a factor of <see cref="Time.deltaTime"/>. If <c>0</c>, no smoothing will be applied.
|
||||
/// </summary>
|
||||
public float smoothingSpeed
|
||||
{
|
||||
get => m_SmoothingSpeed;
|
||||
set => m_SmoothingSpeed = value;
|
||||
}
|
||||
|
||||
[SerializeField]
|
||||
[Tooltip("When this component is no longer the target of the poke, the Poke Follow Transform returns to the original position.")]
|
||||
bool m_ReturnToInitialPosition = true;
|
||||
|
||||
/// <summary>
|
||||
/// When this component is no longer the target of the poke, the <see cref="pokeFollowTransform"/> returns to the original position.
|
||||
/// </summary>
|
||||
public bool returnToInitialPosition
|
||||
{
|
||||
get => m_ReturnToInitialPosition;
|
||||
set => m_ReturnToInitialPosition = value;
|
||||
}
|
||||
|
||||
[SerializeField]
|
||||
[Tooltip("Whether to apply the follow animation if the target of the poke is a child of this transform. " +
|
||||
"This is useful for UI objects that may have child graphics.")]
|
||||
bool m_ApplyIfChildIsTarget = true;
|
||||
|
||||
/// <summary>
|
||||
/// Whether to apply the follow animation if the target of the poke is a child of this transform.
|
||||
/// This is useful for UI objects that may have child graphics.
|
||||
/// </summary>
|
||||
public bool applyIfChildIsTarget
|
||||
{
|
||||
get => m_ApplyIfChildIsTarget;
|
||||
set => m_ApplyIfChildIsTarget = value;
|
||||
}
|
||||
|
||||
[Header("Distance Clamping")]
|
||||
[SerializeField]
|
||||
[Tooltip("Whether to keep the Poke Follow Transform from moving past a minimum distance from the poke target.")]
|
||||
bool m_ClampToMinDistance;
|
||||
|
||||
/// <summary>
|
||||
/// Whether to keep the <see cref="pokeFollowTransform"/> from moving past <see cref="minDistance"/> from the poke target.
|
||||
/// </summary>
|
||||
public bool clampToMinDistance
|
||||
{
|
||||
get => m_ClampToMinDistance;
|
||||
set => m_ClampToMinDistance = value;
|
||||
}
|
||||
|
||||
[SerializeField]
|
||||
[Tooltip("The minimum distance from this transform that the Poke Follow Transform can move.")]
|
||||
float m_MinDistance;
|
||||
|
||||
/// <summary>
|
||||
/// The minimum distance from this transform that the <see cref="pokeFollowTransform"/> can move when
|
||||
/// <see cref="clampToMinDistance"/> is <see langword="true"/>.
|
||||
/// </summary>
|
||||
public float minDistance
|
||||
{
|
||||
get => m_MinDistance;
|
||||
set => m_MinDistance = value;
|
||||
}
|
||||
[Space]
|
||||
[SerializeField]
|
||||
[Tooltip("Whether to keep the Poke Follow Transform from moving past a maximum distance from the poke target.")]
|
||||
bool m_ClampToMaxDistance;
|
||||
|
||||
/// <summary>
|
||||
/// Whether to keep the <see cref="pokeFollowTransform"/> from moving past <see cref="maxDistance"/> from the poke target.
|
||||
/// </summary>
|
||||
public bool clampToMaxDistance
|
||||
{
|
||||
get => m_ClampToMaxDistance;
|
||||
set => m_ClampToMaxDistance = value;
|
||||
}
|
||||
|
||||
[SerializeField]
|
||||
[Tooltip("The maximum distance from this transform that the Poke Follow Transform can move. Will shrink to the distance of initial position if that is smaller, or if this is 0.")]
|
||||
float m_MaxDistance;
|
||||
|
||||
/// <summary>
|
||||
/// The maximum distance from this transform that the <see cref="pokeFollowTransform"/> can move when
|
||||
/// <see cref="clampToMaxDistance"/> is <see langword="true"/>.
|
||||
/// </summary>
|
||||
public float maxDistance
|
||||
{
|
||||
get => m_MaxDistance;
|
||||
set => m_MaxDistance = value;
|
||||
}
|
||||
|
||||
IPokeStateDataProvider m_PokeDataProvider;
|
||||
|
||||
#pragma warning disable CS0618 // Type or member is obsolete
|
||||
readonly Vector3TweenableVariable m_TransformTweenableVariable = new Vector3TweenableVariable();
|
||||
readonly FloatTweenableVariable m_PokeStrengthTweenableVariable = new FloatTweenableVariable();
|
||||
#pragma warning restore CS0618 // Type or member is obsolete
|
||||
readonly BindingsGroup m_BindingsGroup = new BindingsGroup();
|
||||
Vector3 m_InitialPosition;
|
||||
bool m_IsFirstFrame;
|
||||
|
||||
/// <summary>
|
||||
/// See <see cref="MonoBehaviour"/>.
|
||||
/// </summary>
|
||||
protected void Awake()
|
||||
{
|
||||
m_PokeDataProvider = GetComponentInParent<IPokeStateDataProvider>();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// See <see cref="MonoBehaviour"/>.
|
||||
/// </summary>
|
||||
protected void Start()
|
||||
{
|
||||
if (m_PokeFollowTransform != null)
|
||||
{
|
||||
m_InitialPosition = m_PokeFollowTransform.localPosition;
|
||||
m_MaxDistance = m_MaxDistance > 0f ? Mathf.Min(m_InitialPosition.magnitude, m_MaxDistance) : m_InitialPosition.magnitude;
|
||||
m_BindingsGroup.AddBinding(m_TransformTweenableVariable.Subscribe(OnTransformTweenableVariableUpdated));
|
||||
m_BindingsGroup.AddBinding(m_PokeStrengthTweenableVariable.Subscribe(OnPokeStrengthChanged));
|
||||
m_BindingsGroup.AddBinding(m_PokeDataProvider.pokeStateData.SubscribeAndUpdate(OnPokeStateDataUpdated));
|
||||
}
|
||||
else
|
||||
{
|
||||
enabled = false;
|
||||
Debug.LogWarning($"Missing Poke Follow Transform assignment on {this}. Disabling component.", this);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// See <see cref="MonoBehaviour"/>.
|
||||
/// </summary>
|
||||
protected void OnDestroy()
|
||||
{
|
||||
m_BindingsGroup.Clear();
|
||||
m_TransformTweenableVariable?.Dispose();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// See <see cref="MonoBehaviour"/>.
|
||||
/// </summary>
|
||||
protected void LateUpdate()
|
||||
{
|
||||
if (m_IsFirstFrame)
|
||||
{
|
||||
m_TransformTweenableVariable.HandleTween(1f);
|
||||
m_PokeStrengthTweenableVariable.target = 0f;
|
||||
m_PokeStrengthTweenableVariable.HandleTween(1f);
|
||||
m_IsFirstFrame = false;
|
||||
return;
|
||||
}
|
||||
|
||||
float tweenAmt = m_SmoothingSpeed > 0f ? Time.deltaTime * m_SmoothingSpeed : 1f;
|
||||
m_TransformTweenableVariable.HandleTween(tweenAmt);
|
||||
m_PokeStrengthTweenableVariable.HandleTween(tweenAmt);
|
||||
}
|
||||
|
||||
void OnTransformTweenableVariableUpdated(float3 position)
|
||||
{
|
||||
m_PokeFollowTransform.localPosition = position;
|
||||
}
|
||||
|
||||
void OnPokeStrengthChanged(float newStrength)
|
||||
{
|
||||
m_PokeFill.transform.localScale = Vector3.one * newStrength;
|
||||
}
|
||||
|
||||
void OnPokeStateDataUpdated(PokeStateData data)
|
||||
{
|
||||
var pokeTarget = data.target;
|
||||
var applyFollow = m_ApplyIfChildIsTarget
|
||||
? pokeTarget != null && pokeTarget.IsChildOf(transform)
|
||||
: pokeTarget == transform;
|
||||
|
||||
if (applyFollow)
|
||||
{
|
||||
var targetPosition = pokeTarget.InverseTransformPoint(data.axisAlignedPokeInteractionPoint);
|
||||
|
||||
if (m_ClampToMinDistance && targetPosition.sqrMagnitude < m_MinDistance * m_MinDistance)
|
||||
targetPosition = Vector3.ClampMagnitude(targetPosition, m_MinDistance);
|
||||
|
||||
if (m_ClampToMaxDistance && targetPosition.sqrMagnitude > m_MaxDistance * m_MaxDistance)
|
||||
targetPosition = Vector3.ClampMagnitude(targetPosition, m_MaxDistance);
|
||||
|
||||
m_TransformTweenableVariable.target = targetPosition;
|
||||
m_PokeStrengthTweenableVariable.target = Mathf.Clamp01(data.interactionStrength);
|
||||
}
|
||||
else if (m_ReturnToInitialPosition)
|
||||
{
|
||||
m_TransformTweenableVariable.target = m_InitialPosition;
|
||||
m_PokeStrengthTweenableVariable.target = 0f;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: ef5a10acb20b89b408b1854b6ef1cc35
|
||||
@@ -0,0 +1,92 @@
|
||||
namespace UnityEngine.XR.Templates.MR
|
||||
{
|
||||
// Copied from XRI Examples. Commit: c40b958
|
||||
|
||||
/// <summary>
|
||||
/// Component that controls the scale of the GameObject based on the distance from the main camera.
|
||||
/// The distance is divided into zones, and the transform scale is smoothly transitioned when the object moves into a different zone.
|
||||
/// The size of the zones are relative to the viewer's scale.
|
||||
/// </summary>
|
||||
public class ZoneScale : MonoBehaviour
|
||||
{
|
||||
const float k_SmoothTime = 0.3f;
|
||||
|
||||
#pragma warning disable 649
|
||||
[SerializeField]
|
||||
bool m_Clamp;
|
||||
|
||||
[SerializeField]
|
||||
float m_ClampMax = 10f;
|
||||
|
||||
[SerializeField]
|
||||
float m_ClampMin = 1f;
|
||||
|
||||
[SerializeField]
|
||||
float m_ZoneSize = 0.2f;
|
||||
|
||||
[SerializeField]
|
||||
float m_DefaultScale = 1f;
|
||||
#pragma warning restore 649
|
||||
|
||||
float m_YVelocity;
|
||||
float m_LastScale = 1.0f;
|
||||
bool m_Snap;
|
||||
Transform m_MainCameraTransform;
|
||||
int m_CurrentZone;
|
||||
|
||||
//IProvidesViewerScale IFunctionalitySubscriber<IProvidesViewerScale>.provider { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Skip the transition interpolation to the target scale immediately
|
||||
/// </summary>
|
||||
public void Snap()
|
||||
{
|
||||
m_Snap = true;
|
||||
SetScaleForCurrentDistance();
|
||||
}
|
||||
|
||||
void OnEnable()
|
||||
{
|
||||
var mainCamera = Camera.main;
|
||||
if (mainCamera != null)
|
||||
m_MainCameraTransform = mainCamera.transform;
|
||||
|
||||
Snap();
|
||||
}
|
||||
|
||||
void LateUpdate()
|
||||
{
|
||||
SetScaleForCurrentDistance();
|
||||
}
|
||||
|
||||
void SetScaleForCurrentDistance()
|
||||
{
|
||||
if (m_MainCameraTransform == null)
|
||||
return;
|
||||
|
||||
var cameraPosition = m_MainCameraTransform.position;
|
||||
var deltaToCamera = cameraPosition - transform.position;
|
||||
var adjustedDistance = deltaToCamera.magnitude;
|
||||
var scaledZoneSize = m_ZoneSize * Camera.main.transform.lossyScale.x;
|
||||
var zone = Mathf.CeilToInt(adjustedDistance / scaledZoneSize);
|
||||
var bufferSize = scaledZoneSize * 0.5f;
|
||||
if (adjustedDistance > m_CurrentZone * scaledZoneSize + bufferSize ||
|
||||
adjustedDistance < m_CurrentZone * scaledZoneSize - bufferSize)
|
||||
{
|
||||
m_CurrentZone = zone;
|
||||
}
|
||||
|
||||
var targetScale = m_CurrentZone * scaledZoneSize;
|
||||
var newScale = m_Snap ? targetScale : Mathf.SmoothDamp(m_LastScale, targetScale, ref m_YVelocity, k_SmoothTime);
|
||||
|
||||
if (m_Snap)
|
||||
m_Snap = false;
|
||||
|
||||
if (m_Clamp)
|
||||
newScale = Mathf.Clamp(newScale, m_ClampMin, m_ClampMax);
|
||||
|
||||
transform.localScale = Vector3.one * (newScale * m_DefaultScale);
|
||||
m_LastScale = newScale;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: d2c297c58ecf31f4084e39a103b22a1d
|
||||
Reference in New Issue
Block a user