/****************************************************************************** * Copyright (C) Ultraleap, Inc. 2011-2021. * * * * Use subject to the terms of the Apache License 2.0 available at * * http://www.apache.org/licenses/LICENSE-2.0, or another agreement * * between Ultraleap and you, your company or other organization. * ******************************************************************************/ using Leap.Unity.Attributes; using System; using System.Collections; using System.Collections.Generic; #if UNITY_EDITOR using UnityEditor; #endif using UnityEngine; namespace Leap.Unity.Attachments { /// /// Add an GameObject with this script to your scene if you would like to have a /// Transform hierarchy that will follow various important points on a hand, whether /// for visuals or for logic. The AttachmentHands object will maintain two child /// objects, one for each of the player's hands. Use the Inspector to customize /// which points you'd like to see in the hierarchy beneath the individual /// AttachmentHand objects. /// [ExecuteInEditMode] public class AttachmentHands : MonoBehaviour { [SerializeField] private AttachmentPointFlags _attachmentPoints = AttachmentPointFlags.Palm | AttachmentPointFlags.Wrist; public AttachmentPointFlags attachmentPoints { get { return _attachmentPoints; } set { if (_attachmentPoints != value) { #if UNITY_EDITOR Undo.IncrementCurrentGroup(); Undo.SetCurrentGroupName("Modify Attachment Points"); Undo.RecordObject(this, "Modify AttachmentHands Points"); #endif _attachmentPoints = value; refreshAttachmentHandTransforms(); } } } private Func[] _handAccessors; /// /// Gets or sets the functions used to get the latest Leap.Hand data for the corresponding /// AttachmentHand objects in the attachmentHands array. Modify this if you'd like to customize /// how hand data is sent to AttachmentHands; e.g. a networked multiplayer game receiving /// serialized hand data for a networked player representation. /// /// This array is automatically populated if it is null or empty during OnValidate() in the editor, /// but it can be modified freely afterwards. /// public Func[] handAccessors { get { return _handAccessors; } set { _handAccessors = value; } } private AttachmentHand[] _attachmentHands; /// /// Gets or sets the array of AttachmentHand objects that this component manages. The length of this /// array should match the handAccessors array; corresponding-index slots in handAccessors will be /// used to assign transform data to the AttachmentHand objects in this component's Update(). /// /// This array is automatically populated if it is null or empty during OnValidate() in the editor, /// but can be modified freely afterwards. /// public AttachmentHand[] attachmentHands { get { return _attachmentHands; } set { _attachmentHands = value; } } #if UNITY_EDITOR void OnValidate() { if (getIsPrefab()) return; reinitialize(); } #endif void Awake() { #if UNITY_EDITOR if (getIsPrefab()) return; #endif reinitialize(); } private void reinitialize() { refreshHandAccessors(); refreshAttachmentHands(); #if UNITY_EDITOR EditorApplication.delayCall += refreshAttachmentHandTransforms; #else refreshAttachmentHandTransforms(); #endif } void Update() { #if UNITY_EDITOR if (Utils.IsObjectPartOfPrefabAsset(this.gameObject)) { return; } #endif bool requiresReinitialization = false; using (new ProfilerSample("Attachment Hands Update", this.gameObject)) { for (int i = 0; i < _attachmentHands.Length; i++) { var attachmentHand = attachmentHands[i]; if (attachmentHand == null) { requiresReinitialization = true; break; } var leapHand = handAccessors[i](); attachmentHand.isTracked = leapHand != null; using (new ProfilerSample(attachmentHand.gameObject.name + " Update Points")) { foreach (var point in attachmentHand.points) { point.SetTransformUsingHand(leapHand); } } } if (requiresReinitialization) { reinitialize(); } } } private void refreshHandAccessors() { // If necessary, generate a left-hand and right-hand set of accessors. if (_handAccessors == null || _handAccessors.Length == 0) { _handAccessors = new Func[2]; _handAccessors[0] = new Func(() => { return Hands.Left; }); _handAccessors[1] = new Func(() => { return Hands.Right; }); } } private void refreshAttachmentHands() { // If we're a prefab, we'll be unable to set parent transforms, so we shouldn't create new objects in general. bool isPrefab = false; #if UNITY_EDITOR isPrefab = getIsPrefab(); #endif // If necessary, generate a left and right AttachmentHand. if (_attachmentHands == null || _attachmentHands.Length == 0 || (_attachmentHands[0] == null || _attachmentHands[1] == null)) { _attachmentHands = new AttachmentHand[2]; // Try to use existing AttachmentHand objects first. foreach (Transform child in this.transform.GetChildren()) { var attachmentHand = child.GetComponent(); if (attachmentHand != null) { _attachmentHands[attachmentHand.chirality == Chirality.Left ? 0 : 1] = attachmentHand; } } // If we are a prefab and there are missing AttachmentHands, we have to return early. // We can't set parent transforms while a prefab. We're only OK if we already have attachmentHand // objects and their parents are properly set. if (isPrefab && (_attachmentHands[0] == null || _attachmentHands[0].transform.parent != this.transform || _attachmentHands[1] == null || _attachmentHands[1].transform.parent != this.transform)) { return; } // Construct any missing AttachmentHand objects. if (_attachmentHands[0] == null) { GameObject obj = new GameObject(); #if UNITY_EDITOR Undo.RegisterCreatedObjectUndo(obj, "Created GameObject"); #endif _attachmentHands[0] = obj.AddComponent(); _attachmentHands[0].chirality = Chirality.Left; } _attachmentHands[0].gameObject.name = "Attachment Hand (Left)"; if (_attachmentHands[0].transform.parent != this.transform) _attachmentHands[0].transform.parent = this.transform; if (_attachmentHands[1] == null) { GameObject obj = new GameObject(); #if UNITY_EDITOR Undo.RegisterCreatedObjectUndo(obj, "Created GameObject"); #endif _attachmentHands[1] = obj.AddComponent(); _attachmentHands[1].chirality = Chirality.Right; } _attachmentHands[1].gameObject.name = "Attachment Hand (Right)"; if (_attachmentHands[1].transform.parent != this.transform) _attachmentHands[1].transform.parent = this.transform; // Organize left hand first in sibling order. _attachmentHands[0].transform.SetSiblingIndex(0); _attachmentHands[1].transform.SetSiblingIndex(1); } } private void refreshAttachmentHandTransforms() { if (this == null) return; #if UNITY_EDITOR if (getIsPrefab()) return; #endif bool requiresReinitialization = false; if (_attachmentHands == null) { requiresReinitialization = true; } else { foreach (var hand in _attachmentHands) { if (hand == null) { // AttachmentHand must have been destroyed requiresReinitialization = true; break; } hand.refreshAttachmentTransforms(_attachmentPoints); } #if UNITY_EDITOR EditorUtility.SetDirty(this); #endif } if (requiresReinitialization) { reinitialize(); } } #if UNITY_EDITOR private bool getIsPrefab() { return Utils.IsObjectPartOfPrefabAsset(this.gameObject); } #endif } }