Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix docs and improve out of bounds calculation #33

Merged
merged 8 commits into from
May 13, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion Assets~/Profiles/DefaultPlayerServiceProfile.asset
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ MonoBehaviour:
reference: a68682d3-3e9d-4d56-8a32-9805f58928f8
name: Player Bounds Module
priority: 1
profile: {fileID: 0}
profile: {fileID: 11400000, guid: 9fbf13d08b435e747b145c505b5b7896, type: 2}
platformEntries:
runtimePlatforms:
- reference: 9869b29e-43bb-44cc-ae49-eb5c913263f9
Expand Down
15 changes: 15 additions & 0 deletions Assets~/Profiles/PlayerBoundsModuleProfile.asset
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
%YAML 1.1
%TAG !u! tag:unity3d.com,2011:
--- !u!114 &11400000
MonoBehaviour:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 0}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: 45845bc60a29897449c67c8bd5176eb6, type: 3}
m_Name: PlayerBoundsModuleProfile
m_EditorClassIdentifier:
maxSeverityDistanceThreshold: 0.2
8 changes: 8 additions & 0 deletions Assets~/Profiles/PlayerBoundsModuleProfile.asset.meta

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 0 additions & 1 deletion Assets~/StandardAssets/Prefabs/XRPlayerController.prefab
Original file line number Diff line number Diff line change
Expand Up @@ -129,7 +129,6 @@ MonoBehaviour:
m_Name:
m_EditorClassIdentifier:
sphereCollider: {fileID: 367572774002706352}
maxSeverityDistanceThreshold: 0.2
--- !u!135 &367572774002706352
SphereCollider:
m_ObjectHideFlags: 0
Expand Down
52 changes: 35 additions & 17 deletions Runtime/Bounds/IPlayerBoundsModule.cs
Original file line number Diff line number Diff line change
@@ -1,19 +1,24 @@
// Copyright (c) Reality Collective. All rights reserved.
// Licensed under the MIT License. See LICENSE in the project root for license information.

using System;
using UnityEngine;

namespace RealityToolkit.Player.Bounds
{
/// <summary>
/// Event delegate for handling the playerService out of bounds situation.
/// Event delegate for handling the player out of bounds situation.
/// </summary>
/// <param name="severity">A percentage in range <c>[0f, 1f]</c> specifying how far of bounds the player is. Where <c>1f</c> means the player
/// is definitely going places it should not be at.</param>
/// <param name="returnToBoundsDirection">A <see cref="Vector3"/> specifying the direction the player needs to take to return to into bounds.</param>
public delegate void PlayerOutOfBoundsDelegate(float severity, Vector3 returnToBoundsDirection);

/// <summary>
/// Event delegate for handling the the player's return into bounds.
/// </summary>
/// <param name="didAutoReset">If <c>true</c>, the player was reset into bounds by the system itself.</param>
public delegate void PlayerBackInBoundsDelegate(bool didAutoReset);

/// <summary>
/// The player bounds module is used for room scale XR applications
/// where it is necessary to monitor, whether the user has moved physically
Expand All @@ -22,43 +27,56 @@ namespace RealityToolkit.Player.Bounds
public interface IPlayerBoundsModule : IPlayerServiceModule
{
/// <summary>
/// Is the active <see cref="IPlayerRig"/> currently considered out of bounds?
/// If set, the player will be reset into bounds if out of bounds for a given period of time.
/// </summary>
bool AutoResetEnabled { get; set; }

/// <summary>
/// Duration in seconds tolerated out of bounds until the player is automatically reset into bounds.
/// </summary>
float AutoResetTimeout { get; set; }

/// <summary>
/// Is the active <see cref="Rigs.IPlayerRig"/> currently considered out of bounds?
/// </summary>
bool IsPlayerOutOfBounds { get; }

/// <summary>
/// The last saved known <see cref="Pose"/> where the <see cref="IPlayerRig"/> was still in bounds.
/// The last saved known <see cref="Pose"/> where the <see cref="Rigs.IPlayerRig"/> was still in bounds.
/// </summary>
Pose LastInBoundsPose { get; }

/// <summary>
/// Raised while the <see cref="IPlayerRig.RigCamera"/> is out of bounds.
/// Raised while the <see cref="Rigs.IXRPlayerHead"/> is out of bounds.
/// </summary>
event PlayerOutOfBoundsDelegate PlayerOutOfBounds;

/// <summary>
/// Raised when the <see cref="IPlayerRig.RigCamera"/> is back in bounds.
/// Raised when the <see cref="Rigs.IPlayerRig"/> is back in bounds.
/// </summary>
event Action PlayerBackInBounds;
event PlayerBackInBoundsDelegate PlayerBackInBounds;

/// <summary>
/// Force resets the <see cref="IPlayerRig"/> into the last known pose
/// before it went out of bounds.
/// Force resets the <see cref="Rigs.IPlayerRig"/> into the <see cref="LastInBoundsPose"/>.
/// </summary>
void ResetPlayerIntoBounds();

/// <summary>
/// Raises the <see cref="PlayerOutOfBounds"/> event to subsribed
/// <see cref="PlayerOutOfBoundsDelegate"/>s.
/// The <see cref="Rigs.IXRPlayerHead"/> has entered a <see cref="PlayerOutOfBoundsTrigger"/>.
/// </summary>
/// <param name="trigger">The <see cref="PlayerOutOfBoundsTrigger"/>.</param>
void OnTriggerEnter(PlayerOutOfBoundsTrigger trigger);

/// <summary>
/// The <see cref="Rigs.IXRPlayerHead"/> is staying within a <see cref="PlayerOutOfBoundsTrigger"/>.
/// </summary>
/// <param name="severity">A percentage in range <c>[0f, 1f]</c> specifying how far of bounds the player is. Where <c>1f</c> means the player
/// is definitely going places it should not be at.</param>
/// <param name="returnToBoundsDirection">A <see cref="Vector3"/> specifying the direction the player needs to take to return to into bounds.</param>
void RaisePlayerOutOfBounds(float severity, Vector3 returnToBoundsDirection);
/// <param name="trigger">The <see cref="PlayerOutOfBoundsTrigger"/>.</param>
void OnTriggerStay(PlayerOutOfBoundsTrigger trigger);

/// <summary>
/// Raises the <see cref="PlayerBackInBounds"/> event to subscirbed delegates.
/// The <see cref="Rigs.IXRPlayerHead"/> has left a <see cref="PlayerOutOfBoundsTrigger"/>.
/// </summary>
void RaisePlayerBackInBounds();
/// <param name="trigger">The <see cref="PlayerOutOfBoundsTrigger"/>.</param>
void OnTriggerExit(PlayerOutOfBoundsTrigger trigger);
}
}
93 changes: 76 additions & 17 deletions Runtime/Bounds/PlayerBoundsModule.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,10 @@
// Licensed under the MIT License. See LICENSE in the project root for license information.

