// --------------------------------------------------------------------------------------------------------------------
//
// Copyright (c) VRMADA, All rights reserved.
//
// --------------------------------------------------------------------------------------------------------------------
using System.Collections;
using UltimateXR.Avatar;
using UltimateXR.Core;
using UltimateXR.Core.Components.Composite;
using UltimateXR.Manipulation;
using UnityEngine;
namespace UltimateXR.Devices.Visualization
{
///
/// UltimateXR has support for rendering avatars in 'controllers mode' (where the currently
/// active controllers are rendered with optional hands on top) or full Avatar mode, where the actual
/// visual representation of the user is rendered.
/// 'Controllers mode' can be useful during tutorials or menus while the full Avatar mode will be used
/// mainly for the core game/simulation.
/// When we developed the big example scene we included a small gallery with all supported controllers
/// so the user could grab and inspect them. But there was no link between the user input and how the
/// hands and the controllers behaved (like the 'controllers mode').
/// Since one of the coolest UltimateXR features is mapping user input to controllers and having IK drive
/// the hands we also wanted to link the grabbing mechanics to this. In short, what we did was to add
/// UxrControllerHand components to the regular avatar hands as well, and then at runtime link them
/// to the currently grabbed controller and feed the avatar input. This way we are now able to not
/// just grab to controller, but also see how the hand and the controller respond to the user input.
///
[RequireComponent(typeof(UxrGrabbableObject))]
public class UxrMapControllerToHand : UxrGrabbableObjectComponent
{
#region Inspector Properties/Serialized Fields
[SerializeField] private bool _ambidextrous;
#endregion
#region Unity
///
/// Cache components.
///
protected override void Start()
{
base.Start();
_controller3DModel = GetComponentInChildren();
}
#endregion
#region Coroutines
///
/// Initializes the hand:
///
/// -
/// Block input mainly to prevent locomotion while pressing the controllers. We want the user to be able to move
/// the joysticks and press buttons without triggering unwanted events.
///
/// -
/// Use the current hand pose, which is used to grab the controller, to initialize the
/// component.
///
///
/// Grabber that was used to grab the controller
/// The controller hand component
/// Coroutine IEnumerator
private IEnumerator InitializeHandCoroutine(UxrGrabber grabber, UxrControllerHand controllerHand)
{
yield return null;
controllerHand.InitializeFromCurrentHandPose(grabber.Avatar, grabber.Side);
if (grabber.Side == UxrHandSide.Left)
{
_ignoreInputLeft = UxrControllerInput.GetIgnoreControllerInput(UxrHandSide.Left);
UxrControllerInput.SetIgnoreControllerInput(UxrHandSide.Left, true);
}
else
{
_ignoreInputRight = UxrControllerInput.GetIgnoreControllerInput(UxrHandSide.Right);
UxrControllerInput.SetIgnoreControllerInput(UxrHandSide.Right, true);
}
}
#endregion
#region Event Handling Methods
///
/// This callback will be executed at the end of all the UltimateXR frame pipeline when the user is grabbing the
/// controller. We feed both the controller and the hand's IK the user input.
///
private void UxrManager_AvatarsUpdated()
{
_controller3DModel.UpdateFromInput(UxrAvatar.LocalAvatarInput);
if (_controller3DModel.ControllerHand)
{
_controller3DModel.ControllerHand.UpdateIKManually();
}
if (_controller3DModel.ControllerHandLeft)
{
_controller3DModel.ControllerHandLeft.UpdateIKManually();
}
if (_controller3DModel.ControllerHandRight)
{
_controller3DModel.ControllerHandRight.UpdateIKManually();
}
}
#endregion
#region Event Trigger Methods
///
/// Called when the user grabs the controller. We bind the controller to the
/// hand that grabbed it and subscribe to events.
///
/// Grab parameters
protected override void OnObjectGrabbed(UxrManipulationEventArgs e)
{
UxrManager.AvatarsUpdated += UxrManager_AvatarsUpdated;
UxrControllerHand controllerHand = GetControllerHand(e.Grabber);
if (controllerHand)
{
if (_controller3DModel.NeedsBothHands)
{
// Controller can be grabbed with both hands simultaneously
if (e.Grabber.Side == UxrHandSide.Left)
{
_controller3DModel.ControllerHandLeft = controllerHand;
}
else
{
_controller3DModel.ControllerHandRight = controllerHand;
}
}
else
{
// Some controllers are grabbed with one hand only but can be grabbed with both. Switch the component handedness
// if necessary to adapt to the hand that is grabbing it
if (_ambidextrous)
{
if (e.Grabber.Side != _controller3DModel.HandSide)
{
_controller3DModel.SwitchHandedness();
}
}
// Assign hand
_controller3DModel.ControllerHand = controllerHand;
}
// Initialize the hand using a coroutine so that it is performed the next frame.
StartCoroutine(InitializeHandCoroutine(e.Grabber, controllerHand));
controllerHand.enabled = true;
}
}
///
protected override void OnObjectReleased(UxrManipulationEventArgs e)
{
OnControllerReleaseOrPlace(e);
}
///
protected override void OnObjectPlaced(UxrManipulationEventArgs e)
{
OnControllerReleaseOrPlace(e);
}
///
/// Called when the user releases or places this controller. We remove the connection
/// with the controller and the hand that grabbed it, and unsubscribe from events.
///
/// Grab parameters
private void OnControllerReleaseOrPlace(UxrManipulationEventArgs e)
{
UxrManager.AvatarsUpdated -= UxrManager_AvatarsUpdated;
UxrControllerHand controllerHand = GetControllerHand(e.Grabber);
if (controllerHand)
{
controllerHand.enabled = false;
if (_controller3DModel.NeedsBothHands)
{
if (e.Grabber.Side == UxrHandSide.Left)
{
_controller3DModel.ControllerHandLeft = null;
}
else
{
_controller3DModel.ControllerHandRight = null;
}
}
else
{
_controller3DModel.ControllerHand = null;
}
// Restore blocked input
UxrControllerInput.SetIgnoreControllerInput(e.Grabber.Side, e.Grabber.Side == UxrHandSide.Left ? _ignoreInputLeft : _ignoreInputRight);
}
}
#endregion
#region Private Methods
///
/// Gets the UxrControllerHand component that belongs to the grabber passed. This basically
/// just checks if it is the left or right one and looks for the UxrControllerHand component
/// in the hand bone object.
///
/// The grabber we want to get the UxrControllerHand for
/// UxrControllerHand or null if none was found
private UxrControllerHand GetControllerHand(UxrGrabber grabber)
{
if (grabber && grabber.Side == UxrHandSide.Left && grabber.Avatar != null)
{
return grabber.Avatar.LeftHandBone.GetComponent();
}
if (grabber && grabber.Side == UxrHandSide.Right && grabber.Avatar != null)
{
return grabber.Avatar.RightHandBone.GetComponent();
}
return null;
}
#endregion
#region Private Types & Data
private UxrController3DModel _controller3DModel;
private bool _ignoreInputLeft;
private bool _ignoreInputRight;
#endregion
}
}