Add ultimate xr

This commit is contained in:
2024-08-06 21:58:35 +02:00
parent 864033bf10
commit 7165bacd9d
3952 changed files with 2162037 additions and 35 deletions

View File

@@ -0,0 +1,23 @@
// --------------------------------------------------------------------------------------------------------------------
// <copyright file="IUxrLocomotionUpdater.cs" company="VRMADA">
// Copyright (c) VRMADA, All rights reserved.
// </copyright>
// --------------------------------------------------------------------------------------------------------------------
namespace UltimateXR.Locomotion
{
/// <summary>
/// Internal interface for locomotion components to make updating publicly available only from within the framework.
/// Child classes from <see cref="UxrLocomotion" /> will implement these through the protected methods.
/// </summary>
internal interface IUxrLocomotionUpdater
{
#region Public Methods
/// <summary>
/// Updates the locomotion and the avatar's position/orientation the component belongs to.
/// </summary>
void UpdateLocomotion();
#endregion
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: c579b7d7e9ef0514ea3cae3fc3debd1d
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,29 @@
// --------------------------------------------------------------------------------------------------------------------
// <copyright file="UxrDestinationValidatorMode.cs" company="VRMADA">
// Copyright (c) VRMADA, All rights reserved.
// </copyright>
// --------------------------------------------------------------------------------------------------------------------
namespace UltimateXR.Locomotion
{
/// <summary>
/// Enumerates the different ways a destination validator may run. It's used by methods such as
/// <see cref="UxrTeleportLocomotionBase.AddDestinationValidator" />.
/// </summary>
public enum UxrDestinationValidatorMode
{
/// <summary>
/// The destination validator will be executed every frame. For teleportation, for example, this means that the arc
/// will show the valid/invalid state.<br/>
/// This mode can be used when a more complex validation is required each frame.
/// </summary>
EveryFrame = 1,
/// <summary>
/// The destination validator will be executed only when the user confirms the "move to destination". For
/// teleportation this means that even if the arc shows a valid state, when the user inputs the move action, the
/// destination validator may cancel if the validation returned false.<br/>
/// This mode can be used during tutorials to notify the user selected a wrong teleportation destination.
/// </summary>
OnConfirmationOnly = 2
}
}

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 1da9e9cc92e04b9d8764094f87e708d9
timeCreated: 1695633835

View File

@@ -0,0 +1,17 @@
// --------------------------------------------------------------------------------------------------------------------
// <copyright file="UxrIgnoreTeleportDestination.cs" company="VRMADA">
// Copyright (c) VRMADA, All rights reserved.
// </copyright>
// --------------------------------------------------------------------------------------------------------------------
using UltimateXR.Core.Components;
namespace UltimateXR.Locomotion
{
/// <summary>
/// Tells teleporting components to ignore destinations that hang from the tree where the component is added.
/// </summary>
public class UxrIgnoreTeleportDestination : UxrComponent
{
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: e8ccd95984a6d5f4a9f5c351b85fc29b
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,167 @@
// --------------------------------------------------------------------------------------------------------------------
// <copyright file="UxrLocomotion.cs" company="VRMADA">
// Copyright (c) VRMADA, All rights reserved.
// </copyright>
// --------------------------------------------------------------------------------------------------------------------
using System;
using System.Linq;
using UltimateXR.Avatar;
using UltimateXR.Core;
using UltimateXR.Core.Components.Composite;
using UltimateXR.Manipulation;
using UnityEngine;
namespace UltimateXR.Locomotion
{
/// <summary>
/// Base class for locomotion components. Locomotion components enable different ways for an <see cref="UxrAvatar" />
/// to move around the scenario.
/// </summary>
public abstract class UxrLocomotion : UxrAvatarComponent<UxrLocomotion>, IUxrLocomotionUpdater
{
#region Public Types & Data
/// <summary>
/// <para>
/// Gets whether the locomotion updates the avatar each frame. An example of smooth locomotion is
/// <see cref="UxrSmoothLocomotion" /> where the user moves the avatar in an identical way to a FPS video-game.
/// An example of non-smooth locomotion is <see cref="UxrTeleportLocomotion" /> where the avatar is moved only on
/// specific occasions.
/// </para>
/// <para>
/// The smooth locomotion concept should not be confused with the ability to move the head around each frame.
/// Smooth locomotion refers to the avatar position, which is determined by the avatar's root GameObject.
/// It should also not be confused with the ability to perform teleportation in a smooth way. Even if some
/// teleportation locomotion methods can teleport using smooth transitions, it should not be considered as smooth
/// locomotion.
/// </para>
/// <para>
/// The smooth locomotion property can be used to determine whether certain operations, such as LOD switching,
/// should be processed each frame or only when the avatar position changed.
/// </para>
/// </summary>
public abstract bool IsSmoothLocomotion { get; }
#endregion
#region Explicit IUxrLocomotionUpdater
/// <inheritdoc />
void IUxrLocomotionUpdater.UpdateLocomotion()
{
UpdateLocomotion();
}
#endregion
#region Unity
/// <summary>
/// Logs if there is a missing <see cref="Avatar" /> component upwards in the hierarchy.
/// </summary>
protected override void Awake()
{
base.Awake();
if (Avatar == null)
{
UxrManager.LogMissingAvatarInHierarchyError(this);
}
}
#endregion
#region Protected Methods
/// <summary>
/// Updates the locomotion and the avatar's position/orientation the component belongs to.
/// </summary>
protected abstract void UpdateLocomotion();
/// <summary>
/// Checks whether a raycast has anything that is blocking. It filters out invalid raycasts such as against anything
/// part of the avatar or a grabbed object.
/// </summary>
/// <param name="avatar">The avatar to compute the raycast for</param>
/// <param name="origin">Ray origin</param>
/// <param name="direction">Ray direction</param>
/// <param name="maxDistance">Raycast maximum distance</param>
/// <param name="layerMaskRaycast">Raycast layer mask</param>
/// <param name="queryTriggerInteraction">Behaviour against trigger colliders</param>
/// <param name="outputHit">Result blocking raycast</param>
/// <returns>Whether there is a blocking raycast returned in <paramref name="outputHit" /></returns>
protected bool HasBlockingRaycastHit(UxrAvatar avatar, Vector3 origin, Vector3 direction, float maxDistance, int layerMaskRaycast, QueryTriggerInteraction queryTriggerInteraction, out RaycastHit outputHit)
{
RaycastHit[] hits = Physics.RaycastAll(origin, direction.normalized, maxDistance, layerMaskRaycast, queryTriggerInteraction);
return HasBlockingRaycastHit(avatar, hits, out outputHit);
}
/// <summary>
/// Checks whether a capsule cast has anything that is blocking. It filters out invalid positives such as against
/// anything part of the avatar or a grabbed object.
/// </summary>
/// <param name="avatar">The avatar to compute the capsule cast for</param>
/// <param name="point1">The center of the sphere at the start of the capsule</param>
/// <param name="point2">The center of the sphere at the end of the capsule</param>
/// <param name="radius">The radius of the capsule</param>
/// <param name="direction">The direction into which to sweep the capsule</param>
/// <param name="maxDistance">The max length of the sweep</param>
/// <param name="layerMask">A that is used to selectively ignore colliders when casting a capsule</param>
/// <param name="queryTriggerInteraction">Specifies whether this query should hit Triggers</param>
/// <param name="outputHit">Result blocking raycast</param>
/// <returns>Whether there is a blocking raycast returned in <paramref name="outputHit" /></returns>
protected bool HasBlockingCapsuleCastHit(UxrAvatar avatar, Vector3 point1, Vector3 point2, float radius, Vector3 direction, float maxDistance, int layerMask, QueryTriggerInteraction queryTriggerInteraction, out RaycastHit outputHit)
{
RaycastHit[] hits = Physics.CapsuleCastAll(point1, point2, radius, direction, maxDistance, layerMask, queryTriggerInteraction);
return HasBlockingRaycastHit(avatar, hits, out outputHit);
}
#endregion
#region Private Methods
/// <summary>
/// Checks whether the given raycast hits have any that are blocking.
/// This method filters out invalid raycasts such as against anything part the avatar or a grabbed object.
/// </summary>
/// <param name="avatar">The avatar the ray-casting was computed for</param>
/// <param name="inputHits">Set of raycast hits to check</param>
/// <param name="outputHit">Result blocking raycast</param>
/// <returns>Whether there is a blocking raycast returned in <paramref name="outputHit" /></returns>
private bool HasBlockingRaycastHit(UxrAvatar avatar, RaycastHit[] inputHits, out RaycastHit outputHit)
{
bool hasBlockingHit = false;
outputHit = default;
if (inputHits.Count() > 1)
{
Array.Sort(inputHits, (a, b) => a.distance.CompareTo(b.distance));
}
foreach (RaycastHit singleHit in inputHits)
{
if (singleHit.collider.GetComponentInParent<UxrAvatar>() == avatar)
{
// Filter out colliding against part of the avatar
continue;
}
UxrGrabbableObject grabbableObject = singleHit.collider.GetComponentInParent<UxrGrabbableObject>();
if (grabbableObject != null && UxrGrabManager.Instance.IsBeingGrabbedBy(grabbableObject, avatar))
{
// Filter out colliding against a grabbed object
continue;
}
outputHit = singleHit;
hasBlockingHit = true;
break;
}
return hasBlockingHit;
}
#endregion
}
}

View File

@@ -0,0 +1,13 @@
fileFormatVersion: 2
guid: 782b2beb43ede634c9ac4404643f61e4
timeCreated: 1523621693
licenseType: Pro
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,42 @@
// --------------------------------------------------------------------------------------------------------------------
// <copyright file="UxrParentAvatarDestination.cs" company="VRMADA">
// Copyright (c) VRMADA, All rights reserved.
// </copyright>
// --------------------------------------------------------------------------------------------------------------------
using UltimateXR.Core.Components;
using UnityEngine;
namespace UltimateXR.Locomotion
{
/// <summary>
/// Component that tells an avatar should be re-parented to the GameObject whenever any locomotion takes the avatar to
/// the object or any of its children.
/// If a hierarchy contains more than a single <see cref="UxrParentAvatarDestination" />, the closest object or parent
/// upwards will be selected.
/// Some components, such as <see cref="UxrTeleportLocomotion" />, have a setting that controls the default behaviour (
/// <see cref="UxrTeleportLocomotionBase.ParentToDestination" />). The <see cref="UxrParentAvatarDestination" /> can in
/// this case be used to override the default behaviour.
/// </summary>
public class UxrParentAvatarDestination : UxrComponent
{
#region Inspector Properties/Serialized Fields
[SerializeField] private bool _parentAvatar;
#endregion
#region Public Types & Data
/// <summary>
/// Whether the avatar should be re-parented to the object containing the component whenever a locomotion takes the
/// avatar to the object or any of its children.
/// </summary>
public bool ParentAvatar
{
get => _parentAvatar;
set => _parentAvatar = value;
}
#endregion
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: ff0d03b59ffa05f41b5da59946ee5830
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,19 @@
// --------------------------------------------------------------------------------------------------------------------
// <copyright file="UxrRaycastStepsQuality.cs" company="VRMADA">
// Copyright (c) VRMADA, All rights reserved.
// </copyright>
// --------------------------------------------------------------------------------------------------------------------
namespace UltimateXR.Locomotion
{
/// <summary>
/// The number of raycasts to perform over a teleport arc to check where it intersects with the scene.
/// Higher quality steps use more raycasts.
/// </summary>
public enum UxrRaycastStepsQuality
{
LowQuality,
MediumQuality,
HighQuality,
VeryHighQuality
}
}

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 5da228536c8747ea99775f487aeeeaa5
timeCreated: 1643727431

View File

@@ -0,0 +1,28 @@
// --------------------------------------------------------------------------------------------------------------------
// <copyright file="UxrReorientationType.cs" company="VRMADA">
// Copyright (c) VRMADA, All rights reserved.
// </copyright>
// --------------------------------------------------------------------------------------------------------------------
namespace UltimateXR.Locomotion
{
/// <summary>
/// How the avatar can be reoriented when executing a teleportation.
/// </summary>
public enum UxrReorientationType
{
/// <summary>
/// Avatar will teleport and keep the same orientation.
/// </summary>
KeepOrientation,
/// <summary>
/// Avatar new orientation will be the (source, destination) vector.
/// </summary>
UseTeleportFromToDirection,
/// <summary>
/// User can control the new orientation using the joystick.
/// </summary>
AllowUserJoystickRedirect
}
}

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: e4d51bbee6ae4973bf8fc47b2952e0ae
timeCreated: 1643727457

View File

@@ -0,0 +1,33 @@
// --------------------------------------------------------------------------------------------------------------------
// <copyright file="UxrRotationType.cs" company="VRMADA">
// Copyright (c) VRMADA, All rights reserved.
// </copyright>
// --------------------------------------------------------------------------------------------------------------------
namespace UltimateXR.Locomotion
{
/// <summary>
/// Enumerates the supported types of rotation around the avatar's axis.
/// </summary>
public enum UxrRotationType
{
/// <summary>
/// Rotating around is not allowed.
/// </summary>
NotAllowed,
/// <summary>
/// Avatar will rotate immediately.
/// </summary>
Immediate,
/// <summary>
/// Fade-out followed by the rotation and fade-in.
/// </summary>
Fade,
/// <summary>
/// Interpolated rotation.
/// </summary>
Smooth
}
}

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 82c3bae5341f4345bf67aa6aff8d7c81
timeCreated: 1643727446

View File

@@ -0,0 +1,323 @@
// --------------------------------------------------------------------------------------------------------------------
// <copyright file="UxrSmoothLocomotion.cs" company="VRMADA">
// Copyright (c) VRMADA, All rights reserved.
// </copyright>
// --------------------------------------------------------------------------------------------------------------------
using UltimateXR.CameraUtils;
using UltimateXR.Core;
using UltimateXR.Devices;
using UnityEngine;
namespace UltimateXR.Locomotion
{
/// <summary>
/// Type of locomotion where the user moves across the scenario in a similar way to FPS video-games.
/// </summary>
public class UxrSmoothLocomotion : UxrLocomotion
{
#region Inspector Properties/Serialized Fields
[Header("General parameters")] [SerializeField] private bool _parentToDestination;
[SerializeField] private float _metersPerSecondNormal = 2.0f;
[SerializeField] private float _metersPerSecondSprint = 4.0f;
[SerializeField] private UxrWalkDirection _walkDirection = UxrWalkDirection.ControllerForward;
[SerializeField] private float _rotationDegreesPerSecondNormal = 120.0f;
[SerializeField] private float _rotationDegreesPerSecondSprint = 120.0f;
[SerializeField] private float _gravity = -9.81f;
[Header("Input parameters")] [SerializeField] private UxrHandSide _sprintButtonHand = UxrHandSide.Left;
[SerializeField] private UxrInputButtons _sprintButton = UxrInputButtons.Joystick;
[Header("Constraints")] [SerializeField] private QueryTriggerInteraction _triggerCollidersInteraction = QueryTriggerInteraction.Ignore;
[SerializeField] private LayerMask _collisionLayerMask = ~0;
[SerializeField] private float _capsuleRadius = 0.25f;
[SerializeField] private float _maxStepHeight = 0.2f;
[SerializeField] [Range(0.0f, 80.0f)] private float _maxSlopeDegrees = 35.0f;
[SerializeField] private float _stepDistanceCheck = 0.2f;
#endregion
#region Public Types & Data
/// <summary>
/// Meters per second the user will move when walking normally and the joystick is at peak amplitude.
/// </summary>
public float MetersPerSecondNormal
{
get => _metersPerSecondNormal;
set => _metersPerSecondNormal = value;
}
/// <summary>
/// Meters per second the user will move when sprinting and the joystick is at peak amplitude.
/// </summary>
public float MetersPerSecondSprint
{
get => _metersPerSecondSprint;
set => _metersPerSecondSprint = value;
}
/// <summary>
/// Degrees per second the user will rotate when walking normally and the joystick is at peak amplitude.
/// </summary>
public float RotationDegreesPerSecondNormal
{
get => _rotationDegreesPerSecondNormal;
set => _rotationDegreesPerSecondNormal = value;
}
/// <summary>
/// Degrees per second the user will rotate when sprinting and the joystick is at peak amplitude.
/// </summary>
public float RotationDegreesPerSecondSprint
{
get => _rotationDegreesPerSecondSprint;
set => _rotationDegreesPerSecondSprint = value;
}
/// <summary>
/// Gravity when falling.
/// </summary>
public float Gravity
{
get => _gravity;
set => _gravity = value;
}
#endregion
#region Public Overrides UxrLocomotion
/// <inheritdoc />
public override bool IsSmoothLocomotion => true;
#endregion
#region Unity
/// <summary>
/// Tries to place the user on the ground.
/// </summary>
protected override void OnEnable()
{
base.OnEnable();
TryGround();
}
/// <summary>
/// Checks if the user needs to be placed on the ground.
/// </summary>
private void Update()
{
if (_initialized == false)
{
TryGround();
_initialized = true;
}
}
#endregion
#region Protected Overrides UxrLocomotion
/// <summary>
/// Gathers input and updates the physics parameters.
/// </summary>
protected override void UpdateLocomotion()
{
if (Avatar)
{
// Get input
Vector2 joystickLeft = Vector2.zero;
Vector2 joystickRight = Vector2.zero;
if (Avatar.ControllerInput.SetupType == UxrControllerSetupType.Dual)
{
// Two controllers with joystick
joystickLeft = Avatar.ControllerInput.GetInput2D(UxrHandSide.Left, UxrInput2D.Joystick);
joystickRight = Avatar.ControllerInput.GetInput2D(UxrHandSide.Right, UxrInput2D.Joystick);
}
else if (Avatar.ControllerInput.SetupType == UxrControllerSetupType.Single)
{
// Single controller with 2 joysticks (gamepad?)
joystickLeft = Avatar.ControllerInput.GetInput2D(UxrHandSide.Left, UxrInput2D.Joystick);
joystickRight = Avatar.ControllerInput.GetInput2D(UxrHandSide.Left, UxrInput2D.Joystick2);
}
Vector3 offset = Vector3.zero;
if (_walkDirection == UxrWalkDirection.ControllerForward)
{
Transform forwardTransform = Avatar.GetControllerInputForward(UxrHandSide.Left);
if (forwardTransform != null)
{
offset = Vector3.ProjectOnPlane(forwardTransform.forward, Vector3.up).normalized * joystickLeft.y +
Vector3.ProjectOnPlane(forwardTransform.right, Vector3.up).normalized * joystickLeft.x;
}
}
else if (_walkDirection == UxrWalkDirection.AvatarForward)
{
offset = Avatar.transform.forward * joystickLeft.y + Avatar.transform.right * joystickLeft.x;
}
else if (_walkDirection == UxrWalkDirection.LookDirection)
{
offset = Vector3.ProjectOnPlane(Avatar.CameraComponent.transform.forward, Vector3.up).normalized * joystickLeft.y +
Vector3.ProjectOnPlane(Avatar.CameraComponent.transform.right, Vector3.up).normalized * joystickLeft.x;
}
if (offset.magnitude > 1.0f)
{
offset.Normalize();
}
// Compute translation speed for UpdateLocomotionPhysics()
bool isSprinting = Avatar.ControllerInput.GetButtonsPress(_sprintButtonHand, _sprintButton);
float speed = isSprinting ? _metersPerSecondSprint : _metersPerSecondNormal;
_translationSpeed = offset * speed;
// Rotation. We perform it here since it doesn't require any collision checks.
if (!Mathf.Approximately(joystickRight.x, 0.0f))
{
float rotationSpeed = isSprinting ? _rotationDegreesPerSecondSprint : _rotationDegreesPerSecondNormal;
UxrManager.Instance.RotateAvatar(Avatar, joystickRight.x * rotationSpeed * Time.deltaTime);
}
UpdateLocomotionPhysics(Time.deltaTime);
}
}
#endregion
#region Private Methods
/// <summary>
/// Updates the locomotion physics.
/// </summary>
/// <param name="deltaTime">The delta time in seconds</param>
private void UpdateLocomotionPhysics(float deltaTime)
{
Vector3 avatarPos = Avatar.transform.position;
Vector3 cameraPos = Avatar.CameraPosition;
// Translation based on input
if (_translationSpeed.magnitude > 0.0f && !UxrCameraWallFade.IsAvatarPeekingThroughGeometry(Avatar))
{
float cameraHeight = cameraPos.y - avatarPos.y;
Vector3 capsuleTop = cameraPos;
Vector3 capsuleBottom = Avatar.CameraFloorPosition + Vector3.up * (_maxStepHeight * 3.0f + SafeFloorDistance);
Vector3 newRequestedCameraPos = cameraPos + _translationSpeed * deltaTime;
if (!HasBlockingCapsuleCastHit(Avatar, capsuleTop, capsuleBottom, _capsuleRadius, _translationSpeed.normalized, (newRequestedCameraPos - capsuleTop).magnitude, _collisionLayerMask, _triggerCollidersInteraction, out RaycastHit _))
{
// Nothing in front. Now check for slope and maximum step height
if (HasBlockingRaycastHit(Avatar, cameraPos + _translationSpeed.normalized * _stepDistanceCheck, -Vector3.up, cameraHeight + _maxStepHeight, _collisionLayerMask, _triggerCollidersInteraction, out RaycastHit hitInfo))
{
float heightIncrement = hitInfo.point.y - avatarPos.y;
float slopeDegrees = Mathf.Atan(heightIncrement / _stepDistanceCheck) * Mathf.Rad2Deg;
if (heightIncrement <= _maxStepHeight && slopeDegrees < _maxSlopeDegrees)
{
Vector3 cameraFloor = Avatar.CameraFloorPosition;
Vector3 translation = Vector3.Lerp(cameraFloor, hitInfo.point, _translationSpeed.magnitude * deltaTime / _stepDistanceCheck) - cameraFloor;
UxrManager.Instance.TranslateAvatar(Avatar, translation);
}
CheckSetAvatarParent(hitInfo);
}
else
{
// No collisions found, just keep walking. Probably to a fall.
UxrManager.Instance.TranslateAvatar(Avatar, _translationSpeed * deltaTime);
}
}
}
// Check if needs to fall
if (_isFalling)
{
// Falling
if (HasBlockingRaycastHit(Avatar, avatarPos + Vector3.up * SafeFloorDistance, -Vector3.up, Mathf.Abs(_fallSpeed * deltaTime) + SafeFloorDistance * 2.0f, _collisionLayerMask, _triggerCollidersInteraction, out RaycastHit hitInfo))
{
// Hit ground
_isFalling = false;
_fallSpeed = 0.0f;
UxrManager.Instance.MoveAvatarTo(Avatar, hitInfo.point.y);
CheckSetAvatarParent(hitInfo);
}
else
{
// Keep falling
_fallSpeed += deltaTime * _gravity;
UxrManager.Instance.MoveAvatarTo(Avatar, Avatar.transform.position.y + _fallSpeed * deltaTime);
}
}
else if (!_isFalling && !HasBlockingRaycastHit(Avatar, cameraPos, -Vector3.up, cameraPos.y - avatarPos.y + SafeFloorDistance, _collisionLayerMask, _triggerCollidersInteraction, out RaycastHit _))
{
// Start falling
_isFalling = true;
_fallSpeed = 0.0f;
}
else
{
_isFalling = false;
_fallSpeed = 0.0f;
}
}
/// <summary>
/// Checks whether to parent the avatar to a new transform.
/// </summary>
/// <param name="hitInfo">Raycast hit information with the potential parent collider</param>
private void CheckSetAvatarParent(RaycastHit hitInfo)
{
if (_parentToDestination && hitInfo.collider.transform != null && Avatar.transform.parent != hitInfo.collider.transform)
{
Avatar.transform.SetParent(hitInfo.collider.transform);
}
}
/// <summary>
/// Tries to place the user on the ground.
/// </summary>
private void TryGround()
{
if (Avatar)
{
_translationSpeed = Vector3.zero;
_fallSpeed = 0.0f;
if (HasBlockingRaycastHit(Avatar, Avatar.transform.position + Vector3.up, -Vector3.up, 2.0f, _collisionLayerMask, _triggerCollidersInteraction, out RaycastHit hitInfo))
{
UxrManager.Instance.MoveAvatarTo(Avatar, hitInfo.point);
CheckSetAvatarParent(hitInfo);
}
}
}
#endregion
#region Private Types & Data
private const float SafeFloorDistance = 0.01f;
private bool _initialized;
private Vector3 _translationSpeed;
private bool _isFalling;
private float _fallSpeed;
#endregion
}
}

View File

@@ -0,0 +1,13 @@
fileFormatVersion: 2
guid: c2b87341763ed1447b56652023d58f90
timeCreated: 1523621693
licenseType: Pro
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,69 @@
// --------------------------------------------------------------------------------------------------------------------
// <copyright file="UxrTeleportDestination.cs" company="VRMADA">
// Copyright (c) VRMADA, All rights reserved.
// </copyright>
// --------------------------------------------------------------------------------------------------------------------
using UnityEngine;
namespace UltimateXR.Locomotion
{
/// <summary>
/// Describes a teleportation destination.
/// </summary>
public class UxrTeleportDestination
{
#region Public Types & Data
/// <summary>
/// Gets the <see cref="Transform" /> where the avatar will be positioned on.
/// </summary>
public Transform Destination { get; }
/// <summary>
/// Gets the raycast hit information that was used to select the destination.
/// </summary>
public RaycastHit HitInfo { get; }
/// <summary>
/// Gets the new avatar world position.
/// </summary>
public Vector3 Position { get; }
/// <summary>
/// Gets the new avatar world rotation.
/// </summary>
public Quaternion Rotation { get; }
/// <summary>
/// Gets the new avatar position in local <see cref="Destination" /> space.
/// </summary>
public Vector3 LocalDestinationPosition { get; }
/// <summary>
/// Gets the new avatar rotation in local <see cref="Destination" /> space.
/// </summary>
public Quaternion LocalDestinationRotation { get; }
#endregion
#region Constructors & Finalizer
/// <summary>
/// Constructor.
/// </summary>
/// <param name="hitInfo">The raycast hit information that was used to select the destination</param>
/// <param name="position">New avatar world position</param>
/// <param name="rotation">New avatar world rotation</param>
public UxrTeleportDestination(RaycastHit hitInfo, Vector3 position, Quaternion rotation)
{
Destination = hitInfo.collider != null ? hitInfo.collider.transform : null;
HitInfo = hitInfo;
Position = position;
Rotation = rotation;
LocalDestinationPosition = Destination != null ? Destination.InverseTransformPoint(position) : Vector3.zero;
LocalDestinationRotation = Destination != null ? Quaternion.Inverse(Destination.rotation) * rotation : Quaternion.identity;
}
#endregion
}
}

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 87e84a55cc414f928dc162dc2c60d07c
timeCreated: 1695631362

View File

@@ -0,0 +1,41 @@
// --------------------------------------------------------------------------------------------------------------------
// <copyright file="UxrTeleportLocomotion.StateSave.cs" company="VRMADA">
// Copyright (c) VRMADA, All rights reserved.
// </copyright>
// --------------------------------------------------------------------------------------------------------------------
using UltimateXR.Core.StateSave;
namespace UltimateXR.Locomotion
{
public partial class UxrTeleportLocomotion
{
#region Protected Overrides UxrComponent
/// <inheritdoc />
protected override void SerializeState(bool isReading, int stateSerializationVersion, UxrStateSaveLevel level, UxrStateSaveOptions options)
{
base.SerializeState(isReading, stateSerializationVersion, level, options);
// Teleportation logic is already handled through events, we don't serialize parameters in incremental changes
if (level > UxrStateSaveLevel.ChangesSincePreviousSave)
{
SerializeStateValue(level, options, nameof(_previousFrameHadArc), ref _previousFrameHadArc);
SerializeStateValue(level, options, nameof(_arcCancelled), ref _arcCancelled);
SerializeStateValue(level, options, nameof(_arcCancelledByAngle), ref _arcCancelledByAngle);
SerializeStateValue(level, options, nameof(_lastSyncIsArcEnabled), ref _lastSyncIsArcEnabled);
SerializeStateValue(level, options, nameof(_lastSyncIsTargetEnabled), ref _lastSyncIsTargetEnabled);
SerializeStateValue(level, options, nameof(_lastSyncIsValidTeleport), ref _lastSyncIsValidTeleport);
SerializeStateValue(level, options, nameof(_lastSyncTargetArrowLocalRot), ref _lastSyncTargetArrowLocalRot);
if (isReading)
{
EnableArc(_lastSyncIsArcEnabled, _lastSyncIsValidTeleport);
}
}
}
#endregion
}
}

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: a870c5e65a014f5eb05006c75bb5419c
timeCreated: 1706009543

View File

@@ -0,0 +1,542 @@
// --------------------------------------------------------------------------------------------------------------------
// <copyright file="UxrTeleportLocomotion.cs" company="VRMADA">
// Copyright (c) VRMADA, All rights reserved.
// </copyright>
// --------------------------------------------------------------------------------------------------------------------
using UltimateXR.Devices;
using UnityEngine;
#pragma warning disable 0414
namespace UltimateXR.Locomotion
{
/// <summary>
/// Standard locomotion using an arc projected from the controllers.
/// </summary>
public partial class UxrTeleportLocomotion : UxrTeleportLocomotionBase
{
#region Inspector Properties/Serialized Fields
// Arc
[SerializeField] [Range(2, 1000)] private int _arcSegments = 100;
[SerializeField] [Range(0.01f, 0.4f)] private float _arcWidth = 0.1f;
[SerializeField] private float _arcScrollSpeedValid = 1.0f;
[SerializeField] private float _arcScrollSpeedInvalid = 0.5f;
[SerializeField] private Material _arcMaterialValid;
[SerializeField] private Material _arcMaterialInvalid;
[SerializeField] private float _arcFadeLength = 2.0f;
[SerializeField] private UxrRaycastStepsQuality _raycastStepsQuality = UxrRaycastStepsQuality.HighQuality;
#endregion
#region Public Types & Data
/// <summary>
/// Gets or sets whether the arc can be used.
/// </summary>
public bool IsArcAllowed { get; set; } = true;
#endregion
#region Public Overrides UxrLocomotion
/// <inheritdoc />
public override bool IsSmoothLocomotion => false;
#endregion
#region Unity
/// <summary>
/// Initializes the component.
/// </summary>
protected override void Awake()
{
base.Awake();
// Create arc GameObject
_arcGameObject = new GameObject("Arc");
_arcGameObject.transform.SetPositionAndRotation(transform.position, transform.rotation);
_arcGameObject.transform.parent = Avatar.transform;
_arcMeshFilter = _arcGameObject.AddComponent<MeshFilter>();
_arcRenderer = _arcGameObject.AddComponent<MeshRenderer>();
_arcMesh = new Mesh();
_vertices = new Vector3[(_arcSegments + 1) * 2];
_vertexColors = new Color32[(_arcSegments + 1) * 2];
_vertexMapping = new Vector2[(_arcSegments + 1) * 2];
_accumulatedArcLength = new float [(_arcSegments + 1) * 2];
_indices = new int[_arcSegments * 4];
for (int i = 0; i < _arcSegments; i++)
{
int baseIndex = (_arcSegments - 1 - i) * 2;
_indices[i * 4 + 0] = baseIndex;
_indices[i * 4 + 1] = baseIndex + 2;
_indices[i * 4 + 2] = baseIndex + 3;
_indices[i * 4 + 3] = baseIndex + 1;
}
_arcMesh.vertices = _vertices;
_arcMesh.colors32 = _vertexColors;
_arcMesh.uv = _vertexMapping;
_arcMesh.SetIndices(_indices, MeshTopology.Quads, 0);
_arcMeshFilter.mesh = _arcMesh;
_arcRenderer.sharedMaterial = _arcMaterialValid;
}
/// <summary>
/// Resets the component when enabled.
/// </summary>
protected override void OnEnable()
{
base.OnEnable();
// Set initial state
_previousFrameHadArc = false;
_arcCancelled = false;
_arcCancelledByAngle = false;
_scroll = 0.0f;
_lastSyncIsArcEnabled = false;
_lastSyncIsTargetEnabled = false;
_lastSyncIsValidTeleport = false;
_lastSyncTargetArrowLocalRot = Quaternion.identity;
EnableArc(false, false);
}
/// <summary>
/// Disables the teleport graphics.
/// </summary>
protected override void OnDisable()
{
base.OnDisable();
UpdateTeleportState(false, false, false, Quaternion.identity);
}
#endregion
#region Protected Overrides UxrTeleportLocomotionBase
/// <inheritdoc />
protected override bool CanBackStep => !IsArcVisible;
/// <inheritdoc />
protected override bool CanRotate => !IsArcVisible;
/// <inheritdoc />
protected override void UpdateTeleportLocomotion()
{
Vector2 joystickValue = Avatar.ControllerInput.GetInput2D(HandSide, UxrInput2D.Joystick);
if (joystickValue == Vector2.zero)
{
_arcCancelled = false;
_arcCancelledByAngle = false;
}
else
{
if (_arcCancelled)
{
joystickValue = Vector2.zero;
}
}
bool teleportArcActive = false;
// Check if the arc is active.
if (IsArcVisible || _arcCancelledByAngle)
{
// To support both touchpads and joysticks, we need to check in the case of touchpads that it is also pressed.
if (Avatar.ControllerInput.MainJoystickIsTouchpad)
{
if (Avatar.ControllerInput.GetButtonsPress(HandSide, UxrInputButtons.Joystick))
{
teleportArcActive = true;
_arcCancelledByAngle = false;
}
}
else
{
if (joystickValue != Vector2.zero)
{
teleportArcActive = true;
_arcCancelledByAngle = false;
}
}
}
else if (Avatar.ControllerInput.GetButtonsPressDown(HandSide, UxrInputButtons.JoystickUp))
{
teleportArcActive = true;
}
if (!IsArcAllowed)
{
teleportArcActive = false;
}
// If teleport is active update arc & target
bool isArcEnabled;
bool isTargetEnabled;
bool isValidTeleport;
if (teleportArcActive)
{
// Disable others if this one just activated
if (_previousFrameHadArc == false)
{
CancelOtherTeleportTargets();
}
// Compute trajectory
ComputeCurrentArcTrajectory(out isArcEnabled, out isTargetEnabled, out isValidTeleport);
}
else
{
if (_previousFrameHadArc && IsArcAllowed)
{
TryTeleportUsingCurrentTarget();
}
_previousFrameHadArc = false;
isArcEnabled = false;
isTargetEnabled = false;
isValidTeleport = false;
EnableArc(isArcEnabled, isValidTeleport);
NotifyNoDestinationRaycast();
}
// Notify state changes in a simpler way to avoid unnecessary traffic
if (_lastSyncIsArcEnabled != isArcEnabled ||
_lastSyncIsTargetEnabled != isTargetEnabled ||
_lastSyncIsValidTeleport != isValidTeleport ||
Quaternion.Angle(_lastSyncTargetArrowLocalRot, TeleportArrowLocalRotation) > ArrowAngleChangeThreshold)
{
UpdateTeleportState(isArcEnabled, isTargetEnabled, isValidTeleport, TeleportArrowLocalRotation);
}
}
/// <inheritdoc />
protected override void CancelTarget()
{
base.CancelTarget();
EnableArc(false, false);
_previousFrameHadArc = false;
_arcCancelled = true;
_arcCancelledByAngle = false;
}
#endregion
#region Private Methods
/// <summary>
/// Updates the state of elements in the teleport. This is mainly to synchronize the state on a networking environment.
/// It is performed in a separate method in order to have better control over the amount of traffic that is being
/// generated because of the arrow rotation.
/// </summary>
/// <param name="isArcEnabled">Is the arc enabled?</param>
/// <param name="isTargetEnabled">Is the target enabled?</param>
/// <param name="isValidTeleport">Is the teleport destination valid?</param>
/// <param name="teleportArrowLocalRotation">The teleport arrow's local rotation</param>
private void UpdateTeleportState(bool isArcEnabled, bool isTargetEnabled, bool isValidTeleport, Quaternion teleportArrowLocalRotation)
{
// This method will be synchronized through network
BeginSync();
_lastSyncIsArcEnabled = isArcEnabled;
_lastSyncIsTargetEnabled = isTargetEnabled;
_lastSyncIsValidTeleport = isValidTeleport;
_lastSyncTargetArrowLocalRot = teleportArrowLocalRotation;
if (isArcEnabled)
{
// TODO: The target enabled and valid teleport state are computed using the current
// hand transform, which might be slightly different in a network environment.
ComputeCurrentArcTrajectory(out bool _, out bool _, out bool _);
TeleportArrowLocalRotation = teleportArrowLocalRotation;
}
else
{
EnableArc(isArcEnabled, isValidTeleport);
NotifyNoDestinationRaycast();
}
EndSyncMethod(new object[] { isArcEnabled, isTargetEnabled, isValidTeleport, teleportArrowLocalRotation });
}
/// <summary>
/// Enables or disables the teleportation arc.
/// </summary>
/// <param name="enable">Whether the arc is visible</param>
/// <param name="isValidTeleport">Whether the current teleport destination is valid</param>
private void EnableArc(bool enable, bool isValidTeleport)
{
_arcGameObject.SetActive(enable);
_arcRenderer.sharedMaterial = isValidTeleport ? _arcMaterialValid : _arcMaterialInvalid;
}
/// <summary>
/// Computes the current teleport arc trajectory.
/// </summary>
private void ComputeCurrentArcTrajectory(out bool isArcEnabled, out bool isTargetEnabled, out bool isValidTeleport)
{
Vector3 right = Vector3.Cross(ControllerForward, Vector3.up).normalized;
float angle = GetCurrentParabolicAngle();
float timeToTravelHorizontally = GetTimeToTravelHorizontally(angle);
float parabolicSpeed = GetCurrentParabolicSpeed();
if (Mathf.Abs(angle) < AbsoluteMaxArcAngleThreshold && _arcWidth > 0.0f)
{
isTargetEnabled = false;
isValidTeleport = false;
float endTime = timeToTravelHorizontally * 2;
float deltaTime = endTime / BlockingRaycastStepsQualityToSteps(_raycastStepsQuality);
bool hitSomething = false;
for (float time = 0.0f; time < endTime; time += deltaTime)
{
Vector3 point1 = EvaluateArc(ControllerStart, ControllerForward, parabolicSpeed, time);
Vector3 point2 = EvaluateArc(ControllerStart, ControllerForward, parabolicSpeed, time + deltaTime);
float distanceBetweenPoints = Vector3.Distance(point1, point2);
Vector3 direction = (point2 - point1) / distanceBetweenPoints;
// Process blocking hit.
// Use RaycastAll to avoid putting "permitted" objects in between "blocking" objects to teleport through walls or any other cheats.
if (HasBlockingRaycastHit(point1, direction, distanceBetweenPoints, out RaycastHit hit))
{
endTime = time + deltaTime * (hit.distance / distanceBetweenPoints);
hitSomething = true;
isValidTeleport = NotifyDestinationRaycast(hit, false, out isTargetEnabled);
break;
}
}
if (hitSomething == false)
{
NotifyNoDestinationRaycast();
}
_previousFrameHadArc = true;
isArcEnabled = true;
GenerateArcMesh(isValidTeleport, right, parabolicSpeed, endTime);
EnableArc(isArcEnabled, isValidTeleport);
}
else
{
_arcCancelledByAngle = true;
isArcEnabled = false;
isTargetEnabled = false;
isValidTeleport = false;
EnableArc(isArcEnabled, isValidTeleport);
NotifyNoDestinationRaycast();
}
}
/// <summary>
/// Generates the arc mesh.
/// </summary>
/// <param name="isValidTeleport">Whether the current teleport destination is valid</param>
/// <param name="right">Arc world-space right vector</param>
/// <param name="parabolicSpeed">The start speed used for parabolic computation</param>
/// <param name="endTime">The time in the parabolic equation where the arc intersects with the first blocking element</param>
private void GenerateArcMesh(bool isValidTeleport, Vector3 right, float parabolicSpeed, float endTime)
{
Vector3 previousPoint = Vector3.zero;
float currentLength = 0.0f;
float totalLength = 0.0f;
_arcGameObject.transform.SetPositionAndRotation(ControllerStart, Quaternion.LookRotation(ControllerForward, UpVector));
_scroll += Time.deltaTime * (isValidTeleport ? _arcScrollSpeedValid : _arcScrollSpeedInvalid);
for (int i = 0; i <= _arcSegments; ++i)
{
float time = endTime * ((float)i / _arcSegments);
Vector3 point = EvaluateArc(ControllerStart, ControllerForward, parabolicSpeed, time);
_vertices[i * 2 + 0] = _arcGameObject.transform.InverseTransformPoint(point - _arcWidth * 0.5f * right);
_vertices[i * 2 + 1] = _arcGameObject.transform.InverseTransformPoint(point + _arcWidth * 0.5f * right);
float pointDistance = i == 0 ? 0.0f : Vector3.Distance(previousPoint, point);
if (i > 0)
{
currentLength += pointDistance;
}
_accumulatedArcLength[i] = currentLength;
totalLength = currentLength;
float v = currentLength / _arcWidth - _scroll;
_vertexMapping[i * 2 + 0] = new Vector2(0.0f, v);
_vertexMapping[i * 2 + 1] = new Vector2(1.0f, v);
previousPoint = point;
}
// After creating the vertices, assign the colors after because knowing the exact arc length we can fade it nicely at both ends
for (int i = 0; i <= _arcSegments; ++i)
{
byte alpha = 255;
if (_arcFadeLength > 0.0f)
{
float alphaFloat = 1.0f;
if (_accumulatedArcLength[i] < _arcFadeLength)
{
alphaFloat = _accumulatedArcLength[i] / _arcFadeLength;
alpha = (byte)(alphaFloat * 255);
}
if (totalLength - _accumulatedArcLength[i] < _arcFadeLength)
{
alphaFloat = alphaFloat * ((totalLength - _accumulatedArcLength[i]) / _arcFadeLength);
alpha = (byte)(alphaFloat * 255);
}
}
_vertexColors[i * 2 + 0] = new Color32(255, 255, 255, alpha);
_vertexColors[i * 2 + 1] = new Color32(255, 255, 255, alpha);
}
// Assign mesh
_arcMesh.vertices = _vertices;
_arcMesh.colors32 = _vertexColors;
_arcMesh.uv = _vertexMapping;
_arcMesh.SetIndices(_indices, MeshTopology.Quads, 0);
_arcMesh.bounds = new Bounds(Vector3.zero, Vector3.one * Avatar.CameraComponent.farClipPlane);
}
/// <summary>
/// Computes the arc parabola.
/// </summary>
/// <param name="origin">World-space arc start position</param>
/// <param name="forward">World-space arc start direction</param>
/// <param name="speed">Start speed</param>
/// <param name="time">Time value to get the position for</param>
/// <returns>Position in the arc corresponding to the given time value</returns>
private Vector3 EvaluateArc(Vector3 origin, Vector3 forward, float speed, float time)
{
return origin + speed * time * forward - 0.5f * 9.8f * time * time * UpVector;
}
/// <summary>
/// Maps quality to steps.
/// </summary>
/// <param name="stepsQuality">Quality</param>
/// <returns>Step count used for ray-casting</returns>
private int BlockingRaycastStepsQualityToSteps(UxrRaycastStepsQuality stepsQuality)
{
switch (stepsQuality)
{
case UxrRaycastStepsQuality.LowQuality: return BlockingRaycastStepsQualityLow;
case UxrRaycastStepsQuality.MediumQuality: return BlockingRaycastStepsQualityMedium;
case UxrRaycastStepsQuality.HighQuality: return BlockingRaycastStepsQualityHigh;
case UxrRaycastStepsQuality.VeryHighQuality: return BlockingRaycastStepsQualityVeryHigh;
default: return BlockingRaycastStepsQualityHigh;
}
}
/// <summary>
/// Gets the parabolic speed to compute in the arc equation.
/// </summary>
/// <returns>Parabolic speed</returns>
private float GetCurrentParabolicSpeed()
{
return Mathf.Sqrt(ArcGravity * MaxAllowedDistance);
}
/// <summary>
/// Gets the parabolic angle to compute in the arc equation.
/// </summary>
/// <returns>Parabolic angle</returns>
private float GetCurrentParabolicAngle()
{
Vector3 right = Vector3.Cross(ControllerForward, UpVector).normalized;
Vector3 projectedForward = Vector3.ProjectOnPlane(ControllerForward, UpVector).normalized;
return -Vector3.SignedAngle(ControllerForward, projectedForward, right);
}
/// <summary>
/// Gets the time in seconds it would take a parabolic trajectory to travel up and down again.
/// </summary>
/// <param name="angle">Parabolic angle</param>
/// <returns>Time in seconds to go up and get back down again</returns>
private float GetTimeToTravelHorizontally(float angle)
{
return Mathf.Max(2.0f, 2.0f * GetCurrentParabolicSpeed() * Mathf.Abs(Mathf.Sin(angle * Mathf.Deg2Rad)) / ArcGravity);
}
#endregion
#region Private Types & Data
/// <summary>
/// Gets whether the teleport arc is currently visible.
/// </summary>
private bool IsArcVisible => _arcGameObject.activeSelf;
/// <summary>
/// The change in degrees of the arrow direction to consider it a state change and raise the event. It is used to avoid
/// sending repeated data.
/// </summary>
private const float ArrowAngleChangeThreshold = 2.0f;
private const int BlockingRaycastStepsQualityLow = 10;
private const int BlockingRaycastStepsQualityMedium = 20;
private const int BlockingRaycastStepsQualityHigh = 40;
private const int BlockingRaycastStepsQualityVeryHigh = 80;
private const float AbsoluteMaxArcAngleThreshold = 75.0f;
private const float ArcGravity = 9.8f;
private bool _previousFrameHadArc;
private bool _arcCancelled;
private bool _arcCancelledByAngle;
private GameObject _arcGameObject;
private MeshFilter _arcMeshFilter;
private MeshRenderer _arcRenderer;
private Mesh _arcMesh;
private Vector3[] _vertices;
private Color32[] _vertexColors;
private Vector2[] _vertexMapping;
private int[] _indices;
private float[] _accumulatedArcLength;
private float _scroll;
private bool _lastSyncIsArcEnabled;
private bool _lastSyncIsTargetEnabled;
private bool _lastSyncIsValidTeleport;
private Quaternion _lastSyncTargetArrowLocalRot;
#endregion
}
}
#pragma warning restore 0414

View File

@@ -0,0 +1,13 @@
fileFormatVersion: 2
guid: f49112646ffbe3943ad1a450d98ee87e
timeCreated: 1513239658
licenseType: Pro
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,39 @@
// --------------------------------------------------------------------------------------------------------------------
// <copyright file="UxrTeleportLocomotionBase.StateSave.cs" company="VRMADA">
// Copyright (c) VRMADA, All rights reserved.
// </copyright>
// --------------------------------------------------------------------------------------------------------------------
using UltimateXR.Core.StateSave;
namespace UltimateXR.Locomotion
{
public abstract partial class UxrTeleportLocomotionBase
{
#region Protected Overrides UxrComponent
/// <inheritdoc />
protected override void SerializeState(bool isReading, int stateSerializationVersion, UxrStateSaveLevel level, UxrStateSaveOptions options)
{
base.SerializeState(isReading, stateSerializationVersion, level, options);
// Locomotion is are already handled through events, we don't serialize parameters in incremental changes
if (level > UxrStateSaveLevel.ChangesSincePreviousSave)
{
int layerMaskValue = _layerMaskRaycast.value;
SerializeStateValue(level, options, nameof(layerMaskValue), ref layerMaskValue);
SerializeStateValue(level, options, nameof(_teleportTargetEnabled), ref _teleportTargetEnabled);
SerializeStateValue(level, options, nameof(_teleportTargetValid), ref _teleportTargetValid);
if (isReading)
{
_layerMaskRaycast.value = layerMaskValue;
EnableTeleportObjects(_teleportTargetEnabled, _teleportTargetValid);
}
}
}
#endregion
}
}

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 91a3a031734d405d86615844b707c980
timeCreated: 1706010639

View File

@@ -0,0 +1,51 @@
// --------------------------------------------------------------------------------------------------------------------
// <copyright file="UxrTeleportLocomotionBase.Validator.cs" company="VRMADA">
// Copyright (c) VRMADA, All rights reserved.
// </copyright>
// --------------------------------------------------------------------------------------------------------------------
using System;
namespace UltimateXR.Locomotion
{
public abstract partial class UxrTeleportLocomotionBase
{
#region Private Types & Data
/// <summary>
/// Stores a destination validator function and its mode.
/// </summary>
private class Validator
{
#region Public Types & Data
/// <summary>
/// Gets the validator function.
/// </summary>
public Func<UxrTeleportDestination, bool> ValidatorFunc { get; }
/// <summary>
/// Gets the validator execution mode.
/// </summary>
public UxrDestinationValidatorMode Mode { get; }
#endregion
#region Constructors & Finalizer
/// <summary>
/// Constructor.
/// </summary>
/// <param name="validatorFunc">The validator function</param>
/// <param name="mode">The validator execution mode</param>
public Validator(Func<UxrTeleportDestination, bool> validatorFunc, UxrDestinationValidatorMode mode)
{
ValidatorFunc = validatorFunc;
Mode = mode;
}
#endregion
}
#endregion
}
}

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 18e924ca2f2b44989d1deeb8221c4b45
timeCreated: 1695634309

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,13 @@
fileFormatVersion: 2
guid: baf72485c9c1e4b4faba8b6ad76157c4
timeCreated: 1513239658
licenseType: Pro
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,209 @@
// --------------------------------------------------------------------------------------------------------------------
// <copyright file="UxrTeleportSpawnCollider.cs" company="VRMADA">
// Copyright (c) VRMADA, All rights reserved.
// </copyright>
// --------------------------------------------------------------------------------------------------------------------
using System;
using UltimateXR.Avatar;
using UltimateXR.Core.Components;
using UnityEngine;
namespace UltimateXR.Locomotion
{
/// <summary>
/// Component that, added to an object with colliders, allows to define volumes that force a fixed teleportation
/// destination when they are hit with teleporting pointers (arc, ray, etc.).
/// </summary>
public class UxrTeleportSpawnCollider : UxrComponent<UxrTeleportSpawnCollider>
{
#region Inspector Properties/Serialized Fields
[SerializeField] private GameObject _enableWhenSelected;
[SerializeField] private Transform _spawnPosOneSide;
[SerializeField] private Transform _spawnPosOptionalOtherSide;
[SerializeField] private Transform _altTargetPosOneSide;
[SerializeField] private Transform _altTargetPosOtherSide;
[SerializeField] private float _heightDistanceFactor = 1.0f;
#endregion
#region Public Types & Data
/// <summary>
/// Event raised when the user was teleported by using the spawn collider.
/// </summary>
public event EventHandler<UxrTeleportSpawnUsedEventArgs> Teleported;
/// <summary>
/// Gets or sets the <see cref="GameObject" /> that will be enabled while the component is being pointed at. This can
/// be used to enable graphics that help identifying the interactivity and destination.
/// </summary>
public GameObject EnableWhenSelected
{
get => _enableWhenSelected;
set => _enableWhenSelected = value;
}
/// <summary>
/// Gets or sets the spawn point the avatar will be teleported to when using the component.
/// In two-sided spawn setups -a ladder that will allow to climb up or down, for example- it represents one of the
/// sides. In the ladder example, it will be either the ground or the top.
/// </summary>
public Transform SpawnPosOneSide
{
get => _spawnPosOneSide;
set => _spawnPosOneSide = value;
}
/// <summary>
/// Gets or sets the other spawn point the avatar can be teleported to when using the component.
/// In single point spawn setups this should be left null. In two-sided spawn setups -a ladder that will allow to climb
/// up or down, for example- it represents the other side where the avatar can be teleported to.
/// In the ladder example it will be either the ground or the top.
/// </summary>
public Transform SpawnPosOptionalOtherSide
{
get => _spawnPosOptionalOtherSide;
set => _spawnPosOptionalOtherSide = value;
}
/// <summary>
/// Gets or sets the alternate target position for <see cref="SpawnPosOneSide" />.
/// When non-null it will override the preview teleport target position for <see cref="SpawnPosOneSide" />.
/// Overriding can be useful to draw the target position on top of a seat, even though the actual spawn position will
/// be at the bottom of the seat.
/// </summary>
public Transform AltTargetPosOneSide
{
get => _altTargetPosOneSide;
set => _altTargetPosOneSide = value;
}
/// <summary>
/// Gets or sets the alternate target position for <see cref="SpawnPosOptionalOtherSide" />.
/// Same as <see cref="AltTargetPosOneSide" /> but for <see cref="SpawnPosOptionalOtherSide" />.
/// </summary>
public Transform AltTargetPosOtherSide
{
get => _altTargetPosOtherSide;
set => _altTargetPosOtherSide = value;
}
/// <summary>
/// Gets or sets the additional factor that the height difference between <see cref="SpawnPosOneSide" /> and
/// <see cref="SpawnPosOptionalOtherSide" /> will play in computing which spawn point is closer.
/// In two-sided spawn setups, the collider will teleport the avatar to the farthest spawn point of the two. This
/// factor helps give more weight to the spawn point that is at a farther ground level.
/// </summary>
public float HeightDistanceFactor
{
get => _heightDistanceFactor;
set => _heightDistanceFactor = value;
}
#endregion
#region Public Methods
/// <summary>
/// If there are two spawn positions (one side and other side) it will return the farthest one to the avatar.
/// This is useful to simulate ladders and other spawn elements that will allow to go from one side to the other.
/// Otherwise it will just return the single spawn position.
/// </summary>
/// <param name="avatar">Avatar</param>
/// <param name="targetPosition">
/// Where the target position should be rendered. This is useful when you want
/// a user to teleport to a chair, where the spawn position would be the base of the chair, but the target
/// should be rendered on top of the seat instead.
/// </param>
/// <returns>Farthest spawn position to the player available</returns>
public Transform GetSpawnPos(UxrAvatar avatar, out Vector3 targetPosition)
{
if (SpawnPosOneSide != null && SpawnPosOptionalOtherSide != null)
{
Vector3 avatarPos = avatar.CameraFloorPosition;
bool isToOtherSide = Distance(avatarPos, SpawnPosOneSide.position) < Distance(avatarPos, SpawnPosOptionalOtherSide.position);
if (isToOtherSide)
{
targetPosition = AltTargetPosOtherSide != null ? AltTargetPosOtherSide.position : SpawnPosOptionalOtherSide.position;
}
else
{
targetPosition = AltTargetPosOneSide != null ? AltTargetPosOneSide.position : SpawnPosOneSide.position;
}
return isToOtherSide ? SpawnPosOptionalOtherSide : SpawnPosOneSide;
}
if (SpawnPosOneSide != null)
{
targetPosition = AltTargetPosOneSide != null ? AltTargetPosOneSide.position : SpawnPosOneSide.position;
return SpawnPosOneSide;
}
if (SpawnPosOptionalOtherSide != null)
{
targetPosition = AltTargetPosOtherSide != null ? AltTargetPosOtherSide.position : SpawnPosOptionalOtherSide.position;
return SpawnPosOptionalOtherSide;
}
targetPosition = transform.position;
return transform;
}
#endregion
#region Unity
/// <summary>
/// Initializes the _enableWhenSelected GameObject if there is one.
/// </summary>
protected override void Start()
{
base.Start();
if (_enableWhenSelected != null)
{
_enableWhenSelected.gameObject.SetActive(false);
}
}
#endregion
#region Event Trigger Methods
/// <summary>
/// Raises the <see cref="Teleported" /> event.
/// </summary>
/// <param name="e">Event parameters</param>
internal void RaiseTeleported(UxrAvatar avatar, UxrAvatarMoveEventArgs e)
{
Teleported?.Invoke(this, new UxrTeleportSpawnUsedEventArgs(avatar, e));
}
#endregion
#region Private Methods
/// <summary>
/// Computes a distance value between the avatar and a spawn position by adding up the linear distance at floor level
/// and the vertical distance. The distance is used to check which spawn position is closer to the user when two spawn
/// positions are available.
/// </summary>
/// <param name="avatarPosition">Avatar position</param>
/// <param name="spawnPosition">Spawn position</param>
/// <returns>Distance (horizontal + vertical).</returns>
private float Distance(Vector3 avatarPosition, Vector3 spawnPosition)
{
float verticalDistance = Mathf.Abs(avatarPosition.y - spawnPosition.y) * HeightDistanceFactor;
Vector3 a = avatarPosition;
Vector3 b = spawnPosition;
a.y = 0.0f;
b.y = 0.0f;
return Vector3.Distance(a, b) + verticalDistance;
}
#endregion
}
}

View File

@@ -0,0 +1,13 @@
fileFormatVersion: 2
guid: ab5baa0a995fa1e4282592c5f6dd683e
timeCreated: 1517235444
licenseType: Pro
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,45 @@
// --------------------------------------------------------------------------------------------------------------------
// <copyright file="UxrTeleportSpawnUsedEventArgs.cs" company="VRMADA">
// Copyright (c) VRMADA, All rights reserved.
// </copyright>
// --------------------------------------------------------------------------------------------------------------------
using System;
using UltimateXR.Avatar;
namespace UltimateXR.Locomotion
{
/// <summary>
/// Event parameters when an <see cref="UxrAvatar" /> used a <see cref="UxrTeleportSpawnCollider" />.
/// </summary>
public class UxrTeleportSpawnUsedEventArgs : EventArgs
{
#region Public Types & Data
/// <summary>
/// Gets the avatar that used the <see cref="UxrTeleportSpawnCollider" />.
/// </summary>
public UxrAvatar Avatar { get; }
/// <summary>
/// Gets the move event information.
/// </summary>
public UxrAvatarMoveEventArgs AvatarMoveEventArgs { get; }
#endregion
#region Constructors & Finalizer
/// <summary>
/// Constructor.
/// </summary>
/// <param name="avatar">Avatar that used the spawn collider</param>
/// <param name="moveEventArgs">Move parameters</param>
public UxrTeleportSpawnUsedEventArgs(UxrAvatar avatar, UxrAvatarMoveEventArgs moveEventArgs)
{
Avatar = avatar;
AvatarMoveEventArgs = moveEventArgs;
}
#endregion
}
}

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: dd36faf5de864484934b9140e1195890
timeCreated: 1675601934

View File

@@ -0,0 +1,193 @@
// --------------------------------------------------------------------------------------------------------------------
// <copyright file="UxrTeleportTarget.cs" company="VRMADA">
// Copyright (c) VRMADA, All rights reserved.
// </copyright>
// --------------------------------------------------------------------------------------------------------------------
using System.Collections.Generic;
using UltimateXR.Core;
using UltimateXR.Core.Components;
using UltimateXR.Extensions.System.Collections;
using UnityEngine;
namespace UltimateXR.Locomotion
{
/// <summary>
/// Component describing the visual representation of a teleport destination.
/// </summary>
public class UxrTeleportTarget : UxrComponent
{
#region Inspector Properties/Serialized Fields
[SerializeField] private GameObject _reorientArrowRoot;
[SerializeField] private List<GameObject> _validColorStateIgnoreList;
#endregion
#region Public Types & Data
/// <summary>
/// Gets whether the component has a separate object that is used to point to where the avatar will be facing towards
/// after the teleportation.
/// </summary>
public bool HasReorientArrow => _reorientArrowRoot != null;
/// <summary>
/// Gets the forward vector of the reorient arrow.
/// </summary>
public Vector3 ReorientArrowForward => _reorientArrowRoot != null ? _reorientArrowRoot.transform.forward : Vector3.zero;
/// <summary>
/// Gets or sets the local rotation of the reorient arrow.
/// </summary>
public Quaternion ReorientArrowLocalRotation
{
get => _reorientArrowRoot != null ? _reorientArrowRoot.transform.localRotation : Quaternion.identity;
set
{
if (_reorientArrowRoot != null)
{
_reorientArrowRoot.transform.localRotation = value;
}
}
}
#endregion
#region Public Methods
/// <summary>
/// Enables the reorient arrow.
/// </summary>
/// <param name="enable">Whether to enable it</param>
public void EnableReorientArrow(bool enable)
{
if (_reorientArrowRoot != null)
{
_reorientArrowRoot.SetActive(enable);
}
}
/// <summary>
/// Orients the direction arrow.
/// </summary>
/// <param name="rotation">New rotation</param>
public void OrientArrow(Quaternion rotation)
{
if (_reorientArrowRoot != null)
{
_reorientArrowRoot.transform.rotation = rotation;
}
}
/// <summary>
/// Sets the material color for objects whose color need to be changed in order to indicate a valid or invalid
/// teleport.
/// </summary>
/// <param name="color">New color</param>
public void SetMaterialColor(Color color)
{
foreach (Renderer targetRenderer in _targetRenderers)
{
if (_object2MaterialID.TryGetValue(targetRenderer.gameObject, out UxrTeleportTargetMaterialID teleportMaterialID))
{
Material[] materials = targetRenderer.materials;
if (teleportMaterialID.MaterialID >= 0 && teleportMaterialID.MaterialID < materials.Length)
{
materials[teleportMaterialID.MaterialID].color = color;
targetRenderer.materials = materials;
}
}
else
{
targetRenderer.material.color = color;
}
}
foreach (ParticleSystem pSystem in _particleSystems)
{
ParticleSystem.ColorOverLifetimeModule colorOverLifetime = pSystem.colorOverLifetime;
if (colorOverLifetime.enabled && colorOverLifetime.color.gradient != null)
{
GradientColorKey[] keys = colorOverLifetime.color.gradient.colorKeys;
keys.ForEach(k => k.color = color);
colorOverLifetime.color.gradient.colorKeys = keys;
}
}
foreach (ParticleSystemRenderer pSystemRenderer in _particleSystemRenderers)
{
if (_object2MaterialID.TryGetValue(pSystemRenderer.gameObject, out UxrTeleportTargetMaterialID teleportMaterialID))
{
Material[] materials = pSystemRenderer.materials;
if (teleportMaterialID.MaterialID >= 0 && teleportMaterialID.MaterialID < materials.Length)
{
materials[teleportMaterialID.MaterialID].color = color;
materials[teleportMaterialID.MaterialID].SetColor(UxrConstants.Shaders.TintColorVarName, color);
pSystemRenderer.materials = materials;
}
}
else
{
pSystemRenderer.material.color = color;
pSystemRenderer.material.SetColor(UxrConstants.Shaders.TintColorVarName, color);
}
}
}
#endregion
#region Unity
/// <summary>
/// Initializes the component.
/// </summary>
protected override void Awake()
{
base.Awake();
// Register renderers
_targetRenderers = new List<Renderer>(GetComponentsInChildren<Renderer>(false));
_particleSystems = new List<ParticleSystem>(GetComponentsInChildren<ParticleSystem>(false));
_particleSystemRenderers = new List<ParticleSystemRenderer>(GetComponentsInChildren<ParticleSystemRenderer>(false));
_object2MaterialID = new Dictionary<GameObject, UxrTeleportTargetMaterialID>();
_targetRenderers.RemoveAll(r => _validColorStateIgnoreList.Contains(r.gameObject));
_particleSystems.RemoveAll(p => _validColorStateIgnoreList.Contains(p.gameObject));
_particleSystemRenderers.RemoveAll(r => _validColorStateIgnoreList.Contains(r.gameObject));
// Register objects that have a UxrTeleportTargetMaterialID component that will tell us which material's color
// should be changed if the teleport component uses colors to differentiate between valid and invalid targets.
foreach (Renderer targetRenderer in _targetRenderers)
{
if (targetRenderer.gameObject.TryGetComponent<UxrTeleportTargetMaterialID>(out var teleportMaterialID))
{
_object2MaterialID.Add(teleportMaterialID.gameObject, teleportMaterialID);
}
}
foreach (ParticleSystemRenderer pSystemRenderer in _particleSystemRenderers)
{
if (pSystemRenderer.gameObject.TryGetComponent<UxrTeleportTargetMaterialID>(out var teleportMaterialID))
{
_object2MaterialID.Add(teleportMaterialID.gameObject, teleportMaterialID);
}
}
}
#endregion
#region Private Types & Data
private List<Renderer> _targetRenderers;
private List<ParticleSystem> _particleSystems;
private List<ParticleSystemRenderer> _particleSystemRenderers;
private Dictionary<GameObject, UxrTeleportTargetMaterialID> _object2MaterialID;
#endregion
}
}

View File

@@ -0,0 +1,13 @@
fileFormatVersion: 2
guid: b7a14046e346a1a4a80bc53b1c296bb0
timeCreated: 1517235444
licenseType: Pro
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,36 @@
// --------------------------------------------------------------------------------------------------------------------
// <copyright file="UxrTeleportTargetMaterialID.cs" company="VRMADA">
// Copyright (c) VRMADA, All rights reserved.
// </copyright>
// --------------------------------------------------------------------------------------------------------------------
using UltimateXR.Core.Components;
using UnityEngine;
namespace UltimateXR.Locomotion
{
/// <summary>
/// Component that allows to specify which material ID needs to be changed by an <see cref="UxrTeleportTarget" /> when
/// different colors are used to indicate whether the teleport is currently valid or not.
/// </summary>
public class UxrTeleportTargetMaterialID : UxrComponent
{
#region Inspector Properties/Serialized Fields
[SerializeField] private int _materialID;
#endregion
#region Public Types & Data
/// <summary>
/// Gets or sets the Material ID.
/// </summary>
public int MaterialID
{
get => _materialID;
set => _materialID = value;
}
#endregion
}
}

View File

@@ -0,0 +1,13 @@
fileFormatVersion: 2
guid: 13667cb98c4098641b8742b16d227f54
timeCreated: 1517235444
licenseType: Pro
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,28 @@
// --------------------------------------------------------------------------------------------------------------------
// <copyright file="UxrTranslationType.cs" company="VRMADA">
// Copyright (c) VRMADA, All rights reserved.
// </copyright>
// --------------------------------------------------------------------------------------------------------------------
namespace UltimateXR.Locomotion
{
/// <summary>
/// Enumerates the different types an avatar can teleport from one place to another.
/// </summary>
public enum UxrTranslationType
{
/// <summary>
/// Immediate teleportation.
/// </summary>
Immediate,
/// <summary>
/// Fadeout -> teleportation -> Fadein.
/// </summary>
Fade,
/// <summary>
/// Position interpolation.
/// </summary>
Smooth
}
}

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: cef48ead7c2e4a62bb9126b32218dfdf
timeCreated: 1643727465

View File

@@ -0,0 +1,29 @@
// --------------------------------------------------------------------------------------------------------------------
// <copyright file="UxrWalkDirection.cs" company="VRMADA">
// Copyright (c) VRMADA, All rights reserved.
// </copyright>
// --------------------------------------------------------------------------------------------------------------------
namespace UltimateXR.Locomotion
{
/// <summary>
/// Enumerates the different options available to decide which direction the avatar will move when using locomotion
/// components such as <see cref="UxrSmoothLocomotion" />.
/// </summary>
public enum UxrWalkDirection
{
/// <summary>
/// User will move in the direction pointed by the controller.
/// </summary>
ControllerForward,
/// <summary>
/// User will move in the direction currently pointed by the avatar's root transform forward vector.
/// </summary>
AvatarForward,
/// <summary>
/// User will move in the direction currently being looking at.
/// </summary>
LookDirection
}
}

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: d8a5971802b548e29ce944c6a04baf84
timeCreated: 1643727014