using RealityCollective.ServiceFramework.Attributes;
using RealityCollective.ServiceFramework.Definitions;
using RealityCollective.ServiceFramework.Definitions.Platforms;
using RealityCollective.ServiceFramework.Modules;
using RealityCollective.Utilities.Extensions;
using RealityToolkit.Player.Rigs;
using System;
using UnityEngine;

namespace RealityToolkit.Player.Bounds
Expand All @@ -19,8 +18,27 @@ namespace RealityToolkit.Player.Bounds
public class PlayerBoundsModule : BaseServiceModule, IPlayerBoundsModule
{
/// <inheritdoc />
public PlayerBoundsModule(string name, uint priority, BaseProfile profile, IPlayerService parentService)
: base(name, priority, profile, parentService) { }
public PlayerBoundsModule(string name, uint priority, PlayerBoundsModuleProfile profile, IPlayerService parentService)
: base(name, priority, profile, parentService)
{
maxSeverityDistanceThreshold = profile.MaxSeverityDistanceThreshold;
AutoResetEnabled = profile.AutoResetEnabled;
AutoResetTimeout = profile.AutoResetTimeout;
}

private readonly float maxSeverityDistanceThreshold;
private XRPlayerController playerRig;
private const float returnToBoundsPoseOffset = .5f;
private const float maxSeverity = 1f;
private PlayerOutOfBoundsTrigger currentOutOfBoundsTrigger;
private Vector3 boundsExitPosition;
private float autoResetTimer;

/// <inheritdoc />
public bool AutoResetEnabled { get; set; }

/// <inheritdoc />
public float AutoResetTimeout { get; set; }

/// <inheritdoc />
public bool IsPlayerOutOfBounds { get; private set; }
Expand All @@ -32,10 +50,7 @@ public PlayerBoundsModule(string name, uint priority, BaseProfile profile, IPlay
public event PlayerOutOfBoundsDelegate PlayerOutOfBounds;

/// <inheritdoc />
public event Action PlayerBackInBounds;

private IPlayerRig playerRig;
private const float returnToBoundsPoseOffset = .5f;
public event PlayerBackInBoundsDelegate PlayerBackInBounds;

/// <inheritdoc />
public override void Initialize()
Expand All @@ -45,14 +60,23 @@ public override void Initialize()
return;
}

playerRig = (ParentService as IPlayerService).PlayerRig;
playerRig = (ParentService as IPlayerService).PlayerRig as XRPlayerController;
}

/// <inheritdoc />
public override void Update()
{
if (IsPlayerOutOfBounds)
{
if (AutoResetEnabled)
{
autoResetTimer -= Time.deltaTime;
if (autoResetTimer <= 0f)
{
ResetPlayerIntoBounds(true);
}
}

return;
}

Expand All @@ -62,7 +86,9 @@ public override void Update()
}

