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