Commit 29de0c28 authored by BlackAngle233's avatar BlackAngle233
Browse files

10.19 learned

parent 912976bb
<Project>
<!-- Check if MSBuildForUnity is being used to resolve, by checking if its version is present or not -->
<PropertyGroup Condition="'$(MSBuildForUnityVersion)' == ''">
<!-- If $(UnityPlayer) is not set, default to Standalone. -->
<MRTKUnityPlayer Condition=" '$(MRTKUnityPlayer)' == '' ">Standalone</MRTKUnityPlayer>
<!-- Player specific assemblies are in a directory name that starts with the player name and is suffixed with 'Player'. -->
<_MRTKPlayerDirectory>$(MRTKUnityPlayer)Player</_MRTKPlayerDirectory>
</PropertyGroup>
<!-- MSBuild for Unity. -->
<ItemGroup Condition="'$(MSBuildForUnityVersion)' != ''">
<Content Include="$(MSBuildThisFileDirectory)..\MRTK\**">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
<!-- Don't show .meta files in Solution Explorer - it's not useful. -->
<Visible Condition="'%(Extension)' == '.meta'">false</Visible>
<Link>$(MSBuildThisFileName)\%(RecursiveDir)%(Filename)%(Extension)</Link>
</Content>
<Content Include="$(MSBuildThisFileDirectory)..\Plugins\**">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
<!-- Don't show .meta files in Solution Explorer - it's not useful. -->
<Visible Condition="'%(Extension)' == '.meta'">false</Visible>
<Link>$(MSBuildThisFileName)\Plugins\%(RecursiveDir)%(Filename)%(Extension)</Link>
</Content>
</ItemGroup>
<!-- MRW -->
<ItemGroup Condition="'$(MSBuildForUnityVersion)' == ''">
<!-- Include content, but only if explicitly requested. This is useful if an MSBuild project references this
nuget package, and the output of the MSBuild project is copied into a Unity project. -->
<Content Include="$(MSBuildThisFileDirectory)..\MRTK\**" Condition=" '$(MRTKIncludeContent)' == 'true' ">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
<!-- Don't show .meta files in Solution Explorer - it's not useful. -->
<Visible Condition=" '%(Extension)' == '.meta' ">false</Visible>
<Link>MRTK\%(RecursiveDir)%(Filename)%(Extension)</Link>
</Content>
<Content Include="$(MSBuildThisFileDirectory)..\link.xml" Condition=" '$(MRTKIncludeContent)' == 'true' ">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
<Link>MRTK\link.xml</Link>
</Content>
<!-- Get all the dlls for the target player, and store the Identity in a custom OriginalPath metadata.
If Identity is used directly as the HintPath in the Reference, Visual Studio generates a warning
indicating the assemblies are not found, even though the build succeeds. -->
<_MRTKPlayerAssemblies Include="$(MSBuildThisFileDirectory)..\Plugins\$(_MRTKPlayerDirectory)\*.dll">
<OriginalPath>%(Identity)</OriginalPath>
</_MRTKPlayerAssemblies>
<!-- Add a Reference to each assembly, where the FileName is assumed to be the assembly name (true by default),
and the HintPath is just the full path to the assembly. -->
<Reference Include="@(_MRTKPlayerAssemblies -> '%(FileName)')">
<HintPath>%(OriginalPath)</HintPath>
</Reference>
<!-- Include assembly meta files, but only if explicitly requested. This is useful if an MSBuild project references this
nuget package, and the output of the MSBuild project is copied into a Unity project. -->
<Content Include="@(_MRTKPlayerAssemblies -> '%(OriginalPath).meta')" Condition=" '$(MRTKIncludeAssemblyMetaFiles)' == 'true' ">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
<Visible>false</Visible>
</Content>
<!-- Clear the _MRTKPlayerAssemblies item list since it will be reused for each MRTK nuget package. -->
<_MRTKPlayerAssemblies Remove="@(_MRTKPlayerAssemblies)" />
</ItemGroup>
</Project>
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
using Microsoft.MixedReality.Toolkit.Utilities;
using UnityEngine;
namespace Microsoft.MixedReality.Toolkit.CameraSystem
{
public abstract class BaseCameraSettingsProvider : BaseDataProvider<IMixedRealityCameraSystem>, IMixedRealityCameraSettingsProvider
{
/// <summary>
/// Constructor.
/// </summary>
/// <param name="cameraSystem">The instance of the camera system which is managing this provider.</param>
/// <param name="name">Friendly name of the provider.</param>
/// <param name="priority">Provider priority. Used to determine order of instantiation.</param>
/// <param name="profile">The provider's configuration profile.</param>
protected BaseCameraSettingsProvider(
IMixedRealityCameraSystem cameraSystem,
string name = null,
uint priority = DefaultPriority,
BaseCameraSettingsProfile profile = null) : base(cameraSystem, name, priority, profile)
{ }
/// <inheritdoc/>
public virtual bool IsOpaque { get; } = false;
/// <inheritdoc/>
public virtual void ApplyConfiguration()
{
// It is the responsibility of the camera settings provider to set the display settings (this allows overriding the
// default values with per-camera provider values).
MixedRealityCameraProfile cameraProfile = (Service as IMixedRealityCameraSystem)?.CameraProfile;
if (cameraProfile == null) { return; }
if (IsOpaque)
{
CameraCache.Main.clearFlags = cameraProfile.CameraClearFlagsOpaqueDisplay;
CameraCache.Main.nearClipPlane = cameraProfile.NearClipPlaneOpaqueDisplay;
CameraCache.Main.farClipPlane = cameraProfile.FarClipPlaneOpaqueDisplay;
CameraCache.Main.backgroundColor = cameraProfile.BackgroundColorOpaqueDisplay;
QualitySettings.SetQualityLevel(cameraProfile.OpaqueQualityLevel, false);
}
else
{
CameraCache.Main.clearFlags = cameraProfile.CameraClearFlagsTransparentDisplay;
CameraCache.Main.backgroundColor = cameraProfile.BackgroundColorTransparentDisplay;
CameraCache.Main.nearClipPlane = cameraProfile.NearClipPlaneTransparentDisplay;
CameraCache.Main.farClipPlane = cameraProfile.FarClipPlaneTransparentDisplay;
QualitySettings.SetQualityLevel(cameraProfile.TransparentQualityLevel, false);
}
}
}
}
\ No newline at end of file
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
using Microsoft.MixedReality.Toolkit.Utilities;
using System;
using UnityEngine;
namespace Microsoft.MixedReality.Toolkit.Input
{
/// <summary>
/// Base Controller class to inherit from for all controllers.
/// </summary>
public abstract class BaseController : IMixedRealityController
{
/// <summary>
/// Constructor.
/// </summary>
protected BaseController(TrackingState trackingState, Handedness controllerHandedness, IMixedRealityInputSource inputSource = null, MixedRealityInteractionMapping[] interactions = null)
{
TrackingState = trackingState;
ControllerHandedness = controllerHandedness;
InputSource = inputSource;
Interactions = interactions;
IsPositionAvailable = false;
IsPositionApproximate = false;
IsRotationAvailable = false;
Type controllerType = GetType();
if (IsControllerMappingEnabled() && Interactions == null)
{
// We can only enable controller profiles if mappings exist.
MixedRealityControllerMapping[] controllerMappings = GetControllerMappings();
// Have to test that a controller type has been registered in the profiles,
// else its Unity input manager mappings will not have been set up by the inspector.
bool profileFound = false;
if (controllerMappings != null)
{
for (int i = 0; i < controllerMappings.Length; i++)
{
if (controllerMappings[i].ControllerType.Type == controllerType)
{
profileFound = true;
// If it is an exact match, assign interaction mappings.
if (controllerMappings[i].Handedness == ControllerHandedness &&
controllerMappings[i].Interactions.Length > 0)
{
MixedRealityInteractionMapping[] profileInteractions = controllerMappings[i].Interactions;
MixedRealityInteractionMapping[] newInteractions = new MixedRealityInteractionMapping[profileInteractions.Length];
for (int j = 0; j < profileInteractions.Length; j++)
{
newInteractions[j] = new MixedRealityInteractionMapping(profileInteractions[j]);
}
AssignControllerMappings(newInteractions);
break;
}
}
}
}
// If no controller mappings found, try to use default interactions.
if (Interactions == null || Interactions.Length < 1)
{
SetupDefaultInteractions();
// We still don't have controller mappings, so this may be a custom controller.
if (Interactions == null || Interactions.Length < 1)
{
Debug.LogWarning($"No controller interaction mappings found for {controllerType}.");
return;
}
}
// If no profile was found, warn the user. Does not stop the project from running.
if (!profileFound)
{
Debug.LogWarning($"No controller profile found for type {controllerType}; please ensure all controllers are defined in the configured MixedRealityControllerConfigurationProfile.");
}
}
if (GetControllerVisualizationProfile() != null &&
GetControllerVisualizationProfile().RenderMotionControllers &&
InputSource != null)
{
TryRenderControllerModel(controllerType, InputSource.SourceType);
}
Enabled = true;
}
/// <summary>
/// The default interactions for this controller.
/// </summary>
public virtual MixedRealityInteractionMapping[] DefaultInteractions { get; } = null;
/// <summary>
/// The Default Left Handed interactions for this controller.
/// </summary>
public virtual MixedRealityInteractionMapping[] DefaultLeftHandedInteractions { get; } = null;
/// <summary>
/// The Default Right Handed interactions for this controller.
/// </summary>
public virtual MixedRealityInteractionMapping[] DefaultRightHandedInteractions { get; } = null;
#region IMixedRealityController Implementation
/// <inheritdoc />
public bool Enabled { get; set; }
/// <inheritdoc />
public TrackingState TrackingState { get; protected set; }
/// <inheritdoc />
public Handedness ControllerHandedness { get; }
/// <inheritdoc />
public IMixedRealityInputSource InputSource { get; }
public IMixedRealityControllerVisualizer Visualizer { get; protected set; }
/// <inheritdoc />
public bool IsPositionAvailable { get; protected set; }
/// <inheritdoc />
public bool IsPositionApproximate { get; protected set; }
/// <inheritdoc />
public bool IsRotationAvailable { get; protected set; }
/// <inheritdoc />
public MixedRealityInteractionMapping[] Interactions { get; private set; } = null;
public Vector3 AngularVelocity { get; protected set; }
public Vector3 Velocity { get; protected set; }
/// <inheritdoc />
public virtual bool IsInPointingPose => true;
#endregion IMixedRealityController Implementation
/// <summary>
/// Sets up the configuration based on the Mixed Reality Controller Mapping Profile.
/// </summary>
[Obsolete("This method is no longer used. Configuration now happens in the constructor. You can check this controller's Enabled property for configuration state.")]
public bool SetupConfiguration(Type controllerType, InputSourceType inputSourceType = InputSourceType.Controller)
{
// If the constructor succeeded in finding interactions, Enabled will be true.
return Enabled;
}
/// <summary>
/// Sets up the configuration based on the Mixed Reality Controller Mapping Profile.
/// </summary>
/// <param name="controllerType">The type this controller represents.</param>
[Obsolete("This method is no longer used. Configuration now happens in the constructor. You can check this controller's Enabled property for configuration state.")]
public bool SetupConfiguration(Type controllerType)
{
// If the constructor succeeded in finding interactions, Enabled will be true.
return Enabled;
}
/// <summary>
/// Assign the default interactions based on controller handedness, if necessary.
/// </summary>
[Obsolete("The handedness parameter is no longer used. This method now reads from the controller's handedness.")]
public virtual void SetupDefaultInteractions(Handedness controllerHandedness) => SetupDefaultInteractions();
/// <summary>
/// Assign the default interactions based on this controller's handedness, if necessary.
/// </summary>
public virtual void SetupDefaultInteractions()
{
switch (ControllerHandedness)
{
case Handedness.Left:
AssignControllerMappings(DefaultLeftHandedInteractions ?? DefaultInteractions);
break;
case Handedness.Right:
AssignControllerMappings(DefaultRightHandedInteractions ?? DefaultInteractions);
break;
default:
AssignControllerMappings(DefaultInteractions);
break;
}
}
/// <summary>
/// Load the Interaction mappings for this controller from the configured Controller Mapping profile
/// </summary>
/// <param name="mappings">Configured mappings from a controller mapping profile</param>
public void AssignControllerMappings(MixedRealityInteractionMapping[] mappings)
{
Interactions = mappings;
}
/// <summary>
/// Try to render a controller model for this controller from the visualization profile.
/// </summary>
/// <param name="controllerType">The type of controller to load the model for.</param>
/// <param name="inputSourceType">Whether the model represents a hand or a controller.</param>
/// <returns>True if a model was successfully loaded or model rendering is disabled. False if a model tried to load but failed.</returns>
protected virtual bool TryRenderControllerModel(Type controllerType, InputSourceType inputSourceType)
{
GameObject controllerModel = null;
if (GetControllerVisualizationProfile() == null ||
!GetControllerVisualizationProfile().RenderMotionControllers)
{
return true;
}
// If a specific controller template wants to override the global model, assign that instead.
if (IsControllerMappingEnabled() &&
GetControllerVisualizationProfile() != null &&
inputSourceType == InputSourceType.Controller &&
!(GetControllerVisualizationProfile().GetUseDefaultModelsOverride(controllerType, ControllerHandedness)))
{
controllerModel = GetControllerVisualizationProfile().GetControllerModelOverride(controllerType, ControllerHandedness);
}
// Get the global controller model for each hand.
if (controllerModel == null &&
GetControllerVisualizationProfile() != null)
{
if (inputSourceType == InputSourceType.Controller)
{
if (ControllerHandedness == Handedness.Left &&
GetControllerVisualizationProfile().GlobalLeftHandModel != null)
{
controllerModel = GetControllerVisualizationProfile().GlobalLeftHandModel;
}
else if (ControllerHandedness == Handedness.Right &&
GetControllerVisualizationProfile().GlobalRightHandModel != null)
{
controllerModel = GetControllerVisualizationProfile().GlobalRightHandModel;
}
}
else if (inputSourceType == InputSourceType.Hand)
{
if (ControllerHandedness == Handedness.Left &&
GetControllerVisualizationProfile().GlobalLeftHandVisualizer != null)
{
controllerModel = GetControllerVisualizationProfile().GlobalLeftHandVisualizer;
}
else if (ControllerHandedness == Handedness.Right &&
GetControllerVisualizationProfile().GlobalRightHandVisualizer != null)
{
controllerModel = GetControllerVisualizationProfile().GlobalRightHandVisualizer;
}
}
}
if (controllerModel == null)
{
// no controller model available
return false;
}
// If we've got a controller model prefab, then create it and place it in the scene.
GameObject controllerObject = UnityEngine.Object.Instantiate(controllerModel);
return TryAddControllerModelToSceneHierarchy(controllerObject);
}
protected bool TryAddControllerModelToSceneHierarchy(GameObject controllerObject)
{
if (controllerObject != null)
{
controllerObject.name = $"{ControllerHandedness}_{controllerObject.name}";
MixedRealityPlayspace.AddChild(controllerObject.transform);
Visualizer = controllerObject.GetComponent<IMixedRealityControllerVisualizer>();
if (Visualizer != null)
{
Visualizer.Controller = this;
return true;
}
else
{
Debug.LogError($"{controllerObject.name} is missing a IMixedRealityControllerVisualizer component!");
return false;
}
}
return false;
}
#region MRTK instance helpers
protected static MixedRealityControllerVisualizationProfile GetControllerVisualizationProfile()
{
if (CoreServices.InputSystem?.InputSystemProfile != null)
{
return CoreServices.InputSystem.InputSystemProfile.ControllerVisualizationProfile;
}
return null;
}
protected static bool IsControllerMappingEnabled()
{
if (CoreServices.InputSystem?.InputSystemProfile != null)
{
return CoreServices.InputSystem.InputSystemProfile.IsControllerMappingEnabled;
}
return false;
}
protected static MixedRealityControllerMapping[] GetControllerMappings()
{
if (CoreServices.InputSystem?.InputSystemProfile != null &&
CoreServices.InputSystem.InputSystemProfile.ControllerMappingProfile != null)
{
// We can only enable controller profiles if mappings exist.
return CoreServices.InputSystem.InputSystemProfile.ControllerMappingProfile.MixedRealityControllerMappings;
}
return null;
}
#endregion MRTK instance helpers
}
}
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
using System;
using System.Collections;
namespace Microsoft.MixedReality.Toolkit.Input
{
/// <summary>
/// Base class for input sources that don't inherit from MonoBehaviour.
/// </summary>
/// <remarks>This base class does not support adding or removing pointers, because many will never
/// pass pointers in their constructors and will fall back to either the Gaze or Mouse Pointer.</remarks>
public class BaseGenericInputSource : IMixedRealityInputSource, IDisposable
{
/// <summary>
/// Constructor.
/// </summary>
public BaseGenericInputSource(string name, IMixedRealityPointer[] pointers = null, InputSourceType sourceType = InputSourceType.Other)
{
SourceId = (CoreServices.InputSystem != null) ? CoreServices.InputSystem.GenerateNewSourceId() : 0;
SourceName = name;
Pointers = pointers ?? new[] { CoreServices.InputSystem?.GazeProvider?.GazePointer };
SourceType = sourceType;
}
/// <inheritdoc />
public uint SourceId { get; }
/// <inheritdoc />
public string SourceName { get; }
/// <inheritdoc />
public virtual IMixedRealityPointer[] Pointers { get; }
/// <inheritdoc />
public InputSourceType SourceType { get; set; }
#region IEquality Implementation
public static bool Equals(IMixedRealityInputSource left, IMixedRealityInputSource right)
{
return left.Equals(right);
}
/// <inheritdoc />
bool IEqualityComparer.Equals(object left, object right)
{
return left.Equals(right);
}
/// <inheritdoc />
public override bool Equals(object obj)
{
if (ReferenceEquals(null, obj)) { return false; }
if (ReferenceEquals(this, obj)) { return true; }
if (obj.GetType() != GetType()) { return false; }
return Equals((IMixedRealityInputSource)obj);
}
private bool Equals(IMixedRealityInputSource other)
{
return other != null && SourceId == other.SourceId && string.Equals(SourceName, other.SourceName);
}
/// <inheritdoc />
int IEqualityComparer.GetHashCode(object obj)
{
return obj.GetHashCode();
}
/// <inheritdoc />
public override int GetHashCode()
{
unchecked
{
int hashCode = 0;
hashCode = (hashCode * 397) ^ (int)SourceId;
hashCode = (hashCode * 397) ^ (SourceName != null ? SourceName.GetHashCode() : 0);
return hashCode;
}
}
/// <summary>
/// Dispose.
/// </summary>
public virtual void Dispose() { }
#endregion IEquality Implementation
}
}
// 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
{
/// <summary>
/// Class providing a base implementation of the <see cref="IMixedRealityInputDeviceManager"/> interface.
/// </summary>
public abstract class BaseInputDeviceManager : BaseDataProvider<IMixedRealityInputSystem>, IMixedRealityInputDeviceManager
{
private bool enablePointerCache = true;
/// <summary>
/// Control mechanism to enable/disable use of Pointer Cache in request/recycling of pointers by Input System
/// </summary>
public bool EnablePointerCache
{
get => enablePointerCache;
set
{
if (enablePointerCache != value)
{
enablePointerCache = value;
if (!enablePointerCache)
{
DestroyPointerCache();
}
}
}
}
/// <summary>
/// The input system configuration profile in use in the application.
/// </summary>
protected MixedRealityInputSystemProfile InputSystemProfile => Service?.InputSystemProfile;
/// <inheritdoc />
public virtual IMixedRealityController[] GetActiveControllers() => System.Array.Empty<IMixedRealityController>();
/// <summary>
/// Constructor.
/// </summary>
/// <param name="registrar">The <see cref="IMixedRealityServiceRegistrar"/> instance that loaded the data provider.</param>
/// <param name="inputSystem">The <see cref="Microsoft.MixedReality.Toolkit.Input.IMixedRealityInputSystem"/> instance that receives data from this provider.</param>
/// <param name="name">Friendly name of the service.</param>
/// <param name="priority">Service priority. Used to determine order of instantiation.</param>
/// <param name="profile">The service's configuration profile.</param>
[System.Obsolete("This constructor is obsolete (registrar parameter is no longer required) and will be removed in a future version of the Microsoft Mixed Reality Toolkit.")]
protected BaseInputDeviceManager(
IMixedRealityServiceRegistrar registrar,
IMixedRealityInputSystem inputSystem,
string name,
uint priority,
BaseMixedRealityProfile profile) : this(inputSystem, name, priority, profile)
{
Registrar = registrar;
}
/// <summary>
/// Constructor.
/// </summary>
/// <param name="inputSystem">The <see cref="Microsoft.MixedReality.Toolkit.Input.IMixedRealityInputSystem"/> instance that receives data from this provider.</param>
/// <param name="name">Friendly name of the service.</param>
/// <param name="priority">Service priority. Used to determine order of instantiation.</param>
/// <param name="profile">The service's configuration profile.</param>
protected BaseInputDeviceManager(
IMixedRealityInputSystem inputSystem,
string name,
uint priority,
BaseMixedRealityProfile profile) : base(inputSystem, name, priority, profile) { }
#region Private members
private struct PointerConfig
{
public PointerOption profile;
public Stack<IMixedRealityPointer> cache;
}
private PointerConfig[] pointerConfigurations = System.Array.Empty<PointerConfig>();
private class PointerEqualityComparer : IEqualityComparer<IMixedRealityPointer>
{
private static PointerEqualityComparer defaultComparer;
internal static PointerEqualityComparer Default => defaultComparer ?? (defaultComparer = new PointerEqualityComparer());
/// <summary>
/// Check that references equals for two pointers
/// </summary>
public bool Equals(IMixedRealityPointer p1, IMixedRealityPointer p2)
{
return ReferenceEquals(p1, p2);
}
/// <summary>
/// Unity objects have unique equals comparison and to check keys in a dictionary,
/// we want the hash code match to be Unity's unique InstanceID to compare objects.
/// </summary>
public int GetHashCode(IMixedRealityPointer pointer)
{
if (pointer is MonoBehaviour pointerObj)
{
return pointerObj.GetInstanceID();
}
else
{
return pointer.GetHashCode();
}
}
}
private static readonly ProfilerMarker RequestPointersPerfMarker = new ProfilerMarker("[MRTK] BaseInputDeviceManager.RequestPointers");
// Active pointers associated with the config index they were spawned from
private readonly Dictionary<IMixedRealityPointer, uint> activePointersToConfig
= new Dictionary<IMixedRealityPointer, uint>(PointerEqualityComparer.Default);
#endregion
#region IMixedRealityService implementation
/// <inheritdoc />
public override void Initialize()
{
base.Initialize();
if (InputSystemProfile != null && InputSystemProfile.PointerProfile != null)
{
var initPointerOptions = InputSystemProfile.PointerProfile.PointerOptions;
// If we were previously initialized, then clear our old pointer cache
if (pointerConfigurations != null && pointerConfigurations.Length > 0)
{
DestroyPointerCache();
}
pointerConfigurations = new PointerConfig[initPointerOptions.Length];
activePointersToConfig.Clear();
for (int i = 0; i < initPointerOptions.Length; i++)
{
pointerConfigurations[i].profile = initPointerOptions[i];
pointerConfigurations[i].cache = new Stack<IMixedRealityPointer>();
}
}
}
/// <inheritdoc />
public override void Destroy()
{
DestroyPointerCache();
// Loop through active pointers in scene, destroy all gameobjects and clear our tracking dictionary
foreach (var pointer in activePointersToConfig.Keys)
{
if (pointer.TryGetMonoBehaviour(out MonoBehaviour pointerComponent))
{
GameObjectExtensions.DestroyGameObject(pointerComponent.gameObject);
}
}
pointerConfigurations = System.Array.Empty<PointerConfig>();
activePointersToConfig.Clear();
}
#endregion
#region Pointer utilization and caching
/// <summary>
/// Request an array of pointers for the controller type.
/// </summary>
/// <param name="controllerType">The controller type making the request for pointers.</param>
/// <param name="controllingHand">The handedness of the controller making the request.</param>
/// <param name="useSpecificType">Only register pointers with a specific type.</param>
protected virtual IMixedRealityPointer[] RequestPointers(SupportedControllerType controllerType, Handedness controllingHand)
{
using (RequestPointersPerfMarker.Auto())
{
var returnPointers = new List<IMixedRealityPointer>();
CleanActivePointers();
for (int i = 0; i < pointerConfigurations.Length; i++)
{
var option = pointerConfigurations[i].profile;
if (option.ControllerType.HasFlag(controllerType) && option.Handedness.HasFlag(controllingHand))
{
IMixedRealityPointer requestedPointer = null;
if (EnablePointerCache)
{
var pointerCache = pointerConfigurations[i].cache;
while (pointerCache.Count > 0)
{
var p = pointerCache.Pop();
if (p.TryGetMonoBehaviour(out MonoBehaviour pointerComponent))
{
pointerComponent.gameObject.SetActive(true);
// We got pointer from cache, continue to next pointer option to review
requestedPointer = p;
DebugUtilities.LogVerboseFormat("RequestPointers: Reusing a cached pointer {0} for controller type {1} and handedness {2}",
requestedPointer,
controllerType,
controllingHand);
break;
}
}
}
if (requestedPointer == null)
{
// We couldn't obtain a pointer from our cache, resort to creating a new one
requestedPointer = CreatePointer(ref option);
}
if (requestedPointer != null)
{
// Track pointer for recycling
activePointersToConfig.Add(requestedPointer, (uint)i);
returnPointers.Add(requestedPointer);
}
}
}
return returnPointers.Count == 0 ? null : returnPointers.ToArray();
}
}
private static readonly ProfilerMarker RecyclePointersPerfMarker = new ProfilerMarker("[MRTK] BaseInputDeviceManager.RecyclePointers");
/// <summary>
/// Recycle all pointers associated with the provided <see cref="IMixedRealityInputSource"/>.
/// This involves reseting the pointer, disabling the pointer GameObject, and possibly caching it for re-use.
/// </summary>
protected virtual void RecyclePointers(IMixedRealityInputSource inputSource)
{
using (RecyclePointersPerfMarker.Auto())
{
if (inputSource != null)
{
CleanActivePointers();
var pointers = inputSource.Pointers;
for (int i = 0; i < pointers.Length; i++)
{
var pointer = pointers[i];
if (pointers[i].TryGetMonoBehaviour(out MonoBehaviour pointerComponent))
{
// Unfortunately, it's possible the gameobject source is *being* destroyed so we are not null now but will be soon.
// At least if this is a controller we know about and we expect it to be destroyed, skip
if (pointer is IMixedRealityControllerPoseSynchronizer controller && controller.DestroyOnSourceLost)
{
continue;
}
if (EnablePointerCache)
{
pointer.Reset();
pointerComponent.gameObject.SetActive(false);
if (EnablePointerCache && activePointersToConfig.ContainsKey(pointer))
{
uint pointerOptionIndex = activePointersToConfig[pointer];
activePointersToConfig.Remove(pointer);
// Add our pointer back to our cache
pointerConfigurations[(int)pointerOptionIndex].cache.Push(pointer);
}
}
else
{
GameObjectExtensions.DestroyGameObject(pointerComponent.gameObject);
}
}
}
}
}
}
private static readonly ProfilerMarker CreatePointerPerfMarker = new ProfilerMarker("[MRTK] BaseInputDeviceManager.CreatePointer");
/// <summary>
/// Instantiate the Pointer prefab with supplied PointerOption details. If there is no IMixedRealityPointer on the prefab, then destroy and log error
/// </summary>
/// <remarks>
/// PointerOption is passed by ref to reduce copy overhead of struct
/// </remarks>
private IMixedRealityPointer CreatePointer(ref PointerOption option)
{
using (CreatePointerPerfMarker.Auto())
{
var pointerObject = Object.Instantiate(option.PointerPrefab);
MixedRealityPlayspace.AddChild(pointerObject.transform);
var pointer = pointerObject.GetComponent<IMixedRealityPointer>();
if (pointer == null)
{
Debug.LogError($"Ensure that the prefab '{option.PointerPrefab.name}' listed under Input -> Pointers -> Pointer Options has an {typeof(IMixedRealityPointer).Name} component.\nThis prefab can't be used as a pointer as configured and won't be instantiated.");
GameObjectExtensions.DestroyGameObject(pointerObject);
}
return pointer;
}
}
private static readonly ProfilerMarker CleanActivePointersPerfMarker = new ProfilerMarker("[MRTK] BaseInputDeviceManager.CleanActivePointers");
/// <summary>
/// This class tracks pointers that have been requested and thus are considered "active" GameObjects in the scene.
/// As GameObjects, these pointers may be destroyed and thus their entry becomes "null" although the managed object is not destroyed
/// This helper loops through all dictionary entries and checks if it is null, if so it is removed
/// </summary>
private void CleanActivePointers()
{
using (CleanActivePointersPerfMarker.Auto())
{
var removal = new List<IMixedRealityPointer>();
var enumerator = activePointersToConfig.GetEnumerator();
while (enumerator.MoveNext())
{
var pointer = enumerator.Current.Key;
if (pointer.IsNull())
{
removal.Add(pointer);
}
}
for (int i = 0; i < removal.Count; i++)
{
activePointersToConfig.Remove(removal[i]);
}
}
}
/// <summary>
/// Wipes references to cached pointers for every pointer configuration option. All GameObject references are likewise destroyed
/// </summary>
private void DestroyPointerCache()
{
for (int i = 0; i < pointerConfigurations.Length; i++)
{
while (pointerConfigurations[i].cache.Count > 0)
{
if (pointerConfigurations[i].cache.Pop().TryGetMonoBehaviour(out MonoBehaviour pointerComponent))
{
GameObjectExtensions.DestroyGameObject(pointerComponent.gameObject);
}
}
}
}
#endregion
}
}
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
using System.Collections.Generic;
using Unity.Profiling;
using UnityEngine;
using UnityEngine.EventSystems;
namespace Microsoft.MixedReality.Toolkit.SpatialAwareness
{
/// <summary>
/// Class providing a base implementation of the <see cref="IMixedRealitySpatialAwarenessMeshObserver"/> interface.
/// </summary>
public abstract class BaseSpatialMeshObserver : BaseSpatialObserver, IMixedRealitySpatialAwarenessMeshObserver, ISpatialAwarenessPhysicsProperties
{
/// <summary>
/// Constructor.
/// </summary>
/// <param name="spatialAwarenessSystem">The <see cref="SpatialAwareness.IMixedRealitySpatialAwarenessSystem"/> to which the observer is providing data.</param>
/// <param name="name">The friendly name of the data provider.</param>
/// <param name="priority">The registration priority of the data provider.</param>
/// <param name="profile">The configuration profile for the data provider.</param>
protected BaseSpatialMeshObserver(
IMixedRealitySpatialAwarenessSystem spatialAwarenessSystem,
string name = null,
uint priority = DefaultPriority,
BaseMixedRealityProfile profile = null) : base(spatialAwarenessSystem, name, priority, profile)
{
}
#region BaseSpatialMeshObserver Implementation
protected MixedRealitySpatialAwarenessEventData<SpatialAwarenessMeshObject> meshEventData = null;
private GameObject observedObjectParent = null;
/// <summary>
/// The parent GameObject for all observed meshes to be placed under.
/// </summary>
protected virtual GameObject ObservedObjectParent => observedObjectParent != null ? observedObjectParent : (observedObjectParent = SpatialAwarenessSystem?.CreateSpatialAwarenessObservationParent(Name));
protected virtual void ReadProfile()
{
if (ConfigurationProfile == null)
{
Debug.LogError($"{Name} requires a configuration profile to run properly.");
return;
}
MixedRealitySpatialAwarenessMeshObserverProfile profile = ConfigurationProfile as MixedRealitySpatialAwarenessMeshObserverProfile;
if (profile == null)
{
Debug.LogError($"{Name}'s configuration profile must be a MixedRealitySpatialAwarenessMeshObserverProfile.");
return;
}
// IMixedRealitySpatialAwarenessObserver settings
StartupBehavior = profile.StartupBehavior;
IsStationaryObserver = profile.IsStationaryObserver;
ObservationExtents = profile.ObservationExtents;
ObserverVolumeType = profile.ObserverVolumeType;
UpdateInterval = profile.UpdateInterval;
// IMixedRealitySpatialAwarenessMeshObserver settings
DisplayOption = profile.DisplayOption;
LevelOfDetail = profile.LevelOfDetail;
MeshPhysicsLayer = profile.MeshPhysicsLayer;
OcclusionMaterial = profile.OcclusionMaterial;
PhysicsMaterial = profile.PhysicsMaterial;
RecalculateNormals = profile.RecalculateNormals;
TrianglesPerCubicMeter = profile.TrianglesPerCubicMeter;
VisibleMaterial = profile.VisibleMaterial;
}
private static readonly ProfilerMarker ApplyUpdatedMeshDisplayOptionPerfMarker = new ProfilerMarker("[MRTK] BaseSpatialMeshObserver.ApplyUpdatedMeshDisplayOption");
/// <summary>
/// Applies the mesh display option to existing meshes when modified at runtime.
/// </summary>
/// <param name="option">The <see cref="SpatialAwarenessMeshDisplayOptions"/> value to be used to determine the appropriate material.</param>
protected virtual void ApplyUpdatedMeshDisplayOption(SpatialAwarenessMeshDisplayOptions option)
{
using (ApplyUpdatedMeshDisplayOptionPerfMarker.Auto())
{
bool enable = (option != SpatialAwarenessMeshDisplayOptions.None);
foreach (SpatialAwarenessMeshObject meshObject in Meshes.Values)
{
if (meshObject?.Renderer == null) { continue; }
if (enable)
{
meshObject.Renderer.sharedMaterial = (option == SpatialAwarenessMeshDisplayOptions.Visible) ?
VisibleMaterial :
OcclusionMaterial;
}
meshObject.Renderer.enabled = enable;
}
}
}
private static readonly ProfilerMarker ApplyUpdatedMeshPhysicsPerfMarker = new ProfilerMarker("[MRTK] BaseSpatialMeshObserver.ApplyUpdatedMeshPhysics");
/// <summary>
/// Applies the physical material to existing meshes when modified at runtime.
/// </summary>
protected virtual void ApplyUpdatedMeshPhysics()
{
using (ApplyUpdatedMeshPhysicsPerfMarker.Auto())
{
foreach (SpatialAwarenessMeshObject meshObject in Meshes.Values)
{
if (meshObject?.Collider == null) { continue; }
meshObject.Collider.material = PhysicsMaterial;
}
}
}
/// <summary>
/// Maps <see cref="SpatialAwarenessMeshLevelOfDetail"/> to <see cref="TrianglesPerCubicMeter"/>.
/// </summary>
/// <param name="levelOfDetail">The desired level of density for the spatial mesh.</param>
/// <returns>
/// The number of triangles per cubic meter that will result in the desired level of density.
/// </returns>
protected virtual int LookupTriangleDensity(SpatialAwarenessMeshLevelOfDetail levelOfDetail)
{
// By default, returns the existing value. This will be custom defined for each platform, if necessary.
return TrianglesPerCubicMeter;
}
private static readonly ProfilerMarker ApplyUpdatedPhysicsLayerPerfMarker = new ProfilerMarker("[MRTK] BaseSpatialMeshObserver.ApplyUpdatedPhysicsLayer");
/// <summary>
/// Updates the mesh physics layer for current mesh observations.
/// </summary>
protected virtual void ApplyUpdatedPhysicsLayer()
{
using (ApplyUpdatedPhysicsLayerPerfMarker.Auto())
{
foreach (SpatialAwarenessMeshObject meshObject in Meshes.Values)
{
if (meshObject?.GameObject == null) { continue; }
meshObject.GameObject.layer = MeshPhysicsLayer;
}
}
}
private static readonly ProfilerMarker OnMeshAddedPerfMarker = new ProfilerMarker("[MRTK] BaseSpatialMeshObserver.OnMeshAdded - Raising OnObservationAdded");
/// <summary>
/// Event sent whenever a mesh is added.
/// </summary>
protected static readonly ExecuteEvents.EventFunction<IMixedRealitySpatialAwarenessObservationHandler<SpatialAwarenessMeshObject>> OnMeshAdded =
delegate (IMixedRealitySpatialAwarenessObservationHandler<SpatialAwarenessMeshObject> handler, BaseEventData eventData)
{
using (OnMeshAddedPerfMarker.Auto())
{
MixedRealitySpatialAwarenessEventData<SpatialAwarenessMeshObject> spatialEventData = ExecuteEvents.ValidateEventData<MixedRealitySpatialAwarenessEventData<SpatialAwarenessMeshObject>>(eventData);
handler.OnObservationAdded(spatialEventData);
}
};
private static readonly ProfilerMarker OnMeshUpdatedPerfMarker = new ProfilerMarker("[MRTK] BaseSpatialMeshObserver.OnMeshUpdated - Raising OnObservationUpdated");
/// <summary>
/// Event sent whenever a mesh is updated.
/// </summary>
protected static readonly ExecuteEvents.EventFunction<IMixedRealitySpatialAwarenessObservationHandler<SpatialAwarenessMeshObject>> OnMeshUpdated =
delegate (IMixedRealitySpatialAwarenessObservationHandler<SpatialAwarenessMeshObject> handler, BaseEventData eventData)
{
using (OnMeshUpdatedPerfMarker.Auto())
{
MixedRealitySpatialAwarenessEventData<SpatialAwarenessMeshObject> spatialEventData = ExecuteEvents.ValidateEventData<MixedRealitySpatialAwarenessEventData<SpatialAwarenessMeshObject>>(eventData);
handler.OnObservationUpdated(spatialEventData);
}
};
private static readonly ProfilerMarker OnMeshRemovedPerfMarker = new ProfilerMarker("[MRTK] BaseSpatialMeshObserver.OnMeshRemoved - Raising OnObservationRemoved");
/// <summary>
/// Event sent whenever a mesh is discarded.
/// </summary>
protected static readonly ExecuteEvents.EventFunction<IMixedRealitySpatialAwarenessObservationHandler<SpatialAwarenessMeshObject>> OnMeshRemoved =
delegate (IMixedRealitySpatialAwarenessObservationHandler<SpatialAwarenessMeshObject> handler, BaseEventData eventData)
{
using (OnMeshRemovedPerfMarker.Auto())
{
MixedRealitySpatialAwarenessEventData<SpatialAwarenessMeshObject> spatialEventData = ExecuteEvents.ValidateEventData<MixedRealitySpatialAwarenessEventData<SpatialAwarenessMeshObject>>(eventData);
handler.OnObservationRemoved(spatialEventData);
}
};
#endregion BaseSpatialMeshObserver Implementation
#region IMixedRealityDataProvider Implementation
/// <summary>
/// Initializes event data and creates the observer.
/// </summary>
public override void Initialize()
{
meshEventData = new MixedRealitySpatialAwarenessEventData<SpatialAwarenessMeshObject>(EventSystem.current);
ReadProfile();
base.Initialize();
}
#endregion IMixedRealityDataProvider Implementation
#region IMixedRealitySpatialMeshObserver Implementation
private SpatialAwarenessMeshDisplayOptions displayOption = SpatialAwarenessMeshDisplayOptions.Visible;
/// <inheritdoc />
public SpatialAwarenessMeshDisplayOptions DisplayOption
{
get { return displayOption; }
set
{
displayOption = value;
ApplyUpdatedMeshDisplayOption(displayOption);
}
}
private SpatialAwarenessMeshLevelOfDetail levelOfDetail = SpatialAwarenessMeshLevelOfDetail.Coarse;
/// <inheritdoc />
public SpatialAwarenessMeshLevelOfDetail LevelOfDetail
{
get { return levelOfDetail; }
set
{
if (value != SpatialAwarenessMeshLevelOfDetail.Custom)
{
TrianglesPerCubicMeter = LookupTriangleDensity(value);
}
levelOfDetail = value;
}
}
/// <summary>
/// The backing field for Meshes, to allow the mesh observer implementation to track its meshes.
/// </summary>
protected readonly Dictionary<int, SpatialAwarenessMeshObject> meshes = new Dictionary<int, SpatialAwarenessMeshObject>();
/// <inheritdoc />
public IReadOnlyDictionary<int, SpatialAwarenessMeshObject> Meshes => new Dictionary<int, SpatialAwarenessMeshObject>(meshes);
private int meshPhysicsLayer = 31;
/// <inheritdoc />
public int MeshPhysicsLayer
{
get { return meshPhysicsLayer; }
set
{
if ((value < 0) || (value > 31))
{
Debug.LogError("Specified MeshPhysicsLayer is out of bounds. Please set a value between 0 and 31, inclusive.");
return;
}
meshPhysicsLayer = value;
ApplyUpdatedPhysicsLayer();
}
}
/// <inheritdoc />
public int MeshPhysicsLayerMask => (1 << MeshPhysicsLayer);
/// <inheritdoc />
public bool RecalculateNormals { get; set; } = true;
/// <inheritdoc />
public int TrianglesPerCubicMeter { get; set; } = 0;
private Material occlusionMaterial = null;
/// <inheritdoc />
public Material OcclusionMaterial
{
get { return occlusionMaterial; }
set
{
if (value != occlusionMaterial)
{
occlusionMaterial = value;
if (DisplayOption == SpatialAwarenessMeshDisplayOptions.Occlusion)
{
ApplyUpdatedMeshDisplayOption(SpatialAwarenessMeshDisplayOptions.Occlusion);
}
}
}
}
private PhysicMaterial physicsMaterial;
public PhysicMaterial PhysicsMaterial
{
get { return physicsMaterial; }
set
{
if (value != physicsMaterial)
{
physicsMaterial = value;
ApplyUpdatedMeshPhysics();
}
}
}
private Material visibleMaterial = null;
/// <inheritdoc />
public Material VisibleMaterial
{
get { return visibleMaterial; }
set
{
if (value != visibleMaterial)
{
visibleMaterial = value;
if (DisplayOption == SpatialAwarenessMeshDisplayOptions.Visible)
{
ApplyUpdatedMeshDisplayOption(SpatialAwarenessMeshDisplayOptions.Visible);
}
}
}
}
#endregion IMixedRealitySpatialMeshObserver Implementation
}
}
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
using Microsoft.MixedReality.Toolkit.Utilities;
using System.Collections;
using UnityEngine;
namespace Microsoft.MixedReality.Toolkit.SpatialAwareness
{
/// <summary>
/// Class providing a base implementation of the <see cref="IMixedRealitySpatialAwarenessObserver"/> interface.
/// </summary>
public abstract class BaseSpatialObserver : BaseDataProvider<IMixedRealitySpatialAwarenessSystem>, IMixedRealitySpatialAwarenessObserver
{
/// <summary>
/// Default dedicated layer for spatial awareness layer used by most components in MRTK
/// </summary>
public const int DefaultSpatialAwarenessLayer = 31;
/// <summary>
/// Constructor.
/// </summary>
/// <param name="spatialAwarenessSystem">The <see cref="SpatialAwareness.IMixedRealitySpatialAwarenessSystem"/> to which the observer is providing data.</param>
/// <param name="name">The friendly name of the data provider.</param>
/// <param name="priority">The registration priority of the data provider.</param>
/// <param name="profile">The configuration profile for the data provider.</param>
protected BaseSpatialObserver(
IMixedRealitySpatialAwarenessSystem spatialAwarenessSystem,
string name = null,
uint priority = DefaultPriority,
BaseMixedRealityProfile profile = null) : base(spatialAwarenessSystem, name, priority, profile)
{
SpatialAwarenessSystem = spatialAwarenessSystem;
SourceId = (SpatialAwarenessSystem != null) ? SpatialAwarenessSystem.GenerateNewSourceId() : 0;
SourceName = name;
}
/// <summary>
/// The spatial awareness system that is associated with this observer.
/// </summary>
protected IMixedRealitySpatialAwarenessSystem SpatialAwarenessSystem { get; private set; }
/// <summary>
/// Creates the spatial observer and handles the desired startup behavior.
/// </summary>
protected virtual void CreateObserver() { }
/// <summary>
/// Ensures that the spatial observer has been stopped and destroyed.
/// </summary>
protected virtual void CleanupObserver() { }
#region BaseService Implementation
/// <inheritdoc />
protected override void Dispose(bool disposing)
{
if (disposed)
{
return;
}
base.Dispose(disposing);
if (disposing)
{
CleanupObservationsAndObserver();
}
disposed = true;
}
#endregion BaseService Implementation
#region IMixedRealityDataProvider Implementation
/// <summary>
/// Creates the observer.
/// </summary>
public override void Initialize()
{
CreateObserver();
}
/// <summary>
/// Suspends the observer, clears observations, cleans up the observer, then re-initializes.
/// </summary>
public override void Reset()
{
Destroy();
Initialize();
}
/// <inheritdoc />
public override void Enable()
{
if (!IsRunning && StartupBehavior == AutoStartBehavior.AutoStart)
{
Resume();
}
}
/// <inheritdoc />
public override void Disable()
{
// If we are disabled while running...
if (IsRunning)
{
// Suspend the observer
Suspend();
}
}
/// <inheritdoc />
public override void Destroy()
{
CleanupObservationsAndObserver();
}
#endregion IMixedRealityDataProvider Implementation
#region IMixedRealityEventSource Implementation
/// <inheritdoc />
bool IEqualityComparer.Equals(object x, object y)
{
return x.Equals(y);
}
/// <inheritdoc />
public override bool Equals(object obj)
{
if (ReferenceEquals(null, obj)) { return false; }
if (ReferenceEquals(this, obj)) { return true; }
if (obj.GetType() != GetType()) { return false; }
return Equals((IMixedRealitySpatialAwarenessObserver)obj);
}
private bool Equals(IMixedRealitySpatialAwarenessObserver other)
{
return ((other != null) &&
(SourceId == other.SourceId) &&
string.Equals(SourceName, other.SourceName));
}
/// <inheritdoc />
public int GetHashCode(object obj)
{
return obj.GetHashCode();
}
/// <inheritdoc />
public override int GetHashCode()
{
return Mathf.Abs(SourceName.GetHashCode());
}
/// <inheritdoc />
public uint SourceId { get; }
/// <inheritdoc />
public string SourceName { get; }
#endregion IMixedRealityEventSource Implementation
#region IMixedRealitySpatialAwarenessObserver Implementation
/// <inheritdoc />
public AutoStartBehavior StartupBehavior { get; set; } = AutoStartBehavior.AutoStart;
/// <inheritdoc />
public int DefaultPhysicsLayer { get; } = DefaultSpatialAwarenessLayer;
/// <inheritdoc />
public bool IsRunning { get; protected set; } = false;
/// <inheritdoc />
public bool IsStationaryObserver { get; set; } = false;
/// <inheritdoc />
public Quaternion ObserverRotation { get; set; } = Quaternion.identity;
public Vector3 ObserverOrigin { get; set; } = Vector3.zero;
/// <inheritdoc />
public VolumeType ObserverVolumeType { get; set; } = VolumeType.AxisAlignedCube;
/// <inheritdoc />
public Vector3 ObservationExtents { get; set; } = Vector3.one * 3f; // 3 meter sides / radius
/// <inheritdoc />
public float UpdateInterval { get; set; } = 3.5f; // 3.5 seconds
/// <inheritdoc />
public virtual void Resume() { }
/// <inheritdoc />
public virtual void Suspend() { }
/// <inheritdoc />
public virtual void ClearObservations() { }
#endregion IMixedRealitySpatialAwarenessObserver Implementation
#region Helpers
/// <summary>
/// Destroys all observed objects and the observer.
/// </summary>
private void CleanupObservationsAndObserver()
{
Disable();
// Destroys all observed objects and the observer.
ClearObservations();
CleanupObserver();
}
#endregion Helpers
#region Obsolete
/// <summary>
/// Constructor.
/// </summary>
/// <param name="registrar">The <see cref="IMixedRealityServiceRegistrar"/> instance that loaded the observer.</param>
/// <param name="spatialAwarenessSystem">The <see cref="SpatialAwareness.IMixedRealitySpatialAwarenessSystem"/> to which the observer is providing data.</param>
/// <param name="name">The friendly name of the data provider.</param>
/// <param name="priority">The registration priority of the data provider.</param>
/// <param name="profile">The configuration profile for the data provider.</param>
[System.Obsolete("This constructor is obsolete (registrar parameter is no longer required) and will be removed in a future version of the Microsoft Mixed Reality Toolkit.")]
protected BaseSpatialObserver(
IMixedRealityServiceRegistrar registrar,
IMixedRealitySpatialAwarenessSystem spatialAwarenessSystem,
string name = null,
uint priority = DefaultPriority,
BaseMixedRealityProfile profile = null) : this(spatialAwarenessSystem, name, priority, profile)
{
Registrar = registrar;
}
#endregion
}
}
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
using Microsoft.MixedReality.Toolkit.Physics;
using Microsoft.MixedReality.Toolkit.Teleport;
using System.Collections;
using UnityEngine;
namespace Microsoft.MixedReality.Toolkit.Input
{
/// <summary>
/// Base Class for pointers that don't inherit from MonoBehaviour.
/// </summary>
public abstract class GenericPointer : IMixedRealityPointer
{
/// <summary>
/// Constructor.
/// </summary>
protected GenericPointer(string pointerName, IMixedRealityInputSource inputSourceParent)
{
PointerId = (CoreServices.InputSystem?.FocusProvider != null) ? CoreServices.InputSystem.FocusProvider.GenerateNewPointerId() : 0;
PointerName = pointerName;
this.inputSourceParent = inputSourceParent;
}
/// <inheritdoc />
public virtual IMixedRealityController Controller
{
get { return controller; }
set
{
controller = value;
if (controller != null)
{
inputSourceParent = controller.InputSource;
}
}
}
private IMixedRealityController controller;
/// <inheritdoc />
public uint PointerId { get; }
/// <inheritdoc />
public string PointerName { get; set; }
/// <inheritdoc />
public virtual IMixedRealityInputSource InputSourceParent
{
get { return inputSourceParent; }
protected set { inputSourceParent = value; }
}
private IMixedRealityInputSource inputSourceParent;
/// <inheritdoc />
public IMixedRealityCursor BaseCursor { get; set; }
/// <inheritdoc />
public ICursorModifier CursorModifier { get; set; }
/// <inheritdoc />
public IMixedRealityTeleportHotSpot TeleportHotSpot { get; set; }
private bool isInteractionEnabled = true;
/// <inheritdoc />
public bool IsInteractionEnabled
{
get { return isInteractionEnabled && IsActive; }
set
{
if (isInteractionEnabled != value)
{
isInteractionEnabled = value;
if (BaseCursor != null)
{
BaseCursor.SetVisibility(value);
}
}
}
}
public bool IsActive { get; set; }
/// <inheritdoc />
public bool IsFocusLocked { get; set; }
/// <inheritdoc />
public bool IsTargetPositionLockedOnFocusLock { get; set; }
/// <summary>
/// The pointer's maximum extent when raycasting.
/// </summary>
public virtual float PointerExtent { get; set; } = 10f;
/// <inheritdoc />
public RayStep[] Rays { get; protected set; } = { new RayStep(Vector3.zero, Vector3.forward) };
/// <inheritdoc />
public LayerMask[] PrioritizedLayerMasksOverride { get; set; }
/// <inheritdoc />
public IMixedRealityFocusHandler FocusTarget { get; set; }
/// <inheritdoc />
public IPointerResult Result { get; set; }
/// <summary>
/// Ray stabilizer used when calculating position of pointer end point.
/// </summary>
public IBaseRayStabilizer RayStabilizer { get; set; }
/// <inheritdoc />
public SceneQueryType SceneQueryType { get; set; } = SceneQueryType.SimpleRaycast;
/// <inheritdoc />
public float SphereCastRadius { get; set; }
/// <inheritdoc />
public abstract Vector3 Position { get; }
/// <inheritdoc />
public abstract Quaternion Rotation { get; }
/// <inheritdoc />
public abstract void OnPreSceneQuery();
/// <inheritdoc />
public abstract void OnPostSceneQuery();
/// <inheritdoc />
public abstract void OnPreCurrentPointerTargetChange();
/// <inheritdoc />
public abstract void Reset();
#region IEquality Implementation
public static bool Equals(IMixedRealityPointer left, IMixedRealityPointer right)
{
return left.Equals(right);
}
bool IEqualityComparer.Equals(object left, object right)
{
return left.Equals(right);
}
public override bool Equals(object obj)
{
if (ReferenceEquals(null, obj)) { return false; }
if (ReferenceEquals(this, obj)) { return true; }
if (obj.GetType() != GetType()) { return false; }
return Equals((IMixedRealityPointer)obj);
}
private bool Equals(IMixedRealityPointer other)
{
return other != null && PointerId == other.PointerId && string.Equals(PointerName, other.PointerName);
}
int IEqualityComparer.GetHashCode(object obj)
{
return obj.GetHashCode();
}
/// <inheritdoc />
public override int GetHashCode()
{
unchecked
{
int hashCode = 0;
hashCode = (hashCode * 397) ^ (int)PointerId;
hashCode = (hashCode * 397) ^ (PointerName != null ? PointerName.GetHashCode() : 0);
return hashCode;
}
}
#endregion IEquality Implementation
}
}
// 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
{
/// <summary>
/// Defines the interactions and data that an articulated hand can provide.
/// </summary>
public class ArticulatedHandDefinition
{
public ArticulatedHandDefinition(IMixedRealityInputSource source, Handedness handedness)
{
inputSource = source;
this.handedness = handedness;
}
protected readonly IMixedRealityInputSource inputSource;
protected readonly Handedness handedness;
private readonly float cursorBeamBackwardTolerance = 0.5f;
private readonly float cursorBeamUpTolerance = 0.8f;
private Dictionary<TrackedHandJoint, MixedRealityPose> unityJointPoses = new Dictionary<TrackedHandJoint, MixedRealityPose>();
private MixedRealityPose currentIndexPose = MixedRealityPose.ZeroIdentity;
// Minimum distance between the index and the thumb tip required to enter a pinch
private readonly float minimumPinchDistance = 0.015f;
// Maximum distance between the index and thumb tip required to exit the pinch gesture
private readonly float maximumPinchDistance = 0.1f;
// Default enterPinchDistance value
private float enterPinchDistance = 0.02f;
/// <summary>
/// The distance between the index finger tip and the thumb tip required to enter the pinch/air tap selection gesture.
/// The pinch gesture enter will be registered for all values less than the EnterPinchDistance. The default EnterPinchDistance value is 0.02 and must be between 0.015 and 0.1.
/// </summary>
public float EnterPinchDistance
{
get => enterPinchDistance;
set
{
if (value >= minimumPinchDistance && value <= maximumPinchDistance)
{
enterPinchDistance = value;
}
else
{
Debug.LogError("EnterPinchDistance must be between 0.015 and 0.1, please change Enter Pinch Distance in the Leap Motion Device Manager Profile");
}
}
}
// Default exitPinchDistance value
private float exitPinchDistance = 0.05f;
/// <summary>
/// The distance between the index finger tip and the thumb tip required to exit the pinch/air tap gesture.
/// The pinch gesture exit will be registered for all values greater than the ExitPinchDistance. The default ExitPinchDistance value is 0.05 and must be between 0.015 and 0.1.
/// </summary>
public float ExitPinchDistance
{
get => exitPinchDistance;
set
{
if (value >= minimumPinchDistance && value <= maximumPinchDistance)
{
exitPinchDistance = value;
}
else
{
Debug.LogError("ExitPinchDistance must be between 0.015 and 0.1, please change Exit Pinch Distance in the Leap Motion Device Manager Profile");
}
}
}
private bool isPinching = false;
/// <summary>
/// The articulated hands default interactions.
/// </summary>
/// <remarks>A single interaction mapping works for both left and right articulated hands.</remarks>
public MixedRealityInteractionMapping[] DefaultInteractions => new[]
{
new MixedRealityInteractionMapping(0, "Spatial Pointer", AxisType.SixDof, DeviceInputType.SpatialPointer),
new MixedRealityInteractionMapping(1, "Spatial Grip", AxisType.SixDof, DeviceInputType.SpatialGrip),
new MixedRealityInteractionMapping(2, "Select", AxisType.Digital, DeviceInputType.Select),
new MixedRealityInteractionMapping(3, "Grab", AxisType.SingleAxis, DeviceInputType.TriggerPress),
new MixedRealityInteractionMapping(4, "Index Finger Pose", AxisType.SixDof, DeviceInputType.IndexFinger)
};
/// <summary>
/// Calculates whether the current pose allows for pointing/distant interactions.
/// </summary>
public bool IsInPointingPose
{
get
{
MixedRealityPose palmJoint;
if (unityJointPoses.TryGetValue(TrackedHandJoint.Palm, out palmJoint))
{
Vector3 palmNormal = palmJoint.Rotation * (-1 * Vector3.up);
if (cursorBeamBackwardTolerance >= 0)
{
Vector3 cameraBackward = -CameraCache.Main.transform.forward;
if (Vector3.Dot(palmNormal.normalized, cameraBackward) > cursorBeamBackwardTolerance)
{
return false;
}
}
if (cursorBeamUpTolerance >= 0)
{
if (Vector3.Dot(palmNormal, Vector3.up) > cursorBeamUpTolerance)
{
return false;
}
}
}
return true;
}
}
private static readonly ProfilerMarker UpdateHandJointsPerfMarker = new ProfilerMarker("[MRTK] ArticulatedHandDefinition.UpdateHandJoints");
/// <summary>
/// Calculates whether the current the current joint pose is selecting (air tap gesture).
/// </summary>
public bool IsPinching
{
get
{
MixedRealityPose thumbTip;
MixedRealityPose indexTip;
if (unityJointPoses.TryGetValue(TrackedHandJoint.ThumbTip, out thumbTip) && unityJointPoses.TryGetValue(TrackedHandJoint.IndexTip, out indexTip))
{
float distance = Vector3.Distance(thumbTip.Position, indexTip.Position);
if (isPinching && distance > ExitPinchDistance)
{
isPinching = false;
}
else if (!isPinching && distance < EnterPinchDistance)
{
isPinching = true;
}
}
else
{
isPinching = false;
}
return isPinching;
}
}
/// <summary>
/// Updates the current hand joints with new data.
/// </summary>
/// <param name="jointPoses">The new joint poses.</param>
public void UpdateHandJoints(Dictionary<TrackedHandJoint, MixedRealityPose> jointPoses)
{
using (UpdateHandJointsPerfMarker.Auto())
{
unityJointPoses = jointPoses;
CoreServices.InputSystem?.RaiseHandJointsUpdated(inputSource, handedness, unityJointPoses);
}
}
private static readonly ProfilerMarker UpdateCurrentIndexPosePerfMarker = new ProfilerMarker("[MRTK] ArticulatedHandDefinition.UpdateCurrentIndexPose");
/// <summary>
/// Updates the MixedRealityInteractionMapping with the latest index pose and fires a corresponding pose event.
/// </summary>
/// <param name="interactionMapping">The index finger's interaction mapping.</param>
public void UpdateCurrentIndexPose(MixedRealityInteractionMapping interactionMapping)
{
using (UpdateCurrentIndexPosePerfMarker.Auto())
{
if (unityJointPoses.TryGetValue(TrackedHandJoint.IndexTip, out currentIndexPose))
{
// Update the interaction data source
interactionMapping.PoseData = currentIndexPose;
// If our value changed raise it
if (interactionMapping.Changed)
{
// Raise input system event if it's enabled
CoreServices.InputSystem?.RaisePoseInputChanged(inputSource, handedness, interactionMapping.MixedRealityInputAction, currentIndexPose);
}
}
}
}
}
}
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
using Microsoft.MixedReality.Toolkit.Utilities;
using UnityEngine;
namespace Microsoft.MixedReality.Toolkit.Input
{
public abstract class BaseHand : BaseController, IMixedRealityHand
{
// Hand ray
protected virtual IHandRay HandRay { get; } = new HandRay();
public override bool IsInPointingPose => HandRay.ShouldShowRay;
// Velocity internal states
private float deltaTimeStart;
private const int velocityUpdateInterval = 6;
private int frameOn = 0;
private readonly Vector3[] velocityPositionsCache = new Vector3[velocityUpdateInterval];
private readonly Vector3[] velocityNormalsCache = new Vector3[velocityUpdateInterval];
private Vector3 velocityPositionsSum = Vector3.zero;
private Vector3 velocityNormalsSum = Vector3.zero;
/// <summary>
/// Constructor.
/// </summary>
protected BaseHand(TrackingState trackingState, Handedness controllerHandedness, IMixedRealityInputSource inputSource = null, MixedRealityInteractionMapping[] interactions = null)
: base(trackingState, controllerHandedness, inputSource, interactions)
{
}
/// <inheritdoc />
public override MixedRealityInteractionMapping[] DefaultLeftHandedInteractions => DefaultInteractions;
/// <inheritdoc />
public override MixedRealityInteractionMapping[] DefaultRightHandedInteractions => DefaultInteractions;
#region Protected InputSource Helpers
#region Gesture Definitions
protected void UpdateVelocity()
{
if (frameOn < velocityUpdateInterval)
{
velocityPositionsCache[frameOn] = GetJointPosition(TrackedHandJoint.Palm);
velocityPositionsSum += velocityPositionsCache[frameOn];
velocityNormalsCache[frameOn] = GetPalmNormal();
velocityNormalsSum += velocityNormalsCache[frameOn];
}
else
{
int frameIndex = frameOn % velocityUpdateInterval;
float deltaTime = Time.unscaledTime - deltaTimeStart;
Vector3 newPosition = GetJointPosition(TrackedHandJoint.Palm);
Vector3 newNormal = GetPalmNormal();
Vector3 newPositionsSum = velocityPositionsSum - velocityPositionsCache[frameIndex] + newPosition;
Vector3 newNormalsSum = velocityNormalsSum - velocityNormalsCache[frameIndex] + newNormal;
Velocity = (newPositionsSum - velocityPositionsSum) / deltaTime / velocityUpdateInterval;
Quaternion rotation = Quaternion.FromToRotation(velocityNormalsSum / velocityUpdateInterval, newNormalsSum / velocityUpdateInterval);
Vector3 rotationRate = rotation.eulerAngles * Mathf.Deg2Rad;
AngularVelocity = rotationRate / deltaTime;
velocityPositionsCache[frameIndex] = newPosition;
velocityNormalsCache[frameIndex] = newNormal;
velocityPositionsSum = newPositionsSum;
velocityNormalsSum = newNormalsSum;
}
deltaTimeStart = Time.unscaledTime;
frameOn++;
}
#endregion Gesture Definitions
/// <inheritdoc />
public abstract bool TryGetJoint(TrackedHandJoint joint, out MixedRealityPose pose);
private Vector3 GetJointPosition(TrackedHandJoint jointToGet)
{
if (TryGetJoint(jointToGet, out MixedRealityPose pose))
{
return pose.Position;
}
return Vector3.zero;
}
protected Vector3 GetPalmNormal()
{
if (TryGetJoint(TrackedHandJoint.Palm, out MixedRealityPose pose))
{
return -pose.Up;
}
return Vector3.zero;
}
private float DistanceSqrPointToLine(Vector3 lineStart, Vector3 lineEnd, Vector3 point)
{
if (lineStart == lineEnd)
{
return (point - lineStart).magnitude;
}
float lineSegmentMagnitude = (lineEnd - lineStart).magnitude;
Vector3 ray = (lineEnd - lineStart);
ray *= (1.0f / lineSegmentMagnitude);
float dot = Vector3.Dot(point - lineStart, ray);
if (dot <= 0)
{
return (point - lineStart).sqrMagnitude;
}
if (dot >= lineSegmentMagnitude)
{
return (point - lineEnd).sqrMagnitude;
}
return ((lineStart + (ray * dot)) - point).sqrMagnitude;
}
#endregion Private InputSource Helpers
}
}
\ No newline at end of file
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
using Microsoft.MixedReality.Toolkit.Utilities;
using System.Collections.Generic;
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; }
protected readonly Dictionary<TrackedHandJoint, Transform> joints = new Dictionary<TrackedHandJoint, Transform>();
protected MeshFilter handMeshFilter;
// This member stores the last set of hand mesh vertices, to avoid using
// handMeshFilter.mesh.vertices, which does a copy of the vertices.
private Vector3[] lastHandMeshVertices;
private void OnEnable()
{
CoreServices.InputSystem?.RegisterHandler<IMixedRealitySourceStateHandler>(this);
CoreServices.InputSystem?.RegisterHandler<IMixedRealityHandJointHandler>(this);
CoreServices.InputSystem?.RegisterHandler<IMixedRealityHandMeshHandler>(this);
}
private void OnDisable()
{
CoreServices.InputSystem?.UnregisterHandler<IMixedRealitySourceStateHandler>(this);
CoreServices.InputSystem?.UnregisterHandler<IMixedRealityHandJointHandler>(this);
CoreServices.InputSystem?.UnregisterHandler<IMixedRealityHandMeshHandler>(this);
}
private void OnDestroy()
{
foreach (var joint in joints)
{
Destroy(joint.Value.gameObject);
}
if (handMeshFilter != null)
{
Destroy(handMeshFilter.gameObject);
handMeshFilter = null;
}
}
public bool TryGetJointTransform(TrackedHandJoint joint, out Transform jointTransform)
{
if (joints == null)
{
jointTransform = null;
return false;
}
if (joints.TryGetValue(joint, out jointTransform))
{
return true;
}
jointTransform = null;
return false;
}
void IMixedRealitySourceStateHandler.OnSourceDetected(SourceStateEventData eventData) { }
void IMixedRealitySourceStateHandler.OnSourceLost(SourceStateEventData eventData)
{
if (Controller?.InputSource.SourceId == eventData.SourceId)
{
Destroy(gameObject);
}
}
void IMixedRealityHandJointHandler.OnHandJointsUpdated(InputEventData<IDictionary<TrackedHandJoint, MixedRealityPose>> eventData)
{
var inputSystem = CoreServices.InputSystem;
if (eventData.InputSource.SourceId != Controller.InputSource.SourceId)
{
return;
}
Debug.Assert(eventData.Handedness == Controller.ControllerHandedness);
MixedRealityHandTrackingProfile handTrackingProfile = inputSystem?.InputSystemProfile.HandTrackingProfile;
if (handTrackingProfile != null && !handTrackingProfile.EnableHandJointVisualization)
{
// clear existing joint GameObjects / meshes
foreach (var joint in joints)
{
Destroy(joint.Value.gameObject);
}
joints.Clear();
return;
}
foreach (TrackedHandJoint handJoint in eventData.InputData.Keys)
{
Transform jointTransform;
if (joints.TryGetValue(handJoint, out jointTransform))
{
jointTransform.position = eventData.InputData[handJoint].Position;
jointTransform.rotation = eventData.InputData[handJoint].Rotation;
}
else
{
GameObject prefab;
if (handJoint == TrackedHandJoint.None)
{
// No visible mesh for the "None" joint
prefab = null;
}
else if (handJoint == TrackedHandJoint.Palm)
{
prefab = inputSystem.InputSystemProfile.HandTrackingProfile.PalmJointPrefab;
}
else if (handJoint == TrackedHandJoint.IndexTip)
{
prefab = inputSystem.InputSystemProfile.HandTrackingProfile.FingerTipPrefab;
}
else
{
prefab = inputSystem.InputSystemProfile.HandTrackingProfile.JointPrefab;
}
GameObject jointObject;
if (prefab != null)
{
jointObject = Instantiate(prefab);
}
else
{
jointObject = new GameObject();
}
jointObject.name = handJoint.ToString() + " Proxy Transform";
jointObject.transform.position = eventData.InputData[handJoint].Position;
jointObject.transform.rotation = eventData.InputData[handJoint].Rotation;
jointObject.transform.parent = transform;
joints.Add(handJoint, jointObject.transform);
}
}
}
public void OnHandMeshUpdated(InputEventData<HandMeshInfo> eventData)
{
if (eventData.Handedness != Controller?.ControllerHandedness)
{
return;
}
bool newMesh = handMeshFilter == null;
if (newMesh &&
CoreServices.InputSystem?.InputSystemProfile != null &&
CoreServices.InputSystem.InputSystemProfile.HandTrackingProfile != null &&
CoreServices.InputSystem.InputSystemProfile.HandTrackingProfile.HandMeshPrefab != null)
{
handMeshFilter = Instantiate(CoreServices.InputSystem.InputSystemProfile.HandTrackingProfile.HandMeshPrefab).GetComponent<MeshFilter>();
lastHandMeshVertices = handMeshFilter.mesh.vertices;
}
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 ((lastHandMeshVertices == null && eventData.InputData.vertices != null) ||
(lastHandMeshVertices != null &&
lastHandMeshVertices.Length != 0 &&
lastHandMeshVertices.Length != eventData.InputData.vertices?.Length))
{
meshChanged = true;
mesh.Clear();
}
mesh.vertices = eventData.InputData.vertices;
mesh.normals = eventData.InputData.normals;
lastHandMeshVertices = eventData.InputData.vertices;
if (newMesh || meshChanged)
{
mesh.triangles = eventData.InputData.triangles;
if (eventData.InputData.uvs?.Length > 0)
{
mesh.uv = eventData.InputData.uvs;
}
}
if (meshChanged)
{
mesh.RecalculateBounds();
}
handMeshFilter.transform.position = eventData.InputData.position;
handMeshFilter.transform.rotation = eventData.InputData.rotation;
}
}
}
}
\ No newline at end of file
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
using System.Collections.Generic;
using Microsoft.MixedReality.Toolkit.Utilities;
using UnityEngine;
namespace Microsoft.MixedReality.Toolkit.Input
{
/// <summary>
/// Utility behavior to access the axis aligned bounds of IMixedRealityHands (or the proxy visualizer of IMixedRealityControllers).
/// </summary>
[AddComponentMenu("Scripts/MRTK/Core/HandBounds")]
public class HandBounds : MonoBehaviour, IMixedRealitySourceStateHandler, IMixedRealityHandJointHandler
{
/// <summary>
/// Accessor for the bounds associated with a handedness, calculated in global-axis-aligned space.
/// </summary>
public Dictionary<Handedness, Bounds> Bounds { get; private set; } = new Dictionary<Handedness, Bounds>();
/// <summary>
/// Accessor for the bounds associated with a handedness, calculated in local hand-space, locally axis aligned.
/// </summary>
public Dictionary<Handedness, Bounds> LocalBounds { get; private set; } = new Dictionary<Handedness, Bounds>();
[SerializeField]
[Tooltip("Should a gizmo be drawn to represent the hand bounds.")]
private bool drawBoundsGizmo = false;
/// <summary>
/// Should a gizmo be drawn to represent the hand bounds.
/// </summary>
public bool DrawBoundsGizmo
{
get => drawBoundsGizmo;
set => drawBoundsGizmo = value;
}
[SerializeField]
[Tooltip("Should a gizmo be drawn to represent the locally-calculated hand bounds.")]
private bool drawLocalBoundsGizmo = false;
/// <summary>
/// Should a gizmo be drawn to represent the locally-calculated hand bounds.
/// </summary>
public bool DrawLocalBoundsGizmo
{
get => drawLocalBoundsGizmo;
set => drawLocalBoundsGizmo = value;
}
/// <summary>
/// Mapping between controller handedness and associated hand transforms.
/// Used to transform the debug gizmos when rendering the hand AABBs.
/// </summary>
private Dictionary<Handedness, Matrix4x4> BoundsTransforms = new Dictionary<Handedness, Matrix4x4>();
#region MonoBehaviour Implementation
private void OnEnable()
{
CoreServices.InputSystem?.RegisterHandler<IMixedRealitySourceStateHandler>(this);
CoreServices.InputSystem?.RegisterHandler<IMixedRealityHandJointHandler>(this);
}
private void OnDisable()
{
CoreServices.InputSystem?.UnregisterHandler<IMixedRealitySourceStateHandler>(this);
CoreServices.InputSystem?.UnregisterHandler<IMixedRealityHandJointHandler>(this);
}
private void OnDrawGizmos()
{
if (drawBoundsGizmo)
{
Gizmos.color = Color.yellow;
foreach (var kvp in Bounds)
{
Gizmos.DrawWireCube(kvp.Value.center, kvp.Value.size);
}
}
if (drawLocalBoundsGizmo)
{
Gizmos.color = Color.cyan;
foreach (var kvp in LocalBounds)
{
Gizmos.matrix = BoundsTransforms[kvp.Key];
Gizmos.DrawWireCube(kvp.Value.center, kvp.Value.size);
}
}
}
#endregion MonoBehaviour Implementation
#region IMixedRealitySourceStateHandler Implementation
/// <inheritdoc />
public void OnSourceDetected(SourceStateEventData eventData)
{
var hand = eventData.Controller;
if (hand != null)
{
// If a hand does not contain joints, OnHandJointsUpdated will not be called the bounds should
// be calculated based on the proxy visuals.
bool handContainsJoints = (hand as IMixedRealityHand) != null;
if (!handContainsJoints)
{
var proxy = hand.Visualizer?.GameObjectProxy;
if (proxy != null)
{
// Bounds calculated in proxy-space will have an origin of zero, but bounds
// calculated in global space will have an origin centered on the proxy transform.
var newGlobalBounds = new Bounds(proxy.transform.position, Vector3.zero);
var newLocalBounds = new Bounds(Vector3.zero, Vector3.zero);
var boundsPoints = new List<Vector3>();
BoundsExtensions.GetRenderBoundsPoints(proxy, boundsPoints, 0);
foreach (var point in boundsPoints)
{
newGlobalBounds.Encapsulate(point);
// Local hand-space bounds are encapsulated using proxy-space point coordinates
newLocalBounds.Encapsulate(proxy.transform.InverseTransformPoint(point));
}
Bounds[hand.ControllerHandedness] = newGlobalBounds;
LocalBounds[hand.ControllerHandedness] = newLocalBounds;
BoundsTransforms[hand.ControllerHandedness] = proxy.transform.localToWorldMatrix;
}
}
}
}
/// <inheritdoc />
public void OnSourceLost(SourceStateEventData eventData)
{
var hand = eventData.Controller;
if (hand != null)
{
Bounds.Remove(hand.ControllerHandedness);
LocalBounds.Remove(hand.ControllerHandedness);
BoundsTransforms.Remove(hand.ControllerHandedness);
}
}
#endregion IMixedRealitySourceStateHandler Implementation
#region IMixedRealityHandJointHandler Implementation
/// <inheritdoc />
public void OnHandJointsUpdated(InputEventData<IDictionary<TrackedHandJoint, MixedRealityPose>> eventData)
{
MixedRealityPose palmPose;
if (eventData.InputData.TryGetValue(TrackedHandJoint.Palm, out palmPose))
{
var newGlobalBounds = new Bounds(palmPose.Position, Vector3.zero);
var newLocalBounds = new Bounds(Vector3.zero, Vector3.zero);
foreach (var kvp in eventData.InputData)
{
if (kvp.Key == TrackedHandJoint.None ||
kvp.Key == TrackedHandJoint.Palm)
{
continue;
}
newGlobalBounds.Encapsulate(kvp.Value.Position);
newLocalBounds.Encapsulate(Quaternion.Inverse(palmPose.Rotation) * (kvp.Value.Position - palmPose.Position));
}
Bounds[eventData.Handedness] = newGlobalBounds;
LocalBounds[eventData.Handedness] = newLocalBounds;
// We must normalize the quaternion before constructing the TRS matrix; non-unit-length quaternions
// may be emitted from the palm-pose and they must be renormalized.
BoundsTransforms[eventData.Handedness] = Matrix4x4.TRS(palmPose.Position, palmPose.Rotation.normalized, Vector3.one);
}
}
#endregion IMixedRealityHandJointHandler Implementation
}
}
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
using Microsoft.MixedReality.Toolkit.Utilities;
using System.Collections.Generic;
using UnityEngine;
namespace Microsoft.MixedReality.Toolkit.Input
{
[MixedRealityDataProvider(
typeof(IMixedRealityInputSystem),
(SupportedPlatforms)(-1), // All platforms supported by Unity
"Hand Joint Service")]
[HelpURL("https://microsoft.github.io/MixedRealityToolkit-Unity/Documentation/Input/HandTracking.html")]
public class HandJointService : BaseInputDeviceManager, IMixedRealityHandJointService
{
private IMixedRealityHand leftHand;
private IMixedRealityHand rightHand;
private Dictionary<TrackedHandJoint, Transform> leftHandFauxJoints = new Dictionary<TrackedHandJoint, Transform>();
private Dictionary<TrackedHandJoint, Transform> rightHandFauxJoints = new Dictionary<TrackedHandJoint, Transform>();
#region BaseInputDeviceManager Implementation
/// <summary>
/// Constructor.
/// </summary>
/// <param name="registrar">The <see cref="IMixedRealityServiceRegistrar"/> instance that loaded the data provider.</param>
/// <param name="inputSystem">The <see cref="Microsoft.MixedReality.Toolkit.Input.IMixedRealityInputSystem"/> instance that receives data from this provider.</param>
/// <param name="name">Friendly name of the service.</param>
/// <param name="priority">Service priority. Used to determine order of instantiation.</param>
/// <param name="profile">The service's configuration profile.</param>
[System.Obsolete("This constructor is obsolete (registrar parameter is no longer required) and will be removed in a future version of the Microsoft Mixed Reality Toolkit.")]
public HandJointService(
IMixedRealityServiceRegistrar registrar,
IMixedRealityInputSystem inputSystem,
string name,
uint priority,
BaseMixedRealityProfile profile) : this(inputSystem, name, priority, profile)
{
Registrar = registrar;
}
/// <summary>
/// Constructor.
/// </summary>
/// <param name="inputSystem">The <see cref="Microsoft.MixedReality.Toolkit.Input.IMixedRealityInputSystem"/> instance that receives data from this provider.</param>
/// <param name="name">Friendly name of the service.</param>
/// <param name="priority">Service priority. Used to determine order of instantiation.</param>
/// <param name="profile">The service's configuration profile.</param>
public HandJointService(
IMixedRealityInputSystem inputSystem,
string name,
uint priority,
BaseMixedRealityProfile profile) : base(inputSystem, name, priority, profile) { }
/// <inheritdoc />
public override void LateUpdate()
{
base.LateUpdate();
leftHand = null;
rightHand = null;
foreach (var detectedController in Service.DetectedControllers)
{
var hand = detectedController as IMixedRealityHand;
if (hand != null)
{
if (detectedController.ControllerHandedness == Handedness.Left)
{
if (leftHand == null)
{
leftHand = hand;
}
}
else if (detectedController.ControllerHandedness == Handedness.Right)
{
if (rightHand == null)
{
rightHand = hand;
}
}
}
}
if (leftHand != null)
{
foreach (var fauxJoint in leftHandFauxJoints)
{
if (leftHand.TryGetJoint(fauxJoint.Key, out MixedRealityPose pose))
{
fauxJoint.Value.SetPositionAndRotation(pose.Position, pose.Rotation);
}
}
}
if (rightHand != null)
{
foreach (var fauxJoint in rightHandFauxJoints)
{
if (rightHand.TryGetJoint(fauxJoint.Key, out MixedRealityPose pose))
{
fauxJoint.Value.SetPositionAndRotation(pose.Position, pose.Rotation);
}
}
}
}
/// <inheritdoc />
public override void Disable()
{
base.Disable();
// Check existence of fauxJoints before destroying. This avoids a (harmless) race
// condition when the service is getting destroyed at the same time that the gameObjects
// are being destroyed at shutdown.
if (leftHandFauxJoints != null)
{
foreach (var fauxJoint in leftHandFauxJoints.Values)
{
if (fauxJoint != null)
{
Object.Destroy(fauxJoint.gameObject);
}
}
leftHandFauxJoints.Clear();
}
if (rightHandFauxJoints != null)
{
foreach (var fauxJoint in rightHandFauxJoints.Values)
{
if (fauxJoint != null)
{
Object.Destroy(fauxJoint.gameObject);
}
}
rightHandFauxJoints.Clear();
}
}
#endregion BaseInputDeviceManager Implementation
#region IMixedRealityHandJointService Implementation
public Transform RequestJointTransform(TrackedHandJoint joint, Handedness handedness)
{
IMixedRealityHand hand = null;
Dictionary<TrackedHandJoint, Transform> fauxJoints = null;
if (handedness == Handedness.Left)
{
hand = leftHand;
fauxJoints = leftHandFauxJoints;
}
else if (handedness == Handedness.Right)
{
hand = rightHand;
fauxJoints = rightHandFauxJoints;
}
else
{
return null;
}
Transform jointTransform = null;
if (fauxJoints != null && !fauxJoints.TryGetValue(joint, out jointTransform))
{
jointTransform = new GameObject().transform;
// Since this service survives scene loading and unloading, the fauxJoints it manages need to as well.
Object.DontDestroyOnLoad(jointTransform.gameObject);
jointTransform.name = string.Format("Joint Tracker: {1} {0}", joint, handedness);
if (hand != null && hand.TryGetJoint(joint, out MixedRealityPose pose))
{
jointTransform.SetPositionAndRotation(pose.Position, pose.Rotation);
}
fauxJoints.Add(joint, jointTransform);
}
return jointTransform;
}
public bool IsHandTracked(Handedness handedness)
{
return handedness == Handedness.Left ? leftHand != null : handedness == Handedness.Right ? rightHand != null : false;
}
#endregion IMixedRealityHandJointService Implementation
}
}
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
using Microsoft.MixedReality.Toolkit.Utilities;
namespace Microsoft.MixedReality.Toolkit.Input
{
public static class HandJointUtils
{
/// <summary>
/// Tries to get the pose of the requested joint for the first controller with the specified handedness.
/// </summary>
/// <param name="joint">The requested joint</param>
/// <param name="handedness">The specific hand of interest. This should be either Handedness.Left or Handedness.Right</param>
/// <param name="pose">The output pose data</param>
public static bool TryGetJointPose(TrackedHandJoint joint, Handedness handedness, out MixedRealityPose pose)
{
return TryGetJointPose<IMixedRealityHand>(joint, handedness, out pose);
}
/// <summary>
/// Try to find the first matching hand controller of the given type and return the pose of the requested joint for that hand.
/// </summary>
public static bool TryGetJointPose<T>(TrackedHandJoint joint, Handedness handedness, out MixedRealityPose pose) where T : class, IMixedRealityHand
{
T hand = FindHand<T>(handedness);
if (hand != null)
{
return hand.TryGetJoint(joint, out pose);
}
pose = MixedRealityPose.ZeroIdentity;
return false;
}
/// <summary>
/// Find the first detected hand controller with matching handedness.
/// </summary>
/// <remarks>
/// The given handedness should be either Handedness.Left or Handedness.Right.
/// </remarks>
public static IMixedRealityHand FindHand(Handedness handedness)
{
return FindHand<IMixedRealityHand>(handedness);
}
/// <summary>
/// Find the first detected hand controller of the given type with matching handedness.
/// </summary>
public static T FindHand<T>(Handedness handedness) where T : class, IMixedRealityHand
{
foreach (var detectedController in CoreServices.InputSystem.DetectedControllers)
{
var hand = detectedController as T;
if (hand != null)
{
if (detectedController.ControllerHandedness.IsMatch(handedness))
{
return hand;
}
}
}
return null;
}
}
}
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
using Microsoft.MixedReality.Toolkit.Utilities;
using UnityEngine;
namespace Microsoft.MixedReality.Toolkit.Input
{
public class HandRay : IHandRay
{
/// <inheritdoc />
public Ray Ray
{
get
{
ray.origin = stabilizedRay.StabilizedPosition;
ray.direction = stabilizedRay.StabilizedDirection;
return ray;
}
}
/// <inheritdoc />
public bool ShouldShowRay
{
get
{
if (headForward.magnitude < Mathf.Epsilon)
{
return false;
}
bool valid = true;
if (CursorBeamBackwardTolerance >= 0)
{
Vector3 cameraBackward = -headForward;
if (Vector3.Dot(palmNormal.normalized, cameraBackward) > CursorBeamBackwardTolerance)
{
valid = false;
}
}
if (valid && CursorBeamUpTolerance >= 0)
{
if (Vector3.Dot(palmNormal, Vector3.up) > CursorBeamUpTolerance)
{
valid = false;
}
}
return valid;
}
}
private Ray ray = new Ray();
// Constants from Shell Implementation of hand ray.
private const float DynamicPivotBaseY = -0.1f, DynamicPivotMultiplierY = 0.65f, DynamicPivotMinY = -0.6f, DynamicPivotMaxY = -0.2f;
private const float DynamicPivotBaseX = 0.03f, DynamicPivotMultiplierX = 0.65f, DynamicPivotMinX = 0.08f, DynamicPivotMaxX = 0.15f;
private const float HeadToPivotOffsetZ = 0.08f;
private readonly float CursorBeamBackwardTolerance = 0.5f;
private readonly float CursorBeamUpTolerance = 0.8f;
// Smoothing factor for ray stabilization.
private const float StabilizedRayHalfLife = 0.01f;
private readonly StabilizedRay stabilizedRay = new StabilizedRay(StabilizedRayHalfLife);
private Vector3 palmNormal;
private Vector3 headForward;
#region Public Methods
/// <inheritdoc />
public void Update(Vector3 handPosition, Vector3 palmNormal, Transform headTransform, Handedness sourceHandedness)
{
Vector3 rayPivotPoint = ComputeRayPivotPosition(handPosition, headTransform, sourceHandedness);
Vector3 measuredRayPosition = handPosition;
Vector3 measuredDirection = measuredRayPosition - rayPivotPoint;
this.palmNormal = palmNormal;
this.headForward = headTransform.forward;
stabilizedRay.AddSample(new Ray(measuredRayPosition, measuredDirection));
}
#endregion
private Vector3 ComputeRayPivotPosition(Vector3 handPosition, Transform headTransform, Handedness sourceHandedness)
{
Vector3 handPositionHeadSpace = headTransform.InverseTransformPoint(handPosition);
float relativePivotY = DynamicPivotBaseY + Mathf.Min(DynamicPivotMultiplierY * handPositionHeadSpace.y, 0);
relativePivotY = Mathf.Clamp(relativePivotY, DynamicPivotMinY, DynamicPivotMaxY);
float xBase = DynamicPivotBaseX;
float xMultiplier = DynamicPivotMultiplierX;
float xMin = DynamicPivotMinX;
float xMax = DynamicPivotMaxX;
if (sourceHandedness == Handedness.Left)
{
xBase = -xBase;
float tmp = xMin;
xMin = -xMax;
xMax = tmp;
}
float relativePivotX = xBase + xMultiplier * handPositionHeadSpace.x;
relativePivotX = Mathf.Clamp(relativePivotX, xMin, xMax);
Vector3 relativePivot = new Vector3(
relativePivotX,
relativePivotY,
HeadToPivotOffsetZ
);
Quaternion headRotationFlat = Quaternion.Euler(0, headTransform.rotation.eulerAngles.y, 0);
return headTransform.position + headRotationFlat * relativePivot;
}
}
}
\ No newline at end of file
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
using Microsoft.MixedReality.Toolkit.Utilities;
using UnityEngine;
namespace Microsoft.MixedReality.Toolkit.Input
{
/// <summary>
/// Interface defining a hand ray, which is used by far pointers to direct interactions.
/// Implementations of this class are managed and updated by a BaseHand implementation.
/// </summary>
public interface IHandRay
{
/// <summary>
/// Ray used by input system for Far Pointers.
/// </summary>
Ray Ray { get; }
/// <summary>
/// Check whether hand palm is angled in a way that hand rays should be used.
/// </summary>
bool ShouldShowRay { get; }
/// <summary>
/// Update data used by hand implementation, to compute next HandRay.
/// </summary>
/// <param name="handPosition">Position of hand</param>
/// <param name="palmNormal">Palm normal</param>
/// <param name="headTransform">Transform of CameraCache.main</param>
/// <param name="sourceHandedness">Handedness of related hand</param>
void Update(Vector3 handPosition, Vector3 palmNormal, Transform headTransform, Handedness sourceHandedness);
}
}
# Hand devices
Base class for all hand devices, to act be implemented by all backends that want to provide hand input in MRTK. Simulated hands can be used if no hand tracking device is available.
## Simulated hands
Hand input can be simulated in Unity using the "In-Editor Input Simulation" service. This service is enabled by default.
When enabled, virtual left and/or right hands can be moved in the scene using the mouse. Clicking mouse buttons will perform gestures for interacting with objects.
Press shift to control the left hand and/or space to control the right hand. By pressing shift and space simultaneously both hands can be moved at the same time.
The hand simulation has two modes:
1. Quick mode: Hands are shown only as long as they are moved by the mouse. This mode is useful for simple testing of buttons with a single hand.
2. Persistent mode: Hands stay visible on the screen, even if they are not moved. This mode is useful for testing two-hand manipulation and accurate placement. Gestures are toggled on/off by mouse clicks.
The simulation mode can be switched for the left/right hand individually by pressing the T/Y keys respectively.
Detailed settings for hand simulation can be found in the SimulatedHandAPI prefab, which is instantiated through the hand tracking profile. This includes key bindings for hand movement and mouse button bindings for gestures.
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License. See LICENSE in the project root for license information.
[assembly: System.Reflection.AssemblyVersion("2.5.0.0")]
[assembly: System.Reflection.AssemblyFileVersion("2.5.0.0")]
[assembly: System.Reflection.AssemblyProduct("Microsoft® Mixed Reality Toolkit aipmragent_work")]
[assembly: System.Reflection.AssemblyCopyright("Copyright © Microsoft Corporation")]
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
using System;
using UnityEngine;
namespace Microsoft.MixedReality.Toolkit.Input
{
/// <summary>
/// Provides input recording into an internal buffer and exporting to files.
/// </summary>
public interface IMixedRealityInputRecordingService : IMixedRealityInputDeviceManager
{
/// <summary>
/// True if input is being recorded.
/// </summary>
bool IsRecording { get; }
/// <summary>
/// Limit the size of the recording buffer.
/// </summary>
/// <remarks>
/// If recording is limited any input older than the RecordingBufferTimeLimit will be discarded.
/// </remarks>
bool UseBufferTimeLimit { get; set; }
/// <summary>
/// Maximum duration in seconds of the input recording if UseBufferTimeLimit is enabled.
/// </summary>
/// <remarks>
/// If UseBufferTimeLimit is enabled then keyframes older than this limit will be discarded.
/// </remarks>
float RecordingBufferTimeLimit { get; set; }
/// <summary>
/// Start unlimited input recording.
/// </summary>
void StartRecording();
/// <summary>
/// Stop recording input.
/// </summary>
void StopRecording();
/// <summary>
/// Discard all recorded input
/// </summary>
void DiscardRecordedInput();
/// <summary>
/// Save recorded input animation to a file.
/// </summary>
/// <param name="directory">Directory in which to create the file. If null the persistent data path of the app is used.</param>
/// <returns>File path where input has been recorded.</returns>
/// <remarks>
/// Filename is determined automatically.
/// </remarks>
string SaveInputAnimation(string directory = null);
/// <summary>
/// Save recorded input animation to a file.
/// </summary>
/// <param name="filename">Name of the file to create.</param>
/// <param name="directory">Directory in which to create the file. If null the persistent data path of the app is used.</param>
/// <returns>File path where input has been recorded.</returns>
string SaveInputAnimation(string filename, string directory = null);
}
}
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment