// -------------------------------------------------------------------------------------------------------------------- // // 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 } }