// --------------------------------------------------------------------------------------------------------------------
//
// Copyright (c) VRMADA, All rights reserved.
//
// --------------------------------------------------------------------------------------------------------------------
using System;
using System.Linq;
using UltimateXR.Avatar;
using UltimateXR.Core;
using UltimateXR.Core.Components.Composite;
using UltimateXR.Manipulation;
using UnityEngine;
namespace UltimateXR.Locomotion
{
///
/// Base class for locomotion components. Locomotion components enable different ways for an
/// to move around the scenario.
///
public abstract class UxrLocomotion : UxrAvatarComponent, IUxrLocomotionUpdater
{
#region Public Types & Data
///
///
/// Gets whether the locomotion updates the avatar each frame. An example of smooth locomotion is
/// where the user moves the avatar in an identical way to a FPS video-game.
/// An example of non-smooth locomotion is where the avatar is moved only on
/// specific occasions.
///
///
/// 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.
///
///
/// 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.
///
///
public abstract bool IsSmoothLocomotion { get; }
#endregion
#region Explicit IUxrLocomotionUpdater
///
void IUxrLocomotionUpdater.UpdateLocomotion()
{
UpdateLocomotion();
}
#endregion
#region Unity
///
/// Logs if there is a missing component upwards in the hierarchy.
///
protected override void Awake()
{
base.Awake();
if (Avatar == null)
{
UxrManager.LogMissingAvatarInHierarchyError(this);
}
}
#endregion
#region Protected Methods
///
/// Updates the locomotion and the avatar's position/orientation the component belongs to.
///
protected abstract void UpdateLocomotion();
///
/// 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.
///
/// The avatar to compute the raycast for
/// Ray origin
/// Ray direction
/// Raycast maximum distance
/// Raycast layer mask
/// Behaviour against trigger colliders
/// Result blocking raycast
/// Whether there is a blocking raycast returned in
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);
}
///
/// 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.
///
/// The avatar to compute the capsule cast for
/// The center of the sphere at the start of the capsule
/// The center of the sphere at the end of the capsule
/// The radius of the capsule
/// The direction into which to sweep the capsule
/// The max length of the sweep
/// A that is used to selectively ignore colliders when casting a capsule
/// Specifies whether this query should hit Triggers
/// Result blocking raycast
/// Whether there is a blocking raycast returned in
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
///
/// 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.
///
/// The avatar the ray-casting was computed for
/// Set of raycast hits to check
/// Result blocking raycast
/// Whether there is a blocking raycast returned in
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() == avatar)
{
// Filter out colliding against part of the avatar
continue;
}
UxrGrabbableObject grabbableObject = singleHit.collider.GetComponentInParent();
if (grabbableObject != null && UxrGrabManager.Instance.IsBeingGrabbedBy(grabbableObject, avatar))
{
// Filter out colliding against a grabbed object
continue;
}
outputHit = singleHit;
hasBlockingHit = true;
break;
}
return hasBlockingHit;
}
#endregion
}
}