1333 lines
55 KiB
C#
1333 lines
55 KiB
C#
// --------------------------------------------------------------------------------------------------------------------
|
|
// <copyright file="UxrTeleportLocomotionBase.cs" company="VRMADA">
|
|
// Copyright (c) VRMADA, All rights reserved.
|
|
// </copyright>
|
|
// --------------------------------------------------------------------------------------------------------------------
|
|
using System;
|
|
using System.Collections.Generic;
|
|
using System.Linq;
|
|
using UltimateXR.Avatar;
|
|
using UltimateXR.CameraUtils;
|
|
using UltimateXR.Core;
|
|
using UltimateXR.Core.Caching;
|
|
using UltimateXR.Devices;
|
|
using UltimateXR.Extensions.Unity;
|
|
using UnityEngine;
|
|
|
|
namespace UltimateXR.Locomotion
|
|
{
|
|
/// <summary>
|
|
/// Base component for teleport locomotion.
|
|
/// </summary>
|
|
public abstract partial class UxrTeleportLocomotionBase : UxrLocomotion, IUxrPrecacheable
|
|
{
|
|
#region Inspector Properties/Serialized Fields
|
|
|
|
// General parameters
|
|
|
|
[SerializeField] private UxrHandSide _controllerHand = UxrHandSide.Left;
|
|
[SerializeField] private bool _useControllerForward = true;
|
|
[SerializeField] private bool _parentToDestination;
|
|
[SerializeField] private float _shakeFilter = 0.4f;
|
|
[SerializeField] private UxrTranslationType _translationType = UxrTranslationType.Fade;
|
|
[SerializeField] private Color _fadeTranslationColor = Color.black;
|
|
[SerializeField] private float _fadeTranslationSeconds = UxrConstants.TeleportTranslationSeconds;
|
|
[SerializeField] private float _smoothTranslationSeconds = UxrConstants.TeleportTranslationSeconds;
|
|
[SerializeField] private bool _allowJoystickBackStep = true;
|
|
[SerializeField] private float _backStepDistance = 2.0f;
|
|
[SerializeField] private UxrRotationType _rotationType = UxrRotationType.Immediate;
|
|
[SerializeField] private float _rotationStepDegrees = 45.0f;
|
|
[SerializeField] private Color _fadeRotationColor = Color.black;
|
|
[SerializeField] private float _fadeRotationSeconds = UxrConstants.TeleportRotationSeconds;
|
|
[SerializeField] private float _smoothRotationSeconds = UxrConstants.TeleportRotationSeconds;
|
|
[SerializeField] private UxrReorientationType _reorientationType = UxrReorientationType.AllowUserJoystickRedirect;
|
|
|
|
// Target
|
|
|
|
[SerializeField] private UxrTeleportTarget _target;
|
|
[SerializeField] private float _targetPlacementAboveHit = 0.01f;
|
|
[SerializeField] private bool _showTargetAlsoWhenInvalid;
|
|
[SerializeField] private Color _validMaterialColorTargets = Color.white;
|
|
[SerializeField] private Color _invalidMaterialColorTargets = Color.red;
|
|
|
|
// Constraints
|
|
|
|
[SerializeField] private QueryTriggerInteraction _triggerCollidersInteraction = QueryTriggerInteraction.Ignore;
|
|
[SerializeField] private float _maxAllowedDistance = 20.0f;
|
|
[SerializeField] private float _maxAllowedHeightDifference = 10.0f;
|
|
[SerializeField] private float _maxAllowedSlopeDegrees = 30.0f;
|
|
[SerializeField] private float _destinationValidationRadius = 0.25f;
|
|
[SerializeField] private LayerMask _validTargetLayers = ~0;
|
|
[SerializeField] private LayerMask _blockingTargetLayers = ~0;
|
|
|
|
#endregion
|
|
|
|
#region Public Types & Data
|
|
|
|
/// <summary>
|
|
/// Called when a destination validator using <see cref="AddDestinationValidator" /> with
|
|
/// <see cref="UxrDestinationValidatorMode.EveryFrame" /> invalidated a destination. If there is more than one
|
|
/// validator, it will be raised only by the first validator that returns false during the frame.
|
|
/// </summary>
|
|
public event Action<UxrTeleportDestination> ValidatorInvalidated;
|
|
|
|
/// <summary>
|
|
/// Called when a destination validator using <see cref="AddDestinationValidator" /> with
|
|
/// <see cref="UxrDestinationValidatorMode.OnConfirmationOnly" /> canceled a destination. IF there is more than one
|
|
/// validator, it will be raised only by the first validator that returns false.
|
|
/// </summary>
|
|
public event Action<UxrTeleportDestination> ValidatorCanceled;
|
|
|
|
/// <summary>
|
|
/// Gets the hand used to control the teleport component.
|
|
/// </summary>
|
|
public UxrHandSide HandSide => _controllerHand;
|
|
|
|
/// <summary>
|
|
/// Gets or sets the teleport translation type.
|
|
/// </summary>
|
|
public UxrTranslationType TranslationType
|
|
{
|
|
get => _translationType;
|
|
set => _translationType = value;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Gets or sets the fade color when using <see cref="UxrTranslationType.Fade" /> translation teleporting.
|
|
/// </summary>
|
|
public Color FadeTranslationColor
|
|
{
|
|
get => _fadeTranslationColor;
|
|
set => _fadeTranslationColor = value;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Gets or sets the transition duration in seconds for the <see cref="UxrTranslationType.Fade" /> translation type.
|
|
/// </summary>
|
|
public float FadeTranslationSeconds
|
|
{
|
|
get => _fadeTranslationSeconds;
|
|
set => _fadeTranslationSeconds = value;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Gets or sets the transition duration in seconds for the <see cref="UxrTranslationType.Smooth" /> translation type.
|
|
/// </summary>
|
|
public float SmoothTranslationSeconds
|
|
{
|
|
get => _smoothTranslationSeconds;
|
|
set => _smoothTranslationSeconds = value;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Gets or sets whether the back-step is permitted.
|
|
/// </summary>
|
|
public bool AllowJoystickBackStep
|
|
{
|
|
get => _allowJoystickBackStep;
|
|
set => _allowJoystickBackStep = value;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Gets or sets the back-step distance.
|
|
/// </summary>
|
|
public float BackStepDistance
|
|
{
|
|
get => _backStepDistance;
|
|
set => _backStepDistance = value;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Gets or sets the teleport rotation type.
|
|
/// </summary>
|
|
public UxrRotationType RotationType
|
|
{
|
|
get => _rotationType;
|
|
set => _rotationType = value;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Gets or sets the amount of degrees rotated around the avatar axis when the user presses the left or right joystick
|
|
/// buttons.
|
|
/// </summary>
|
|
public float RotationStepDegrees
|
|
{
|
|
get => _rotationStepDegrees;
|
|
set => _rotationStepDegrees = value;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Gets or sets the fade color when using <see cref="UxrRotationType.Fade" /> translation teleporting.
|
|
/// </summary>
|
|
public Color FadeRotationColor
|
|
{
|
|
get => _fadeRotationColor;
|
|
set => _fadeRotationColor = value;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Gets or sets the transition duration in seconds for the <see cref="UxrRotationType.Fade" /> rotation type.
|
|
/// </summary>
|
|
public float FadeRotationSeconds
|
|
{
|
|
get => _fadeRotationSeconds;
|
|
set => _fadeRotationSeconds = value;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Gets or sets the transition duration in seconds for the <see cref="UxrRotationType.Smooth" /> rotation type.
|
|
/// </summary>
|
|
public float SmoothRotationSeconds
|
|
{
|
|
get => _smoothRotationSeconds;
|
|
set => _smoothRotationSeconds = value;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Gets or sets how the teleport target direction is set.
|
|
/// </summary>
|
|
public UxrReorientationType ReorientationType
|
|
{
|
|
get => _reorientationType;
|
|
set => _reorientationType = value;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Gets or sets the target object.
|
|
/// </summary>
|
|
public UxrTeleportTarget Target
|
|
{
|
|
get => _teleportTarget;
|
|
set => _teleportTarget = value;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Gets or sets the distance above the ground the target is positioned.
|
|
/// </summary>
|
|
public float TargetPlacementAboveHit
|
|
{
|
|
get => _targetPlacementAboveHit;
|
|
set => _targetPlacementAboveHit = value;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Gets or sets whether the target should also be visible when the teleport destination is not valid.
|
|
/// </summary>
|
|
public bool ShowTargetAlsoWhenInvalid
|
|
{
|
|
get => _showTargetAlsoWhenInvalid;
|
|
set => _showTargetAlsoWhenInvalid = value;
|
|
}
|
|
|
|
/// <summary>
|
|
/// When <see cref="ShowTargetAlsoWhenInvalid" /> is true, sets the teleport target color used when the destination is
|
|
/// valid.
|
|
/// </summary>
|
|
public Color ValidMaterialColorTargets
|
|
{
|
|
get => _validMaterialColorTargets;
|
|
set => _validMaterialColorTargets = value;
|
|
}
|
|
|
|
/// <summary>
|
|
/// When <see cref="ShowTargetAlsoWhenInvalid" /> is true, sets the teleport target color used when the destination is
|
|
/// invalid.
|
|
/// </summary>
|
|
public Color InvalidMaterialColorTargets
|
|
{
|
|
get => _invalidMaterialColorTargets;
|
|
set => _invalidMaterialColorTargets = value;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Gets or sets the behaviour for raycasts against trigger volumes.
|
|
/// </summary>
|
|
public QueryTriggerInteraction TriggerCollidersInteraction
|
|
{
|
|
get => _triggerCollidersInteraction;
|
|
set => _triggerCollidersInteraction = value;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Gets or sets the maximum teleport distance.
|
|
/// </summary>
|
|
public float MaxAllowedDistance
|
|
{
|
|
get => _maxAllowedDistance;
|
|
set => _maxAllowedDistance = value;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Gets or sets the maximum height difference allowed from the current position to a destination.
|
|
/// </summary>
|
|
public float MaxAllowedHeightDifference
|
|
{
|
|
get => _maxAllowedHeightDifference;
|
|
set => _maxAllowedHeightDifference = value;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Gets or sets the maximum slop for a destination to be considered valid.
|
|
/// </summary>
|
|
public float MaxAllowedSlopeDegrees
|
|
{
|
|
get => _maxAllowedSlopeDegrees;
|
|
set => _maxAllowedSlopeDegrees = value;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Gets or sets the radius of a cylinder used when validating if a teleport destination is valid.
|
|
/// </summary>
|
|
public float DestinationValidationRadius
|
|
{
|
|
get => _destinationValidationRadius;
|
|
set => _destinationValidationRadius = value;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Gets or sets the layers over which teleportation is allowed.
|
|
/// </summary>
|
|
public LayerMask ValidTargetLayers
|
|
{
|
|
get => _validTargetLayers;
|
|
set => _validTargetLayers = value;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Gets or sets the layers which should be considered when ray-casting looking for either valid or invalid
|
|
/// teleportation surfaces.
|
|
/// </summary>
|
|
public LayerMask BlockingTargetLayers
|
|
{
|
|
get => _blockingTargetLayers;
|
|
set => _blockingTargetLayers = value;
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region Implicit IUxrPrecacheable
|
|
|
|
/// <inheritdoc />
|
|
public IEnumerable<GameObject> PrecachedInstances
|
|
{
|
|
get
|
|
{
|
|
if (Target != null)
|
|
{
|
|
yield return Target.gameObject;
|
|
}
|
|
}
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region Public Methods
|
|
|
|
/// <summary>
|
|
/// Adds a destination validator, which can cancel a teleport based on custom conditions.
|
|
/// </summary>
|
|
/// <param name="validator">
|
|
/// The destination validator, a function that receives a <see cref="UxrTeleportDestination" /> and
|
|
/// returns a boolean telling whether the teleport can be executed or not
|
|
/// </param>
|
|
/// <param name="mode">The validator execution mode</param>
|
|
/// <exception cref="ArgumentNullException">The validator is null</exception>
|
|
public void AddDestinationValidator(Func<UxrTeleportDestination, bool> validator, UxrDestinationValidatorMode mode)
|
|
{
|
|
if (validator == null)
|
|
{
|
|
throw new ArgumentNullException(nameof(validator));
|
|
}
|
|
|
|
_destinationValidators.Add(new Validator(validator, mode));
|
|
}
|
|
|
|
/// <summary>
|
|
/// Removes a destination validator added using <see cref="AddDestinationValidator" />.
|
|
/// </summary>
|
|
/// <param name="validator">Validator to remove</param>
|
|
/// <returns>Whether the validator was removed, or false if the validator was not found</returns>
|
|
/// <exception cref="ArgumentNullException">the validator function is null</exception>
|
|
public bool RemoveDestinationValidator(Func<UxrTeleportDestination, bool> validator)
|
|
{
|
|
if (validator == null)
|
|
{
|
|
throw new ArgumentNullException(nameof(validator));
|
|
}
|
|
|
|
for (int i = 0; i < _destinationValidators.Count; ++i)
|
|
{
|
|
if (_destinationValidators[i].ValidatorFunc == validator)
|
|
{
|
|
_destinationValidators.RemoveAt(i);
|
|
return true;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Removes all destination validators added using <see cref="AddDestinationValidator" />.
|
|
/// </summary>
|
|
public void RemoveAllDestinationValidators()
|
|
{
|
|
_destinationValidators.Clear();
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region Unity
|
|
|
|
/// <summary>
|
|
/// Initializes the component. Should also be called in child classes.
|
|
/// </summary>
|
|
protected override void Awake()
|
|
{
|
|
base.Awake();
|
|
|
|
// Look for other avatar teleports
|
|
|
|
_otherAvatarTeleports = new List<UxrTeleportLocomotionBase>();
|
|
|
|
UxrTeleportLocomotionBase[] allAvatarTeleports = Avatar.GetComponentsInChildren<UxrTeleportLocomotionBase>();
|
|
|
|
_otherAvatarTeleports.AddRange(allAvatarTeleports.Where(teleport => teleport != this));
|
|
|
|
// If the teleport target is a prefab, instantiate. Otherwise just reference the object in the scene
|
|
|
|
if (_target.IsInPrefab())
|
|
{
|
|
_teleportTarget = Instantiate(_target, Avatar.transform);
|
|
}
|
|
else
|
|
{
|
|
_teleportTarget = _target;
|
|
|
|
if (_teleportTarget != null)
|
|
{
|
|
_teleportTarget.transform.parent = Avatar.transform;
|
|
}
|
|
}
|
|
|
|
// Set initial state
|
|
|
|
if (_teleportTarget != null)
|
|
{
|
|
_teleportTarget.transform.rotation = Avatar.transform.rotation;
|
|
TeleportReference = null;
|
|
TeleportLocalDirection = Avatar.ProjectedCameraForward;
|
|
TeleportLocalPosition = Avatar.transform.position;
|
|
}
|
|
|
|
_layerMaskRaycast.value = BlockingTargetLayers.value | ValidTargetLayers.value;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Resets the component and subscribes to events.
|
|
/// </summary>
|
|
protected override void OnEnable()
|
|
{
|
|
base.OnEnable();
|
|
|
|
UxrAvatar.GlobalAvatarMoved += UxrAvatar_GlobalAvatarMoved;
|
|
UxrManager.AvatarsUpdated += UxrManager_AvatarsUpdated;
|
|
|
|
EnableTeleportObjects(false, false);
|
|
|
|
_isBackStepAvailable = true;
|
|
_isValidTeleport = false;
|
|
IsTeleporting = false;
|
|
ControllerStart = RawControllerStart;
|
|
ControllerForward = RawControllerForward;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Clear some states and unsubscribes from events.
|
|
/// </summary>
|
|
protected override void OnDisable()
|
|
{
|
|
base.OnDisable();
|
|
|
|
UxrAvatar.GlobalAvatarMoved -= UxrAvatar_GlobalAvatarMoved;
|
|
UxrManager.AvatarsUpdated -= UxrManager_AvatarsUpdated;
|
|
|
|
NotifyTeleportSpawnCollider(null);
|
|
|
|
EnableTeleportObjects(false, false);
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region Event Handling Methods
|
|
|
|
/// <summary>
|
|
/// Called when the avatar moved.
|
|
/// </summary>
|
|
/// <param name="sender">Event sender</param>
|
|
/// <param name="e">Event parameters</param>
|
|
private void UxrAvatar_GlobalAvatarMoved(object sender, UxrAvatarMoveEventArgs e)
|
|
{
|
|
if (ReferenceEquals(sender, Avatar))
|
|
{
|
|
ControllerStart = RawControllerStart;
|
|
ControllerForward = RawControllerForward;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// When the avatar is in UpdateExternally mode, still smooth the transforms to support multiplayer and replays.
|
|
/// </summary>
|
|
private void UxrManager_AvatarsUpdated()
|
|
{
|
|
if (Avatar.AvatarMode != UxrAvatarMode.Local)
|
|
{
|
|
UpdateSmoothTransforms();
|
|
}
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region Protected Overrides UxrLocomotion
|
|
|
|
/// <inheritdoc />
|
|
protected override void UpdateLocomotion()
|
|
{
|
|
if (Avatar == null)
|
|
{
|
|
return;
|
|
}
|
|
|
|
// Check for back-step and rotations
|
|
|
|
if (Avatar.ControllerInput.GetButtonsPressUp(_controllerHand, UxrInputButtons.JoystickDown))
|
|
{
|
|
_isBackStepAvailable = true;
|
|
}
|
|
|
|
bool backStepInput = false;
|
|
|
|
if (IsAllowedToTeleport)
|
|
{
|
|
backStepInput = _allowJoystickBackStep && CanBackStep && Avatar.ControllerInput.GetButtonsPress(_controllerHand, UxrInputButtons.JoystickDown);
|
|
|
|
if (RotationType != UxrRotationType.NotAllowed)
|
|
{
|
|
if (Avatar.ControllerInput.GetButtonsPressDown(_controllerHand, UxrInputButtons.JoystickLeft) && CanRotate)
|
|
{
|
|
Rotate(-RotationStepDegrees);
|
|
return;
|
|
}
|
|
|
|
if (Avatar.ControllerInput.GetButtonsPressDown(_controllerHand, UxrInputButtons.JoystickRight) && CanRotate)
|
|
{
|
|
Rotate(RotationStepDegrees);
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Back step?
|
|
|
|
if (backStepInput && _isBackStepAvailable && IsAllowedToTeleport)
|
|
{
|
|
Vector3 newPosition = Avatar.CameraFloorPosition - Avatar.ProjectedCameraForward * _backStepDistance;
|
|
|
|
if (HasBlockingRaycastHit(Avatar,
|
|
newPosition + UpVector * RaycastAboveGround,
|
|
-UpVector,
|
|
_maxAllowedHeightDifference > 0.0f ? _maxAllowedHeightDifference : RaycastLongDistance,
|
|
BlockingTargetLayers,
|
|
TriggerCollidersInteraction,
|
|
out RaycastHit backStepRaycast))
|
|
{
|
|
if (NotifyDestinationRaycast(backStepRaycast, true, out bool _))
|
|
{
|
|
_isBackStepAvailable = false;
|
|
TeleportLocalDirection = TransformExt.GetLocalDirection(TeleportReference, Avatar.ProjectedCameraForward);
|
|
TryTeleportUsingCurrentTarget();
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Update smoothing of transforms that track the hands
|
|
|
|
UpdateSmoothTransforms();
|
|
|
|
// Update locomotion in child classes
|
|
|
|
UpdateTeleportLocomotion();
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region Protected Methods
|
|
|
|
/// <summary>
|
|
/// Can be overriden in child classes to execute the additional per-frame teleport locomotion logic.
|
|
/// </summary>
|
|
protected virtual void UpdateTeleportLocomotion()
|
|
{
|
|
}
|
|
|
|
/// <summary>
|
|
/// Cancels the current teleport target. When overriden in child classes the base class should be called too.
|
|
/// </summary>
|
|
protected virtual void CancelTarget()
|
|
{
|
|
EnableTeleportObjects(false, false);
|
|
_isValidTeleport = false;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Checks if the teleport position is valid.
|
|
/// </summary>
|
|
/// <param name="checkBlockingInBetween">
|
|
/// Should it check for blocking elements in a straight line from the current position to the new position?
|
|
/// </param>
|
|
/// <param name="newPosition">
|
|
/// Teleport position. If should passed as reference because it may have slight corrections
|
|
/// </param>
|
|
/// <param name="hitNormal">The hit normal that generated the teleport position candidate</param>
|
|
/// <param name="isValidSlope">Returns a boolean telling if the slope at the destination is valid or not</param>
|
|
/// <returns>Boolean telling whether the new position is a valid teleport destination or not</returns>
|
|
protected bool IsValidTeleport(bool checkBlockingInBetween, ref Vector3 newPosition, Vector3 hitNormal, out bool isValidSlope)
|
|
{
|
|
isValidSlope = true;
|
|
|
|
if (!IsAllowedToTeleport)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
Vector3 localNewPosition = Avatar.transform.InverseTransformPoint(newPosition);
|
|
|
|
if (Mathf.Abs(localNewPosition.y) > MaxAllowedHeightDifference)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
float eyeHeight = Avatar.transform.InverseTransformPoint(Avatar.CameraPosition).y;
|
|
Vector3 eyePosStart = Avatar.CameraPosition;
|
|
Vector3 eyePosEnd = newPosition + UpVector * eyeHeight;
|
|
|
|
// Check if there is something blocking in a straight line if requested, used in a back step
|
|
|
|
if (checkBlockingInBetween)
|
|
{
|
|
Vector3 direction = eyePosEnd - eyePosStart;
|
|
|
|
if (HasBlockingRaycastHit(eyePosStart, direction.normalized, direction.magnitude, out RaycastHit _))
|
|
{
|
|
// There is something blocking in between
|
|
return false;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// First perform a sphere test on the place where the head would be teleported to see if we can have an early negative.
|
|
if (Physics.CheckSphere(eyePosEnd, HeadRadius, BlockingTargetLayers, TriggerCollidersInteraction))
|
|
{
|
|
return false;
|
|
}
|
|
|
|
if (MaxAllowedHeightDifference > 0.0f && Vector3.Angle(hitNormal, UpVector) > MaxAllowedSlopeDegrees)
|
|
{
|
|
// Check if we are hitting a tall enough wall to see if we can have an early negative. This avoids the filtering
|
|
// below to allow climbing up the first portion of the wall.
|
|
// What we do is raycast in an inclined upwards direction to see if the wall is significantly enough above the raycast.
|
|
|
|
Vector3 rayStart = newPosition + hitNormal * 0.1f;
|
|
Vector3 rayEnd = newPosition + UpVector * MaxAllowedHeightDifference;
|
|
|
|
if (HasBlockingRaycastHit(Avatar,
|
|
rayStart,
|
|
(rayEnd - rayStart).normalized,
|
|
Vector3.Distance(rayStart, rayEnd) + 0.01f,
|
|
BlockingTargetLayers,
|
|
TriggerCollidersInteraction,
|
|
out RaycastHit _))
|
|
{
|
|
// We are hitting the base of a tall wall
|
|
isValidSlope = false;
|
|
return false;
|
|
}
|
|
}
|
|
}
|
|
|
|
// If not, we want to check also a radius around the destination pos. If a certain number of positions within
|
|
// this radius are valid we consider it also a valid destination. This removes some unwanted false negatives due
|
|
// to small height or slope differences on the ground.
|
|
|
|
int positives = 0;
|
|
int validSlopes = 0;
|
|
|
|
for (int i = 0; i < DestinationValidationSubdivisions; ++i)
|
|
{
|
|
float offsetT = 1.0f / DestinationValidationSubdivisions * 0.5f;
|
|
float radians = Mathf.PI * 2.0f * (i * (1.0f / DestinationValidationSubdivisions) + offsetT);
|
|
|
|
Vector3 offset = new Vector3(Mathf.Cos(radians), 0.0f, Mathf.Sin(radians));
|
|
|
|
if (IsValidDestination(newPosition, eyePosEnd + offset.normalized * DestinationValidationRadius, out bool isValidSlopeSubdivision))
|
|
{
|
|
positives++;
|
|
}
|
|
|
|
validSlopes += isValidSlopeSubdivision ? 1 : 0;
|
|
}
|
|
|
|
isValidSlope = validSlopes >= DestinationValidationPositivesNeeded;
|
|
|
|
return positives >= DestinationValidationPositivesNeeded && isValidSlope;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Cancels all other current teleport targets. When overriden in child classes the base class should be called too.
|
|
/// </summary>
|
|
protected void CancelOtherTeleportTargets()
|
|
{
|
|
foreach (UxrTeleportLocomotionBase otherTeleport in _otherAvatarTeleports)
|
|
{
|
|
otherTeleport.CancelTarget();
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Checks whether the given raycast hits have any that are blocking. A blocking raycast can either be a valid or
|
|
/// invalid teleport destination depending on many factors. Use <see cref="IsValidDestination" /> to check whether the
|
|
/// given position is valid.
|
|
/// This method filters out invalid raycasts such as against anything part of an avatar or a grabbed object.
|
|
/// </summary>
|
|
/// <param name="origin">Ray origin</param>
|
|
/// <param name="direction">Ray direction</param>
|
|
/// <param name="maxDistance">Raycast maximum distance</param>
|
|
/// <param name="outputHit">Result blocking raycast</param>
|
|
/// <returns>Whether there is a blocking raycast returned in <paramref name="outputHit" /></returns>
|
|
protected bool HasBlockingRaycastHit(Vector3 origin, Vector3 direction, float maxDistance, out RaycastHit outputHit)
|
|
{
|
|
return HasBlockingRaycastHit(Avatar, origin, direction, maxDistance, LayerMaskRaycast, TriggerCollidersInteraction, out outputHit);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Notifies a raycast was selected to be a potential destination. Computes whether the destination is valid. If it is,
|
|
/// sets the appropriate internal state that can later be executed using <see cref="TryTeleportUsingCurrentTarget" />.
|
|
/// </summary>
|
|
/// <param name="hit">Raycast that will be processed as a potential teleport destination</param>
|
|
/// <param name="checkBlockingInBetween">
|
|
/// Should it check for blocking elements in a straight line from the current position to the new position?
|
|
/// </param>
|
|
/// <param name="isTargetEnabled">Will return whether the target was enabled</param>
|
|
/// <returns>Whether the destination is a valid teleport location</returns>
|
|
protected bool NotifyDestinationRaycast(RaycastHit hit, bool checkBlockingInBetween, out bool isTargetEnabled)
|
|
{
|
|
_isValidTeleport = true;
|
|
isTargetEnabled = true;
|
|
|
|
UxrIgnoreTeleportDestination ignoreDestinationComponent = hit.collider.GetComponentInParent<UxrIgnoreTeleportDestination>();
|
|
|
|
bool ignoreDestination = ignoreDestinationComponent != null && ignoreDestinationComponent.enabled;
|
|
TeleportReference = hit.collider != null ? hit.collider.transform : null;
|
|
|
|
_hitInfo = hit;
|
|
|
|
// Check for UxrTeleportSpawnCollider component
|
|
|
|
UxrTeleportSpawnCollider teleportSpawnCollider = hit.collider.GetComponentInParent<UxrTeleportSpawnCollider>();
|
|
|
|
NotifyTeleportSpawnCollider(teleportSpawnCollider);
|
|
|
|
if (teleportSpawnCollider != null && teleportSpawnCollider.enabled && !ignoreDestination)
|
|
{
|
|
Transform spawnPos = teleportSpawnCollider.GetSpawnPos(Avatar, out Vector3 _);
|
|
|
|
if (spawnPos != null)
|
|
{
|
|
TeleportReference = spawnPos;
|
|
TeleportLocalPosition = TransformExt.GetLocalPosition(TeleportReference, spawnPos.position);
|
|
TeleportLocalDirection = TransformExt.GetLocalDirection(TeleportReference, Vector3.ProjectOnPlane(spawnPos.forward, spawnPos.up));
|
|
|
|
_isValidTeleport = true;
|
|
isTargetEnabled = true;
|
|
EnableTeleportObjects(isTargetEnabled, _isValidTeleport);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
Vector3 teleportPos = hit.point;
|
|
Vector3 teleportLocalDirection = Vector3.zero;
|
|
|
|
isTargetEnabled = true;
|
|
|
|
// Compute the new local avatar direction
|
|
|
|
if (ReorientationType == UxrReorientationType.KeepOrientation)
|
|
{
|
|
teleportLocalDirection = TransformExt.GetLocalDirection(TeleportReference, Avatar.ProjectedCameraForward);
|
|
}
|
|
else if (ReorientationType == UxrReorientationType.UseTeleportFromToDirection)
|
|
{
|
|
teleportLocalDirection = TransformExt.GetLocalDirection(TeleportReference, Vector3.ProjectOnPlane(teleportPos - Avatar.CameraPosition, UpVector));
|
|
}
|
|
else if (ReorientationType == UxrReorientationType.AllowUserJoystickRedirect)
|
|
{
|
|
Vector2 joystickValue = Avatar.ControllerInput.GetInput2D(HandSide, UxrInput2D.Joystick);
|
|
Vector3 projectedForward = Vector3.ProjectOnPlane(ControllerForward, UpVector).normalized;
|
|
Vector3 joystickDirection = new Vector3(joystickValue.x, 0.0f, joystickValue.y).normalized;
|
|
|
|
teleportLocalDirection = TransformExt.GetLocalDirection(TeleportReference, Quaternion.LookRotation(projectedForward, UpVector) * Quaternion.LookRotation(joystickDirection, UpVector) * Vector3.forward);
|
|
}
|
|
|
|
// Run "EveryFrame" validators if there are any
|
|
|
|
UxrTeleportDestination destination = null;
|
|
|
|
foreach (Validator validator in _destinationValidators)
|
|
{
|
|
if (validator.Mode == UxrDestinationValidatorMode.EveryFrame)
|
|
{
|
|
if (destination == null)
|
|
{
|
|
destination = new UxrTeleportDestination(hit, teleportPos, Quaternion.LookRotation(TransformExt.GetWorldDirection(TeleportReference, teleportLocalDirection), UpVector));
|
|
}
|
|
|
|
if (!validator.ValidatorFunc.Invoke(destination))
|
|
{
|
|
_isValidTeleport = false;
|
|
isTargetEnabled = ShowTargetAlsoWhenInvalid;
|
|
ValidatorInvalidated?.Invoke(destination);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (_isValidTeleport)
|
|
{
|
|
// Run internal validation
|
|
|
|
_isValidTeleport = IsValidTeleport(checkBlockingInBetween, ref teleportPos, hit.normal, out bool validSlope) && !ignoreDestination;
|
|
|
|
if (_isValidTeleport && validSlope)
|
|
{
|
|
// Hit against valid target
|
|
EnableTeleportObjects(true, _isValidTeleport);
|
|
}
|
|
else
|
|
{
|
|
// Hit against blocking object or invalid slope
|
|
_isValidTeleport = false;
|
|
isTargetEnabled = ShowTargetAlsoWhenInvalid && validSlope;
|
|
EnableTeleportObjects(isTargetEnabled, _isValidTeleport);
|
|
}
|
|
}
|
|
|
|
if (isTargetEnabled)
|
|
{
|
|
// Place target
|
|
|
|
TeleportLocalPosition = TransformExt.GetLocalPosition(TeleportReference, teleportPos);
|
|
|
|
if (Avatar.AvatarMode == UxrAvatarMode.Local)
|
|
{
|
|
TeleportLocalDirection = teleportLocalDirection;
|
|
}
|
|
}
|
|
}
|
|
|
|
return _isValidTeleport;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Notifies that no raycast were found to be processed as a potential teleport destination.
|
|
/// </summary>
|
|
protected void NotifyNoDestinationRaycast()
|
|
{
|
|
_isValidTeleport = false;
|
|
EnableTeleportObjects(false, false);
|
|
NotifyTeleportSpawnCollider(null);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Tries to teleport the avatar using the current <see cref="TeleportPosition" /> and <see cref="TeleportDirection" />
|
|
/// values, only if the current destination is valid and the avatar isn't currently being teleported.
|
|
/// </summary>
|
|
protected void TryTeleportUsingCurrentTarget()
|
|
{
|
|
// Teleport if we can!
|
|
|
|
if (_isValidTeleport && !IsTeleporting && !IsOtherComponentTeleporting && IsAllowedToTeleport)
|
|
{
|
|
Transform avatarTransform = Avatar.transform;
|
|
Vector3 avatarPos = avatarTransform.position;
|
|
Vector3 avatarUp = avatarTransform.up;
|
|
Quaternion avatarRot = avatarTransform.rotation;
|
|
|
|
if (TranslationType == UxrTranslationType.Fade)
|
|
{
|
|
UxrManager.Instance.TeleportFadeColor = FadeTranslationColor;
|
|
}
|
|
|
|
bool parentToDestination = ParentToDestination;
|
|
|
|
if (TeleportReference != null && TeleportReference.TryGetComponent(out UxrParentAvatarDestination parentAvatarDestination))
|
|
{
|
|
parentToDestination = parentAvatarDestination.ParentAvatar;
|
|
}
|
|
|
|
UxrTeleportSpawnCollider spawnCollider = _lastSpawnCollider;
|
|
|
|
Vector3 teleportPos = TransformExt.GetWorldPosition(TeleportReference, TeleportLocalPosition);
|
|
Quaternion teleportRot = Quaternion.LookRotation(TransformExt.GetWorldDirection(TeleportReference, TeleportLocalDirection), UpVector);
|
|
|
|
// Run validators if there are any
|
|
|
|
bool isValid = true;
|
|
|
|
if (spawnCollider == null)
|
|
{
|
|
UxrTeleportDestination destination = null;
|
|
|
|
foreach (Validator validator in _destinationValidators)
|
|
{
|
|
if (validator.Mode == UxrDestinationValidatorMode.OnConfirmationOnly)
|
|
{
|
|
if (destination == null)
|
|
{
|
|
destination = new UxrTeleportDestination(_hitInfo, teleportPos, teleportRot);
|
|
}
|
|
|
|
if (!validator.ValidatorFunc.Invoke(destination))
|
|
{
|
|
ValidatorCanceled?.Invoke(destination);
|
|
isValid = false;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if (isValid)
|
|
{
|
|
IsTeleporting = true;
|
|
|
|
UxrManager.Instance.TeleportLocalAvatarRelative(TeleportReference,
|
|
parentToDestination,
|
|
teleportPos,
|
|
teleportRot,
|
|
_translationType,
|
|
TranslationSeconds,
|
|
() =>
|
|
{
|
|
if (spawnCollider != null)
|
|
{
|
|
spawnCollider.RaiseTeleported(Avatar, new UxrAvatarMoveEventArgs(avatarPos, avatarRot, Avatar.CameraFloorPosition, Quaternion.LookRotation(Avatar.ProjectedCameraForward, avatarUp)));
|
|
}
|
|
},
|
|
finished =>
|
|
{
|
|
_isValidTeleport = false;
|
|
IsTeleporting = false;
|
|
ControllerStart = RawControllerStart;
|
|
ControllerForward = RawControllerForward;
|
|
});
|
|
}
|
|
}
|
|
|
|
NotifyTeleportSpawnCollider(null);
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region Private Methods
|
|
|
|
/// <summary>
|
|
/// Applies smoothing to the source transforms to avoid too much jitter.
|
|
/// </summary>
|
|
private void UpdateSmoothTransforms()
|
|
{
|
|
if (_shakeFilter > 0.0f && IsTeleporting && IsOtherComponentTeleporting == false)
|
|
{
|
|
float deltaTimeMultiplier = Mathf.Lerp(DeltaTimeMultiplierFilterMin, DeltaTimeMultiplierFilterMax, Mathf.Clamp01(_shakeFilter));
|
|
|
|
ControllerStart = Vector3.Lerp(ControllerStart, RawControllerStart, Time.deltaTime * deltaTimeMultiplier);
|
|
ControllerForward = Vector3.Lerp(ControllerForward, RawControllerForward, Time.deltaTime * deltaTimeMultiplier);
|
|
}
|
|
else
|
|
{
|
|
ControllerStart = RawControllerStart;
|
|
ControllerForward = RawControllerForward;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Checks if a given teleport position is valid. We use the eye position of the teleport destination as
|
|
/// a reference to be able to raycast to the ground and check for valid layers and slope angle and if there
|
|
/// is a discrepancy between the raycast and the expected floor height.
|
|
/// </summary>
|
|
/// <param name="teleportPos">The floor level position of the teleport</param>
|
|
/// <param name="newEyePos">The eye position that will be used as reference for the teleport destination</param>
|
|
/// <param name="isValidSlope">Will return if it is a valid slope</param>
|
|
/// <returns>Boolean telling whether newEyePos is a valid teleport position or not</returns>
|
|
private bool IsValidDestination(Vector3 teleportPos, Vector3 newEyePos, out bool isValidSlope)
|
|
{
|
|
isValidSlope = false;
|
|
|
|
Vector3 localNewEyePos = Avatar.transform.InverseTransformPoint(newEyePos);
|
|
Vector3 localTeleportPos = Avatar.transform.InverseTransformPoint(teleportPos);
|
|
float eyeHeight = localNewEyePos.y - localTeleportPos.y;
|
|
|
|
if (HasBlockingRaycastHit(Avatar, newEyePos, -UpVector, eyeHeight * 1.2f, LayerMaskRaycast, TriggerCollidersInteraction, out RaycastHit hit))
|
|
{
|
|
float slopeDegrees = Mathf.Abs(Vector3.Angle(hit.normal, UpVector));
|
|
|
|
isValidSlope = slopeDegrees < MaxAllowedSlopeDegrees;
|
|
bool valid = isValidSlope && (ValidTargetLayers.value & 1 << hit.collider.gameObject.layer) != 0;
|
|
|
|
Vector3 localHitPoint = Avatar.transform.InverseTransformPoint(hit.point);
|
|
valid = valid && Mathf.Abs(localHitPoint.y - localTeleportPos.y) < MaxVerticalHeightDisparity;
|
|
|
|
UxrIgnoreTeleportDestination ignoreDestinationComponent = hit.collider.GetComponentInParent<UxrIgnoreTeleportDestination>();
|
|
|
|
if (ignoreDestinationComponent != null && ignoreDestinationComponent.enabled)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
if (valid)
|
|
{
|
|
// Raycast upwards to see if there is something between the ground and eye level. Since we can be teleported inside a box
|
|
// at eye level for instance, the previous raycast will not handle that case. We need to raycast from outside as well
|
|
|
|
return !HasBlockingRaycastHit(Avatar, teleportPos, UpVector, eyeHeight, LayerMaskRaycast, TriggerCollidersInteraction, out RaycastHit _);
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Notifies a change in the currently targeted <see cref="UxrTeleportSpawnCollider" /> component.
|
|
/// </summary>
|
|
/// <param name="teleportSpawnCollider">New currently targeted component or null if none is selected</param>
|
|
private void NotifyTeleportSpawnCollider(UxrTeleportSpawnCollider teleportSpawnCollider)
|
|
{
|
|
if (teleportSpawnCollider && teleportSpawnCollider.enabled)
|
|
{
|
|
if (_lastSpawnCollider != null && teleportSpawnCollider != _lastSpawnCollider && _lastSpawnCollider.EnableWhenSelected)
|
|
{
|
|
_lastSpawnCollider.EnableWhenSelected.SetActive(false);
|
|
}
|
|
else if (_lastSpawnCollider != teleportSpawnCollider && teleportSpawnCollider.EnableWhenSelected)
|
|
{
|
|
if (teleportSpawnCollider.EnableWhenSelected.activeSelf == false)
|
|
{
|
|
teleportSpawnCollider.EnableWhenSelected.SetActive(true);
|
|
}
|
|
}
|
|
|
|
_lastSpawnCollider = teleportSpawnCollider;
|
|
}
|
|
else
|
|
{
|
|
if (_lastSpawnCollider != null && _lastSpawnCollider.EnableWhenSelected)
|
|
{
|
|
_lastSpawnCollider.EnableWhenSelected.SetActive(false);
|
|
}
|
|
|
|
_lastSpawnCollider = null;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Enables or disables teleport graphical components.
|
|
/// </summary>
|
|
/// <param name="enableTarget">Whether to enable the teleport target object</param>
|
|
/// <param name="validTeleport">Whether the current teleport destination is valid</param>
|
|
private void EnableTeleportObjects(bool enableTarget, bool validTeleport)
|
|
{
|
|
_teleportTargetEnabled = enableTarget;
|
|
_teleportTargetValid = validTeleport;
|
|
|
|
// Enable / disable
|
|
|
|
if (_teleportTarget != null)
|
|
{
|
|
_teleportTarget.gameObject.SetActive(enableTarget);
|
|
}
|
|
|
|
// Set materials
|
|
|
|
if (ShowTargetAlsoWhenInvalid)
|
|
{
|
|
_teleportTarget.SetMaterialColor(validTeleport ? ValidMaterialColorTargets : InvalidMaterialColorTargets);
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Rotates the avatar around its vertical axis, where a positive angle turns it to the right and a negative angle to
|
|
/// the left.
|
|
/// </summary>
|
|
/// <param name="degrees">Degrees to rotate</param>
|
|
private void Rotate(float degrees)
|
|
{
|
|
if (!IsTeleporting && !IsOtherComponentTeleporting)
|
|
{
|
|
IsTeleporting = true;
|
|
|
|
Transform avatarTransform = Avatar.transform;
|
|
|
|
if (RotationType == UxrRotationType.Fade)
|
|
{
|
|
UxrManager.Instance.TeleportFadeColor = FadeRotationColor;
|
|
}
|
|
|
|
UxrManager.Instance.RotateLocalAvatar(degrees,
|
|
RotationType,
|
|
RotationSeconds,
|
|
null,
|
|
finished =>
|
|
{
|
|
IsTeleporting = false;
|
|
ControllerStart = RawControllerStart;
|
|
ControllerForward = RawControllerForward;
|
|
});
|
|
}
|
|
|
|
NotifyTeleportSpawnCollider(null);
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region Protected Types & Data
|
|
|
|
/// <summary>
|
|
/// Gets whether the avatar can currently receive input to step backwards.
|
|
/// </summary>
|
|
protected virtual bool CanBackStep => true;
|
|
|
|
/// <summary>
|
|
/// Gets whether the avatar can currently receive input to rotate around.
|
|
/// </summary>
|
|
protected virtual bool CanRotate => true;
|
|
|
|
/// <summary>
|
|
/// Gets whether other teleport component is currently teleporting the avatar.
|
|
/// </summary>
|
|
protected bool IsOtherComponentTeleporting => _otherAvatarTeleports != null && _otherAvatarTeleports.Any(otherTeleport => otherTeleport.IsTeleporting);
|
|
|
|
/// <summary>
|
|
/// Gets whether the component is currently allowed to teleport the avatar.
|
|
/// </summary>
|
|
protected bool IsAllowedToTeleport
|
|
{
|
|
get
|
|
{
|
|
if (IsTeleporting)
|
|
{
|
|
// Component is currently teleporting
|
|
return false;
|
|
}
|
|
|
|
if (UxrCameraWallFade.IsAvatarPeekingThroughGeometry(Avatar))
|
|
{
|
|
// Head is currently inside a wall. Avoid teleportation for "cheating".
|
|
return false;
|
|
}
|
|
|
|
Vector3 cameraPos = Avatar.CameraPosition;
|
|
Vector3 cameraToController = ControllerStart - cameraPos;
|
|
|
|
return !HasBlockingRaycastHit(cameraPos, cameraToController.normalized, cameraToController.magnitude, out RaycastHit hit);
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Gets the <see cref="LayerMask" /> used for ray-casting either valid or invalid teleport destinations.
|
|
/// </summary>
|
|
protected LayerMask LayerMaskRaycast => _layerMaskRaycast;
|
|
|
|
/// <summary>
|
|
/// Gets the up vector used to compute rotations so that it is always computed in the correct space.
|
|
/// </summary>
|
|
protected Vector3 UpVector => Avatar.transform.up;
|
|
|
|
/// <summary>
|
|
/// Gets or sets whether to parent the avatar to the destination object (<see cref="TeleportReference" />) after
|
|
/// teleporting.
|
|
/// This can also be overriden using a <see cref="UxrParentAvatarDestination" /> component.
|
|
/// </summary>
|
|
protected bool ParentToDestination
|
|
{
|
|
get => _parentToDestination;
|
|
set => _parentToDestination = value;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Gets or sets whether the component is currently teleporting the avatar.
|
|
/// </summary>
|
|
protected bool IsTeleporting { get; private set; }
|
|
|
|
/// <summary>
|
|
/// Gets the smoothed source of ray-casting when it starts on the controller.
|
|
/// </summary>
|
|
protected Vector3 ControllerStart { get; private set; }
|
|
|
|
/// <summary>
|
|
/// Gets the smoothed direction of ray-casting when starts on the controller.
|
|
/// </summary>
|
|
protected Vector3 ControllerForward { get; private set; }
|
|
|
|
/// <summary>
|
|
/// Gets or sets the transform that will be used as reference for <see cref="TeleportPosition" /> and
|
|
/// <see cref="TeleportDirection" /> to keep the relative positioning/orientation to while performing potential
|
|
/// transitions, such as fades, before the actual teleporting. It is usually assigned the transform of the object that
|
|
/// was hit with the destination raycast.
|
|
/// The reference transform is used to make teleport transitions work correctly when the avatar is on a moving object.
|
|
/// Without it, using absolute position and rotation only, would spawn the avatar with an incorrect offset due to the
|
|
/// delay the transition introduces before the teleport.
|
|
/// </summary>
|
|
protected Transform TeleportReference { get; private set; }
|
|
|
|
/// <summary>
|
|
/// Gets or sets the current teleport destination in <see cref="TeleportReference" /> space. If
|
|
/// <see cref="TeleportReference" /> is null, it will be considered as world-space position.
|
|
/// </summary>
|
|
protected Vector3 TeleportLocalPosition
|
|
{
|
|
get => _teleportLocalPosition;
|
|
set
|
|
{
|
|
_teleportLocalPosition = value;
|
|
|
|
if (_teleportTarget != null)
|
|
{
|
|
_teleportTarget.transform.position = TransformExt.GetWorldPosition(TeleportReference, value) + UpVector * TargetPlacementAboveHit;
|
|
_teleportTarget.OrientArrow(Quaternion.LookRotation(TransformExt.GetWorldDirection(TeleportReference, TeleportLocalDirection), UpVector));
|
|
}
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Gets or sets the current teleport direction in in <see cref="TeleportReference" /> space. If
|
|
/// <see cref="TeleportReference" /> is null, it will be considered as world-space rotation.
|
|
/// </summary>
|
|
protected Vector3 TeleportLocalDirection
|
|
{
|
|
get => _teleportLocalDirection;
|
|
set
|
|
{
|
|
_teleportLocalDirection = value;
|
|
|
|
if (_teleportTarget != null)
|
|
{
|
|
_teleportTarget.OrientArrow(Quaternion.LookRotation(TeleportReference != null ? TeleportReference.rotation * value : value, UpVector));
|
|
}
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Gets or sets teleport arrow's local rotation.
|
|
/// </summary>
|
|
protected Quaternion TeleportArrowLocalRotation
|
|
{
|
|
get => _teleportTarget.ReorientArrowLocalRotation;
|
|
set => _teleportTarget.ReorientArrowLocalRotation = value;
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region Private Types & Data
|
|
|
|
/// <summary>
|
|
/// Gets the raw unprocessed world position on the controller where the ray-casting starts.
|
|
/// </summary>
|
|
private Vector3 RawControllerStart
|
|
{
|
|
get
|
|
{
|
|
if (_useControllerForward && Avatar != null)
|
|
{
|
|
Transform forwardTransform = Avatar.GetControllerInputForward(_controllerHand);
|
|
return forwardTransform ? forwardTransform.position : transform.position;
|
|
}
|
|
|
|
return transform.position;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Gets the raw unprocessed world direction on the controller used for ray-casting.
|
|
/// </summary>
|
|
private Vector3 RawControllerForward
|
|
{
|
|
get
|
|
{
|
|
if (_useControllerForward && Avatar != null)
|
|
{
|
|
Transform forwardTransform = Avatar.GetControllerInputForward(_controllerHand);
|
|
return forwardTransform ? forwardTransform.forward : transform.forward;
|
|
}
|
|
|
|
return transform.forward;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Gets the translation transition in seconds depending on <see cref="TranslationType" />.
|
|
/// </summary>
|
|
private float TranslationSeconds
|
|
{
|
|
get
|
|
{
|
|
return _translationType switch
|
|
{
|
|
UxrTranslationType.Fade => _fadeTranslationSeconds,
|
|
UxrTranslationType.Smooth => _smoothTranslationSeconds,
|
|
_ => 0.0f
|
|
};
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Gets the rotation transition in seconds depending on <see cref="RotationType" />.
|
|
/// </summary>
|
|
private float RotationSeconds
|
|
{
|
|
get
|
|
{
|
|
return _rotationType switch
|
|
{
|
|
UxrRotationType.Fade => _fadeRotationSeconds,
|
|
UxrRotationType.Smooth => _smoothRotationSeconds,
|
|
_ => 0.0f
|
|
};
|
|
}
|
|
}
|
|
|
|
private const float RaycastAboveGround = 0.05f;
|
|
private const float RaycastLongDistance = 1000.0f;
|
|
private const float HeadRadius = 0.2f;
|
|
private const float DeltaTimeMultiplierFilterMin = 25.0f;
|
|
private const float DeltaTimeMultiplierFilterMax = 5.0f;
|
|
private const float MaxVerticalHeightDisparity = 0.2f;
|
|
private const float DestinationValidationSubdivisions = 8;
|
|
private const float DestinationValidationPositivesNeeded = 5;
|
|
private readonly List<Validator> _destinationValidators = new List<Validator>();
|
|
|
|
private List<UxrTeleportLocomotionBase> _otherAvatarTeleports;
|
|
|
|
private bool _isBackStepAvailable;
|
|
private bool _isValidTeleport;
|
|
private Vector3 _teleportLocalPosition;
|
|
private Vector3 _teleportLocalDirection;
|
|
private bool _teleportTargetEnabled;
|
|
private bool _teleportTargetValid;
|
|
private LayerMask _layerMaskRaycast = 0;
|
|
private UxrTeleportTarget _teleportTarget;
|
|
private UxrTeleportSpawnCollider _lastSpawnCollider;
|
|
private RaycastHit _hitInfo;
|
|
|
|
#endregion
|
|
}
|
|
} |