diff --git a/com.microsoft.mrtk.input/Assets/Input Actions/MRTK Default Input Actions.inputactions b/com.microsoft.mrtk.input/Assets/Input Actions/MRTK Default Input Actions.inputactions index e53be4c62af..d0db5d263fc 100644 --- a/com.microsoft.mrtk.input/Assets/Input Actions/MRTK Default Input Actions.inputactions +++ b/com.microsoft.mrtk.input/Assets/Input Actions/MRTK Default Input Actions.inputactions @@ -1239,6 +1239,94 @@ "isPartOfComposite": true } ] + }, + { + "name": "MRTK Spatial Mouse", + "id": "d61a5015-577c-4668-b95f-2ab49dd82913", + "actions": [ + { + "name": "Select", + "type": "Button", + "id": "03075bc5-ac04-4deb-986e-20a2e8203b74", + "expectedControlType": "Button", + "processors": "", + "interactions": "", + "initialStateCheck": false + }, + { + "name": "MouseMove", + "type": "Value", + "id": "68f39cfe-a99b-4b0c-9ff5-12f59905d3a9", + "expectedControlType": "Vector2", + "processors": "", + "interactions": "", + "initialStateCheck": true + }, + { + "name": "MouseScroll", + "type": "Value", + "id": "ff47dab1-a950-4924-b86d-0dfe19d7ba0c", + "expectedControlType": "Vector2", + "processors": "", + "interactions": "", + "initialStateCheck": true + }, + { + "name": "Position", + "type": "Value", + "id": "6c8df8e3-46c8-4ab1-9c61-e32073f5a43b", + "expectedControlType": "Vector3", + "processors": "", + "interactions": "", + "initialStateCheck": true + } + ], + "bindings": [ + { + "name": "", + "id": "621f9734-1689-41a1-b9e4-ecd04939b8b8", + "path": "/press", + "interactions": "", + "processors": "", + "groups": "Generic XR Controller", + "action": "Select", + "isComposite": false, + "isPartOfComposite": false + }, + { + "name": "", + "id": "861aef1b-f193-4c99-9f68-caabf2d22ecd", + "path": "/delta", + "interactions": "", + "processors": "", + "groups": "Generic XR Controller", + "action": "MouseMove", + "isComposite": false, + "isPartOfComposite": false + }, + { + "name": "", + "id": "3a3379e8-1ad6-4435-8e72-c06c0b27ed31", + "path": "/scroll", + "interactions": "", + "processors": "", + "groups": "Generic XR Controller", + "action": "MouseScroll", + "isComposite": false, + "isPartOfComposite": false + }, + { + "name": "", + "id": "01f16594-5472-4876-8596-9c4290ad4a06", + "path": "/centerEyePosition", + "interactions": "", + "processors": "", + "groups": "Generic XR Controller", + "action": "Position", + "isComposite": false, + "isPartOfComposite": false + } + ] } ], "controlSchemes": [ diff --git a/com.microsoft.mrtk.input/Assets/Prefabs/MRTK Spatial Mouse Controller.prefab b/com.microsoft.mrtk.input/Assets/Prefabs/MRTK Spatial Mouse Controller.prefab new file mode 100644 index 00000000000..5586015377c --- /dev/null +++ b/com.microsoft.mrtk.input/Assets/Prefabs/MRTK Spatial Mouse Controller.prefab @@ -0,0 +1,475 @@ +%YAML 1.1 +%TAG !u! tag:unity3d.com,2011: +--- !u!1 &5491103498108965383 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 176000564354740067} + - component: {fileID: 4681310561030822292} + - component: {fileID: 1753030325} + m_Layer: 0 + m_Name: SpatialMouseInteractor + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!4 &176000564354740067 +Transform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 5491103498108965383} + m_LocalRotation: {x: 0, y: 0, z: 0, w: 1} + m_LocalPosition: {x: 0, y: 0, z: 0} + m_LocalScale: {x: 1, y: 1, z: 1} + m_ConstrainProportionsScale: 0 + m_Children: + - {fileID: 147216125} + m_Father: {fileID: 5604401826787249420} + m_RootOrder: 0 + m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} +--- !u!114 &4681310561030822292 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 5491103498108965383} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: 2f09ae66a2a046045969d2df513dbfac, type: 3} + m_Name: + m_EditorClassIdentifier: + m_InteractionManager: {fileID: 0} + m_InteractionLayerMask: + serializedVersion: 2 + m_Bits: 4294967295 + m_InteractionLayers: + m_Bits: 4294967295 + m_AttachTransform: {fileID: 0} + m_KeepSelectedTargetValid: 1 + m_DisableVisualsWhenBlockedInGroup: 1 + m_StartingSelectedInteractable: {fileID: 0} + m_StartingTargetFilter: {fileID: 0} + m_HoverEntered: + m_PersistentCalls: + m_Calls: [] + m_HoverExited: + m_PersistentCalls: + m_Calls: [] + m_SelectEntered: + m_PersistentCalls: + m_Calls: [] + m_SelectExited: + m_PersistentCalls: + m_Calls: [] + m_StartingHoverFilters: [] + m_StartingSelectFilters: [] + m_OnHoverEntered: + m_PersistentCalls: + m_Calls: [] + m_OnHoverExited: + m_PersistentCalls: + m_Calls: [] + m_OnSelectEntered: + m_PersistentCalls: + m_Calls: [] + m_OnSelectExited: + m_PersistentCalls: + m_Calls: [] + m_SelectActionTrigger: 1 + m_HideControllerOnSelect: 0 + m_AllowHoveredActivate: 0 + m_TargetPriorityMode: 0 + m_PlayAudioClipOnSelectEntered: 0 + m_AudioClipForOnSelectEntered: {fileID: 0} + m_PlayAudioClipOnSelectExited: 0 + m_AudioClipForOnSelectExited: {fileID: 0} + m_PlayAudioClipOnSelectCanceled: 0 + m_AudioClipForOnSelectCanceled: {fileID: 0} + m_PlayAudioClipOnHoverEntered: 0 + m_AudioClipForOnHoverEntered: {fileID: 0} + m_PlayAudioClipOnHoverExited: 0 + m_AudioClipForOnHoverExited: {fileID: 0} + m_PlayAudioClipOnHoverCanceled: 0 + m_AudioClipForOnHoverCanceled: {fileID: 0} + m_AllowHoverAudioWhileSelecting: 1 + m_PlayHapticsOnSelectEntered: 0 + m_HapticSelectEnterIntensity: 0 + m_HapticSelectEnterDuration: 0 + m_PlayHapticsOnSelectExited: 0 + m_HapticSelectExitIntensity: 0 + m_HapticSelectExitDuration: 0 + m_PlayHapticsOnSelectCanceled: 0 + m_HapticSelectCancelIntensity: 0 + m_HapticSelectCancelDuration: 0 + m_PlayHapticsOnHoverEntered: 0 + m_HapticHoverEnterIntensity: 0 + m_HapticHoverEnterDuration: 0 + m_PlayHapticsOnHoverExited: 0 + m_HapticHoverExitIntensity: 0 + m_HapticHoverExitDuration: 0 + m_PlayHapticsOnHoverCanceled: 0 + m_HapticHoverCancelIntensity: 0 + m_HapticHoverCancelDuration: 0 + m_AllowHoverHapticsWhileSelecting: 1 + m_LineType: 0 + m_BlendVisualLinePoints: 1 + m_MaxRaycastDistance: 8 + m_RayOriginTransform: {fileID: 0} + m_ReferenceFrame: {fileID: 0} + m_Velocity: 16 + m_Acceleration: 9.8 + m_AdditionalGroundHeight: 0.1 + m_AdditionalFlightTime: 0.5 + m_EndPointDistance: 30 + m_EndPointHeight: -10 + m_ControlPointDistance: 10 + m_ControlPointHeight: 5 + m_SampleFrequency: 20 + m_HitDetectionType: 0 + m_SphereCastRadius: 0.1 + m_RaycastMask: + serializedVersion: 2 + m_Bits: 4294967295 + m_RaycastTriggerInteraction: 1 + m_RaycastSnapVolumeInteraction: 1 + m_HitClosestOnly: 1 + m_HoverToSelect: 0 + m_HoverTimeToSelect: 0.5 + m_AutoDeselect: 0 + m_TimeToAutoDeselect: 3 + m_EnableUIInteraction: 1 + m_AllowAnchorControl: 0 + m_UseForceGrab: 0 + m_RotateSpeed: 180 + m_TranslateSpeed: 1 + m_AnchorRotateReferenceFrame: {fileID: 0} + m_AnchorRotationMode: 0 + mouseMoveAction: + m_UseReference: 1 + m_Action: + m_Name: Mouse Move + m_Type: 0 + m_ExpectedControlType: + m_Id: 98bdd78d-ee96-4cff-a687-20e3ed2f4619 + m_Processors: + m_Interactions: + m_SingletonActionBindings: [] + m_Flags: 0 + m_Reference: {fileID: 6428155659162902554, guid: 18c412191cdc9274897f101c7fd5316f, type: 3} + mouseScrollAction: + m_UseReference: 1 + m_Action: + m_Name: Mouse Scroll + m_Type: 0 + m_ExpectedControlType: + m_Id: e1c03ce3-8ea6-4fcd-9ba7-25f3c4fb68db + m_Processors: + m_Interactions: + m_SingletonActionBindings: [] + m_Flags: 0 + m_Reference: {fileID: 6404053549896120058, guid: 18c412191cdc9274897f101c7fd5316f, type: 3} + mouseSensitivity: 0.05 + mouseWheelSensitivity: 0.002 + mouseHideThreshold: 20 + mouseResetThreshold: 0.2 +--- !u!114 &1753030325 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 5491103498108965383} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: dd37d3de25ec0844d9ea7e898410084c, type: 3} + m_Name: + m_EditorClassIdentifier: + baseReticle: {fileID: 1275538619} + mouseInteractor: {fileID: 4681310561030822292} + defaultDistance: 1 +--- !u!1 &6862721871616905633 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 5604401826787249420} + - component: {fileID: 8777930775890352138} + m_Layer: 0 + m_Name: MRTK Spatial Mouse Controller + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!4 &5604401826787249420 +Transform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 6862721871616905633} + m_LocalRotation: {x: 0, y: 0, z: 0, w: 1} + m_LocalPosition: {x: 0, y: 0, z: 0} + m_LocalScale: {x: 1, y: 1, z: 1} + m_ConstrainProportionsScale: 0 + m_Children: + - {fileID: 176000564354740067} + m_Father: {fileID: 0} + m_RootOrder: 0 + m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} +--- !u!114 &8777930775890352138 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 6862721871616905633} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: caff514de9b15ad48ab85dcff5508221, type: 3} + m_Name: + m_EditorClassIdentifier: + m_UpdateTrackingType: 0 + m_EnableInputTracking: 1 + m_EnableInputActions: 1 + m_ModelPrefab: {fileID: 0} + m_ModelParent: {fileID: 0} + m_Model: {fileID: 0} + m_AnimateModel: 0 + m_ModelSelectTransition: + m_ModelDeSelectTransition: + m_PositionAction: + m_UseReference: 1 + m_Action: + m_Name: Position + m_Type: 0 + m_ExpectedControlType: + m_Id: b2869c6b-4abe-4d28-831f-f7355b6cf1d6 + m_Processors: + m_Interactions: + m_SingletonActionBindings: [] + m_Flags: 0 + m_Reference: {fileID: -681459848031527356, guid: 18c412191cdc9274897f101c7fd5316f, type: 3} + m_RotationAction: + m_UseReference: 0 + m_Action: + m_Name: Rotation + m_Type: 0 + m_ExpectedControlType: + m_Id: c0c2ca38-51a5-4cad-88d9-5dd7e8f700c3 + m_Processors: + m_Interactions: + m_SingletonActionBindings: [] + m_Flags: 0 + m_Reference: {fileID: 0} + m_TrackingStateAction: + m_UseReference: 0 + m_Action: + m_Name: Tracking State + m_Type: 0 + m_ExpectedControlType: + m_Id: 3d829a82-a0fe-4f57-a08f-0d6158711dfc + m_Processors: + m_Interactions: + m_SingletonActionBindings: [] + m_Flags: 0 + m_Reference: {fileID: 0} + m_SelectAction: + m_UseReference: 1 + m_Action: + m_Name: Select + m_Type: 0 + m_ExpectedControlType: + m_Id: 30760b30-3d1a-400f-bddf-3c769fb6edf8 + m_Processors: + m_Interactions: + m_SingletonActionBindings: [] + m_Flags: 0 + m_Reference: {fileID: 924797614382256527, guid: 18c412191cdc9274897f101c7fd5316f, type: 3} + m_SelectActionValue: + m_UseReference: 0 + m_Action: + m_Name: Select Action Value + m_Type: 0 + m_ExpectedControlType: + m_Id: 8a94463f-59e4-4a2d-831a-7c6dce33a3c5 + m_Processors: + m_Interactions: + m_SingletonActionBindings: [] + m_Flags: 0 + m_Reference: {fileID: 0} + m_ActivateAction: + m_UseReference: 0 + m_Action: + m_Name: Activate + m_Type: 0 + m_ExpectedControlType: + m_Id: 07e2d5ee-4b12-4d38-b103-341575c3f589 + m_Processors: + m_Interactions: + m_SingletonActionBindings: [] + m_Flags: 0 + m_Reference: {fileID: 0} + m_ActivateActionValue: + m_UseReference: 0 + m_Action: + m_Name: Activate Action Value + m_Type: 0 + m_ExpectedControlType: + m_Id: c62437cd-a54e-4e97-8f8e-1c40310e8c7f + m_Processors: + m_Interactions: + m_SingletonActionBindings: [] + m_Flags: 0 + m_Reference: {fileID: 0} + m_UIPressAction: + m_UseReference: 0 + m_Action: + m_Name: UI Press + m_Type: 0 + m_ExpectedControlType: + m_Id: 3eeaabd6-a992-4a11-a86f-48defc36339b + m_Processors: + m_Interactions: + m_SingletonActionBindings: [] + m_Flags: 0 + m_Reference: {fileID: 0} + m_UIPressActionValue: + m_UseReference: 0 + m_Action: + m_Name: UI Press Action Value + m_Type: 0 + m_ExpectedControlType: + m_Id: fae7bc04-c230-40e9-b0f5-27204e899a51 + m_Processors: + m_Interactions: + m_SingletonActionBindings: [] + m_Flags: 0 + m_Reference: {fileID: 0} + m_HapticDeviceAction: + m_UseReference: 0 + m_Action: + m_Name: Haptic Device + m_Type: 0 + m_ExpectedControlType: + m_Id: 8862254d-b68b-4674-9dd9-2669de855063 + m_Processors: + m_Interactions: + m_SingletonActionBindings: [] + m_Flags: 0 + m_Reference: {fileID: 0} + m_RotateAnchorAction: + m_UseReference: 0 + m_Action: + m_Name: Rotate Anchor + m_Type: 0 + m_ExpectedControlType: + m_Id: 8c236782-0d94-44e8-9aba-b47fa898375d + m_Processors: + m_Interactions: + m_SingletonActionBindings: [] + m_Flags: 0 + m_Reference: {fileID: 0} + m_DirectionalAnchorRotationAction: + m_UseReference: 0 + m_Action: + m_Name: Directional Anchor Rotation + m_Type: 0 + m_ExpectedControlType: + m_Id: 5a183a71-e0e6-4e9a-bed8-6d10c30627f1 + m_Processors: + m_Interactions: + m_SingletonActionBindings: [] + m_Flags: 0 + m_Reference: {fileID: 0} + m_TranslateAnchorAction: + m_UseReference: 0 + m_Action: + m_Name: Translate Anchor + m_Type: 0 + m_ExpectedControlType: + m_Id: 7349aff8-5149-4c6c-a7ce-4129f87e5af8 + m_Processors: + m_Interactions: + m_SingletonActionBindings: [] + m_Flags: 0 + m_Reference: {fileID: 0} + m_ButtonPressPoint: 0.5 +--- !u!1001 &8113086299026845985 +PrefabInstance: + m_ObjectHideFlags: 0 + serializedVersion: 2 + m_Modification: + m_TransformParent: {fileID: 176000564354740067} + m_Modifications: + - target: {fileID: 8113086299156719580, guid: f7c9215713002d34a9107dd69004e749, type: 3} + propertyPath: m_RootOrder + value: 0 + objectReference: {fileID: 0} + - target: {fileID: 8113086299156719580, guid: f7c9215713002d34a9107dd69004e749, type: 3} + propertyPath: m_LocalPosition.x + value: 0 + objectReference: {fileID: 0} + - target: {fileID: 8113086299156719580, guid: f7c9215713002d34a9107dd69004e749, type: 3} + propertyPath: m_LocalPosition.y + value: 0 + objectReference: {fileID: 0} + - target: {fileID: 8113086299156719580, guid: f7c9215713002d34a9107dd69004e749, type: 3} + propertyPath: m_LocalPosition.z + value: 0 + objectReference: {fileID: 0} + - target: {fileID: 8113086299156719580, guid: f7c9215713002d34a9107dd69004e749, type: 3} + propertyPath: m_LocalRotation.w + value: 1 + objectReference: {fileID: 0} + - target: {fileID: 8113086299156719580, guid: f7c9215713002d34a9107dd69004e749, type: 3} + propertyPath: m_LocalRotation.x + value: -0 + objectReference: {fileID: 0} + - target: {fileID: 8113086299156719580, guid: f7c9215713002d34a9107dd69004e749, type: 3} + propertyPath: m_LocalRotation.y + value: -0 + objectReference: {fileID: 0} + - target: {fileID: 8113086299156719580, guid: f7c9215713002d34a9107dd69004e749, type: 3} + propertyPath: m_LocalRotation.z + value: -0 + objectReference: {fileID: 0} + - target: {fileID: 8113086299156719580, guid: f7c9215713002d34a9107dd69004e749, type: 3} + propertyPath: m_LocalEulerAnglesHint.x + value: 0 + objectReference: {fileID: 0} + - target: {fileID: 8113086299156719580, guid: f7c9215713002d34a9107dd69004e749, type: 3} + propertyPath: m_LocalEulerAnglesHint.y + value: 0 + objectReference: {fileID: 0} + - target: {fileID: 8113086299156719580, guid: f7c9215713002d34a9107dd69004e749, type: 3} + propertyPath: m_LocalEulerAnglesHint.z + value: 0 + objectReference: {fileID: 0} + - target: {fileID: 8113086299156719581, guid: f7c9215713002d34a9107dd69004e749, type: 3} + propertyPath: m_Name + value: MouseCursor + objectReference: {fileID: 0} + m_RemovedComponents: [] + m_SourcePrefab: {fileID: 100100000, guid: f7c9215713002d34a9107dd69004e749, type: 3} +--- !u!4 &147216125 stripped +Transform: + m_CorrespondingSourceObject: {fileID: 8113086299156719580, guid: f7c9215713002d34a9107dd69004e749, type: 3} + m_PrefabInstance: {fileID: 8113086299026845985} + m_PrefabAsset: {fileID: 0} +--- !u!1 &1275538619 stripped +GameObject: + m_CorrespondingSourceObject: {fileID: 8113086300167495066, guid: f7c9215713002d34a9107dd69004e749, type: 3} + m_PrefabInstance: {fileID: 8113086299026845985} + m_PrefabAsset: {fileID: 0} diff --git a/com.microsoft.mrtk.input/Assets/Prefabs/MRTK Spatial Mouse Controller.prefab.meta b/com.microsoft.mrtk.input/Assets/Prefabs/MRTK Spatial Mouse Controller.prefab.meta new file mode 100644 index 00000000000..af18947da44 --- /dev/null +++ b/com.microsoft.mrtk.input/Assets/Prefabs/MRTK Spatial Mouse Controller.prefab.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: dc525621b8522034e867ed2799129315 +PrefabImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/com.microsoft.mrtk.input/Experimental.meta b/com.microsoft.mrtk.input/Experimental.meta new file mode 100644 index 00000000000..0606fc1b70d --- /dev/null +++ b/com.microsoft.mrtk.input/Experimental.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 4c9cebb1dbb4313479d03a9661664836 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/com.microsoft.mrtk.input/Experimental/SpatialMouse.meta b/com.microsoft.mrtk.input/Experimental/SpatialMouse.meta new file mode 100644 index 00000000000..5a1d7a6eaa8 --- /dev/null +++ b/com.microsoft.mrtk.input/Experimental/SpatialMouse.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 2f1702cd51414c74c9cb3b2474433f86 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/com.microsoft.mrtk.input/Experimental/SpatialMouse/Interactor.meta b/com.microsoft.mrtk.input/Experimental/SpatialMouse/Interactor.meta new file mode 100644 index 00000000000..28f71dfe5af --- /dev/null +++ b/com.microsoft.mrtk.input/Experimental/SpatialMouse/Interactor.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 03f9372e846294148ad7d55e7ee82b0a +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/com.microsoft.mrtk.input/Experimental/SpatialMouse/Interactor/SpatialMouseInteractor.cs b/com.microsoft.mrtk.input/Experimental/SpatialMouse/Interactor/SpatialMouseInteractor.cs new file mode 100644 index 00000000000..8a9cb202901 --- /dev/null +++ b/com.microsoft.mrtk.input/Experimental/SpatialMouse/Interactor/SpatialMouseInteractor.cs @@ -0,0 +1,215 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +using Microsoft.MixedReality.Toolkit.Subsystems; +using System.Collections.Generic; +using Unity.Profiling; +using UnityEngine; +using UnityEngine.InputSystem; +using UnityEngine.XR.Interaction.Toolkit; +using UnityEngine.XR.Interaction.Toolkit.Inputs; +using UnityEngine.XR.Interaction.Toolkit.UI; + +namespace Microsoft.MixedReality.Toolkit.Input.Experimental +{ + /// + /// An extension of XRRayInteractor which has extra functionality for handling spatial mouse movement and scrolling. + /// + /// + /// This is an experimental feature. This class is early in the cycle, it has + /// been labeled as experimental to indicate that it is still evolving, and + /// subject to change over time. Parts of the MRTK, such as this class, appear + /// to have a lot of value even if the details haven’t fully been fleshed out. + /// For these types of features, we want the community to see them and get + /// value out of them early enough so to provide feedback. + /// + [AddComponentMenu("Scripts/Microsoft/MRTK/Input/MRTK Spatial Mouse Interactor")] + public class SpatialMouseInteractor : XRRayInteractor, IRayInteractor + { + #region Private Properties + + private CursorLockMode restoreLockState; + private float timeSinceLastMouseEvent = Mathf.Infinity; + + #endregion Private Properties + + #region Serialized Fields + /// + /// The Input System action used for mouse movement. Must be a Vector2 Control. + /// + [field: SerializeField, Experimental, Tooltip("The Input System action used for mouse movement. Must be a Vector2 Control.")] + public InputActionProperty mouseMoveAction; + + /// + /// The Input System action used for mouse scrolling. Must be a Vector2 Control. + /// + [field: SerializeField, Tooltip("The Input System action used for mouse scrolling. Must be a Vector2 Control.")] + public InputActionProperty mouseScrollAction; + + [SerializeField] + [Tooltip("The scale factor to apply to the mouse deltas.")] + private float mouseSensitivity = .05f; + + [SerializeField] + [Tooltip("The scale factor to apply to the mouse wheel.")] + private float mouseWheelSensitivity = .002f; + + [SerializeField] + [Tooltip("The time (in seconds) of no mouse activity before hiding the mouse cursor.")] + private float mouseHideThreshold = 20.0f; + + [SerializeField] + [Tooltip("The time (in seconds) of no mouse activity before reseting the mouse cursor to the center of the FoV.")] + private float mouseResetThreshold = 0.2f; + + #endregion Serialized Fields + + #region Public Properties + + /// + /// Returns true if mouse is actively in use. Should be used to show/hide mouse-specific feedback (e.g. cursor). + /// + public bool IsInUse => hasSelection || (timeSinceLastMouseEvent < mouseHideThreshold); + + #endregion Public Properties + + /// + protected override void OnEnable() + { + base.OnEnable(); + + if (mouseMoveAction != null) + { + mouseMoveAction.action.performed += OnMouseMove; + mouseMoveAction.EnableDirectAction(); + } + if (mouseScrollAction != null) + { + mouseScrollAction.action.performed += OnMouseScroll; + mouseScrollAction.EnableDirectAction(); + } + + restoreLockState = Cursor.lockState; + Cursor.lockState = CursorLockMode.Locked; + } + + /// + protected override void OnDisable() + { + base.OnDisable(); + + Cursor.lockState = restoreLockState; + + if (mouseMoveAction != null) + { + mouseMoveAction.action.performed -= OnMouseMove; + mouseMoveAction.DisableDirectAction(); + } + if (mouseScrollAction != null) + { + mouseScrollAction.action.performed -= OnMouseScroll; + mouseScrollAction.DisableDirectAction(); + } + } + + private void Update() + { + timeSinceLastMouseEvent += Time.deltaTime; + } + + private void OnMouseMove(InputAction.CallbackContext context) + { + Vector2 mouseDelta = context.ReadValue(); + + Vector3 screenPoint = Camera.main.WorldToViewportPoint(rayOriginTransform.position + rayOriginTransform.forward); + bool onScreen = screenPoint.z > 0 && screenPoint.x > 0 && screenPoint.x < 1 && screenPoint.y > 0 && screenPoint.y < 1; + + // Reset the cursor to the center of the FoV if it's hidden or it's out of the user's FoV and + // the user hasn't interacted with it in the mouseResetThreshold + bool shouldResetMousePosition = !IsInUse || + (!onScreen && (timeSinceLastMouseEvent >= mouseResetThreshold)); + + if (shouldResetMousePosition) + { + rayOriginTransform.rotation = Camera.main.transform.rotation; + } + else + { + float rotateByRadiansX = mouseDelta.x * mouseSensitivity; + float rotateByRadiansY = -mouseDelta.y * mouseSensitivity; + + rayOriginTransform.RotateAround(rayOriginTransform.position, Camera.main.transform.up, rotateByRadiansX); + rayOriginTransform.RotateAround(rayOriginTransform.position, Camera.main.transform.right, rotateByRadiansY); + + if (hasSelection) + { + float distanceToAttachTransform = Vector3.Distance(rayOriginTransform.position, attachTransform.position); + attachTransform.position = rayOriginTransform.position + rayOriginTransform.forward * distanceToAttachTransform; + } + } + + timeSinceLastMouseEvent = 0; + } + + private void OnMouseScroll(InputAction.CallbackContext context) + { + Vector2 scrollDelta = context.ReadValue(); + + if (hasSelection) + { + float translateByZ = (float)scrollDelta.y * mouseWheelSensitivity; + float currentDistanceToAttachTransform = Vector3.Distance(rayOriginTransform.position, attachTransform.position); + float newDistanceToAttachTransform = Mathf.Max(0.1f, Mathf.Min(maxRaycastDistance, currentDistanceToAttachTransform + translateByZ)); + + attachTransform.position = rayOriginTransform.position + rayOriginTransform.forward * newDistanceToAttachTransform; + } + + timeSinceLastMouseEvent = 0; + } + + #region XRBaseControllerInteractor + + /// + public override bool CanHover(IXRHoverInteractable interactable) + { + // We stay hovering if we have selected anything. + bool stickyHover = hasSelection && IsSelecting(interactable); + if (stickyHover) + { + return true; + } + + // We are ready to pinch if we are in the PinchReady position, + // or if we are already selecting something. + bool ready = isHoverActive || isSelectActive; + + return ready && base.CanHover(interactable); + } + + /// + public override bool CanSelect(IXRSelectInteractable interactable) + { + return base.CanSelect(interactable) && (!hasSelection || IsSelecting(interactable)) && IsInUse; + } + + /// + public override void GetValidTargets(List targets) + { + // When selection is active, force valid targets to be the current selection. This is done to ensure that selected objects remained hovered. + if (hasSelection && isActiveAndEnabled) + { + targets.Clear(); + for (int i = 0; i < interactablesSelected.Count; i++) + { + targets.Add(interactablesSelected[i]); + } + } + else + { + base.GetValidTargets(targets); + } + } + + #endregion + } +} diff --git a/com.microsoft.mrtk.input/Experimental/SpatialMouse/Interactor/SpatialMouseInteractor.cs.meta b/com.microsoft.mrtk.input/Experimental/SpatialMouse/Interactor/SpatialMouseInteractor.cs.meta new file mode 100644 index 00000000000..5a6c1eb069e --- /dev/null +++ b/com.microsoft.mrtk.input/Experimental/SpatialMouse/Interactor/SpatialMouseInteractor.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 2f09ae66a2a046045969d2df513dbfac +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/com.microsoft.mrtk.input/Experimental/SpatialMouse/Interactor/SpatialMouseInteractorCursorVisual.cs b/com.microsoft.mrtk.input/Experimental/SpatialMouse/Interactor/SpatialMouseInteractorCursorVisual.cs new file mode 100644 index 00000000000..b0f41da6d40 --- /dev/null +++ b/com.microsoft.mrtk.input/Experimental/SpatialMouse/Interactor/SpatialMouseInteractorCursorVisual.cs @@ -0,0 +1,172 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +using System.Diagnostics; +using UnityEngine; +using UnityEngine.Rendering; +using UnityEngine.XR.Interaction.Toolkit; + +namespace Microsoft.MixedReality.Toolkit.Input.Experimental +{ + /// + /// The cursor visual for a spatial mouse interactor. This behavior takes care of + /// positioning the cursor and hiding it when the mouse is not in use. + /// + /// + /// This is an experimental feature. This class is early in the cycle, it has + /// been labeled as experimental to indicate that it is still evolving, and + /// subject to change over time. Parts of the MRTK, such as this class, appear + /// to have a lot of value even if the details haven’t fully been fleshed out. + /// For these types of features, we want the community to see them and get + /// value out of them early enough so to provide feedback. + /// + public class SpatialMouseInteractorCursorVisual : BaseReticleVisual + { + /// + /// The ray interactor which this visual represents. + /// + [field: SerializeField, Experimental, Tooltip("The ray interactor which this visual represents.")] + public SpatialMouseInteractor mouseInteractor; + + + [SerializeField] + [Tooltip("The default distance of the reticle (cursor)")] + private float defaultDistance = 1f; + + /// + /// See . + /// + protected virtual void OnEnable() + { + mouseInteractor.selectEntered.AddListener(LocateTargetHitPoint); + + Application.onBeforeRender += OnBeforeRenderCursor; + } + + /// + /// See . + /// + protected virtual void OnDisable() + { + mouseInteractor.selectEntered.RemoveListener(LocateTargetHitPoint); + + Application.onBeforeRender -= OnBeforeRenderCursor; + } + + private Vector3 targetLocalHitPoint; + private Vector3 targetLocalHitNormal; + private Transform hitTargetTransform; + + // reusable lists of the points returned by the XRRayInteractor + Vector3[] rayPositions; + int rayPositionsCount = -1; + + // reusable vectors for determining the raycast hit data + private Vector3 reticlePosition; + private Vector3 reticleNormal; + private int endPositionInLine; + + private void LocateTargetHitPoint(SelectEnterEventArgs args) + { + // If no hit, abort. + if (!mouseInteractor.TryGetCurrentRaycast( + out RaycastHit? raycastHit, + out _, + out UnityEngine.EventSystems.RaycastResult? raycastResult, + out _, + out bool isUIHitClosest)) + { + return; + } + + // Sanity check. + if (rayPositions == null || + rayPositions.Length == 0 || + rayPositionsCount == 0 || + rayPositionsCount > rayPositions.Length) + { + return; + } + + // Record relevant data about the hit point. + if (raycastResult.HasValue && isUIHitClosest) + { + hitTargetTransform = raycastResult.Value.gameObject.transform; + targetLocalHitPoint = hitTargetTransform.InverseTransformPoint(raycastResult.Value.worldPosition); + targetLocalHitNormal = hitTargetTransform.InverseTransformDirection(raycastResult.Value.worldNormal); + } + else if (raycastHit.HasValue) + { + // In the case of affordances/handles, we can stick the ray right on to the handle. + if (args.interactableObject is ISnapInteractable snappable) + { + hitTargetTransform = snappable.HandleTransform; + targetLocalHitPoint = Vector3.zero; + targetLocalHitNormal = Vector3.up; + } + else + { + hitTargetTransform = raycastHit.Value.collider.transform; + targetLocalHitPoint = hitTargetTransform.InverseTransformPoint(raycastHit.Value.point); + targetLocalHitNormal = hitTargetTransform.InverseTransformPoint(raycastHit.Value.normal); + } + } + } + + private void OnBeforeRenderCursor() + { + if (Reticle == null) { return; } + + // Hide the cursor if the mouse isn't in use + if (!mouseInteractor.IsInUse) + { + Reticle.SetActive(false); + return; + } + + // Get all the line sample points + if (!mouseInteractor.GetLinePoints(ref rayPositions, out rayPositionsCount)) + { + return; + } + + // Sanity check. + if (rayPositions == null || + rayPositions.Length == 0 || + rayPositionsCount == 0 || + rayPositionsCount > rayPositions.Length) + { + return; + } + + // If the mouse is selecting an interactable, then position the cursor based on the target transform + if (mouseInteractor.interactablesSelected.Count > 0) + { + reticlePosition = hitTargetTransform.TransformPoint(targetLocalHitPoint); + } + // otherwise, try getting reticlePosition from the ray hit or set it a default distance from the user + else if (!mouseInteractor.TryGetHitInfo(out reticlePosition, out reticleNormal, out endPositionInLine, out bool isValidTarget)) + { + reticlePosition = mouseInteractor.rayOriginTransform.position + mouseInteractor.rayOriginTransform.forward * defaultDistance; + } + + // Mouse cursor should always face the user + reticleNormal = -mouseInteractor.rayOriginTransform.forward; + + // Set the relevant reticle position/normal and ensure it's active. + Reticle.transform.position = reticlePosition; + Reticle.transform.forward = reticleNormal; + + // If the reticle is an IVariableSelectReticle, have the reticle update based on selectedness + if (VariableReticle != null) + { + VariableReticle.UpdateVisuals(new VariableReticleUpdateArgs(mouseInteractor, reticlePosition, reticleNormal)); + } + + if (Reticle.activeSelf == false) + { + Reticle.SetActive(true); + } + } + } +} diff --git a/com.microsoft.mrtk.input/Experimental/SpatialMouse/Interactor/SpatialMouseInteractorCursorVisual.cs.meta b/com.microsoft.mrtk.input/Experimental/SpatialMouse/Interactor/SpatialMouseInteractorCursorVisual.cs.meta new file mode 100644 index 00000000000..e2c63ef3e1e --- /dev/null +++ b/com.microsoft.mrtk.input/Experimental/SpatialMouse/Interactor/SpatialMouseInteractorCursorVisual.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: dd37d3de25ec0844d9ea7e898410084c +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/com.microsoft.mrtk.input/Experimental/SpatialMouse/InteractorVisuals.meta b/com.microsoft.mrtk.input/Experimental/SpatialMouse/InteractorVisuals.meta new file mode 100644 index 00000000000..ba0c8b2272e --- /dev/null +++ b/com.microsoft.mrtk.input/Experimental/SpatialMouse/InteractorVisuals.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 2f176e64b25930f4f91ded07150f6c9c +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/com.microsoft.mrtk.input/Experimental/SpatialMouse/InteractorVisuals/SpatialMouse.meta b/com.microsoft.mrtk.input/Experimental/SpatialMouse/InteractorVisuals/SpatialMouse.meta new file mode 100644 index 00000000000..c389cfccb53 --- /dev/null +++ b/com.microsoft.mrtk.input/Experimental/SpatialMouse/InteractorVisuals/SpatialMouse.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 18fd42bd74096c54d92d47d7888e30af +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/com.microsoft.mrtk.input/Experimental/SpatialMouse/InteractorVisuals/SpatialMouse/MouseCursor.mat b/com.microsoft.mrtk.input/Experimental/SpatialMouse/InteractorVisuals/SpatialMouse/MouseCursor.mat new file mode 100644 index 00000000000..c2debd5b665 --- /dev/null +++ b/com.microsoft.mrtk.input/Experimental/SpatialMouse/InteractorVisuals/SpatialMouse/MouseCursor.mat @@ -0,0 +1,86 @@ +%YAML 1.1 +%TAG !u! tag:unity3d.com,2011: +--- !u!21 &2100000 +Material: + serializedVersion: 6 + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_Name: MouseCursor + m_Shader: {fileID: 4800000, guid: a31d1bdd36df72c4cb3b46eb7b1dcdfb, type: 3} + m_ShaderKeywords: + m_LightmapFlags: 4 + m_EnableInstancingVariants: 0 + m_DoubleSidedGI: 0 + m_CustomRenderQueue: -1 + stringTagMap: {} + disabledShaderPasses: [] + m_SavedProperties: + serializedVersion: 3 + m_TexEnvs: + - _AlphaTex: + m_Texture: {fileID: 0} + m_Scale: {x: 1, y: 1} + m_Offset: {x: 0, y: 0} + - _BumpMap: + m_Texture: {fileID: 0} + m_Scale: {x: 1, y: 1} + m_Offset: {x: 0, y: 0} + - _DetailAlbedoMap: + m_Texture: {fileID: 0} + m_Scale: {x: 1, y: 1} + m_Offset: {x: 0, y: 0} + - _DetailMask: + m_Texture: {fileID: 0} + m_Scale: {x: 1, y: 1} + m_Offset: {x: 0, y: 0} + - _DetailNormalMap: + m_Texture: {fileID: 0} + m_Scale: {x: 1, y: 1} + m_Offset: {x: 0, y: 0} + - _EmissionMap: + m_Texture: {fileID: 0} + m_Scale: {x: 1, y: 1} + m_Offset: {x: 0, y: 0} + - _MainTex: + m_Texture: {fileID: 0} + m_Scale: {x: 1, y: 1} + m_Offset: {x: 0, y: 0} + - _MetallicGlossMap: + m_Texture: {fileID: 0} + m_Scale: {x: 1, y: 1} + m_Offset: {x: 0, y: 0} + - _OcclusionMap: + m_Texture: {fileID: 0} + m_Scale: {x: 1, y: 1} + m_Offset: {x: 0, y: 0} + - _ParallaxMap: + m_Texture: {fileID: 0} + m_Scale: {x: 1, y: 1} + m_Offset: {x: 0, y: 0} + m_Floats: + - PixelSnap: 0 + - _BumpScale: 1 + - _Cutoff: 0.5 + - _DetailNormalMapScale: 1 + - _DstBlend: 0 + - _EnableExternalAlpha: 0 + - _GlossMapScale: 1 + - _Glossiness: 0.5 + - _GlossyReflections: 1 + - _Metallic: 0 + - _Mode: 0 + - _OcclusionStrength: 1 + - _Parallax: 0.02 + - _SmoothnessTextureChannel: 0 + - _SpecularHighlights: 1 + - _SrcBlend: 1 + - _UVSec: 0 + - _ZWrite: 1 + m_Colors: + - _Color: {r: 1, g: 1, b: 1, a: 1} + - _EmissionColor: {r: 0, g: 0, b: 0, a: 1} + - _Flip: {r: 1, g: 1, b: 1, a: 1} + - _RendererColor: {r: 1, g: 1, b: 1, a: 1} + m_BuildTextureStacks: [] diff --git a/com.microsoft.mrtk.input/Experimental/SpatialMouse/InteractorVisuals/SpatialMouse/MouseCursor.mat.meta b/com.microsoft.mrtk.input/Experimental/SpatialMouse/InteractorVisuals/SpatialMouse/MouseCursor.mat.meta new file mode 100644 index 00000000000..fba000ee3b9 --- /dev/null +++ b/com.microsoft.mrtk.input/Experimental/SpatialMouse/InteractorVisuals/SpatialMouse/MouseCursor.mat.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: eeeb890566c5db34cbe5cfd221a67547 +NativeFormatImporter: + externalObjects: {} + mainObjectFileID: 2100000 + userData: + assetBundleName: + assetBundleVariant: diff --git a/com.microsoft.mrtk.input/Experimental/SpatialMouse/InteractorVisuals/SpatialMouse/MouseCursor.png b/com.microsoft.mrtk.input/Experimental/SpatialMouse/InteractorVisuals/SpatialMouse/MouseCursor.png new file mode 100644 index 00000000000..4d088368003 Binary files /dev/null and b/com.microsoft.mrtk.input/Experimental/SpatialMouse/InteractorVisuals/SpatialMouse/MouseCursor.png differ diff --git a/com.microsoft.mrtk.input/Experimental/SpatialMouse/InteractorVisuals/SpatialMouse/MouseCursor.png.meta b/com.microsoft.mrtk.input/Experimental/SpatialMouse/InteractorVisuals/SpatialMouse/MouseCursor.png.meta new file mode 100644 index 00000000000..802dcd06bc0 --- /dev/null +++ b/com.microsoft.mrtk.input/Experimental/SpatialMouse/InteractorVisuals/SpatialMouse/MouseCursor.png.meta @@ -0,0 +1,132 @@ +fileFormatVersion: 2 +guid: b221c57e5830f5f4c94b4c050eacdd7b +TextureImporter: + internalIDToNameTable: [] + externalObjects: {} + serializedVersion: 11 + mipmaps: + mipMapMode: 0 + enableMipMap: 0 + sRGBTexture: 1 + linearTexture: 0 + fadeOut: 0 + borderMipMap: 0 + mipMapsPreserveCoverage: 0 + alphaTestReferenceValue: 0.5 + mipMapFadeDistanceStart: 1 + mipMapFadeDistanceEnd: 3 + bumpmap: + convertToNormalMap: 0 + externalNormalMap: 0 + heightScale: 0.25 + normalMapFilter: 0 + isReadable: 0 + streamingMipmaps: 0 + streamingMipmapsPriority: 0 + vTOnly: 0 + grayScaleToAlpha: 0 + generateCubemap: 6 + cubemapConvolution: 0 + seamlessCubemap: 0 + textureFormat: 1 + maxTextureSize: 2048 + textureSettings: + serializedVersion: 2 + filterMode: 1 + aniso: 1 + mipBias: 0 + wrapU: 1 + wrapV: 1 + wrapW: 0 + nPOTScale: 0 + lightmap: 0 + compressionQuality: 50 + spriteMode: 1 + spriteExtrude: 1 + spriteMeshType: 1 + alignment: 0 + spritePivot: {x: 0.5, y: 0.5} + spritePixelsToUnits: 100 + spriteBorder: {x: 0, y: 0, z: 0, w: 0} + spriteGenerateFallbackPhysicsShape: 1 + alphaUsage: 1 + alphaIsTransparency: 1 + spriteTessellationDetail: -1 + textureType: 8 + textureShape: 1 + singleChannelComponent: 0 + flipbookRows: 1 + flipbookColumns: 1 + maxTextureSizeSet: 0 + compressionQualitySet: 0 + textureFormatSet: 0 + ignorePngGamma: 0 + applyGammaDecoding: 0 + platformSettings: + - serializedVersion: 3 + buildTarget: DefaultTexturePlatform + maxTextureSize: 2048 + resizeAlgorithm: 0 + textureFormat: -1 + textureCompression: 1 + compressionQuality: 50 + crunchedCompression: 0 + allowsAlphaSplitting: 0 + overridden: 0 + androidETC2FallbackOverride: 0 + forceMaximumCompressionQuality_BC6H_BC7: 0 + - serializedVersion: 3 + buildTarget: Standalone + maxTextureSize: 2048 + resizeAlgorithm: 0 + textureFormat: -1 + textureCompression: 1 + compressionQuality: 50 + crunchedCompression: 0 + allowsAlphaSplitting: 0 + overridden: 0 + androidETC2FallbackOverride: 0 + forceMaximumCompressionQuality_BC6H_BC7: 0 + - serializedVersion: 3 + buildTarget: Windows Store Apps + maxTextureSize: 2048 + resizeAlgorithm: 0 + textureFormat: -1 + textureCompression: 1 + compressionQuality: 50 + crunchedCompression: 0 + allowsAlphaSplitting: 0 + overridden: 0 + androidETC2FallbackOverride: 0 + forceMaximumCompressionQuality_BC6H_BC7: 0 + - serializedVersion: 3 + buildTarget: Android + maxTextureSize: 2048 + resizeAlgorithm: 0 + textureFormat: -1 + textureCompression: 1 + compressionQuality: 50 + crunchedCompression: 0 + allowsAlphaSplitting: 0 + overridden: 0 + androidETC2FallbackOverride: 0 + forceMaximumCompressionQuality_BC6H_BC7: 0 + spriteSheet: + serializedVersion: 2 + sprites: [] + outline: [] + physicsShape: [] + bones: [] + spriteID: 5e97eb03825dee720800000000000000 + internalID: 0 + vertices: [] + indices: + edges: [] + weights: [] + secondaryTextures: [] + spritePackingTag: + pSDRemoveMatte: 0 + pSDShowRemoveMatteOption: 0 + userData: + assetBundleName: + assetBundleVariant: diff --git a/com.microsoft.mrtk.input/Experimental/SpatialMouse/InteractorVisuals/SpatialMouse/MouseCursor.prefab b/com.microsoft.mrtk.input/Experimental/SpatialMouse/InteractorVisuals/SpatialMouse/MouseCursor.prefab new file mode 100644 index 00000000000..efaa22e65a9 --- /dev/null +++ b/com.microsoft.mrtk.input/Experimental/SpatialMouse/InteractorVisuals/SpatialMouse/MouseCursor.prefab @@ -0,0 +1,118 @@ +%YAML 1.1 +%TAG !u! tag:unity3d.com,2011: +--- !u!1 &8113086299156719581 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 8113086299156719580} + m_Layer: 0 + m_Name: MouseCursor + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!4 &8113086299156719580 +Transform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 8113086299156719581} + m_LocalRotation: {x: -0, y: -0, z: -0, w: 1} + m_LocalPosition: {x: 0, y: 0, z: 0} + m_LocalScale: {x: 1, y: 1, z: 1} + m_ConstrainProportionsScale: 0 + m_Children: + - {fileID: 8113086300167495069} + m_Father: {fileID: 0} + m_RootOrder: 0 + m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} +--- !u!1 &8113086300167495066 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 8113086300167495069} + - component: {fileID: 8113086300167495068} + m_Layer: 0 + m_Name: CursorVisual + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!4 &8113086300167495069 +Transform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 8113086300167495066} + m_LocalRotation: {x: 0, y: 1, z: 0, w: 0} + m_LocalPosition: {x: -0.005, y: -0.0062, z: 0} + m_LocalScale: {x: 0.024, y: 0.024, z: 0.048} + m_ConstrainProportionsScale: 0 + m_Children: [] + m_Father: {fileID: 8113086299156719580} + m_RootOrder: 0 + m_LocalEulerAnglesHint: {x: 0, y: 180, z: 0} +--- !u!212 &8113086300167495068 +SpriteRenderer: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 8113086300167495066} + m_Enabled: 1 + m_CastShadows: 0 + m_ReceiveShadows: 0 + m_DynamicOccludee: 1 + m_StaticShadowCaster: 0 + m_MotionVectors: 1 + m_LightProbeUsage: 1 + m_ReflectionProbeUsage: 1 + m_RayTracingMode: 0 + m_RayTraceProcedural: 0 + m_RenderingLayerMask: 1 + m_RendererPriority: 0 + m_Materials: + - {fileID: 2100000, guid: eeeb890566c5db34cbe5cfd221a67547, type: 2} + m_StaticBatchInfo: + firstSubMesh: 0 + subMeshCount: 0 + m_StaticBatchRoot: {fileID: 0} + m_ProbeAnchor: {fileID: 0} + m_LightProbeVolumeOverride: {fileID: 0} + m_ScaleInLightmap: 1 + m_ReceiveGI: 1 + m_PreserveUVs: 0 + m_IgnoreNormalsForChartDetection: 0 + m_ImportantGI: 0 + m_StitchLightmapSeams: 1 + m_SelectedEditorRenderState: 0 + m_MinimumChartSize: 4 + m_AutoUVMaxDistance: 0.5 + m_AutoUVMaxAngle: 89 + m_LightmapParameters: {fileID: 0} + m_SortingLayerID: 0 + m_SortingLayer: 0 + m_SortingOrder: 0 + m_Sprite: {fileID: 21300000, guid: b221c57e5830f5f4c94b4c050eacdd7b, type: 3} + m_Color: {r: 1, g: 1, b: 1, a: 1} + m_FlipX: 0 + m_FlipY: 0 + m_DrawMode: 0 + m_Size: {x: 0.64, y: 0.64} + m_AdaptiveModeThreshold: 0.5 + m_SpriteTileMode: 0 + m_WasSpriteAssigned: 1 + m_MaskInteraction: 0 + m_SpriteSortPoint: 0 diff --git a/com.microsoft.mrtk.input/Experimental/SpatialMouse/InteractorVisuals/SpatialMouse/MouseCursor.prefab.meta b/com.microsoft.mrtk.input/Experimental/SpatialMouse/InteractorVisuals/SpatialMouse/MouseCursor.prefab.meta new file mode 100644 index 00000000000..977c693f31a --- /dev/null +++ b/com.microsoft.mrtk.input/Experimental/SpatialMouse/InteractorVisuals/SpatialMouse/MouseCursor.prefab.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: f7c9215713002d34a9107dd69004e749 +PrefabImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/com.microsoft.mrtk.input/Experimental/SpatialMouse/InteractorVisuals/SpatialMouse/MouseCursor.shader b/com.microsoft.mrtk.input/Experimental/SpatialMouse/InteractorVisuals/SpatialMouse/MouseCursor.shader new file mode 100644 index 00000000000..fe7fa2dea38 --- /dev/null +++ b/com.microsoft.mrtk.input/Experimental/SpatialMouse/InteractorVisuals/SpatialMouse/MouseCursor.shader @@ -0,0 +1,48 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +Shader "Mixed Reality Toolkit/MouseCursor" +{ + Properties + { + [PerRendererData] _MainTex ("Sprite Texture", 2D) = "white" {} + _Color ("Tint", Color) = (1,1,1,1) + [MaterialToggle] PixelSnap ("Pixel snap", Float) = 0 + [HideInInspector] _RendererColor ("RendererColor", Color) = (1,1,1,1) + [HideInInspector] _Flip ("Flip", Vector) = (1,1,1,1) + [PerRendererData] _AlphaTex ("External Alpha", 2D) = "white" {} + [PerRendererData] _EnableExternalAlpha ("Enable External Alpha", Float) = 0 + } + + SubShader + { + ZWrite Off + ZTest Always + Tags + { + "Queue"="Transparent" + "IgnoreProjector"="True" + "RenderType"="Overlay" + "PreviewType"="Plane" + "CanUseSpriteAtlas"="True" + } + + Cull Off + Lighting Off + ZWrite Off + Blend One OneMinusSrcAlpha + + Pass + { + CGPROGRAM + #pragma vertex SpriteVert + #pragma fragment SpriteFrag + #pragma target 2.0 + #pragma multi_compile_instancing + #pragma multi_compile _ PIXELSNAP_ON + #pragma multi_compile _ ETC1_EXTERNAL_ALPHA + #include "UnitySprites.cginc" + ENDCG + } + } +} \ No newline at end of file diff --git a/com.microsoft.mrtk.input/Experimental/SpatialMouse/InteractorVisuals/SpatialMouse/MouseCursor.shader.meta b/com.microsoft.mrtk.input/Experimental/SpatialMouse/InteractorVisuals/SpatialMouse/MouseCursor.shader.meta new file mode 100644 index 00000000000..691560b162c --- /dev/null +++ b/com.microsoft.mrtk.input/Experimental/SpatialMouse/InteractorVisuals/SpatialMouse/MouseCursor.shader.meta @@ -0,0 +1,10 @@ +fileFormatVersion: 2 +guid: a31d1bdd36df72c4cb3b46eb7b1dcdfb +ShaderImporter: + externalObjects: {} + defaultTextures: [] + nonModifiableTextures: [] + preprocessorOverride: 0 + userData: + assetBundleName: + assetBundleVariant: diff --git a/com.microsoft.mrtk.input/Assets/Prefabs/MRTK Gaze Controller.prefab b/com.microsoft.mrtk.input/Experimental/SpatialMouse/MRTK Gaze Controller.prefab similarity index 100% rename from com.microsoft.mrtk.input/Assets/Prefabs/MRTK Gaze Controller.prefab rename to com.microsoft.mrtk.input/Experimental/SpatialMouse/MRTK Gaze Controller.prefab diff --git a/com.microsoft.mrtk.input/Assets/Prefabs/MRTK Gaze Controller.prefab.meta b/com.microsoft.mrtk.input/Experimental/SpatialMouse/MRTK Gaze Controller.prefab.meta similarity index 100% rename from com.microsoft.mrtk.input/Assets/Prefabs/MRTK Gaze Controller.prefab.meta rename to com.microsoft.mrtk.input/Experimental/SpatialMouse/MRTK Gaze Controller.prefab.meta diff --git a/com.microsoft.mrtk.input/Tests/Runtime/SpatialMouseInputTests.cs b/com.microsoft.mrtk.input/Tests/Runtime/SpatialMouseInputTests.cs new file mode 100644 index 00000000000..d70bf2e430c --- /dev/null +++ b/com.microsoft.mrtk.input/Tests/Runtime/SpatialMouseInputTests.cs @@ -0,0 +1,154 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + + +using Microsoft.MixedReality.Toolkit.Input.Experimental; +using Microsoft.MixedReality.Toolkit.Core.Tests; +using NUnit.Framework; +using System.Collections; +using System.Collections.Generic; +using UnityEngine; +using UnityEngine.InputSystem; +using UnityEngine.InputSystem.Controls; +using UnityEngine.InputSystem.LowLevel; +using UnityEngine.TestTools; +using UnityEngine.XR; +using UnityEngine.XR.Interaction.Toolkit; + +#if UNITY_EDITOR +using UnityEditor; +#endif + +namespace Microsoft.MixedReality.Toolkit.Input.Tests +{ + /// + /// Basic tests for verifying mouse interactions. + /// + public class SpatialMouseInputTests : BaseRuntimeInputTests + { + private const string SpatialMouseControllerPrefabGuid = "dc525621b8522034e867ed2799129315"; + private static readonly string SpatialMouseControllerPrefabPath = AssetDatabase.GUIDToAssetPath(SpatialMouseControllerPrefabGuid); + + private static GameObject controllerReference; + + /// + /// Very basic test of SpatialMouseInteractor clicking an Interactable. + /// + [UnityTest] + public IEnumerator SpatialMouseInteractorSmokeTest() + { + var mouse = InputSystem.AddDevice(); + + GameObject cube = GameObject.CreatePrimitive(PrimitiveType.Cube); + cube.AddComponent(); + cube.transform.position = new Vector3(0, 0, 0.4f); + cube.transform.localScale = Vector3.one * 0.1f; + + // For this test, we won't use poke selection. + cube.GetComponent().DisableInteractorType(typeof(PokeInteractor)); + + StatefulInteractable firstCubeInteractable = cube.GetComponent(); + + // Verify that the mouse is hidden by default. + var notHoveringMouseInteractor = firstCubeInteractable.HoveringRayInteractors.Find( + (i) => i.GetType() == typeof(SpatialMouseInteractor)); + + Assert.IsNull(notHoveringMouseInteractor, + "SpatialMouseInteractor is hovering without initial input."); + + // Inject mouse deltas. + using (StateEvent.From(mouse, out var eventPtr)) + { + ((DeltaControl)mouse["delta"]).WriteValueIntoEvent(new Vector2(1, 1), eventPtr); + InputSystem.QueueEvent(eventPtr); + InputSystem.Update(); + + ((DeltaControl)mouse["delta"]).WriteValueIntoEvent(new Vector2(-1, -1), eventPtr); + InputSystem.QueueEvent(eventPtr); + InputSystem.Update(); + } + + yield return RuntimeTestUtilities.WaitForUpdates(); + + // Verify that the mouse is hovering the cube. + var hoveringMouseInteractor = firstCubeInteractable.HoveringRayInteractors.Find( + (i) => i.GetType() == typeof(SpatialMouseInteractor)); + + Assert.IsNotNull(hoveringMouseInteractor, + "StatefulInteractable did not get Hovered by SpatialMouseInteractor."); + + // Inject mouse down. + using (StateEvent.From(mouse, out var eventPtr)) + { + ((ButtonControl)mouse["press"]).WriteValueIntoEvent(1f, eventPtr); + InputSystem.QueueEvent(eventPtr); + InputSystem.Update(); + } + + yield return RuntimeTestUtilities.WaitForUpdates(); + + // Verify that the mouse is selecting the cube. + var selectingMouseInteractor = firstCubeInteractable.interactorsSelecting.Find( + (i) => i.GetType() == typeof(SpatialMouseInteractor)); + + Assert.IsNotNull(selectingMouseInteractor, + "StatefulInteractable did not get Selected by SpatialMouseInteractor."); + + // Inject mouse up. + using (StateEvent.From(mouse, out var eventPtr)) + { + ((ButtonControl)mouse["press"]).WriteValueIntoEvent(0f, eventPtr); + InputSystem.QueueEvent(eventPtr); + InputSystem.Update(); + } + + yield return RuntimeTestUtilities.WaitForUpdates(); + + // Verify that the mouse is no longer selecting the cube. + var notSelectingMouseInteractor = firstCubeInteractable.interactorsSelecting.Find( + (i) => i.GetType() == typeof(SpatialMouseInteractor)); + + Assert.IsNull(notSelectingMouseInteractor, + "StatefulInteractable did not get Unselected by SpatialMouseInteractor."); + + yield return null; + } + + /// + /// Creates and returns the Spatial Mouse Controller. + /// + public static GameObject InstantiateSpatialMouseController() + { + Object prefab = AssetDatabase.LoadAssetAtPath(SpatialMouseControllerPrefabPath, typeof(Object)); + controllerReference = Object.Instantiate(prefab) as GameObject; + return controllerReference; + } + + /// + /// Destroys the Spatial Mouse Controller. + /// + public static void TeardownSpatialMouseController() + { + if (Application.isPlaying) + { + UnityEngine.Object.Destroy(controllerReference); + } + } + + public override IEnumerator Setup() + { + yield return base.Setup(); + + InstantiateSpatialMouseController(); + + yield return null; + } + + public override IEnumerator TearDown() + { + TeardownSpatialMouseController(); + + yield return base.TearDown(); + } + } +} diff --git a/com.microsoft.mrtk.input/Tests/Runtime/SpatialMouseInputTests.cs.meta b/com.microsoft.mrtk.input/Tests/Runtime/SpatialMouseInputTests.cs.meta new file mode 100644 index 00000000000..f4530865fad --- /dev/null +++ b/com.microsoft.mrtk.input/Tests/Runtime/SpatialMouseInputTests.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: a2724631c3cbde74682877c983f9cc59 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: