// --------------------------------------------------------------------------------------------------------------------
//
// Copyright (c) VRMADA, All rights reserved.
//
// --------------------------------------------------------------------------------------------------------------------
using System;
using System.Collections.Generic;
using System.Linq;
using UltimateXR.Avatar.Controllers;
using UltimateXR.Avatar.Rig;
using UltimateXR.CameraUtils;
using UltimateXR.Core;
using UltimateXR.Core.Components;
using UltimateXR.Core.Settings;
using UltimateXR.Devices;
using UltimateXR.Devices.Integrations;
using UltimateXR.Devices.Visualization;
using UltimateXR.Extensions.System.Collections;
using UltimateXR.Extensions.Unity;
using UltimateXR.Locomotion;
using UltimateXR.Manipulation;
using UltimateXR.Manipulation.HandPoses;
using UltimateXR.UI;
using UnityEngine;
#if ULTIMATEXR_UNITY_XR_MANAGEMENT
using UnityEngine.SpatialTracking;
#endif
#pragma warning disable 414 // Disable warnings due to unused values
namespace UltimateXR.Avatar
{
///
///
/// Main class for avatars in the scene. An avatar is the visual representation of a user in the application.
/// The minimal representation requires a single camera that allows the user to look around. More elements
/// can be added such as hands to interact with the environment or a half/full body to have a more complete
/// representation.
///
///
/// A special avatar is the local avatar, which is the avatar controlled by the user using the headset and input
/// controllers. Non-local avatars are other avatars instantiated in the scene but not controlled by the user,
/// either other users through the network or special cases such as automated replays. A quick way to access the
/// local avatar is by using the UxrAvatar.LocalAvatar static property.
///
///
/// Although avatars may contain significant information and components, the avatar is not in charge of updating
/// itself.
/// Local avatars are updated by the added to the same GameObject where the
/// is. This allows to separate the update logic and functionality from the avatar
/// representation.
/// The standard avatar controller provided by UltimateXR is the
/// component. It provides high-level functionality to interact with the virtual world.
/// Different update logic can be created by programming a new component if
/// required.
///
///
/// Non-local avatars (Avatars whose UxrAvatar.AvatarMode is
/// ) can be updated by accessing their rig using
/// .
///
///
/// Local avatar update logic is handled automatically by the singleton without requiring
/// any user intervention.
///
///
[DisallowMultipleComponent]
public partial class UxrAvatar : UxrComponent
{
#region Inspector Properties/Serialized Fields
[SerializeField] private string _prefabGuid;
[SerializeField] private GameObject _parentPrefab;
[SerializeField] private UxrAvatarMode _avatarMode = UxrAvatarMode.Local;
[SerializeField] private UxrAvatarRenderModes _renderMode = UxrAvatarRenderModes.Avatar;
[SerializeField] private bool _showControllerHands = true;
[SerializeField] private List _avatarRenderers = new List();
[SerializeField] private UxrAvatarRigType _rigType = UxrAvatarRigType.HandsOnly;
[SerializeField] private bool _rigExpandedInitialized;
[SerializeField] private bool _rigFoldout = true;
[SerializeField] private UxrAvatarRig _rig = new UxrAvatarRig();
[SerializeField] private UxrAvatarRigInfo _rigInfo = new UxrAvatarRigInfo();
[SerializeField] private bool _handPosesFoldout = true;
[SerializeField] private List _handPoses;
[SerializeField] private UxrHandPoseAsset _defaultHandPose;
#endregion
#region Public Types & Data
///
/// Event called right after after the local avatar called its Start().
///
public static event EventHandler LocalAvatarStarted;
///
/// Event called right after after the local avatar changed. called its Start(). This is useful when the avatar is
/// instantiated
/// in a deferred way, such as a networking environment, and the avatar isn't ready during Awake()/OnEnable()/Start().
///
public static event EventHandler LocalAvatarChanged;
///
/// Event called right before an is about to be moved.
///
public static event EventHandler GlobalAvatarMoving;
///
/// Event called right after an was moved.
///
public static event EventHandler GlobalAvatarMoved;
///
/// Event called right before the avatar is about to change a hand's pose.
///
public event EventHandler HandPoseChanging;
///
/// Event called right after the avatar changed a hand's pose.
///
public event EventHandler HandPoseChanged;
///
/// Event called right before before the avatar goes through an of the updating process.
///
public event EventHandler AvatarUpdating;
///
/// Event called right after the avatar goes through an of the updating process.
///
public event EventHandler AvatarUpdated;
///
/// Gets the local avatar or null if there is none.
/// The local avatar is the avatar controller by the user. Non-local avatars are other avatars in a multiplayer
/// session or any avatar in a replay. The local avatar is updated through user input, while non-local avatars are
/// updated externally. In multiplayer sessions, for example, non-local avatars are updated using network transform
/// syncing.
///
public static UxrAvatar LocalAvatar => AllComponents.FirstOrDefault(c => c.AvatarMode == UxrAvatarMode.Local);
///
/// Gets the camera component of the local avatar. If there is no local avatar it will return null.
///
public static Camera LocalAvatarCamera
{
get
{
UxrAvatar localAvatar = LocalAvatar;
return localAvatar != null ? localAvatar.CameraComponent : null;
}
}
///
/// Gets the local avatar camera, if it exists, or the first camera component found in the enabled avatars.
/// This can be used in avatars that are updated externally using
/// but still are rendering using the camera.
/// If no avatar with a camara was found, it returns Camera.main.
///
public static Camera LocalOrFirstEnabledCamera
{
get
{
foreach (UxrAvatar avatar in EnabledComponents)
{
if (avatar.CameraComponent != null && avatar.CameraComponent.enabled)
{
return avatar.CameraComponent;
}
}
return Camera.main;
}
}
///
/// Gets the controller input of the local avatar. If there is a local avatar present, the controller input is
/// guaranteed to be non-null. If there is no input available, the controller input will simply return a dummy object
/// that will generate no input events. It will return null if there is no local avatar currently in the scene.
///
public static UxrControllerInput LocalAvatarInput => LocalAvatar != null ? LocalAvatar.ControllerInput : null;
///
/// Gets the standard avatar controller component if it exists.
///
public static UxrStandardAvatarController LocalStandardAvatarController => LocalAvatar != null ? LocalAvatar.GetCachedComponent() : null;
///
/// Gets the avatar rig.
///
public UxrAvatarRig AvatarRig => _rig;
///
/// Gets the avatar rig information.
///
public UxrAvatarRigInfo AvatarRigInfo
{
get
{
if (_rigInfo.SerializedVersion != UxrAvatarRigInfo.CurrentVersion)
{
// We have new serialization data from a newer version, so compute it.
CreateRigInfo();
}
return _rigInfo;
}
}
///
/// Gets the first enabled controller input belonging to the avatar. If there is no enabled
/// controller input available, to avoid null checks, it returns a dummy component that sends no
/// input events.
///
public UxrControllerInput ControllerInput
{
get
{
if (AvatarMode == UxrAvatarMode.UpdateExternally)
{
// This makes sure that when synchronizing multiplayer/replays we use the correct input model
return _externalControllerInput ?? gameObject.GetOrAddComponent();
}
// First look for a controller that is not dummy nor gamepad:
UxrControllerInput controllerInput = UxrControllerInput.GetComponents(this).FirstOrDefault(i => i.GetType() != typeof(UxrDummyControllerInput) && i.GetType() != typeof(UxrGamepadInput));
// No controllers found? Try gamepad
if (controllerInput == null)
{
controllerInput = UxrControllerInput.GetComponents(this).FirstOrDefault(i => i.GetType() == typeof(UxrGamepadInput));
}
// No controllers found? Return dummy to avoid null reference exceptions.
if (controllerInput == null)
{
UxrDummyControllerInput inputDummy = gameObject.GetOrAddComponent();
return inputDummy;
}
if (_externalControllerInput != controllerInput)
{
// Propagate controller input
OnControllerInputChanged(controllerInput);
}
return controllerInput;
}
}
///
/// Gets whether the current controller input is a dummy controller input component. Dummy controller input is used
/// when there is no VR input device available in order to avoid null checking.
///
public bool HasDummyControllerInput => ControllerInput.GetType() == typeof(UxrDummyControllerInput);
///
/// Gets the avatar's camera position.
///
public Vector3 CameraPosition => CameraComponent != null ? CameraComponent.transform.position : Vector3.zero;
///
/// Gets the avatar's camera floor position: the camera position projected on the floor. This is useful
/// when testing if the avatar is near a well known place or to trigger certain events.
///
public Vector3 CameraFloorPosition
{
get
{
Vector3 cameraPosition = CameraPosition;
return new Vector3(cameraPosition.x, transform.position.y, cameraPosition.z);
}
}
///
/// Gets the avatar's camera forward vector.
///
public Vector3 CameraForward => CameraComponent != null ? CameraComponent.transform.forward : Vector3.zero;
///
/// Gets the avatar's camera forward vector projected onto the XZ plane, the floor.
///
public Vector3 ProjectedCameraForward => CameraComponent != null ? Vector3.ProjectOnPlane(CameraComponent.transform.forward, transform.up).normalized : Vector3.zero;
///
/// Gets the currently enabled controller inputs belonging to the avatar, except for any
/// component.
///
public IEnumerable EnabledControllerInputs => UxrControllerInput.GetComponents(this).Where(i => i.GetType() != typeof(UxrDummyControllerInput));
///
/// Gets all (enabled or disabled) controller inputs belonging to the avatar, except for any
/// component.
///
public IEnumerable AllControllerInputs => UxrControllerInput.GetComponents(this, true).Where(i => i.GetType() != typeof(UxrDummyControllerInput));
///
/// Returns all available enabled tracking devices.
///
public IEnumerable TrackingDevices => UxrTrackingDevice.GetComponents(this);
///
/// Gets the first enabled tracking device that inherits from ,
/// meaning a standard left+right controller setup.
///
public IUxrControllerTracking FirstControllerTracking => (IUxrControllerTracking)TrackingDevices.FirstOrDefault(i => i is IUxrControllerTracking);
///
/// Gets all the enabled components in the avatar.
///
public IEnumerable FingerTips => UxrFingerTip.GetComponents(this);
///
/// Gets all the enabled components in the avatar.
///
public IEnumerable LaserPointers => UxrLaserPointer.GetComponents(this);
///
/// Gets the avatar prefab Guid. It is stored instead of the GameObject because storing the GameObject would make an
/// instantiated avatar reference itself, instead of the source prefab.
///
public string PrefabGuid => _prefabGuid;
///
/// Gets the parent prefab GameObject, if it exists.
///
public GameObject ParentPrefab => _parentPrefab;
///
/// Gets the 's component.
///
public UxrAvatar ParentAvatarPrefab => ParentPrefab != null ? ParentPrefab.GetComponent() : null;
///
/// Gets whether the avatar's prefab has a parent avatar prefab.
///
public bool IsPrefabVariant => ParentPrefab != null;
///
/// Gets the avatar rig type.
///
public UxrAvatarRigType AvatarRigType => _rigType;
///
/// Gets the avatar's camera component.
///
public Camera CameraComponent
{
get
{
if (_camera == null || _camera.enabled == false)
{
Camera newCamera = GetComponentInChildren();
if (newCamera == null)
{
newCamera = GetComponentInChildren(true);
}
if (newCamera != null)
{
_camera = newCamera;
}
}
return _camera;
}
}
///
/// Gets the avatar's camera transform.
///
public Transform CameraTransform => CameraComponent ? CameraComponent.transform : null;
///
/// Gets the avatar's camera fade component. If the camera doesn't currently have one, it will be added.
///
public UxrCameraFade CameraFade => CameraComponent.gameObject.GetOrAddComponent();
///
/// Gets the renderers used by the avatar, excluding controllers and everything related to them.
///
public IEnumerable AvatarRenderers => _avatarRenderers;
///
/// Gets the left hand.
///
public UxrAvatarHand LeftHand => AvatarRig.LeftArm.Hand;
///
/// Gets the right hand.
///
public UxrAvatarHand RightHand => AvatarRig.RightArm.Hand;
///
/// Gets the left hand bone.
///
public Transform LeftHandBone => AvatarRig.LeftArm.Hand.Wrist;
///
/// Gets the right hand bone.
///
public Transform RightHandBone => AvatarRig.RightArm.Hand.Wrist;
///
/// Gets the left hand's grabber component. Null if no left component was found.
///
public UxrGrabber LeftGrabber
{
get
{
#if UNITY_EDITOR
if (Application.isEditor && !Application.isPlaying)
{
return GetComponentsInChildren().FirstOrDefault(g => g.Side == UxrHandSide.Left);
}
#endif
return UxrGrabber.GetComponents(this).FirstOrDefault(g => g.Side == UxrHandSide.Left);
}
}
///
/// Gets the right hand's grabber component. Null if no right component was found.
///
public UxrGrabber RightGrabber
{
get
{
#if UNITY_EDITOR
if (Application.isEditor && !Application.isPlaying)
{
return GetComponentsInChildren().FirstOrDefault(g => g.Side == UxrHandSide.Right);
}
#endif
return UxrGrabber.GetComponents(this).FirstOrDefault(g => g.Side == UxrHandSide.Right);
}
}
///
/// Gets the default hand pose name or null if there isn't any default hand pose set.
///
public string DefaultHandPoseName => DefaultHandPose != null ? DefaultHandPose.name : null;
///
/// Gets or sets whether the avatar camera is driven by the VR headset. It can be useful when implementing non-VR
/// modes.
///
public bool CameraTrackingEnabled
{
get => _cameraTrackingEnabled;
set
{
_cameraTrackingEnabled = value;
#if ULTIMATEXR_UNITY_XR_MANAGEMENT
if (CameraController != null)
{
Camera[] avatarCameras = CameraController.GetComponentsInChildren();
foreach (Camera camera in avatarCameras)
{
#if ULTIMATEXR_USE_UNITYINPUTSYSTEM_SDK
TrackedPoseDriver inputSystemPoseDriver = camera.GetComponent();
if (inputSystemPoseDriver != null)
{
inputSystemPoseDriver.enabled = value;
}
#endif
UnityEngine.SpatialTracking.TrackedPoseDriver trackedPoseDriver = camera.GetComponent();
if (trackedPoseDriver != null)
{
trackedPoseDriver.enabled = value;
}
}
}
#endif
}
}
///
/// Gets the of the camera controller (the parent transform of the avatar's camera).
///
public Transform CameraController { get; private set; }
///
/// Gets the avatar controller, responsible for updating the avatar.
///
public UxrAvatarController AvatarController => GetCachedComponent();
///
/// Gets or sets the avatar operating mode.
///
public UxrAvatarMode AvatarMode
{
get => _avatarMode;
set
{
bool localAvatarChanged = (ReferenceEquals(this, s_localAvatar) && value != UxrAvatarMode.Local) || (!ReferenceEquals(this, s_localAvatar) && value == UxrAvatarMode.Local);
_avatarMode = value;
// We do this instead of SetActive(false) on the CameraController because the camera
// drives the body IK and some network components can be added to the camera GameObject
// which would be ignored in that case.
// TODO: Check if there are other components that should be looked for and disabled
bool isLocalAvatar = value == UxrAvatarMode.Local;
if (CameraController)
{
Camera[] avatarCameras = CameraController.GetComponentsInChildren();
foreach (Camera camera in avatarCameras)
{
camera.enabled = isLocalAvatar;
#if ULTIMATEXR_UNITY_XR_MANAGEMENT
if (camera.TryGetComponent(out UnityEngine.SpatialTracking.TrackedPoseDriver trackedPoseDriver))
{
trackedPoseDriver.enabled = isLocalAvatar && CameraTrackingEnabled;
}
#endif
if (camera.TryGetComponent(out AudioListener audioListener))
{
audioListener.enabled = isLocalAvatar;
}
}
}
if (value == UxrAvatarMode.Local && _started && !_localStartedInvoked)
{
// Avatar had a delayed switch to local, probably due to a spawn in multiplayer
OnLocalAvatarStarted(new UxrAvatarStartedEventArgs(this));
}
if (localAvatarChanged || !s_localAvatarReferenceInitialized)
{
OnLocalAvatarChanged(new UxrAvatarEventArgs(value == UxrAvatarMode.Local ? this : null));
}
}
}
///
/// Gets or sets the avatar render mode.
///
public UxrAvatarRenderModes RenderMode
{
get => _renderMode;
set
{
if (_renderMode != value || !_started)
{
// Get the enabled controllers and call the synchronized method, so that when it is played back in
// multiplayer or replays it is done using the original UxrControllerInputs.
SetAvatarRenderMode(value, UxrControllerInput.GetComponents(this).ToList());
}
}
}
///
/// Gets or sets whether the hands will be shown when the controllers are being rendered.
///
public bool ShowControllerHands
{
get => _showControllerHands;
set
{
if (_showControllerHands != value || !_started)
{
BeginSync();
_showControllerHands = value;
foreach (UxrControllerInput controllerInput in UxrControllerInput.GetComponents(this))
{
if (controllerInput.LeftController3DModel)
{
controllerInput.LeftController3DModel.IsHandVisible = _showControllerHands;
}
if (controllerInput.RightController3DModel)
{
controllerInput.RightController3DModel.IsHandVisible = _showControllerHands;
}
}
EndSyncProperty(value);
}
}
}
///
/// Gets or sets the default hand pose. The default hand pose is the pose a hand will have when no other event that
/// changes the pose is happening. Usually it is a neutral, relaxed pose.
///
public UxrHandPoseAsset DefaultHandPose
{
get => GetDefaultPoseInHierarchy();
set => _defaultHandPose = value;
}
#endregion
#region Public Methods
///
/// Enables or disables the components in the avatar.
///
/// Whether to enable or disable the components.
public void EnableFingerTips(bool enable)
{
UxrFingerTip.GetComponents(this, true).ForEach(t => t.enabled = enable);
}
///
/// Enables or disables the components in the avatar.
///
/// Whether to enable or disable the components.
public void EnableLaserPointers(bool enable)
{
UxrLaserPointer.GetComponents(this, true).ForEach(t => t.gameObject.SetActive(enable));
}
///
/// Enables or disables the components in the avatar.
///
/// Whether to enable or disable the components.
public void EnableLocomotion(bool enable)
{
UxrLocomotion.GetComponents(this, true).ForEach(t => t.enabled = enable);
}
///
/// Gets the transform in the given hand controller that points forward. The controller needs to have a 3D model
/// assigned to it, which all controllers in the framework have.
///
/// Hand to get the forward of
///
/// The transform describing the controller forward direction (use to get the
/// actual vector). Null if there is no input controller available or if it doesn't have a 3D model assigned.
///
public Transform GetControllerInputForward(UxrHandSide handSide)
{
if (!HasDummyControllerInput)
{
UxrController3DModel model = ControllerInput.GetController3DModel(handSide);
if (model != null)
{
return model.Forward;
}
}
return null;
}
///
/// Checks if the avatar is looking at a point in space.
///
/// World position to check.
///
/// Radius threshold in viewport coordinates [0.0, 1.0] above which it isn't
/// considered looking at anymore.
///
/// Whether the avatar is looking at the specific point in space.
public bool IsLookingAt(Vector3 worldPosition, float radiusFromViewportCenterAllowed)
{
Vector3 viewPortPoint = CameraComponent.WorldToViewportPoint(worldPosition);
return Vector3.Distance(new Vector3(0.5f, 0.5f, viewPortPoint.z), viewPortPoint) <= radiusFromViewportCenterAllowed;
}
///
/// Gets the initial local position of the given avatar bone.
///
/// Bone to get the initial local position of.
/// Gets the initial local position.
///
/// Boolean telling whether the position was successfully retrieved or if the given bone was not found in the
/// avatar.
///
public bool GetInitialBoneLocalPosition(Transform bone, out Vector3 position)
{
return _initialBoneLocalPositions.TryGetValue(bone, out position);
}
///
/// Gets the initial local rotation of the given avatar bone.
///
/// Bone to get the initial local rotation of.
/// Gets the initial local rotation.
///
/// Boolean telling whether the rotation was successfully retrieved or if the given bone was not found in the
/// avatar.
///
public bool GetInitialBoneLocalRotation(Transform bone, out Quaternion rotation)
{
return _initialBoneLocalRotations.TryGetValue(bone, out rotation);
}
///
/// Places the camera at floor level.
///
public void SetCameraAtFloorLevel()
{
if (_camera && CameraController)
{
Transform cameraTransform = CameraController.transform;
Vector3 cameraControllerPos = cameraTransform.position;
cameraControllerPos.y = transform.position.y + _startCameraControllerHeight - _startCameraHeight;
cameraTransform.position = cameraControllerPos;
}
}
///
/// Gets the rig information for the given arm.
///
/// Which arm to get
/// rig information
public UxrAvatarArm GetArm(UxrHandSide handSide)
{
return handSide == UxrHandSide.Left ? AvatarRig.LeftArm : AvatarRig.RightArm;
}
///
/// Gets the rig information for the given hand.
///
/// Which hand to get
/// rig information
public UxrAvatarHand GetHand(UxrHandSide handSide)
{
return handSide == UxrHandSide.Left ? LeftHand : RightHand;
}
///
/// Gets the given hand bone.
///
/// Which hand bone to get
/// Bone transform
public Transform GetHandBone(UxrHandSide handSide)
{
return handSide == UxrHandSide.Left ? LeftHandBone : RightHandBone;
}
///
/// Gets the component for the given hand.
///
/// Which hand to get the component for
/// component or null if no component was found for the given side
public UxrGrabber GetGrabber(UxrHandSide handSide)
{
return handSide == UxrHandSide.Left ? LeftGrabber : RightGrabber;
}
///
/// Sets up the using information from the if it describes a humanoid
/// avatar.
///
/// Whether any humanoid data was found and assigned
public bool SetupRigElementsFromAnimator()
{
Animator[] animators = GetComponentsInChildren();
foreach (Animator animator in animators)
{
if (UxrAvatarRig.SetupRigElementsFromAnimator(_rig, animator))
{
return true;
}
}
return false;
}
///
/// Clears all the element references.
///
public void ClearRigElements()
{
_rig?.ClearRigElements();
}
///
/// Tries to infer rig elements by doing some checks on names and bone hierarchy.
/// This is useful when we have a rig that has no full humanoid avatar set up on its animator .
///
public void TryToInferMissingRigElements()
{
UxrAvatarRig.TryToInferMissingRigElements(_rig, GetAllAvatarRendererComponents());
}
///
/// Gets all components except those hanging from a object,
/// which are renderers that are not part of the avatar itself but part of the supported input controllers / controller
/// hands that can be rendered too.
///
public IEnumerable GetAllAvatarRendererComponents()
{
SkinnedMeshRenderer[] skins = GetComponentsInChildren(true);
return skins.Where(s => s.SafeGetComponentInParent() == null);
}
///
/// Gets the component chain. This is the avatar's own component
/// followed by all parent prefab components up to the root parent prefab.
/// If the avatar is an instance the first component will reference its own component instantiated in the scene,
/// not the avatar component in the source prefab.
/// can be used to traverse the prefab chain information including the source
/// prefab.
///
///
/// Upwards component chain. If the avatar is a prefab itself, it will be the first component
/// in the list. If the avatar is an instance, the first component will be the one from the instance instead of the
/// source prefab. Components cannot store references to their own prefab because on instantiation they will
/// automatically point to the instantiated component. can be used to traverse the
/// prefab information chain, including the source prefab.
///
///
public IEnumerable GetAvatarChain()
{
yield return this;
UxrAvatar current = ParentAvatarPrefab;
while (current != null && current != this)
{
yield return current;
current = current.ParentAvatarPrefab;
}
}
///
/// Gets the prefab GUID chain. This is the source prefab Guid followed by all parent prefab GUIDs up to the root
/// parent prefab.
///
///
/// Upwards prefab GUID chain. If the avatar is a prefab itself, it will be the first GUID in the list.
///
///
public IEnumerable GetPrefabGuidChain()
{
if (!string.IsNullOrEmpty(_prefabGuid))
{
yield return _prefabGuid;
UxrAvatar current = ParentAvatarPrefab;
while (current != null && current != this)
{
yield return current.PrefabGuid;
current = current.ParentAvatarPrefab;
}
}
}
///
/// Gets the parent prefab chain. These are all parent prefabs up to the root parent prefab.
///
///
/// Upwards prefab chain starting by the parent prefab.
///
public IEnumerable GetParentPrefabChain()
{
UxrAvatar current = ParentAvatarPrefab;
while (current != null && current != this)
{
yield return current;
current = current.ParentAvatarPrefab;
}
}
///
/// Gets the parent prefab that stores a given hand pose.
///
/// Hand pose to look for
/// Parent prefab that stores the given hand pose or null if the pose was not found
public UxrAvatar GetParentPrefab(UxrHandPoseAsset handPoseAsset)
{
return GetParentPrefabChain().FirstOrDefault(avatar => avatar._handPoses.Contains(handPoseAsset));
}
///
/// Gets the parent prefab that stores a given hand pose.
///
/// Hand pose name to look for
/// Parent prefab that stores the given hand pose or null if the pose was not found
public UxrAvatar GetParentPrefab(string poseName)
{
return GetParentPrefabChain().FirstOrDefault(avatar => avatar._handPoses.Any(p => p != null && p.name == poseName));
}
///
/// Checks whether the avatar shares the same prefab or is a prefab variant of another avatar.
///
/// Avatar instance or prefab. If it is an instance, it will be checked against the instance's prefab
/// Whether the avatar shares the same prefab or is a prefab variant
public bool IsPrefabCompatibleWith(UxrAvatar avatar)
{
foreach (string guid in GetPrefabGuidChain())
{
if (_prefabGuid == guid)
{
return true;
}
}
return false;
}
///
/// Gets all hand poses in the avatar, including those inherited from parent prefabs. If more than one pose exists
/// with the same name, only the overriden will be in the results.
///
/// List of all poses in the avatar and any of its parent prefabs
public IEnumerable GetAllHandPoses()
{
Dictionary poses = new Dictionary();
foreach (UxrAvatar avatar in GetAvatarChain())
{
foreach (UxrHandPoseAsset handPoseAsset in avatar._handPoses)
{
if (handPoseAsset != null && !poses.ContainsKey(handPoseAsset.name))
{
poses.Add(handPoseAsset.name, handPoseAsset);
}
}
}
return poses.Values.OrderBy(p => p.name);
}
///
/// Gets the hand poses in the avatar, not counting those inherited from parent prefabs.
///
/// Pose names
public IEnumerable GetHandPoses()
{
return _handPoses.Where(p => p != null).OrderBy(p => p.name);
}
///
/// Gets a given hand pose. It can happen that the pose name is present in a prefab/instance and at the same time also
/// in any of the parent prefabs upwards in the hierarchy. In this case the hand pose in the child will always have
/// priority so that child prefabs can override poses that they inherit from parent prefabs.
///
/// Pose to get
/// Whether to look for the pose in any prefab above the first prefab in the hierarchy.
/// Hand pose object
public UxrHandPoseAsset GetHandPose(string poseName, bool usePrefabInheritance = true)
{
foreach (UxrAvatar avatar in GetAvatarChain())
{
foreach (UxrHandPoseAsset handPoseAsset in avatar.GetHandPoses())
{
if (handPoseAsset.name == poseName)
{
return handPoseAsset;
}
}
if (!usePrefabInheritance)
{
break;
}
}
// Not found
return null;
}
///
/// Checks if a given pose has been overriden in any point in the prefab hierarchy.
///
/// Pose name
/// Returns the avatar prefab that has the original pose
/// Whether the given pose has been overriden at any point in the prefab hierarchy
public bool IsHandPoseOverriden(string poseName, out UxrAvatar avatarPrefab)
{
avatarPrefab = null;
int count = 0;
foreach (UxrAvatar avatar in GetAvatarChain())
{
if (avatar._handPoses.Any(p => p != null && p.name == poseName))
{
count++;
if (count == 2)
{
avatarPrefab = avatar;
return true;
}
}
}
return false;
}
///
/// Checks if a given pose has been overriden in the prefab hierarchy.
///
/// Hand pose
/// Whether the given pose has been overriden in the prefab hierarchy
public bool IsHandPoseOverriden(UxrHandPoseAsset handPoseAsset)
{
// Traverse upwards in the prefab hierarchy
foreach (UxrAvatar avatar in GetAvatarChain())
{
if (avatar.GetHandPoses().Contains(handPoseAsset))
{
// Found pose itself
return false;
}
if (avatar._handPoses.Any(p => p != null && p.name == handPoseAsset.name))
{
// Found pose name, which is an override
return true;
}
}
return false;
}
///
/// Gets a given hand pose. This method is intended for use at runtime to animate the avatars. While
/// uses , uses
/// .
/// The main differences are:
///
/// -
/// objects contain transforms that require less operations and are valid
/// only for this avatar. They are high-performant and cached at the beginning, making them only available at
/// runtime. They are used to animate the avatar hands at runtime.
///
/// -
/// objects contain transforms that are independent of the coordinate system
/// used.
/// They require more operations but can be applied to any avatar. They are mainly used in editor mode.
///
///
///
///
/// Hand pose
public UxrRuntimeHandPose GetRuntimeHandPose(string poseName)
{
if (_cachedRuntimeHandPoses != null && !string.IsNullOrEmpty(poseName) && _cachedRuntimeHandPoses.TryGetValue(poseName, out UxrRuntimeHandPose handPose))
{
return handPose;
}
return null;
}
///
/// Gets the current runtime hand pose.
///
/// Which hand to retrieve
/// Runtime hand pose or null if there isn't any
public UxrRuntimeHandPose GetCurrentRuntimeHandPose(UxrHandSide handSide)
{
HandState handState = handSide == UxrHandSide.Left ? _leftHandState : _rightHandState;
return handState.CurrentHandPose;
}
///
/// Gets the current hand pose blend value, which is the interpolation value in a blend pose type.
///
/// Which side to retrieve
/// Blend value [0.0, 1.0]
public float GetCurrentHandPoseBlendValue(UxrHandSide handSide)
{
HandState handState = handSide == UxrHandSide.Left ? _leftHandState : _rightHandState;
return handState.CurrentBlendValue;
}
///
/// Sets the currently active pose for a given hand in the avatar at runtime. Blending is used to transition between
/// poses smoothly, which means the pose is not immediately adopted. To adopt a pose immediately at a given instant use
/// instead.
///
/// Which hand the new pose will be applied to
/// The new pose name
/// The blend value if the pose is a blend pose
///
/// Whether the change should generate events (,
/// , , ).
///
/// Whether the pose was found
public bool SetCurrentHandPose(UxrHandSide handSide, string poseName, float blendValue = 0.0f, bool propagateEvents = true)
{
if (string.IsNullOrEmpty(poseName))
{
return false;
}
UxrRuntimeHandPose handPose = GetRuntimeHandPose(poseName);
if (handPose == null)
{
if (UxrGlobalSettings.Instance.LogLevelAvatar >= UxrLogLevel.Warnings)
{
Debug.LogWarning($"{UxrConstants.AvatarModule} Pose {poseName} was not found in avatar {name}");
}
return false;
}
HandState handState = handSide == UxrHandSide.Left ? _leftHandState : _rightHandState;
UxrAvatarHandPoseChangeEventArgs avatarHandPoseChangeArgs = new UxrAvatarHandPoseChangeEventArgs(handSide, poseName, blendValue);
if (!handState.IsChange(avatarHandPoseChangeArgs))
{
return true;
}
// This method will be synchronized through network
BeginSync();
if (propagateEvents)
{
OnHandPoseChanging(avatarHandPoseChangeArgs);
}
handState.SetPose(handPose, blendValue);
if (propagateEvents)
{
OnHandPoseChanged(avatarHandPoseChangeArgs);
}
EndSyncMethod(new object[] { handSide, poseName, blendValue, propagateEvents });
return true;
}
///
/// Sets the currently active pose blend value, if the current pose is .
///
/// Which hand the new pose will be applied to
/// The blend value if the pose is a blend pose
///
/// Whether the change should generate events (,
/// , , ).
///
public void SetCurrentHandPoseBlendValue(UxrHandSide handSide, float blendValue, bool propagateEvents = true)
{
HandState handState = handSide == UxrHandSide.Left ? _leftHandState : _rightHandState;
UxrAvatarHandPoseChangeEventArgs avatarHandPoseChangeArgs = new UxrAvatarHandPoseChangeEventArgs(handSide, handState.CurrentHandPoseName, blendValue);
if (!handState.IsChange(avatarHandPoseChangeArgs))
{
return;
}
if (propagateEvents)
{
OnHandPoseChanging(avatarHandPoseChangeArgs);
}
handState.SetPose(handState.CurrentHandPose, blendValue);
if (handSide == UxrHandSide.Left)
{
_leftHandState.Update(this, UxrHandSide.Left, Time.deltaTime);
}
if (handSide == UxrHandSide.Right)
{
_rightHandState.Update(this, UxrHandSide.Right, Time.deltaTime);
}
if (propagateEvents)
{
OnHandPoseChanged(avatarHandPoseChangeArgs);
}
}
///
/// Adopts a given hand pose by changing the transforms immediately. The bones may be overriden at any other point or
/// the next frame if there is a hand pose currently enabled using .
///
/// Which hand to apply the pose to
/// Hand pose name
/// Blend pose to use if the hand pose is
public void SetCurrentHandPoseImmediately(UxrHandSide handSide, string poseName, UxrBlendPoseType blendPoseType = UxrBlendPoseType.None)
{
if (string.IsNullOrEmpty(poseName))
{
return;
}
UxrHandPoseAsset handPoseAsset = GetHandPose(poseName);
if (handPoseAsset != null)
{
SetCurrentHandPoseImmediately(handSide, handPoseAsset, blendPoseType);
}
}
///
/// Adopts a given hand pose by changing the transforms immediately. The bones may be overriden at any other point or
/// the next frame if there is a hand pose currently enabled using .
///
/// Which hand to apply the pose to
/// Hand pose
/// Blend pose to use if the hand pose is
public void SetCurrentHandPoseImmediately(UxrHandSide handSide, UxrHandPoseAsset handPoseAsset, UxrBlendPoseType blendPoseType = UxrBlendPoseType.None)
{
UxrAvatarRig.UpdateHandUsingDescriptor(GetHand(handSide),
handPoseAsset.GetHandDescriptor(handSide, handPoseAsset.PoseType, blendPoseType),
AvatarRigInfo.GetArmInfo(handSide).HandUniversalLocalAxes,
AvatarRigInfo.GetArmInfo(handSide).FingerUniversalLocalAxes);
}
#endregion
#region Internal Methods
///
/// Updates the hand transforms using their current pose information using smooth blending.
///
internal void UpdateHandPoseTransforms()
{
_leftHandState.Update(this, UxrHandSide.Left, Time.deltaTime);
_rightHandState.Update(this, UxrHandSide.Right, Time.deltaTime);
}
#endregion
#region Unity
///
/// Initializes the avatar.
///
protected override void Awake()
{
base.Awake();
// Make sure the UxrManager singleton is available at this point.
UxrManager.Instance.Poke();
_camera = GetComponentInChildren();
// Find Camera controller
if (_camera != null)
{
CameraController = _camera.transform.parent;
}
while (CameraController != null && CameraController.parent != transform)
{
CameraController = CameraController.parent;
}
if (CameraController.parent != transform)
{
if (UxrGlobalSettings.Instance.LogLevelAvatar >= UxrLogLevel.Warnings)
{
Debug.LogWarning($"{UxrConstants.AvatarModule} Error finding Camera Controller. Camera Controller is a GameObject that needs to be child of the avatar and have the avatar camera as a child");
}
}
if (_camera != null)
{
_startCameraHeight = _camera.transform.position.y - transform.position.y;
}
if (CameraController != null)
{
_startCameraControllerHeight = CameraController.position.y - transform.position.y;
}
// We do the following because we update the skin bones manually
foreach (Renderer avatarRenderer in _avatarRenderers)
{
SkinnedMeshRenderer skin = avatarRenderer as SkinnedMeshRenderer;
if (skin != null)
{
skin.updateWhenOffscreen = true;
}
}
// Compute initial bone positions/rotations
_initialBoneLocalRotations = new Dictionary();
_initialBoneLocalPositions = new Dictionary();
foreach (Renderer avatarRenderer in _avatarRenderers)
{
SkinnedMeshRenderer skin = avatarRenderer as SkinnedMeshRenderer;
if (skin != null)
{
foreach (Transform bone in skin.bones)
{
if (bone != null && _initialBoneLocalRotations.ContainsKey(bone) == false)
{
_initialBoneLocalRotations.Add(bone, bone.localRotation);
_initialBoneLocalPositions.Add(bone, bone.localPosition);
}
}
}
}
// Try to infer missing elements
TryToInferMissingRigElements();
if (AvatarRigInfo.SerializedVersion != UxrAvatarRigInfo.CurrentVersion)
{
CreateRigInfo();
}
// Subscribe to device events
foreach (UxrTrackingDevice tracking in UxrTrackingDevice.GetComponents(this, true))
{
tracking.DeviceConnected += Tracking_DeviceConnected;
}
foreach (UxrControllerInput controllerInput in UxrControllerInput.GetComponents(this, true))
{
controllerInput.DeviceConnected += ControllerInput_DeviceConnected;
}
// Cache hand poses by name
CreateHandPoseCache();
}
///
/// Makes sure the rig constructor is called when the component is reset.
///
protected override void Reset()
{
_rig = new UxrAvatarRig();
_rigInfo = new UxrAvatarRigInfo();
}
///
/// Additional avatar initialization.
///
protected override void Start()
{
base.Start();
#if ULTIMATEXR_UNITY_XR_MANAGEMENT
// New Unity XR requires TrackedPoseDriver component in cameras
if (CameraController != null)
{
Camera[] avatarCameras = CameraController.GetComponentsInChildren();
foreach (Camera camera in avatarCameras)
{
bool hasInputSystemPoseDriver = false;
#if ULTIMATEXR_USE_UNITYINPUTSYSTEM_SDK
TrackedPoseDriver inputSystemPoseDriver = camera.GetComponent();
hasInputSystemPoseDriver = inputSystemPoseDriver != null;
if (inputSystemPoseDriver != null)
{
inputSystemPoseDriver.enabled = CameraTrackingEnabled;
}
#endif
UnityEngine.SpatialTracking.TrackedPoseDriver trackedPoseDriver = camera.GetComponent();
if (trackedPoseDriver == null && !hasInputSystemPoseDriver)
{
trackedPoseDriver = camera.gameObject.AddComponent();
trackedPoseDriver.SetPoseSource(UnityEngine.SpatialTracking.TrackedPoseDriver.DeviceType.GenericXRDevice, UnityEngine.SpatialTracking.TrackedPoseDriver.TrackedPose.Center);
trackedPoseDriver.trackingType = UnityEngine.SpatialTracking.TrackedPoseDriver.TrackingType.RotationAndPosition;
trackedPoseDriver.updateType = UnityEngine.SpatialTracking.TrackedPoseDriver.UpdateType.UpdateAndBeforeRender;
trackedPoseDriver.enabled = CameraTrackingEnabled;
}
}
}
#endif
// Refresh state
AvatarMode = _avatarMode;
RenderMode = _renderMode;
if (AvatarMode == UxrAvatarMode.Local)
{
OnLocalAvatarStarted(new UxrAvatarStartedEventArgs(this));
if (!s_localAvatarReferenceInitialized)
{
OnLocalAvatarChanged(new UxrAvatarEventArgs(this));
}
}
_started = true;
}
///
/// Called when inspector fields are changed. It is used to regenerate the rig information.
///
protected override void OnValidate()
{
base.OnValidate();
CreateRigInfo();
if (_avatarRenderers == null || _avatarRenderers.Count == 0)
{
_avatarRenderers = GetAllAvatarRendererComponents().Cast().ToList();
}
}
#endregion
#region Event Handling Methods
///
/// Called whenever a tracking device is connected. The tracking device list is sorted so that they can be iterated in
/// the correct update order.
///
/// Event sender
/// Event parameters
private void Tracking_DeviceConnected(object sender, UxrDeviceConnectEventArgs e)
{
// Sort tracking components by update priority to perform avatar updates in the correct order
UxrTrackingDevice.SortComponents((a, b) => a.TrackingUpdateOrder.CompareTo(b.TrackingUpdateOrder));
}
///
/// Called whenever an input device is connected. The avatar render mode is refreshed in order to update possible
/// controller 3D models.
///
/// Event sender
/// Event parameters
private void ControllerInput_DeviceConnected(object sender, UxrDeviceConnectEventArgs e)
{
// Refresh avatar
RenderMode = _renderMode;
}
#endregion
#region Event Trigger Methods
///
/// Event trigger for the and events.
///
/// Event parameters
protected virtual void OnHandPoseChanging(UxrAvatarHandPoseChangeEventArgs e)
{
HandPoseChanging?.Invoke(this, e);
}
///
/// Event trigger for the and events.
///
/// Event parameters
protected virtual void OnHandPoseChanged(UxrAvatarHandPoseChangeEventArgs e)
{
HandPoseChanged?.Invoke(this, e);
}
///
/// Event trigger for .
///
/// Event parameters
private void OnLocalAvatarStarted(UxrAvatarStartedEventArgs e)
{
LocalAvatarStarted?.Invoke(this, e);
_localStartedInvoked = true;
}
///
/// Event trigger for .
///
/// Event parameters
private void OnLocalAvatarChanged(UxrAvatarEventArgs e)
{
if (!s_localAvatarReferenceInitialized)
{
s_localAvatarReferenceInitialized = true;
}
s_localAvatar = e.Avatar;
LocalAvatarChanged?.Invoke(this, e);
}
///
/// Notifies that the input component changed.
///
/// New controller input
private void OnControllerInputChanged(UxrControllerInput controllerInput)
{
// Sync this mainly to:
// -Point to the correct ControllerForward direction during multiplayer/replays.
// -Display the correct input 3D models during multiplayer/replays.
BeginSync();
_externalControllerInput = controllerInput;
EndSyncMethod(new object[] { controllerInput });
}
///
/// Called whenever the avatar is about to be moved.
///
/// Event parameters
internal void RaiseAvatarMoving(UxrAvatarMoveEventArgs e)
{
GlobalAvatarMoving?.Invoke(this, e);
}
///
/// Called whenever the avatar was moved.
///
/// Event parameters
internal void RaiseAvatarMoved(UxrAvatarMoveEventArgs e)
{
GlobalAvatarMoved?.Invoke(this, e);
}
///
/// Raises the event.
///
/// Event parameters
internal void RaiseAvatarUpdating(UxrAvatarUpdateEventArgs e)
{
AvatarUpdating?.Invoke(this, e);
}
///
/// Raises the event.
///
/// Event parameters
internal void RaiseAvatarUpdated(UxrAvatarUpdateEventArgs e)
{
AvatarUpdated?.Invoke(this, e);
}
#endregion
#region Private Methods
///
/// Gets the first non-null default hand pose in the prefab hierarchy.
///
/// Default hand pose asset or null if not found
private UxrHandPoseAsset GetDefaultPoseInHierarchy()
{
foreach (UxrAvatar avatar in GetAvatarChain())
{
if (avatar._defaultHandPose != null)
{
return avatar._defaultHandPose;
}
}
return null;
}
///
/// Creates the rig information object.
///
private void CreateRigInfo()
{
_rigInfo.Compute(this);
}
///
/// Creates a dictionary to map pose names to . If a given pose name is present more
/// than once, the overriden pose in the child prefab/instance is stored.
///
private void CreateHandPoseCache()
{
if (!AvatarRig.HasFingerData())
{
return;
}
_cachedRuntimeHandPoses = new Dictionary();
foreach (UxrAvatar avatar in GetAvatarChain())
{
foreach (UxrHandPoseAsset handPoseAsset in avatar.GetHandPoses())
{
if (handPoseAsset != null && !_cachedRuntimeHandPoses.ContainsKey(handPoseAsset.name))
{
_cachedRuntimeHandPoses.Add(handPoseAsset.name, new UxrRuntimeHandPose(this, handPoseAsset));
}
}
}
}
///
/// Synced method that helps implementing in a way so that it is synchronized in multiplayer
/// and replays.
///
/// The new render mode
/// The enabled controller inputs at the moment of calling
private void SetAvatarRenderMode(UxrAvatarRenderModes avatarRenderModes, List enabledControllerInputs)
{
BeginSync();
_renderMode = avatarRenderModes;
// Enable or disable avatar renderers
_avatarRenderers?.ForEach(r => r.enabled = avatarRenderModes.HasFlag(UxrAvatarRenderModes.Avatar));
// Enable/disable controller 3d models (and controller hands) depending on if their input component is active
IEnumerable controllerInputs = UxrControllerInput.GetComponents(this, true);
foreach (UxrControllerInput controllerInput in controllerInputs)
{
bool isEnabled = enabledControllerInputs.Contains(controllerInput);
// Here we do some additional checks in case two components reference the same 3D model(s):
bool leftControllerEnabled = controllerInputs.Any(c => c.LeftController3DModel == controllerInput.LeftController3DModel && isEnabled);
bool rightControllerEnabled = controllerInputs.Any(c => c.RightController3DModel == controllerInput.RightController3DModel && isEnabled);
bool showAvatar = avatarRenderModes.HasFlag(UxrAvatarRenderModes.Avatar);
bool showControllerLeft = avatarRenderModes.HasFlag(UxrAvatarRenderModes.LeftController);
bool showControllerRight = avatarRenderModes.HasFlag(UxrAvatarRenderModes.RightController);
if (controllerInput.SetupType == UxrControllerSetupType.Single)
{
// In single setups there is only one device for both hands.
if (controllerInput.LeftController3DModel)
{
controllerInput.LeftController3DModel.IsControllerVisible = (leftControllerEnabled && showControllerLeft) || (rightControllerEnabled && showControllerRight);
controllerInput.LeftController3DModel.IsHandVisible = _showControllerHands;
}
controllerInput.EnableObjectListSingle((leftControllerEnabled || rightControllerEnabled) && showAvatar);
}
else if (controllerInput.SetupType == UxrControllerSetupType.Dual)
{
if (controllerInput.LeftController3DModel)
{
controllerInput.LeftController3DModel.IsControllerVisible = leftControllerEnabled && showControllerLeft;
controllerInput.LeftController3DModel.IsHandVisible = _showControllerHands;
}
if (controllerInput.RightController3DModel)
{
controllerInput.RightController3DModel.IsControllerVisible = rightControllerEnabled && showControllerRight;
controllerInput.RightController3DModel.IsHandVisible = _showControllerHands;
}
controllerInput.EnableObjectListLeft(leftControllerEnabled && showAvatar);
controllerInput.EnableObjectListRight(rightControllerEnabled && showAvatar);
}
}
EndSyncMethod(new object[] { avatarRenderModes, enabledControllerInputs });
}
#endregion
#region Private Types & Data
private static UxrAvatar s_localAvatar;
private static bool s_localAvatarReferenceInitialized;
private readonly HandState _leftHandState = new HandState();
private readonly HandState _rightHandState = new HandState();
private bool _started;
private bool _localStartedInvoked;
private UxrControllerInput _externalControllerInput;
private float _startCameraHeight;
private float _startCameraControllerHeight;
private Camera _camera;
private bool _cameraTrackingEnabled = true;
private Dictionary _initialBoneLocalRotations = new Dictionary();
private Dictionary _initialBoneLocalPositions = new Dictionary();
private Dictionary _cachedRuntimeHandPoses;
#endregion
}
}
#pragma warning restore 414