Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
49 commits
Select commit Hold shift + click to select a range
6b03ef7
Initial drop
keveleigh Feb 27, 2025
ae61e24
Iterate
keveleigh Mar 14, 2025
6c1fd65
Update PlatformHandMeshVisualizer.cs
keveleigh Mar 15, 2025
920b4e4
Update PlatformHandMeshVisualizer.cs
keveleigh Mar 15, 2025
e5d3cee
Docs update
keveleigh Mar 17, 2025
6777560
Move prefabs into own folder
keveleigh Mar 17, 2025
5dd2b45
Update PlatformHandMeshVisualizer.cs
keveleigh Mar 17, 2025
b146ea9
Remove temp hand mesh prefabs
keveleigh Mar 17, 2025
464f0a1
Add hand mesh manager
keveleigh Mar 17, 2025
04c91a9
Iterate
keveleigh Mar 17, 2025
6bfd6fe
Iterate
keveleigh Mar 18, 2025
eb16746
Update PlatformHandMeshVisualizer.cs
keveleigh Mar 18, 2025
dae1ea2
Add AXR hand mesh support
keveleigh Mar 6, 2025
b02dc18
Update PlatformHandMeshVisualizer.cs
keveleigh Mar 18, 2025
941b07e
Update PlatformHandMeshVisualizer.cs
keveleigh Mar 18, 2025
9660491
Update PlatformHandMeshVisualizer.cs
keveleigh Mar 19, 2025
6cb69de
Ensure the bounds are recalculated
keveleigh Mar 28, 2025
3d68798
Ensure we transform from the playspace pose
keveleigh Apr 9, 2025
f4be443
Update CHANGELOG.md
keveleigh Apr 11, 2025
c2322a1
Update PlatformHandMeshVisualizer.cs
keveleigh Apr 14, 2025
aa77219
Update PlatformHandMeshVisualizer.cs
keveleigh Apr 14, 2025
bcf3c89
Update shader to use wrist position for fade, if given
keveleigh Apr 14, 2025
6bae25e
Iterate back to sphere
keveleigh Apr 17, 2025
523e67a
Iterate shaders again
keveleigh Apr 18, 2025
bc8ba5b
Iterate shaders again
keveleigh Apr 22, 2025
8594aac
Update to include outline
keveleigh Apr 22, 2025
0df4acf
Update material defaults
keveleigh Apr 23, 2025
96b1371
Iterate to using Unity's API instead of Google's
keveleigh May 6, 2025
0385ca6
Update AndroidXRConfig.cs
keveleigh May 6, 2025
221b560
Update HandMeshVisualizer.cs
keveleigh May 6, 2025
aea0616
Don't update more than once per frame
keveleigh May 8, 2025
74938c0
Update PlatformHandMeshVisualizer.cs
keveleigh May 8, 2025
7b37a39
Revert "Iterate to using Unity's API instead of Google's"
keveleigh May 8, 2025
96e6235
Update PlatformHandMeshVisualizer.cs
keveleigh May 8, 2025
e67fa3b
Use input action references for tracked state
keveleigh May 27, 2025
c995946
Reapply "Iterate to using Unity's API instead of Google's"
keveleigh Jul 9, 2025
c84fcff
Update Hands version
keveleigh Jul 9, 2025
13467cf
Some optimizations and improvements
keveleigh Jul 9, 2025
03662cf
Another iteration
keveleigh Jul 9, 2025
222f74a
Revert "Reapply "Iterate to using Unity's API instead of Google's""
keveleigh Jul 9, 2025
4f1690f
Mark the hand mesh as dynamic
keveleigh Jul 9, 2025
4caf3ed
Iterate visualizers
keveleigh Jul 31, 2025
08f070c
Update PlatformHandMeshVisualizer.cs
keveleigh Aug 1, 2025
fa27087
Reapply "Reapply "Iterate to using Unity's API instead of Google's""
keveleigh Aug 4, 2025
092ea4b
Update PlatformHandMeshVisualizer.cs
keveleigh Aug 4, 2025
c10a28c
Update PlatformHandMeshVisualizer.cs
keveleigh Aug 5, 2025
c3253ed
Move to event handler?
keveleigh Oct 1, 2025
5d3ab69
Move back to polling
keveleigh Oct 1, 2025
5f57f02
Update PlatformHandMeshVisualizer.cs
keveleigh Oct 1, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -24,16 +24,16 @@ public static void InstallPackages()
return;
}

