/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
* All rights reserved.
*
* Licensed under the Oculus SDK License Agreement (the "License");
* you may not use the Oculus SDK except in compliance with the License,
* which is provided at the time of installation or download, or which
* otherwise accompanies this software in either electronic or hard copy form.
*
* You may obtain a copy of the License at
*
* https://developer.oculus.com/licenses/oculussdk/
*
* Unless required by applicable law or agreed to in writing, the Oculus SDK
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
using Oculus.Interaction.Input;
using System;
using System.Collections.Generic;
using UnityEngine;
namespace Oculus.Interaction
{
///
/// Renders an . This component drives a transform hierarchy corresponding
/// to the bone positions of a Hand skeleton, and toggles the Mesh Renderer of the hand model.
///
public class HandVisual : MonoBehaviour, IHandVisual
{
[SerializeField, Interface(typeof(IHand))]
private UnityEngine.Object _hand;
///
/// Implementation of . For detailed
/// information, refer to the related documentation provided for that interface.
///
public IHand Hand { get; private set; }
[SerializeField]
private bool _updateRootPose = true;
[SerializeField]
private bool _updateRootScale = true;
[SerializeField]
private bool _updateVisibility = true;
#if ISDK_OPENXR_HAND
[HideInInspector]
#endif
[SerializeField]
private SkinnedMeshRenderer _skinnedMeshRenderer;
#if ISDK_OPENXR_HAND
[HideInInspector]
#endif
[SerializeField, Optional]
private Transform _root = null;
#if ISDK_OPENXR_HAND
[HideInInspector]
#endif
[SerializeField, Optional]
private MaterialPropertyBlockEditor _handMaterialPropertyBlockEditor;
[HideInInspector]
[SerializeField]
private List _jointTransforms = new List();
#if !ISDK_OPENXR_HAND
[HideInInspector]
#endif
[SerializeField]
private SkinnedMeshRenderer _openXRSkinnedMeshRenderer;
#if !ISDK_OPENXR_HAND
[HideInInspector]
#endif
[SerializeField, Optional]
private Transform _openXRRoot = null;
#if !ISDK_OPENXR_HAND
[HideInInspector]
#endif
[SerializeField, Optional]
private MaterialPropertyBlockEditor _openXRHandMaterialPropertyBlockEditor;
[HideInInspector]
[SerializeField]
private List _openXRJointTransforms = new List();
///
/// Implementation of . For detailed
/// information, refer to the related documentation provided for that interface.
///
public event Action WhenHandVisualUpdated = delegate { };
///
/// Implementation of . For detailed
/// information, refer to the related documentation provided for that interface.
///
public bool IsVisible => SkinnedMeshRenderer != null && SkinnedMeshRenderer.enabled;
private int _wristScalePropertyId;
///
/// List of transforms corresponding to the hand joint poses from .
/// Specific joints in this list can be accessed by using the integer value of
/// as the index.
///
public IList Joints
{
#if ISDK_OPENXR_HAND
get => _openXRJointTransforms;
#else
get => _jointTransforms;
#endif
}
///
/// The root of the transform hierarchy corresponding to
/// the joint poses from .
///
public Transform Root
{
#if ISDK_OPENXR_HAND
get => _openXRRoot;
private set => _openXRRoot = value;
#else
get => _root;
private set => _root = value;
#endif
}
private SkinnedMeshRenderer SkinnedMeshRenderer
{
#if ISDK_OPENXR_HAND
get => _openXRSkinnedMeshRenderer;
set => _openXRSkinnedMeshRenderer = value;
#else
get => _skinnedMeshRenderer;
set => _skinnedMeshRenderer = value;
#endif
}
private MaterialPropertyBlockEditor HandMaterialPropertyBlockEditor
{
#if ISDK_OPENXR_HAND
get => _openXRHandMaterialPropertyBlockEditor;
set => _openXRHandMaterialPropertyBlockEditor = value;
#else
get => _handMaterialPropertyBlockEditor;
set => _handMaterialPropertyBlockEditor = value;
#endif
}
private bool _forceOffVisibility;
///
/// Implementation of . For detailed
/// information, refer to the related documentation provided for that interface.
///
public bool ForceOffVisibility
{
get
{
return _forceOffVisibility;
}
set
{
_forceOffVisibility = value;
if (_started)
{
UpdateVisibility();
}
}
}
private bool _started = false;
protected virtual void Awake()
{
Hand = _hand as IHand;
if (Root == null && Joints.Count > 0 && Joints[0] != null)
{
Root = Joints[0].parent;
}
#if ISDK_OPENXR_HAND
if (_root != null)
{
_root.gameObject.SetActive(false);
}
if (_openXRRoot != null)
{
_openXRRoot.gameObject.SetActive(true);
}
#else
if (_root != null)
{
_root.gameObject.SetActive(true);
}
if (_openXRRoot != null)
{
_openXRRoot.gameObject.SetActive(false);
}
#endif
}
protected virtual void Start()
{
this.BeginStart(ref _started);
this.AssertField(Hand, nameof(Hand));
this.AssertField(SkinnedMeshRenderer, nameof(SkinnedMeshRenderer));
if (HandMaterialPropertyBlockEditor != null)
{
_wristScalePropertyId = Shader.PropertyToID("_WristScale");
}
UpdateVisibility();
this.EndStart(ref _started);
}
protected virtual void OnEnable()
{
if (_started)
{
Hand.WhenHandUpdated += UpdateSkeleton;
}
}
protected virtual void OnDisable()
{
if (_started && _hand != null)
{
Hand.WhenHandUpdated -= UpdateSkeleton;
}
}
private void UpdateVisibility()
{
if (!_updateVisibility)
{
return;
}
if (!Hand.IsTrackedDataValid)
{
if (IsVisible || ForceOffVisibility)
{
SkinnedMeshRenderer.enabled = false;
}
}
else
{
if (!IsVisible && !ForceOffVisibility)
{
SkinnedMeshRenderer.enabled = true;
}
else if (IsVisible && ForceOffVisibility)
{
SkinnedMeshRenderer.enabled = false;
}
}
}
///
/// This method updates the skeleton transform hierarchy and renderer visibility.
/// This method is specific to the component and
/// should not be called directly.
///
public void UpdateSkeleton()
{
UpdateVisibility();
if (!Hand.IsTrackedDataValid)
{
WhenHandVisualUpdated.Invoke();
return;
}
if (_updateRootPose)
{
if (Root != null && Hand.GetRootPose(out Pose handRootPose))
{
Root.position = handRootPose.position;
Root.rotation = handRootPose.rotation;
}
}
if (_updateRootScale)
{
if (Root != null)
{
float parentScale = Root.parent != null ? Root.parent.lossyScale.x : 1f;
Root.localScale = Hand.Scale / parentScale * Vector3.one;
}
}
if (!Hand.GetJointPosesLocal(out ReadOnlyHandJointPoses localJoints))
{
return;
}
for (var i = 0; i < Constants.NUM_HAND_JOINTS; ++i)
{
if (Joints[i] == null)
{
continue;
}
Joints[i].SetPose(localJoints[i], Space.Self);
}
if (HandMaterialPropertyBlockEditor != null)
{
HandMaterialPropertyBlockEditor.MaterialPropertyBlock.SetFloat(_wristScalePropertyId, Hand.Scale);
HandMaterialPropertyBlockEditor.UpdateMaterialPropertyBlock();
}
WhenHandVisualUpdated.Invoke();
}
///
/// Returns the transform that this component is driving,
/// corresponding to a provided .
///
public Transform GetTransformByHandJointId(HandJointId handJointId)
{
return Joints[(int)handJointId];
}
///
/// Implementation of . For detailed
/// information, refer to the related documentation provided for that interface.
///
public Pose GetJointPose(HandJointId jointId, Space space)
{
return GetTransformByHandJointId(jointId).GetPose(space);
}
#region Inject
///
/// Injects all required dependencies for a dynamically instantiated .
/// This method exists to support Interaction SDK's dependency injection pattern and is not
/// needed for typical Unity Editor-based usage.
///
public void InjectAllHandSkeletonVisual(IHand hand, SkinnedMeshRenderer skinnedMeshRenderer)
{
InjectHand(hand);
InjectSkinnedMeshRenderer(skinnedMeshRenderer);
}
///
/// Sets the underlying for a dynamically instantiated .
/// This method exists to support Interaction SDK's dependency injection pattern and is not
/// needed for typical Unity Editor-based usage.
///
public void InjectHand(IHand hand)
{
_hand = hand as UnityEngine.Object;
Hand = hand;
}
///
/// Sets the underlying for a
/// dynamically instantiated .
/// This method exists to support Interaction SDK's dependency injection pattern and is not
/// needed for typical Unity Editor-based usage.
///
public void InjectSkinnedMeshRenderer(SkinnedMeshRenderer skinnedMeshRenderer)
{
SkinnedMeshRenderer = skinnedMeshRenderer;
}
///
/// Sets the field for a dynamically instantiated .
/// This method exists to support Interaction SDK's dependency injection pattern and is not
/// needed for typical Unity Editor-based usage.
///
public void InjectOptionalUpdateRootPose(bool updateRootPose)
{
_updateRootPose = updateRootPose;
}
///
/// Sets the field for a dynamically instantiated .
/// This method exists to support Interaction SDK's dependency injection pattern and is not
/// needed for typical Unity Editor-based usage.
///
public void InjectOptionalUpdateRootScale(bool updateRootScale)
{
_updateRootScale = updateRootScale;
}
///
/// Sets the property for a dynamically instantiated .
/// This method exists to support Interaction SDK's dependency injection pattern and is not
/// needed for typical Unity Editor-based usage.
///
public void InjectOptionalRoot(Transform root)
{
Root = root;
}
///
/// Sets the property for a dynamically
/// instantiated .
/// This method exists to support Interaction SDK's dependency injection pattern and is not
/// needed for typical Unity Editor-based usage.
///
public void InjectOptionalMaterialPropertyBlockEditor(MaterialPropertyBlockEditor editor)
{
HandMaterialPropertyBlockEditor = editor;
}
#endregion
}
}