/// <inheritdoc />
public void ResetPlayerIntoBounds()
public void ResetPlayerIntoBounds() => RaisePlayerBackInBounds(false);

private void ResetPlayerIntoBounds(bool didAutoReset)
{
if (!IsPlayerOutOfBounds)
{
Expand All @@ -76,24 +102,57 @@ public void ResetPlayerIntoBounds()
position -= returnToBoundsPoseOffset * direction;

playerRig.SetPositionAndRotation(position, Quaternion.identity);
RaisePlayerBackInBounds();
RaisePlayerBackInBounds(didAutoReset);
}

/// <inheritdoc />
public void RaisePlayerOutOfBounds(float severity, Vector3 returnToBoundsDirection)
public void OnTriggerEnter(PlayerOutOfBoundsTrigger trigger)
{
IsPlayerOutOfBounds = severity > 0f;
if (IsPlayerOutOfBounds)
if (currentOutOfBoundsTrigger.IsNull())
{
currentOutOfBoundsTrigger = trigger;
boundsExitPosition = playerRig.Head.Pose.position;
}
}

/// <inheritdoc />
public void OnTriggerStay(PlayerOutOfBoundsTrigger trigger)
{
if (currentOutOfBoundsTrigger.IsNotNull())
{
PlayerOutOfBounds?.Invoke(severity, returnToBoundsDirection);
var distance = Vector3.Distance(boundsExitPosition, playerRig.Head.Pose.position);
var severity = Mathf.Clamp01(distance / maxSeverityDistanceThreshold);
var direction = (boundsExitPosition - playerRig.Head.Pose.position).normalized;

var wasAlreadyOutOfBounds = IsPlayerOutOfBounds;
IsPlayerOutOfBounds = severity > 0f;

if (IsPlayerOutOfBounds)
{
if (!wasAlreadyOutOfBounds || severity < maxSeverity)
{
autoResetTimer = AutoResetTimeout;
}

PlayerOutOfBounds?.Invoke(severity, direction);
}
}
}

/// <inheritdoc />
public void RaisePlayerBackInBounds()
public void OnTriggerExit(PlayerOutOfBoundsTrigger trigger)
{
if (trigger == currentOutOfBoundsTrigger)
{
RaisePlayerBackInBounds(false);
currentOutOfBoundsTrigger = null;
}
}

private void RaisePlayerBackInBounds(bool didAutoReset)
{
IsPlayerOutOfBounds = false;
PlayerBackInBounds?.Invoke();
PlayerBackInBounds?.Invoke(didAutoReset);
}
}
}
38 changes: 38 additions & 0 deletions Runtime/Bounds/PlayerBoundsModuleProfile.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
// Copyright (c) Reality Collective. All rights reserved.
// Licensed under the MIT License. See LICENSE in the project root for license information.

using RealityCollective.ServiceFramework.Definitions;
using UnityEngine;

namespace RealityToolkit.Player.Bounds
{
/// <summary>
/// Configuration profile for the <see cref="PlayerBoundsModule"/>.
/// </summary>
public class PlayerBoundsModuleProfile : BaseProfile
{
[SerializeField, Tooltip("The distance the head is allowed to move out of bounds before it is considered severely out of bounds.")]
private float maxSeverityDistanceThreshold = .2f;

/// <summary>
/// The distance the head is allowed to move out of bounds before it is considered severely out of bounds.
/// </summary>
public float MaxSeverityDistanceThreshold => maxSeverityDistanceThreshold;

[SerializeField, Tooltip("If set, the player will be reset into bounds if out of bounds for a given period of time.")]
private bool autoResetEnabled = true;

/// <summary>
/// If set, the player will be reset into bounds if out of bounds for a given period of time.
/// </summary>
public bool AutoResetEnabled => autoResetEnabled;

[SerializeField, Tooltip("Duration in seconds tolerated out of bounds until the player is automatically reset into bounds.")]
private float autoResetTimeout = 5f;

/// <summary>
/// Duration in seconds tolerated out of bounds until the player is automatically reset into bounds.
/// </summary>
public float AutoResetTimeout => autoResetTimeout;
}
}
11 changes: 11 additions & 0 deletions Runtime/Bounds/PlayerBoundsModuleProfile.cs.meta

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion Runtime/Bounds/PlayerOutOfBoundsFade.cs
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ private void PlayerService_PlayerOutOfBounds(float severity, Vector3 returnToBou
cameraFade.SetFade(severity);
}

private void PlayerService_PlayerBackInBounds()
private void PlayerService_PlayerBackInBounds(bool didAutoReset)
{
cameraFade.SetFade(0f);
}
Expand Down
Loading