Debug.Log("Adding com.unity.xr.androidxr-openxr and com.google.xr.extensions...");
request = Client.AddAndRemove(new[] { "com.unity.xr.androidxr-openxr", "https://github.com/android/android-xr-unity-package.git" });
Debug.Log("Adding the Unity OpenXR Android XR package...");
request = Client.AddAndRemove(new[] { "com.unity.xr.androidxr-openxr" });
EditorApplication.update += Progress;
}

private static void Progress()
{
if (request.IsCompleted)
{
Debug.Log($"Package install request complete ({request.Status})");
Debug.Log($"Package install request complete ({request.Status}).");
EditorApplication.update -= Progress;
request = null;
}
Expand Down
5 changes: 3 additions & 2 deletions UnityProjects/MRTKDevTemplate/Packages/packages-lock.json
Original file line number Diff line number Diff line change
Expand Up @@ -264,12 +264,13 @@
"url": "https://packages.unity.com"
},
"com.unity.xr.hands": {
"version": "1.3.0",
"version": "1.6.0",
"depth": 1,
"source": "registry",
"dependencies": {
"com.unity.modules.xr": "1.0.0",
"com.unity.inputsystem": "1.3.0",
"com.unity.mathematics": "1.2.6",
"com.unity.xr.core-utils": "2.2.0",
"com.unity.xr.management": "4.0.1"
},
Expand Down Expand Up @@ -389,7 +390,7 @@
"com.unity.inputsystem": "1.6.1",
"com.unity.xr.arfoundation": "5.0.5",
"com.unity.xr.core-utils": "2.1.0",
"com.unity.xr.hands": "1.3.0",
"com.unity.xr.hands": "1.6.0",
"com.unity.xr.interaction.toolkit": "3.0.4",
"org.mixedrealitytoolkit.core": "4.0.0"
}
Expand Down
1 change: 1 addition & 0 deletions org.mixedrealitytoolkit.input/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/).

* Added `MRTKFocusFeature` to provide XrSession focus info to MRTK components. [PR #1057](https://github.com/MixedRealityToolkit/MixedRealityToolkit-Unity/pull/1057)
* Added input action focus handling to disable controller/hand tracked state when the XrSession goes out of focus. [PR #1057](https://github.com/MixedRealityToolkit/MixedRealityToolkit-Unity/pull/1057)
* Added support for XR_MSFT_hand_tracking_mesh and XR_ANDROID_hand_mesh on compatible runtimes. [PR #993](https://github.com/MixedRealityToolkit/MixedRealityToolkit-Unity/pull/993)

## [4.0.0-pre.2] - 2025-12-05

Expand Down
10 changes: 7 additions & 3 deletions org.mixedrealitytoolkit.input/Controllers/HandModel.cs
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ public Transform ModelPrefab
/// the hand model prefab when implementing <see cref="ISelectInputVisualizer"/>.
/// </summary>
public XRInputButtonReader SelectInput => selectInput;

#endregion Associated hand select values

/// <summary>
Expand Down Expand Up @@ -88,9 +88,13 @@ protected virtual void Start()
Debug.Assert(selectInput != null, $"The Select Input reader for {name} is not set and will not be used with the instantiated hand model.");

// Set the select input reader for the model if it implements ISelectInputVisualizer
if (selectInput != null && model != null && model.TryGetComponent(out ISelectInputVisualizer selectInputVisualizer))
if (selectInput != null && model != null)
{
selectInputVisualizer.SelectInput = selectInput;
ISelectInputVisualizer[] selectInputVisualizers = model.GetComponentsInChildren<ISelectInputVisualizer>();
foreach (ISelectInputVisualizer selectInputVisualizer in selectInputVisualizers)
{
selectInputVisualizer.SelectInput = selectInput;
}
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -263,8 +263,7 @@ private XRHand GetTrackedHand()
}
}

XRHand hand = HandNode == XRNode.LeftHand ? xrHandSubsystem.leftHand : xrHandSubsystem.rightHand;
return hand;
return HandNode == XRNode.LeftHand ? xrHandSubsystem.leftHand : xrHandSubsystem.rightHand;
}
}

