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