// -------------------------------------------------------------------------------------------------------------------- // // Copyright (c) VRMADA, All rights reserved. // // -------------------------------------------------------------------------------------------------------------------- using System.Collections.Generic; using System.Linq; using UltimateXR.Animation.IK; using UltimateXR.Avatar.Rig; using UltimateXR.Core; using UltimateXR.Devices; using UltimateXR.Extensions.System.Collections; using UltimateXR.Extensions.Unity; using UltimateXR.Locomotion; using UltimateXR.Manipulation; using UltimateXR.Manipulation.HandPoses; using UltimateXR.UI; using UnityEngine; namespace UltimateXR.Avatar.Controllers { /// /// default implementation provided by UltimateXR to update user-controlled avatars. /// It is in charge of updating the avatar in the correct order, granting access to all features in the framework. /// By adding it to a GameObject with an component, it will automatically update the avatar /// each frame. /// [RequireComponent(typeof(UxrAvatar))] public sealed partial class UxrStandardAvatarController : UxrAvatarController { #region Inspector Properties/Serialized Fields // add a small margin -in Unity units- to be added to the box when checking if a finger tip gets out of it. [SerializeField] private bool _useArmIK = true; [SerializeField] private float _armIKElbowAperture = UxrArmIKSolver.DefaultElbowAperture; [SerializeField] private UxrArmOverExtendMode _armIKOverExtendMode = UxrArmOverExtendMode.LimitHandReach; [SerializeField] private bool _useBodyIK = true; [SerializeField] private UxrBodyIKSettings _bodyIKSettings = new UxrBodyIKSettings(); [SerializeField] private bool _useLegIK = true; [SerializeField] private List _listControllerEvents = new List(); #endregion #region Public Types & Data /// /// Gets the list of controller events. /// public IReadOnlyList ControllerEvents => _listControllerEvents; /// /// Gets the list of controller events that belong to the left hand. /// public IEnumerable LeftControllerEvents { get { foreach (UxrAvatarControllerEvent controllerEvent in _listControllerEvents) { if (IsLeftSideAnimation(controllerEvent.TypeOfAnimation)) { yield return controllerEvent; } } } } /// /// Gets the list of controller events that belong to the right hand. /// public IEnumerable RightControllerEvents { get { foreach (UxrAvatarControllerEvent controllerEvent in _listControllerEvents) { if (!IsLeftSideAnimation(controllerEvent.TypeOfAnimation)) { yield return controllerEvent; } } } } /// /// Gets the pose name that is used for the default left hand animation. That is, the animation that is played /// when no other animation is played. /// public string LeftHandDefaultPoseName => Avatar.DefaultHandPoseName; /// /// Gets the pose name that is used for the default right hand animation. That is, the animation that is played /// when no other animation is played. /// public string RightHandDefaultPoseName => Avatar.DefaultHandPoseName; /// /// Gets the pose name that is used for the left hand grab animation. That is, the animation that is played for /// the event that has the animation. /// public string LeftHandGrabPoseName => _leftHandInfo.InitialHandGrabPoseName; /// /// Gets the pose name that is used for the right hand grab animation. That is, the animation that is played /// for the event that has the animation. /// public string RightHandGrabPoseName => _rightHandInfo.InitialHandGrabPoseName; /// /// Gets the button(s) that is/are required to play the left hand grab animation. That is, the animation that is played /// for the event that has the animation. /// public UxrInputButtons LeftHandGrabButtons => _leftHandInfo.InitialHandGrabButtons; /// /// Gets the button(s) that is/are used to play the right hand grab animation. That is, the animation that is played /// for the event that has the animation. /// public UxrInputButtons RightHandGrabButtons => _rightHandInfo.InitialHandGrabButtons; /// /// Gets whether the left hand is inside an . /// public bool IsLeftHandInsideFingerPointingVolume => _leftHandInfo.IsInsideFingerPointingVolume; /// /// Gets whether the right hand is inside an . /// public bool IsRightHandInsideFingerPointingVolume => _rightHandInfo.IsInsideFingerPointingVolume; /// /// Gets or sets whether the avatar controller is using IK for the arms. /// public bool UseArmIK { get => _useArmIK; set { _useArmIK = value; if (_leftArmIK) { _leftArmIK.enabled = value; } if (_rightArmIK) { _rightArmIK.enabled = value; } } } /// /// Gets or sets the arm IK relaxed elbow aperture. The value between range [0.0, 1.0] that determines how tight /// the elbows are to the body. /// public float ArmIKElbowAperture { get => _armIKElbowAperture; set { _armIKElbowAperture = value; if (_leftArmIK) { _leftArmIK.RelaxedElbowAperture = value; } if (_rightArmIK) { _rightArmIK.RelaxedElbowAperture = value; } } } /// /// Gets or sets whether body IK are used. /// public bool UseBodyIK { get => _useBodyIK; set => _useBodyIK = value; } /// /// Gets or sets the pose name that overrides if it is other than /// null. If it is set to null, is back to being used. /// public string LeftHandDefaultPoseNameOverride { get; set; } = null; /// /// Gets or sets the pose name that overrides if it is other /// than null. If it is set to null, is back to being used. /// public string RightHandDefaultPoseNameOverride { get; set; } = null; /// /// Gets or sets the pose name that overrides if it is other than /// null. If it is set to null, is back to being used. /// public string LeftHandGrabPoseNameOverride { get => GetEventPoseNameOverride(_leftHandInfo.GrabEventIndex, LeftHandGrabPoseName); set => OverrideEventPoseName(_leftHandInfo.GrabEventIndex, value, LeftHandGrabPoseName); } /// /// Gets or sets the pose name that overrides if it is other than /// null. If it is set to null, is back to being used. /// public string RightHandGrabPoseNameOverride { get => GetEventPoseNameOverride(_rightHandInfo.GrabEventIndex, RightHandGrabPoseName); set => OverrideEventPoseName(_rightHandInfo.GrabEventIndex, value, RightHandGrabPoseName); } /// /// Gets or sets the button(s) that override if it is other than /// . If it is set to , /// is back to being used. /// public UxrInputButtons LeftHandGrabButtonsOverride { get => GetEventButtonsOverride(_leftHandInfo.GrabEventIndex, LeftHandGrabButtons); set => OverrideEventButtons(_leftHandInfo.GrabEventIndex, value, LeftHandGrabButtons); } /// /// Gets or sets the button(s) that override if it is other than /// . If it is set to , /// is back to being used. /// public UxrInputButtons RightHandGrabButtonsOverride { get => GetEventButtonsOverride(_rightHandInfo.GrabEventIndex, RightHandGrabButtons); set => OverrideEventButtons(_rightHandInfo.GrabEventIndex, value, RightHandGrabButtons); } /// /// Gets or sets whether to also process ignored input for events. Input can be ignored by using /// and tells whether to /// actually ignore it or not. By default the ignored input is also processed, which may seem counterintuitive but most /// of the time the input is ignored when specific objects are being grabbed. Since the avatar controller is /// responsible for triggering the grab events depending on user input, it was chosen that the default behaviour would /// be to also process ignored input. /// public bool ProcessIgnoredInput { get; set; } = true; #endregion #region Public Overrides UxrAvatarController /// public override bool Initialized { get { if (!_initialized) { return false; } if (_bodyIK != null && !_bodyIK.Initialized) { return false; } if (_leftArmIK && !_leftArmIK.Initialized) { return false; } if (_rightArmIK && !_rightArmIK.Initialized) { return false; } return true; } } /// public override bool UsesSmoothLocomotion => UxrLocomotion.GetComponents(Avatar, false).Any(l => l.IsSmoothLocomotion); /// public override bool CanHandInteractWithUI(UxrHandSide handSide) { if (Avatar.ControllerInput.GetControllerCapabilities(handSide).HasFlag(UxrControllerInputCapabilities.TrackedHandPose)) { // With tracked hand pose controllers (for example Valve Index) we cannot use the IsGrabbing property. The pointing // gesture reports grabbing due to the middle finger pressure. We will rely on the index finger direction with // respect to the wrist instead, to check if it is curled. Transform forearm = Avatar.GetArm(handSide).Forearm; UxrAvatarHand hand = Avatar.GetHand(handSide); Transform wrist = hand.Wrist; Transform intermediate = hand.GetFinger(UxrFingerType.Index).Intermediate; Transform distal = hand.GetFinger(UxrFingerType.Index).Distal; if (forearm != null && wrist != null && intermediate != null && distal != null) { Vector3 wristDir = wrist.position - forearm.position; Vector3 fingerDir = distal.position - intermediate.position; return Vector3.Angle(wristDir, fingerDir) < 90.0f; } } return handSide == UxrHandSide.Left ? !_leftHandInfo.IsGrabbing : !_rightHandInfo.IsGrabbing; } #endregion #region Public Methods /// /// Solves the body IK using the current headset and controller positions. /// public void SolveBodyIK() { if (_bodyIK != null && _useBodyIK) { _bodyIK.PreSolveAvatarIK(); } // Update arms without clavicles to check how much tension is applied on the shoulders IEnumerable autoUpdateArmSolvers = UxrIKSolver.GetComponents(Avatar).OfType().Where(s => s.NeedsAutoUpdate); autoUpdateArmSolvers.ForEach(s => s.SolveIKPass(UxrArmSolveOptions.None, UxrArmOverExtendMode.ExtendForearm)); // Update torso rotation if (_bodyIK != null && _useBodyIK) { _bodyIK.PostSolveAvatarIK(); } // Update arms normally autoUpdateArmSolvers.ForEach(s => s.SolveIK()); // Update non-arm IKs UxrIKSolver.GetComponents(Avatar).Where(s => s.GetType() != typeof(UxrArmIKSolver) && s.NeedsAutoUpdate).ForEach(s => s.SolveIK()); } #endregion #region Unity /// /// Initializes the component. /// protected override void Awake() { base.Awake(); _leftHandInfo.GrabEventIndex = -1; _rightHandInfo.GrabEventIndex = -1; _leftHandInfo.InitialHandGrabPoseName = string.Empty; _rightHandInfo.InitialHandGrabPoseName = string.Empty; _leftHandInfo.InitialHandGrabButtons = UxrInputButtons.Everything; _rightHandInfo.InitialHandGrabButtons = UxrInputButtons.Everything; if (Avatar != null) { if (TryFindTypeOfAnimationEventIndex(_listControllerEvents, UxrAnimationType.LeftHandGrab, out int leftGrabEventIndex)) { _leftHandInfo.GrabEventIndex = leftGrabEventIndex; } if (TryFindTypeOfAnimationEventIndex(_listControllerEvents, UxrAnimationType.RightHandGrab, out int rightGrabEventIndex)) { _rightHandInfo.GrabEventIndex = rightGrabEventIndex; } _leftHandInfo.InitialHandGrabPoseName = _leftHandInfo.GrabEventIndex != -1 ? _listControllerEvents[_leftHandInfo.GrabEventIndex].PoseName : string.Empty; _rightHandInfo.InitialHandGrabPoseName = _rightHandInfo.GrabEventIndex != -1 ? _listControllerEvents[_rightHandInfo.GrabEventIndex].PoseName : string.Empty; _leftHandInfo.InitialHandGrabButtons = _leftHandInfo.GrabEventIndex != -1 ? _listControllerEvents[_leftHandInfo.GrabEventIndex].Buttons : UxrInputButtons.Everything; _rightHandInfo.InitialHandGrabButtons = _rightHandInfo.GrabEventIndex != -1 ? _listControllerEvents[_rightHandInfo.GrabEventIndex].Buttons : UxrInputButtons.Everything; if (_useArmIK && Avatar.AvatarRigType == UxrAvatarRigType.HalfOrFullBody) { _leftArmIK = SetupArmIK(Avatar.AvatarRig.LeftArm, UxrHandSide.Left); _rightArmIK = SetupArmIK(Avatar.AvatarRig.RightArm, UxrHandSide.Right); } } _leftHandInfo.LetGrabAgain = true; _rightHandInfo.LetGrabAgain = true; // IK if (Avatar != null && Avatar.AvatarRigType == UxrAvatarRigType.HalfOrFullBody && _useBodyIK) { _bodyIK = new UxrBodyIK(); _bodyIK.Initialize(Avatar, _bodyIKSettings, _useArmIK, _useLegIK); } _initialized = true; } /// /// Subscribes to events. /// protected override void OnEnable() { base.OnEnable(); UxrAvatar.GlobalAvatarMoved += UxrAvatar_GlobalAvatarMoved; UxrGrabManager.Instance.ObjectGrabbed += UxrGrabManager_ObjectGrabbed; UxrGrabManager.Instance.ObjectPlaced += UxrGrabManager_ObjectPlacedOrReleased; UxrGrabManager.Instance.ObjectReleased += UxrGrabManager_ObjectPlacedOrReleased; } /// /// Subscribes from events. /// protected override void OnDisable() { base.OnDisable(); UxrAvatar.GlobalAvatarMoved -= UxrAvatar_GlobalAvatarMoved; UxrGrabManager.Instance.ObjectGrabbed -= UxrGrabManager_ObjectGrabbed; UxrGrabManager.Instance.ObjectPlaced -= UxrGrabManager_ObjectPlacedOrReleased; UxrGrabManager.Instance.ObjectReleased -= UxrGrabManager_ObjectPlacedOrReleased; } #endregion #region Event Handling Methods /// /// Called whenever the avatar has moved. Use it to notify the body IK component. /// /// /// private void UxrAvatar_GlobalAvatarMoved(object sender, UxrAvatarMoveEventArgs e) { if (_bodyIK != null && ReferenceEquals(sender, Avatar)) { _bodyIK.NotifyAvatarMoved(e); } } /// /// Event handling method called when an object was grabbed. /// It's used to check if the avatar hand grab pose needs to be changed. /// /// Event sender /// Event parameters private void UxrGrabManager_ObjectGrabbed(object sender, UxrManipulationEventArgs e) { if (e.Grabber != null && Avatar == e.Grabber.Avatar) { UpdateGrabPoseInfo(e.Grabber, e.GrabbableObject); } } /// /// Event handling method called when an object was released. /// It's used to check if the avatar hand grab pose needs to be changed. /// /// Event sender /// Event parameters private void UxrGrabManager_ObjectPlacedOrReleased(object sender, UxrManipulationEventArgs e) { if (e.Grabber != null && Avatar == e.Grabber.Avatar) { ClearGrabButtonsOverride(e.Grabber.Side); GetHandInfo(e.Grabber.Side).GrabBlendValue = -1.0f; if (e.Grabber.Side == UxrHandSide.Left) { LeftHandGrabPoseNameOverride = null; } if (e.Grabber.Side == UxrHandSide.Right) { RightHandGrabPoseNameOverride = null; } } } #endregion #region Protected Overrides UxrAvatarController /// protected override void UpdateAvatar() { base.UpdateAvatar(); // Update controller inputs first, then locomotion (which need inputs and may affect the avatar position) and then the tracking (which may use the avatar position) associated to this avatar UpdateInputDevice(); UpdateLocomotion(); UpdateTrackingDevices(); // Initialize some internal vars CheckFingerTipInsideFingerPointingVolume(Avatar.FingerTips, out bool hasLeftFingerTipInside, out bool hasRightFingerTipInside, _leftHandInfo.IsInsideFingerPointingVolume, _rightHandInfo.IsInsideFingerPointingVolume); _leftHandInfo.IsInsideFingerPointingVolume = hasLeftFingerTipInside; _rightHandInfo.IsInsideFingerPointingVolume = hasRightFingerTipInside; _leftHandInfo.WasGrabbingLastFrame = _leftHandInfo.IsGrabbing; _rightHandInfo.WasGrabbingLastFrame = _rightHandInfo.IsGrabbing; _leftHandInfo.WasPointingLastFrame = _leftHandInfo.IsPointing; _rightHandInfo.WasPointingLastFrame = _rightHandInfo.IsPointing; _leftHandInfo.IsGrabbing = false; _rightHandInfo.IsGrabbing = false; _leftHandInfo.IsPointing = false; _rightHandInfo.IsPointing = false; // Check with the help of the grab manager if we need to override the grab buttons based on proximity to a grabbable object that used non-default grab buttons. UxrInputButtons GetRequiredGrabButtonsOverride(UxrHandSide handSide) { if (UxrGrabManager.Instance.GetClosestGrabbableObject(Avatar, handSide, out UxrGrabbableObject grabbableObject, out int grabPoint) && !grabbableObject.GetGrabPoint(grabPoint).UseDefaultGrabButtons && (Avatar.ControllerInput.GetButtonsEvent(handSide, grabbableObject.GetGrabPoint(grabPoint).InputButtons, UxrButtonEventType.PressDown, ProcessIgnoredInput) || Avatar.ControllerInput.GetButtonsEvent(handSide, handSide == UxrHandSide.Left ? LeftHandGrabButtons : RightHandGrabButtons, UxrButtonEventType.PressDown, ProcessIgnoredInput))) { return grabbableObject.GetGrabPoint(grabPoint).InputButtons; } return UxrInputButtons.Everything; } if (!UxrGrabManager.Instance.IsHandGrabbing(Avatar, UxrHandSide.Left)) { LeftHandGrabButtonsOverride = GetRequiredGrabButtonsOverride(UxrHandSide.Left); } if (!UxrGrabManager.Instance.IsHandGrabbing(Avatar, UxrHandSide.Right)) { RightHandGrabButtonsOverride = GetRequiredGrabButtonsOverride(UxrHandSide.Right); } // Update only internal vars (are we grabbing and/or pointing?) but don't execute the actions. We might change things like the grab pose name at runtime // after the UxrGrabManager grab event and then it's when we want to execute the var actions. // Also, we want to do it in a priority order where grab has priority over point and point has priority over the rest of the animations. The rest of // the animations have top to bottom priority. ProcessControllerEvents(_listControllerEvents, ControllerEventTypes.Grab, EventProcessing.InternalVars); ProcessControllerEvents(_listControllerEvents, ControllerEventTypes.Point, EventProcessing.InternalVars); ProcessControllerEvents(_listControllerEvents, ControllerEventTypes.Other, EventProcessing.InternalVars); } /// protected override void UpdateAvatarAnimation() { base.UpdateAvatarAnimation(); // Execute the event actions that potentially change the current poses ProcessControllerEvents(_listControllerEvents, ControllerEventTypes.All, EventProcessing.ExecuteActions); // Update animation // We update the avatar animation in a different method so that it can be called at a different stage. // When the avatar has Animator components that update the bone transforms, pose animations and IK components need to be processed after. Avatar.UpdateHandPoseTransforms(); } /// protected override void UpdateAvatarManipulation() { ProcessHandManipulation(_leftHandInfo, UxrHandSide.Left); ProcessHandManipulation(_rightHandInfo, UxrHandSide.Right); } /// protected override void UpdateAvatarPostProcess() { base.UpdateAvatarPostProcess(); // Needs to be done after managers since the grab manager may force hands to be in certain positions SolveBodyIK(); } #endregion #region Private Methods /// /// Gets the pose name that overrides the event with the given index. /// /// The event index to get the override from /// The initial pose name, required to check if the pose name has been overriden or not /// /// Pose name that overrides the given event, or null if the pose name is currently the one from the beginning. /// private string GetEventPoseNameOverride(int eventIndex, string defaultPoseName) { if (Avatar == null || eventIndex == -1) { return string.Empty; } UxrAvatarControllerEvent controllerEvent = _listControllerEvents[eventIndex]; return controllerEvent.PoseName == defaultPoseName ? null : controllerEvent.PoseName; } /// /// Sets the pose name that overrides the event with the given index. /// /// The event index to override /// The pose name that will be used to override /// The initial pose name, required to check if the pose name is being overriden or not private void OverrideEventPoseName(int eventIndex, string poseName, string defaultPoseName) { if (Avatar == null || eventIndex == -1) { return; } // Change the pose name if necessary UxrAvatarControllerEvent controllerEvent = _listControllerEvents[eventIndex]; if (string.IsNullOrEmpty(poseName) || poseName == defaultPoseName) { controllerEvent.PoseNameOverride = null; } else { controllerEvent.PoseNameOverride = poseName; } } /// /// Gets the button(s) that override the event with the given index. /// /// The event index to get the override from /// The initial buttons value, required to check if they has been overriden or not /// /// Button(s) that override the given event, or if the button(s) /// currently in use is/are the default. /// private UxrInputButtons GetEventButtonsOverride(int eventIndex, UxrInputButtons defaultButtons) { if (Avatar == null || eventIndex == -1) { return UxrInputButtons.Everything; } UxrAvatarControllerEvent controllerEvent = _listControllerEvents[eventIndex]; return controllerEvent.Buttons == defaultButtons ? UxrInputButtons.Everything : controllerEvent.Buttons; } /// /// Sets the button(s) that overrides the event with the given index. /// /// The event index to override /// Button(s) value to override /// The initial buttons value, required to check if the value is being overriden or not. private void OverrideEventButtons(int eventIndex, UxrInputButtons buttons, UxrInputButtons defaultButtons) { if (Avatar == null || eventIndex == -1) { return; } UxrAvatarControllerEvent controllerEvent = _listControllerEvents[eventIndex]; controllerEvent.Buttons = buttons == UxrInputButtons.Everything ? defaultButtons : buttons; } /// /// Processes a list of controller events. /// /// The list of events to process /// The type of events to process /// The way the events are going to be processed private void ProcessControllerEvents(List controllerEvents, ControllerEventTypes eventTypes, EventProcessing eventProcessing) { bool eventProcessedLeft = false; bool eventProcessedRight = false; foreach (UxrAvatarControllerEvent e in controllerEvents) { // Filter events if (IsGrabAnimation(e.TypeOfAnimation) && !eventTypes.HasFlag(ControllerEventTypes.Grab)) { continue; } if (IsPointAnimation(e.TypeOfAnimation) && !eventTypes.HasFlag(ControllerEventTypes.Point)) { continue; } if (IsOtherAnimation(e.TypeOfAnimation) && !eventTypes.HasFlag(eventTypes & ControllerEventTypes.Other)) { continue; } // Get hand side bool isLeftSide = IsLeftSideAnimation(e.TypeOfAnimation); UxrHandSide handSide = isLeftSide ? UxrHandSide.Left : UxrHandSide.Right; HandInfo handInfo = GetHandInfo(handSide); // Prepare additional grab vars that we may need bool eventProcessed = isLeftSide ? eventProcessedLeft : eventProcessedRight; // Input is true? UxrInputButtons inputButtons = e.Buttons; if (IsGrabAnimation(e.TypeOfAnimation) && GetGrabButtonsOverride(handSide) != UxrInputButtons.Everything) { inputButtons = GetGrabButtonsOverride(handSide); } if (Avatar.ControllerInput.GetButtonsEvent(handSide, inputButtons, UxrButtonEventType.Pressing, ProcessIgnoredInput) && !eventProcessed) { // First check the special case of finger pointing and grabbing. Inside FingerPointVolumes you can't try to grab (to force the finger to be always pointing for UI elements f.e), // unless there is something that can be grabbed. bool allowAnimation = true; switch (e.TypeOfAnimation) { case UxrAnimationType.LeftHandGrab: case UxrAnimationType.RightHandGrab: if (eventProcessing.HasFlag(EventProcessing.InternalVars)) { handInfo.IsGrabbing = !handInfo.IsInsideFingerPointingVolume || (handInfo.IsInsideFingerPointingVolume && (UxrGrabManager.Instance.IsHandGrabbing(Avatar, handSide) || UxrGrabManager.Instance.CanGrabSomething(Avatar, handSide))); handInfo.LetGrabAgain = Avatar.ControllerInput.GetButtonsEvent(handSide, inputButtons, UxrButtonEventType.PressDown, ProcessIgnoredInput); } allowAnimation = handInfo.IsGrabbing; break; case UxrAnimationType.LeftFingerPoint: case UxrAnimationType.RightFingerPoint: if (eventProcessing.HasFlag(EventProcessing.InternalVars)) { handInfo.IsPointing = !handInfo.IsGrabbing && !UxrGrabManager.Instance.IsHandGrabbing(Avatar, handSide); } allowAnimation = handInfo.IsPointing; break; case UxrAnimationType.LeftHandOther: case UxrAnimationType.RightHandOther: allowAnimation = !handInfo.IsPointing && !handInfo.IsGrabbing && !UxrGrabManager.Instance.IsHandGrabbing(Avatar, handSide); break; } // Execute the action if (eventProcessing.HasFlag(EventProcessing.ExecuteActions) && allowAnimation) { ExecuteEventAction(handSide, e); } // Ignore all other events from the side due to priority if (isLeftSide && !eventProcessedLeft) { eventProcessedLeft = allowAnimation; } if (!isLeftSide && !eventProcessedRight) { eventProcessedRight = allowAnimation; } } else { bool forceAnimation = false; switch (e.TypeOfAnimation) { case UxrAnimationType.LeftFingerPoint: case UxrAnimationType.RightFingerPoint: if (eventProcessing.HasFlag(EventProcessing.InternalVars)) { if (handInfo.IsInsideFingerPointingVolume) { if (UxrGrabManager.Instance.IsHandGrabbing(Avatar, handSide) || (UxrGrabManager.Instance.CanGrabSomething(Avatar, handSide) && handInfo.IsGrabbing && handInfo.LetGrabAgain)) { handInfo.IsPointing = false; } else { handInfo.IsPointing = true; handInfo.IsGrabbing = false; } } } forceAnimation = handInfo.IsPointing; break; } // Execute the action if (eventProcessing.HasFlag(EventProcessing.ExecuteActions) && forceAnimation) { ExecuteEventAction(handSide, e); } // Ignore all other events from the side due to priority if (isLeftSide && !eventProcessedLeft) { eventProcessedLeft = forceAnimation; } if (!isLeftSide && !eventProcessedRight) { eventProcessedRight = forceAnimation; } } } // If no events were found, set the default or grab pose depending on the current state if (eventProcessing.HasFlag(EventProcessing.ExecuteActions)) { if (!eventProcessedLeft) { if (UxrGrabManager.Instance.IsHandGrabbing(Avatar, UxrHandSide.Left)) { Avatar.SetCurrentHandPose(UxrHandSide.Left, LeftHandGrabPoseNameOverride ?? LeftHandGrabPoseName, _leftHandInfo.GrabBlendValue); } else { Avatar.SetCurrentHandPose(UxrHandSide.Left, LeftHandDefaultPoseNameOverride ?? Avatar.DefaultHandPoseName); } } if (!eventProcessedRight) { if (UxrGrabManager.Instance.IsHandGrabbing(Avatar, UxrHandSide.Right)) { Avatar.SetCurrentHandPose(UxrHandSide.Right, RightHandGrabPoseNameOverride ?? RightHandGrabPoseName, _rightHandInfo.GrabBlendValue); } else { Avatar.SetCurrentHandPose(UxrHandSide.Right, RightHandDefaultPoseNameOverride ?? Avatar.DefaultHandPoseName); } } } } /// /// Executes an event action. /// /// Which hand to process /// The event to process private void ExecuteEventAction(UxrHandSide handSide, UxrAvatarControllerEvent controllerEvent) { HandInfo handInfo = GetHandInfo(handSide); Avatar.SetCurrentHandPose(handSide, controllerEvent.PoseName, IsGrabAnimation(controllerEvent.TypeOfAnimation) && handInfo.GrabBlendValue >= 0.0f ? handInfo.GrabBlendValue : controllerEvent.PoseBlendValue); } /// /// Processes an avatar hand, updating the internal state. /// /// Hand information /// Which hand is being processed private void ProcessHandManipulation(HandInfo handInfo, UxrHandSide handSide) { UxrGrabber grabber = Avatar.GetGrabber(handSide); if (!handInfo.WasGrabbingLastFrame && handInfo.IsGrabbing && handInfo.LetGrabAgain) { // Pressed grab action. // We get the grabber instead of the return value of TryGrab() because we may already be grabbing an object with a special UxrGrabMode. UxrGrabManager.Instance.TryGrab(Avatar, handSide); } else if (handInfo.WasGrabbingLastFrame && !handInfo.IsGrabbing) { // Released grab action. UxrGrabManager.Instance.TryRelease(Avatar, handSide); } else if (UxrGrabManager.Instance.IsHandGrabbing(Avatar, handSide) && grabber.GrabbedObject) { // Update pose info to refresh potential blend value mainly UpdateGrabPoseInfo(grabber, grabber.GrabbedObject); } } /// /// Updates the internal grab information for the given grabber and grabbed object. /// /// Grabber /// Grabbed object private void UpdateGrabPoseInfo(UxrGrabber grabber, UxrGrabbableObject grabbableObject) { // Change the avatar's grab pose string overrideGrabPoseName = UxrGrabManager.Instance.GetOverrideGrabPoseName(grabber, grabbableObject); UxrRuntimeHandPose overrideGrabPose = !string.IsNullOrEmpty(overrideGrabPoseName) ? Avatar.GetRuntimeHandPose(overrideGrabPoseName) : null; if (grabber.Side == UxrHandSide.Left) { LeftHandGrabPoseNameOverride = overrideGrabPose != null ? overrideGrabPoseName : null; } else { RightHandGrabPoseNameOverride = overrideGrabPose != null ? overrideGrabPoseName : null; } // Is it a blend pose? if (overrideGrabPose != null && overrideGrabPose.PoseType == UxrHandPoseType.Blend) { if (grabber.Side == UxrHandSide.Left) { _leftHandInfo.GrabBlendValue = UxrGrabManager.Instance.GetOverrideGrabPoseBlendValue(grabber, grabbableObject); } else { _rightHandInfo.GrabBlendValue = UxrGrabManager.Instance.GetOverrideGrabPoseBlendValue(grabber, grabbableObject); } } else { if (grabber.Side == UxrHandSide.Left) { if (overrideGrabPose == null) { LeftHandGrabPoseNameOverride = null; } _leftHandInfo.GrabBlendValue = -1.0f; } else { if (overrideGrabPose == null) { RightHandGrabPoseNameOverride = null; } _rightHandInfo.GrabBlendValue = -1.0f; } } } /// /// Checks if finger tips from a list are inside a , which are components that /// define a volume inside of which a hand will adopt a pointing pose using the index finger. /// /// List of finger tips to check /// Returns whether the left hand has any finger tip inside the volume /// Returns whether the right hand has any finger tip inside the volume /// Whether the left hand had any finger tip inside last frame /// Whether the right hand had any finger tip inside last frame private void CheckFingerTipInsideFingerPointingVolume(IEnumerable fingerTips, out bool hasLeftFingerTipInside, out bool hasRightFingerTipInside, bool lastLeftInsideFingerPointingVolume, bool lastRightInsideFingerPointingVolume) { hasLeftFingerTipInside = false; hasRightFingerTipInside = false; if (fingerTips != null) { foreach (UxrFingerTip fingerTip in fingerTips) { if (fingerTip.Side == UxrHandSide.Left) { if (IsInsideFingerPointingVolume(fingerTip, lastLeftInsideFingerPointingVolume)) { hasLeftFingerTipInside = true; } } else if (fingerTip.Side == UxrHandSide.Right) { if (IsInsideFingerPointingVolume(fingerTip, lastRightInsideFingerPointingVolume)) { hasRightFingerTipInside = true; } } } } } /// /// Checks if a finger tip is inside a given . /// /// Finger tip to check /// Whether the hand had a finger tip inside last frame /// Whether the finger tip is inside a given . private bool IsInsideFingerPointingVolume(UxrFingerTip fingerTip, bool lastHandWasInsideFingerPointingVolume) { foreach (UxrFingerPointingVolume volume in UxrFingerPointingVolume.AllComponents) { if (volume != null && volume.isActiveAndEnabled && volume.IsCompatible(fingerTip.Side) && volume.IsPointInside(fingerTip.transform.position, lastHandWasInsideFingerPointingVolume ? FlexibleFingerVolumeMargin : 0.0f)) { return true; } } return false; } /// /// Try to find the index of a certain event animation type. /// /// The list of events where to look /// The animation type to look for /// If found, returns the index where it is. Otherwise -1. /// Whether the given animation type was found private bool TryFindTypeOfAnimationEventIndex(List listControllerEvents, UxrAnimationType animationType, out int indexOut) { indexOut = -1; for (int i = 0; i < listControllerEvents.Count; ++i) { if (listControllerEvents[i].TypeOfAnimation == animationType) { indexOut = i; return true; } } return false; } /// /// Sets up Inverse Kinematics for a given arm. /// /// The arm to set Inverse Kinematics for /// Whether it is the left arm or right arm /// The Inverse Kinematics solver private UxrArmIKSolver SetupArmIK(UxrAvatarArm arm, UxrHandSide side) { if (arm.UpperArm && arm.Forearm && arm.Hand.Wrist) { UxrArmIKSolver armIK = arm.UpperArm.gameObject.GetOrAddComponent(); armIK.RelaxedElbowAperture = _armIKElbowAperture; armIK.OverExtendMode = _armIKOverExtendMode; return armIK; } return null; } /// /// Returns whether the given animation type is for the left hand. Otherwise it's for the right hand. /// /// Animation type /// Whether the given animation is for the left hand. If false, it's for the right hand private bool IsLeftSideAnimation(UxrAnimationType animationType) { return animationType == UxrAnimationType.LeftHandOther || animationType == UxrAnimationType.LeftHandGrab || animationType == UxrAnimationType.LeftFingerPoint; } /// /// Returns whether the given animation type is for grabbing. /// /// Animation type /// Whether the given animation is for grabbing private bool IsGrabAnimation(UxrAnimationType animationType) { return animationType == UxrAnimationType.LeftHandGrab || animationType == UxrAnimationType.RightHandGrab; } /// /// Returns whether the given animation type is for pointing with the index finger. /// /// Animation type /// Whether the given animation is for pointing with the index finger private bool IsPointAnimation(UxrAnimationType animationType) { return animationType == UxrAnimationType.LeftFingerPoint || animationType == UxrAnimationType.RightFingerPoint; } /// /// Returns whether the given animation type is of other type, not grabbing nor pointing. /// /// Animation type /// Whether the given animation is of other type, not grabbing nor pointing private bool IsOtherAnimation(UxrAnimationType animationType) { return animationType == UxrAnimationType.LeftHandOther || animationType == UxrAnimationType.RightHandOther; } /// /// Gets the hand information given the side. /// /// Which side to retrieve /// Hand information private HandInfo GetHandInfo(UxrHandSide handSide) { return handSide == UxrHandSide.Left ? _leftHandInfo : _rightHandInfo; } /// /// Returns the current override button that requires to be pressed to execute the grab action. It will return /// if no override is active. /// /// Which side to check /// The override grab button or if the default grab button is required private UxrInputButtons GetGrabButtonsOverride(UxrHandSide handSide) { return handSide == UxrHandSide.Left ? LeftHandGrabButtonsOverride : RightHandGrabButtonsOverride; } /// /// Clears the current override button that requires to be pressed to execute the grab action. /// /// Which side to reset private void ClearGrabButtonsOverride(UxrHandSide handSide) { if (handSide == UxrHandSide.Left) { LeftHandGrabButtonsOverride = UxrInputButtons.Everything; } else { RightHandGrabButtonsOverride = UxrInputButtons.Everything; } } #endregion #region Private Types & Data private const float FlexibleFingerVolumeMargin = 0.1f; private readonly HandInfo _leftHandInfo = new HandInfo(); private readonly HandInfo _rightHandInfo = new HandInfo(); private bool _initialized; private UxrArmIKSolver _leftArmIK; private UxrArmIKSolver _rightArmIK; private UxrBodyIK _bodyIK; #endregion } }