Expand Down
232 changes: 232 additions & 0 deletions org.mixedrealitytoolkit.input/Visualizers/HandMeshVisualizer.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,232 @@
// Copyright (c) Mixed Reality Toolkit Contributors
// Licensed under the BSD 3-Clause

using System;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.XR;
using UnityEngine.XR.Interaction.Toolkit;
using UnityEngine.XR.Interaction.Toolkit.Inputs.Readers;

namespace MixedReality.Toolkit.Input
{
public abstract class HandMeshVisualizer : MonoBehaviour, ISelectInputVisualizer
{
[SerializeField]
[Tooltip("The XRNode on which this hand is located.")]
private XRNode handNode = XRNode.LeftHand;

/// <summary> The XRNode on which this hand is located. </summary>
public XRNode HandNode { get => handNode; set => handNode = value; }

[SerializeField]
[Tooltip("When true, this visualizer will render rigged hands even on XR devices " +
"with transparent displays or with passthrough enabled. When false, the rigged hands will only render " +
"on devices with opaque displays. This behavior uses XRDisplaySubsystem.displayOpaque.")]
private bool showHandsOnTransparentDisplays;

/// <summary>
/// When true, this visualizer will render rigged hands even on XR devices with transparent displays or with passthrough enabled.
/// When false, the rigged hands will only render on devices with opaque displays.
/// </summary>
/// <remarks>
/// This behavior uses <see cref="XRDisplaySubsystem.displayOpaque"/>.
/// Usually, it's recommended not to show hand visualization on transparent displays as it can
/// distract from the user's real hands, and cause a "double image" effect that can be disconcerting.
/// </remarks>
public bool ShowHandsOnTransparentDisplays
{
get => showHandsOnTransparentDisplays;
set => showHandsOnTransparentDisplays = value;
}

[SerializeField]
[Tooltip("Name of the shader property used to drive pinch-amount-based visual effects. " +
"Generally, maps to something like a glow or an outline color!")]
private string pinchAmountMaterialProperty = "_PinchAmount";

[SerializeField]
[Tooltip("The input reader used when pinch selecting an interactable.")]
private XRInputButtonReader selectInput = new XRInputButtonReader("Select");

#region ISelectInputVisualizer implementation

/// <summary>
/// Input reader used when pinch selecting an interactable.
/// </summary>
public XRInputButtonReader SelectInput
{
get => selectInput;
set => SetInputProperty(ref selectInput, value);
}

#endregion ISelectInputVisualizer implementation

// The property block used to modify the pinch amount property on the material
private MaterialPropertyBlock propertyBlock = null;

// Scratch list for checking for the presence of display subsystems.
private readonly List<XRDisplaySubsystem> displaySubsystems = new List<XRDisplaySubsystem>();

// The XRController that is used to determine the pinch strength (i.e., select value!)
[Obsolete("This field has been deprecated in version 4.0.0 and will be removed in a future version. Use the SelectInput property instead.")]
private XRBaseController controller;

/// <summary>
/// The list of button input readers used by this interactor. This interactor will automatically enable or disable direct actions
/// if that mode is used during <see cref="OnEnable"/> and <see cref="OnDisable"/>.
/// </summary>
/// <seealso cref="XRInputButtonReader.EnableDirectActionIfModeUsed"/>
/// <seealso cref="XRInputButtonReader.DisableDirectActionIfModeUsed"/>
private readonly List<XRInputButtonReader> buttonReaders = new List<XRInputButtonReader>();

/// <summary>
/// Whether this visualizer currently has a loaded and visible hand mesh or not.
/// </summary>
protected internal bool IsRendering => HandRenderer != null && HandRenderer.enabled;

/// <summary>
/// The renderer for this visualizer, to use to visualize the pinch amount.
/// </summary>
protected abstract Renderer HandRenderer { get; }

/// <summary>
/// A Unity event function that is called when an enabled script instance is being loaded.
/// </summary>
protected virtual void Awake()
{
propertyBlock = new MaterialPropertyBlock();
buttonReaders.Add(selectInput);
}

/// <summary>
/// A Unity event function that is called when the script component has been enabled.
/// </summary>
protected virtual void OnEnable()
{
buttonReaders.ForEach(reader => reader?.EnableDirectActionIfModeUsed());

// Ensure hand is not visible until we can update position first time.
HandRenderer.enabled = false;

Debug.Assert(handNode == XRNode.LeftHand || handNode == XRNode.RightHand,
$"HandVisualizer has an invalid XRNode ({handNode})!");
}

/// <summary>
/// A Unity event function that is called when the script component has been disabled.
/// </summary>
protected virtual void OnDisable()
{
buttonReaders.ForEach(reader => reader?.DisableDirectActionIfModeUsed());

// Disable the rigged hand renderer when this component is disabled
HandRenderer.enabled = false;
}

/// <summary>
/// Helper method for setting an input property.
/// </summary>
/// <param name="property">The <see langword="ref"/> to the field.</param>
/// <param name="value">The new value being set.</param>
/// <remarks>
/// If the application is playing, this method will also enable or disable directly embedded input actions
/// serialized by the input if that mode is used. It will also add or remove the input from the list of button inputs
/// to automatically manage enabling and disabling direct actions with this behavior.
/// </remarks>
/// <seealso cref="buttonReaders"/>
protected void SetInputProperty(ref XRInputButtonReader property, XRInputButtonReader value)
{
if (value == null)
{
Debug.LogError("Setting XRInputButtonReader property to null is disallowed and has therefore been ignored.");
return;
}

if (Application.isPlaying && property != null)
{
buttonReaders?.Remove(property);
property.DisableDirectActionIfModeUsed();
}

property = value;

if (Application.isPlaying)
{
buttonReaders?.Add(property);
if (isActiveAndEnabled)
{
property.EnableDirectActionIfModeUsed();
}
}
}

protected virtual bool ShouldRenderHand()
{
if (displaySubsystems.Count == 0)
{
SubsystemManager.GetSubsystems(displaySubsystems);
}

// Are we running on an XR display and it happens to be transparent?
// Probably shouldn't be showing rigged hands! (Users can
// specify showHandsOnTransparentDisplays if they disagree.)
if (displaySubsystems.Count > 0 &&
displaySubsystems[0].running &&
!displaySubsystems[0].displayOpaque &&
!showHandsOnTransparentDisplays)
{
return false;
}

// All checks out!
return true;
}

protected virtual void UpdateHandMaterial()
{
if (HandRenderer == null)
{
return;
}

// Update the hand material
float pinchAmount = TryGetSelectionValue(out float selectionValue) ? Mathf.Pow(selectionValue, 2.0f) : 0;
HandRenderer.GetPropertyBlock(propertyBlock);
propertyBlock.SetFloat(pinchAmountMaterialProperty, pinchAmount);
HandRenderer.SetPropertyBlock(propertyBlock);
}

/// <summary>
/// Try to obtain the tracked devices selection value from the provided input reader.
/// </summary>
/// <remarks>
/// For backwards compatibility, this method will also attempt to get the selection amount from a
/// legacy XRI controller if the input reader is not set.
/// </remarks>
private bool TryGetSelectionValue(out float value)
{
if (selectInput != null && selectInput.TryReadValue(out value))
{
return true;
}

bool success = false;
value = 0.0f;

#pragma warning disable CS0618 // XRBaseController is obsolete
if (controller == null)
{
controller = GetComponentInParent<XRBaseController>();
}
if (controller != null)
{
value = controller.selectInteractionState.value;
success = true;
}
#pragma warning restore CS0618 // XRBaseController is obsolete

return success;
}
}
}

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading