320 lines
13 KiB
C#
320 lines
13 KiB
C#
// Copyright (c) Microsoft Corporation.
|
|
// Licensed under the MIT License.
|
|
|
|
using Microsoft.MixedReality.Toolkit.Utilities;
|
|
using System.Collections.Generic;
|
|
using Unity.Profiling;
|
|
using UnityEngine;
|
|
|
|
namespace Microsoft.MixedReality.Toolkit.Input
|
|
{
|
|
[AddComponentMenu("Scripts/MRTK/Core/BaseHandVisualizer")]
|
|
public class BaseHandVisualizer : MonoBehaviour, IMixedRealityHandVisualizer, IMixedRealitySourceStateHandler, IMixedRealityHandJointHandler, IMixedRealityHandMeshHandler
|
|
{
|
|
public virtual Handedness Handedness { get; set; }
|
|
|
|
public GameObject GameObjectProxy => gameObject;
|
|
|
|
public IMixedRealityController Controller { get; set; }
|
|
|
|
[System.Obsolete("This has been replaced with jointsArray with TrackedHandJoint as the int index.", true)]
|
|
protected readonly Dictionary<TrackedHandJoint, Transform> joints = new Dictionary<TrackedHandJoint, Transform>();
|
|
|
|
protected IMixedRealityHand MixedRealityHand { get; private set; } = null;
|
|
protected Transform[] JointsArray { get; private set; } = new Transform[ArticulatedHandPose.JointCount];
|
|
protected MeshFilter handMeshFilter;
|
|
|
|
// This member stores the last count of hand mesh vertices, to avoid using
|
|
// handMeshFilter.mesh.vertices, which does a copy of the vertices.
|
|
private int lastHandMeshVerticesCount = 0;
|
|
|
|
private bool handJointsUpdated = false;
|
|
private HandMeshInfo lastHandMeshInfo = null;
|
|
|
|
private void OnEnable()
|
|
{
|
|
CoreServices.InputSystem?.RegisterHandler<IMixedRealitySourceStateHandler>(this);
|
|
CoreServices.InputSystem?.RegisterHandler<IMixedRealityHandJointHandler>(this);
|
|
CoreServices.InputSystem?.RegisterHandler<IMixedRealityHandMeshHandler>(this);
|
|
}
|
|
|
|
protected virtual void Start()
|
|
{
|
|
if (Controller != null)
|
|
{
|
|
Handedness = Controller.ControllerHandedness;
|
|
MixedRealityHand = Controller as IMixedRealityHand;
|
|
}
|
|
}
|
|
|
|
protected virtual void Update()
|
|
{
|
|
UpdateHandJoints();
|
|
UpdateHandMesh();
|
|
}
|
|
|
|
private void OnDisable()
|
|
{
|
|
CoreServices.InputSystem?.UnregisterHandler<IMixedRealitySourceStateHandler>(this);
|
|
CoreServices.InputSystem?.UnregisterHandler<IMixedRealityHandJointHandler>(this);
|
|
CoreServices.InputSystem?.UnregisterHandler<IMixedRealityHandMeshHandler>(this);
|
|
}
|
|
|
|
private void OnDestroy()
|
|
{
|
|
foreach (Transform joint in JointsArray)
|
|
{
|
|
if (joint != null)
|
|
{
|
|
Destroy(joint.gameObject);
|
|
}
|
|
|
|
JointsArray = System.Array.Empty<Transform>();
|
|
}
|
|
|
|
if (handMeshFilter != null)
|
|
{
|
|
Destroy(handMeshFilter.gameObject);
|
|
handMeshFilter = null;
|
|
}
|
|
}
|
|
|
|
[System.Obsolete("Use HandJointUtils.TryGetJointPose instead of this")]
|
|
public bool TryGetJointTransform(TrackedHandJoint joint, out Transform jointTransform)
|
|
{
|
|
if (JointsArray == null)
|
|
{
|
|
jointTransform = null;
|
|
return false;
|
|
}
|
|
|
|
jointTransform = JointsArray[(int)joint];
|
|
return jointTransform != null;
|
|
}
|
|
|
|
/// <inheritdoc/>
|
|
void IMixedRealitySourceStateHandler.OnSourceDetected(SourceStateEventData eventData) { }
|
|
|
|
/// <inheritdoc/>
|
|
void IMixedRealitySourceStateHandler.OnSourceLost(SourceStateEventData eventData)
|
|
{
|
|
// We must check if either this or gameObject equate to null because this callback may be triggered after
|
|
// the object has been destroyed. Although event handlers are unregistered in OnDisable(), this may in fact
|
|
// be postponed (see BaseEventSystem.UnregisterHandler()).
|
|
if (this.IsNotNull() && gameObject != null && Controller?.InputSource.SourceId == eventData.SourceId)
|
|
{
|
|
Destroy(gameObject);
|
|
}
|
|
}
|
|
|
|
private static readonly ProfilerMarker OnHandJointsUpdatedPerfMarker = new ProfilerMarker("[MRTK] BaseHandVisualizer.OnHandJointsUpdated");
|
|
|
|
/// <inheritdoc/>
|
|
void IMixedRealityHandJointHandler.OnHandJointsUpdated(InputEventData<IDictionary<TrackedHandJoint, MixedRealityPose>> eventData)
|
|
{
|
|
using (OnHandJointsUpdatedPerfMarker.Auto())
|
|
{
|
|
if (eventData.InputSource.SourceId != Controller.InputSource.SourceId
|
|
|| eventData.Handedness != Controller.ControllerHandedness)
|
|
{
|
|
return;
|
|
}
|
|
|
|
handJointsUpdated = true;
|
|
eventData.Use();
|
|
}
|
|
}
|
|
|
|
private static readonly ProfilerMarker OnHandMeshUpdatedPerfMarker = new ProfilerMarker("[MRTK] BaseHandVisualizer.OnHandMeshUpdated");
|
|
|
|
/// <inheritdoc/>
|
|
void IMixedRealityHandMeshHandler.OnHandMeshUpdated(InputEventData<HandMeshInfo> eventData)
|
|
{
|
|
using (OnHandMeshUpdatedPerfMarker.Auto())
|
|
{
|
|
if (eventData.InputSource.SourceId != Controller.InputSource.SourceId
|
|
|| eventData.Handedness != Controller.ControllerHandedness)
|
|
{
|
|
return;
|
|
}
|
|
|
|
lastHandMeshInfo = eventData.InputData;
|
|
eventData.Use();
|
|
}
|
|
}
|
|
|
|
private static readonly ProfilerMarker UpdateHandJointsPerfMarker = new ProfilerMarker("[MRTK] BaseHandVisualizer.UpdateHandJoints");
|
|
|
|
protected virtual bool UpdateHandJoints()
|
|
{
|
|
using (UpdateHandJointsPerfMarker.Auto())
|
|
{
|
|
if (!handJointsUpdated || MixedRealityHand.IsNull())
|
|
{
|
|
return false;
|
|
}
|
|
|
|
IMixedRealityInputSystem inputSystem = CoreServices.InputSystem;
|
|
MixedRealityHandTrackingProfile handTrackingProfile = inputSystem?.InputSystemProfile != null ? inputSystem.InputSystemProfile.HandTrackingProfile : null;
|
|
if (handTrackingProfile != null && !handTrackingProfile.EnableHandJointVisualization)
|
|
{
|
|
// clear existing joint GameObjects / meshes
|
|
foreach (Transform joint in JointsArray)
|
|
{
|
|
if (joint != null)
|
|
{
|
|
Destroy(joint.gameObject);
|
|
}
|
|
}
|
|
|
|
JointsArray = System.Array.Empty<Transform>();
|
|
|
|
// Even though the base class isn't handling joint visualization, we still received new joints.
|
|
// Return true here in case any derived classes want to update.
|
|
return true;
|
|
}
|
|
|
|
if (JointsArray.Length != ArticulatedHandPose.JointCount)
|
|
{
|
|
JointsArray = new Transform[ArticulatedHandPose.JointCount];
|
|
}
|
|
|
|
// This starts at 1 to skip over TrackedHandJoint.None.
|
|
for (int i = 1; i < ArticulatedHandPose.JointCount; i++)
|
|
{
|
|
TrackedHandJoint handJoint = (TrackedHandJoint)i;
|
|
|
|
// Skip this hand joint if the event data doesn't have an entry for it
|
|
if (!MixedRealityHand.TryGetJoint(handJoint, out MixedRealityPose handJointPose))
|
|
{
|
|
continue;
|
|
}
|
|
|
|
Transform jointTransform = JointsArray[i];
|
|
if (jointTransform != null)
|
|
{
|
|
jointTransform.SetPositionAndRotation(handJointPose.Position, handJointPose.Rotation);
|
|
}
|
|
else
|
|
{
|
|
GameObject prefab;
|
|
if (handJoint == TrackedHandJoint.None || handTrackingProfile == null)
|
|
{
|
|
// No visible mesh for the "None" joint
|
|
prefab = null;
|
|
}
|
|
else if (handJoint == TrackedHandJoint.Palm)
|
|
{
|
|
prefab = handTrackingProfile.PalmJointPrefab;
|
|
}
|
|
else if (handJoint == TrackedHandJoint.IndexTip)
|
|
{
|
|
prefab = handTrackingProfile.FingerTipPrefab;
|
|
}
|
|
else
|
|
{
|
|
prefab = handTrackingProfile.JointPrefab;
|
|
}
|
|
|
|
GameObject jointObject;
|
|
if (prefab != null)
|
|
{
|
|
jointObject = Instantiate(prefab);
|
|
}
|
|
else
|
|
{
|
|
jointObject = new GameObject();
|
|
}
|
|
|
|
jointObject.name = handJoint.ToString() + " Proxy Transform";
|
|
jointObject.transform.SetPositionAndRotation(handJointPose.Position, handJointPose.Rotation);
|
|
jointObject.transform.parent = transform;
|
|
|
|
JointsArray[i] = jointObject.transform;
|
|
}
|
|
}
|
|
|
|
handJointsUpdated = false;
|
|
return true;
|
|
}
|
|
}
|
|
|
|
private static readonly ProfilerMarker UpdateHandMeshPerfMarker = new ProfilerMarker("[MRTK] BaseHandVisualizer.UpdateHandMesh");
|
|
|
|
protected virtual void UpdateHandMesh()
|
|
{
|
|
using (UpdateHandMeshPerfMarker.Auto())
|
|
{
|
|
if (lastHandMeshInfo == null)
|
|
{
|
|
return;
|
|
}
|
|
|
|
bool newMesh = handMeshFilter == null;
|
|
|
|
IMixedRealityInputSystem inputSystem = CoreServices.InputSystem;
|
|
MixedRealityHandTrackingProfile handTrackingProfile = inputSystem?.InputSystemProfile != null ? inputSystem.InputSystemProfile.HandTrackingProfile : null;
|
|
if (newMesh && handTrackingProfile != null)
|
|
{
|
|
// Create the hand mesh in the scene and assign the proper material to it
|
|
if(handTrackingProfile.SystemHandMeshMaterial.IsNotNull())
|
|
{
|
|
handMeshFilter = new GameObject("System Hand Mesh").EnsureComponent<MeshFilter>();
|
|
handMeshFilter.EnsureComponent<MeshRenderer>().material = handTrackingProfile.SystemHandMeshMaterial;
|
|
}
|
|
#pragma warning disable 0618
|
|
else if (handTrackingProfile.HandMeshPrefab.IsNotNull())
|
|
{
|
|
handMeshFilter = Instantiate(handTrackingProfile.HandMeshPrefab).GetComponent<MeshFilter>();
|
|
}
|
|
#pragma warning restore 0618
|
|
|
|
// Initialize the hand mesh if we generated it successfully
|
|
if (handMeshFilter != null)
|
|
{
|
|
lastHandMeshVerticesCount = handMeshFilter.mesh.vertices.Length;
|
|
handMeshFilter.transform.parent = transform;
|
|
}
|
|
}
|
|
|
|
if (handMeshFilter != null)
|
|
{
|
|
Mesh mesh = handMeshFilter.mesh;
|
|
|
|
bool meshChanged = false;
|
|
// On some platforms, mesh length counts may change as the hand mesh is updated.
|
|
// In order to update the vertices when the array sizes change, the mesh
|
|
// must be cleared per instructions here:
|
|
// https://docs.unity3d.com/ScriptReference/Mesh.html
|
|
if (lastHandMeshVerticesCount != lastHandMeshInfo.vertices?.Length)
|
|
{
|
|
meshChanged = true;
|
|
mesh.Clear();
|
|
}
|
|
|
|
mesh.vertices = lastHandMeshInfo.vertices;
|
|
mesh.normals = lastHandMeshInfo.normals;
|
|
lastHandMeshVerticesCount = lastHandMeshInfo.vertices != null ? lastHandMeshInfo.vertices.Length : 0;
|
|
|
|
if (newMesh || meshChanged)
|
|
{
|
|
mesh.triangles = lastHandMeshInfo.triangles;
|
|
|
|
if (lastHandMeshInfo.uvs?.Length > 0)
|
|
{
|
|
mesh.uv = lastHandMeshInfo.uvs;
|
|
}
|
|
}
|
|
|
|
if (meshChanged)
|
|
{
|
|
mesh.RecalculateBounds();
|
|
}
|
|
|
|
handMeshFilter.transform.SetPositionAndRotation(lastHandMeshInfo.position, lastHandMeshInfo.rotation);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|