AR Design
UBC EML collab with UBC SALA - visualizing IoT data in AR
MotionControllerVisualizer.cs
Go to the documentation of this file.
1 // Copyright (c) Microsoft Corporation. All rights reserved.
2 // Licensed under the MIT License. See LICENSE in the project root for license information.
3 
4 using System;
5 using System.Collections.Generic;
6 using UnityEngine;
7 
8 #if UNITY_EDITOR_WIN
9 using System.Runtime.InteropServices;
10 #endif
11 
12 #if UNITY_WSA && UNITY_2017_2_OR_NEWER
13 using System.Collections;
14 using System.IO;
15 using UnityEngine.XR.WSA.Input;
16 using UnityGLTF;
17 
18 #if !UNITY_EDITOR
19 using Windows.Foundation;
20 using Windows.Storage.Streams;
21 #endif
22 #endif
23 
24 namespace HoloToolkit.Unity.InputModule
25 {
31  public class MotionControllerVisualizer : Singleton<MotionControllerVisualizer>
32  {
33  [Tooltip("This setting will be used to determine if the model, override or otherwise, should attempt to be animated based on the user's input.")]
34  public bool AnimateControllerModel = true;
35 
36  [Tooltip("This setting will be used to determine if the model should always be the left alternate. If false, the platform controller models will be preferred, only if they can't be loaded will the alternate be used. Otherwise, it will always use the alternate model.")]
37  public bool AlwaysUseAlternateLeftModel = false;
38  [Tooltip("This setting will be used to determine if the model should always be the right alternate. If false, the platform controller models will be preferred, only if they can't be loaded will the alternate be used. Otherwise, it will always use the alternate model.")]
39  public bool AlwaysUseAlternateRightModel = false;
40 
41  [Tooltip("Use a model with the tip in the positive Z direction and the front face in the positive Y direction. To override the platform left controller model set AlwaysUseAlternateModel to true; otherwise this will be the default if the model can't be found.")]
42  [SerializeField]
43  protected GameObject AlternateLeftController;
44  [Tooltip("Use a model with the tip in the positive Z direction and the front face in the positive Y direction. To override the platform right controller model set AlwaysUseAlternateModel to true; otherwise this will be the default if the model can't be found.")]
45  [SerializeField]
46  protected GameObject AlternateRightController;
47  [Tooltip("Use this to override the indicator used to show the user's touch location on the touchpad. Default is a sphere.")]
48  [SerializeField]
49  protected GameObject TouchpadTouchedOverride;
50 
51  [Tooltip("This material will be used on the loaded glTF controller model. This does not affect the above overrides.")]
52  [SerializeField]
53  protected UnityEngine.Material GLTFMaterial;
54 
55 #if UNITY_WSA && UNITY_2017_2_OR_NEWER
56  // This will be used to keep track of our controllers, indexed by their unique source ID.
57  private Dictionary<string, MotionControllerInfo> controllerDictionary = new Dictionary<string, MotionControllerInfo>(0);
58  private List<string> loadingControllers = new List<string>();
59 
60  private MotionControllerInfo leftControllerModel;
61  private MotionControllerInfo rightControllerModel;
62 
63  public event Action<MotionControllerInfo> OnControllerModelLoaded;
64  public event Action<MotionControllerInfo> OnControllerModelUnloaded;
65 #endif
66 
67 #if UNITY_EDITOR_WIN
68  [DllImport("EditorMotionController")]
69  private static extern bool TryGetMotionControllerModel([In] uint controllerId, [Out] out uint outputSize, [Out] out IntPtr outputBuffer);
70 #endif
71 
72  protected override void Awake()
73  {
74  base.Awake();
75 
76 #if UNITY_WSA && UNITY_2017_2_OR_NEWER
77  foreach (var sourceState in InteractionManager.GetCurrentReading())
78  {
79  if (sourceState.source.kind == InteractionSourceKind.Controller)
80  {
81  StartTrackingController(sourceState.source);
82  }
83  }
84 
85  Application.onBeforeRender += Application_onBeforeRender;
86 
87  if (GLTFMaterial == null)
88  {
89  if (AlternateLeftController == null && AlternateRightController == null)
90  {
91  Debug.Log("If using glTF, please specify a material on " + name + ". Otherwise, please specify controller alternates.");
92  }
93  else if (AlternateLeftController == null || AlternateRightController == null)
94  {
95  Debug.Log("Only one alternate is specified, and no material is specified for the glTF model. Please set the material or the " + ((AlternateLeftController == null) ? "left" : "right") + " controller alternate on " + name + ".");
96  }
97  }
98 
99  InteractionManager.InteractionSourceDetected += InteractionManager_InteractionSourceDetected;
100  InteractionManager.InteractionSourceLost += InteractionManager_InteractionSourceLost;
101 #endif
102  }
103 
104  private void Update()
105  {
106  // NOTE: The controller's state is being updated here in order to provide a good position and rotation
107  // for any child GameObjects that might want to raycast or otherwise reason about their location in the world.
108  UpdateControllerState();
109  }
110 
111  protected override void OnDestroy()
112  {
113  base.OnDestroy();
114 
115 #if UNITY_WSA && UNITY_2017_2_OR_NEWER
116  InteractionManager.InteractionSourceDetected -= InteractionManager_InteractionSourceDetected;
117  InteractionManager.InteractionSourceLost -= InteractionManager_InteractionSourceLost;
118  Application.onBeforeRender -= Application_onBeforeRender;
119 #endif
120  }
121 
122  private void Application_onBeforeRender()
123  {
124  // NOTE: This work is being done here to present the most correct rendered location of the controller each frame.
125  // Any app logic depending on the controller state should happen in Update() or using InteractionManager's events.
126  UpdateControllerState();
127  }
128 
129  private void UpdateControllerState()
130  {
131 #if UNITY_WSA && UNITY_2017_2_OR_NEWER
132  foreach (var sourceState in InteractionManager.GetCurrentReading())
133  {
134  MotionControllerInfo currentController;
135  if (sourceState.source.kind == InteractionSourceKind.Controller && controllerDictionary.TryGetValue(GenerateKey(sourceState.source), out currentController))
136  {
137  if (AnimateControllerModel)
138  {
139  currentController.AnimateSelect(sourceState.selectPressedAmount);
140 
141  if (sourceState.source.supportsGrasp)
142  {
143  currentController.AnimateGrasp(sourceState.grasped);
144  }
145 
146  if (sourceState.source.supportsMenu)
147  {
148  currentController.AnimateMenu(sourceState.menuPressed);
149  }
150 
151  if (sourceState.source.supportsThumbstick)
152  {
153  currentController.AnimateThumbstick(sourceState.thumbstickPressed, sourceState.thumbstickPosition);
154  }
155 
156  if (sourceState.source.supportsTouchpad)
157  {
158  currentController.AnimateTouchpad(sourceState.touchpadPressed, sourceState.touchpadTouched, sourceState.touchpadPosition);
159  }
160  }
161 
162  Vector3 newPosition;
163  if (sourceState.sourcePose.TryGetPosition(out newPosition, InteractionSourceNode.Grip) && ValidPosition(newPosition))
164  {
165  currentController.ControllerParent.transform.localPosition = newPosition;
166  }
167 
168  Quaternion newRotation;
169  if (sourceState.sourcePose.TryGetRotation(out newRotation, InteractionSourceNode.Grip) && ValidRotation(newRotation))
170  {
171  currentController.ControllerParent.transform.localRotation = newRotation;
172  }
173  }
174  }
175 #endif
176  }
177 
178  private bool ValidRotation(Quaternion newRotation)
179  {
180  return !float.IsNaN(newRotation.x) && !float.IsNaN(newRotation.y) && !float.IsNaN(newRotation.z) && !float.IsNaN(newRotation.w) &&
181  !float.IsInfinity(newRotation.x) && !float.IsInfinity(newRotation.y) && !float.IsInfinity(newRotation.z) && !float.IsInfinity(newRotation.w);
182  }
183 
184  private bool ValidPosition(Vector3 newPosition)
185  {
186  return !float.IsNaN(newPosition.x) && !float.IsNaN(newPosition.y) && !float.IsNaN(newPosition.z) &&
187  !float.IsInfinity(newPosition.x) && !float.IsInfinity(newPosition.y) && !float.IsInfinity(newPosition.z);
188  }
189 
190 #if UNITY_WSA && UNITY_2017_2_OR_NEWER
191  private void InteractionManager_InteractionSourceDetected(InteractionSourceDetectedEventArgs obj)
192  {
193  StartTrackingController(obj.state.source);
194  }
195 
196  private void StartTrackingController(InteractionSource source)
197  {
198  string key = GenerateKey(source);
199 
200  MotionControllerInfo controllerInfo;
201  if (source.kind == InteractionSourceKind.Controller)
202  {
203  if (!controllerDictionary.ContainsKey(key) && !loadingControllers.Contains(key))
204  {
205  StartCoroutine(LoadControllerModel(source));
206  }
207  else if (controllerDictionary.TryGetValue(key, out controllerInfo))
208  {
209  if (controllerInfo.Handedness == InteractionSourceHandedness.Left)
210  {
211  leftControllerModel = controllerInfo;
212  }
213  else if (controllerInfo.Handedness == InteractionSourceHandedness.Right)
214  {
215  rightControllerModel = controllerInfo;
216  }
217 
218  controllerInfo.ControllerParent.SetActive(true);
219 
220  if (OnControllerModelLoaded != null)
221  {
222  OnControllerModelLoaded(controllerInfo);
223  }
224  }
225  }
226  }
227 
233  private void InteractionManager_InteractionSourceLost(InteractionSourceLostEventArgs obj)
234  {
235  InteractionSource source = obj.state.source;
236  if (source.kind == InteractionSourceKind.Controller)
237  {
238  MotionControllerInfo controllerInfo;
239  if (controllerDictionary != null && controllerDictionary.TryGetValue(GenerateKey(source), out controllerInfo))
240  {
241  if (OnControllerModelUnloaded != null)
242  {
243  OnControllerModelUnloaded(controllerInfo);
244  }
245 
246  if (controllerInfo.Handedness == InteractionSourceHandedness.Left)
247  {
248  leftControllerModel = null;
249  }
250  else if (controllerInfo.Handedness == InteractionSourceHandedness.Right)
251  {
252  rightControllerModel = null;
253  }
254 
255  controllerInfo.ControllerParent.SetActive(false);
256  }
257  }
258  }
259 
260  private IEnumerator LoadControllerModel(InteractionSource source)
261  {
262  loadingControllers.Add(GenerateKey(source));
263 
264  if (AlwaysUseAlternateLeftModel && source.handedness == InteractionSourceHandedness.Left)
265  {
266  if (AlternateLeftController == null)
267  {
268  Debug.LogWarning("Always use the alternate left model is set on " + name + ", but the alternate left controller model was not specified.");
269  yield return LoadSourceControllerModel(source);
270  }
271  else
272  {
273  LoadAlternateControllerModel(source);
274  }
275  }
276  else if (AlwaysUseAlternateRightModel && source.handedness == InteractionSourceHandedness.Right)
277  {
278  if (AlternateRightController == null)
279  {
280  Debug.LogWarning("Always use the alternate right model is set on " + name + ", but the alternate right controller model was not specified.");
281  yield return LoadSourceControllerModel(source);
282  }
283  else
284  {
285  LoadAlternateControllerModel(source);
286  }
287  }
288  else
289  {
290  yield return LoadSourceControllerModel(source);
291  }
292  }
293 
294  private IEnumerator LoadSourceControllerModel(InteractionSource source)
295  {
296  byte[] fileBytes;
297  GameObject controllerModelGameObject;
298 
299  if (GLTFMaterial == null)
300  {
301  Debug.Log("If using glTF, please specify a material on " + name + ".");
302  yield break;
303  }
304 
305 #if !UNITY_EDITOR
306  // This API returns the appropriate glTF file according to the motion controller you're currently using, if supported.
307  IAsyncOperation<IRandomAccessStreamWithContentType> modelTask = source.TryGetRenderableModelAsync();
308 
309  if (modelTask == null)
310  {
311  Debug.Log("Model task is null; loading alternate.");
312  LoadAlternateControllerModel(source);
313  yield break;
314  }
315 
316  while (modelTask.Status == AsyncStatus.Started)
317  {
318  yield return null;
319  }
320 
321  IRandomAccessStreamWithContentType modelStream = modelTask.GetResults();
322 
323  if (modelStream == null)
324  {
325  Debug.Log("Model stream is null; loading alternate.");
326  LoadAlternateControllerModel(source);
327  yield break;
328  }
329 
330  if (modelStream.Size == 0)
331  {
332  Debug.Log("Model stream is empty; loading alternate.");
333  LoadAlternateControllerModel(source);
334  yield break;
335  }
336 
337  fileBytes = new byte[modelStream.Size];
338 
339  using (DataReader reader = new DataReader(modelStream))
340  {
341  DataReaderLoadOperation loadModelOp = reader.LoadAsync((uint)modelStream.Size);
342 
343  while (loadModelOp.Status == AsyncStatus.Started)
344  {
345  yield return null;
346  }
347 
348  reader.ReadBytes(fileBytes);
349  }
350 #else
351  IntPtr controllerModel = new IntPtr();
352  uint outputSize = 0;
353 
354  if (TryGetMotionControllerModel(source.id, out outputSize, out controllerModel))
355  {
356  fileBytes = new byte[Convert.ToInt32(outputSize)];
357 
358  Marshal.Copy(controllerModel, fileBytes, 0, Convert.ToInt32(outputSize));
359  }
360  else
361  {
362  Debug.Log("Unable to load controller models; loading alternate.");
363  LoadAlternateControllerModel(source);
364  yield break;
365  }
366 #endif
367 
368  controllerModelGameObject = new GameObject { name = "glTFController" };
369  controllerModelGameObject.transform.Rotate(0, 180, 0);
370 
371  var sceneImporter = new GLTFSceneImporter(
372  "",
373  new MemoryStream(fileBytes, 0, fileBytes.Length, false, true),
374  controllerModelGameObject.transform
375  );
376 
377  sceneImporter.SetShaderForMaterialType(GLTFSceneImporter.MaterialType.PbrMetallicRoughness, GLTFMaterial.shader);
378  sceneImporter.SetShaderForMaterialType(GLTFSceneImporter.MaterialType.KHR_materials_pbrSpecularGlossiness, GLTFMaterial.shader);
379  sceneImporter.SetShaderForMaterialType(GLTFSceneImporter.MaterialType.CommonConstant, GLTFMaterial.shader);
380 
381  yield return sceneImporter.Load();
382 
383  FinishControllerSetup(controllerModelGameObject, source.handedness, GenerateKey(source));
384  }
385 
386  private void LoadAlternateControllerModel(InteractionSource source)
387  {
388  GameObject controllerModelGameObject;
389  if (source.handedness == InteractionSourceHandedness.Left && AlternateLeftController != null)
390  {
391  controllerModelGameObject = Instantiate(AlternateLeftController);
392  }
393  else if (source.handedness == InteractionSourceHandedness.Right && AlternateRightController != null)
394  {
395  controllerModelGameObject = Instantiate(AlternateRightController);
396  }
397  else
398  {
399  loadingControllers.Remove(GenerateKey(source));
400  return;
401  }
402 
403  FinishControllerSetup(controllerModelGameObject, source.handedness, GenerateKey(source));
404  }
405 
406  private string GenerateKey(InteractionSource source)
407  {
408  return source.vendorId + "/" + source.productId + "/" + source.productVersion + "/" + source.handedness;
409  }
410 
411  private void FinishControllerSetup(GameObject controllerModelGameObject, InteractionSourceHandedness handedness, string dictionaryKey)
412  {
413  var parentGameObject = new GameObject
414  {
415  name = handedness + "Controller"
416  };
417 
418  parentGameObject.transform.parent = transform;
419  controllerModelGameObject.transform.parent = parentGameObject.transform;
420 
421  var newControllerInfo = new MotionControllerInfo(parentGameObject, handedness);
422 
423  newControllerInfo.LoadInfo(controllerModelGameObject.GetComponentsInChildren<Transform>());
424 
425  if (handedness == InteractionSourceHandedness.Left)
426  {
427  leftControllerModel = newControllerInfo;
428  }
429  else if (handedness == InteractionSourceHandedness.Right)
430  {
431  rightControllerModel = newControllerInfo;
432  }
433 
434  if (OnControllerModelLoaded != null)
435  {
436  OnControllerModelLoaded(newControllerInfo);
437  }
438 
439  loadingControllers.Remove(dictionaryKey);
440  controllerDictionary.Add(dictionaryKey, newControllerInfo);
441  }
442 
443  public bool TryGetControllerModel(InteractionSourceHandedness handedness, out MotionControllerInfo controller)
444  {
445  if (handedness == InteractionSourceHandedness.Left && leftControllerModel != null)
446  {
447  controller = leftControllerModel;
448  return true;
449  }
450  else if (handedness == InteractionSourceHandedness.Right && rightControllerModel != null)
451  {
452  controller = rightControllerModel;
453  return true;
454  }
455  else
456  {
457  controller = null;
458  return false;
459  }
460  }
461 #endif
462 
463  public GameObject SpawnTouchpadVisualizer(Transform parentTransform)
464  {
465  GameObject touchVisualizer;
466  if (TouchpadTouchedOverride != null)
467  {
468  touchVisualizer = Instantiate(TouchpadTouchedOverride);
469  }
470  else
471  {
472  touchVisualizer = GameObject.CreatePrimitive(PrimitiveType.Sphere);
473  touchVisualizer.transform.localScale = new Vector3(0.0025f, 0.0025f, 0.0025f);
474  touchVisualizer.GetComponent<Renderer>().sharedMaterial = GLTFMaterial;
475  }
476 
477  Destroy(touchVisualizer.GetComponent<Collider>());
478  touchVisualizer.transform.parent = parentTransform;
479  touchVisualizer.transform.localPosition = Vector3.zero;
480  touchVisualizer.transform.localRotation = Quaternion.identity;
481  touchVisualizer.SetActive(false);
482  return touchVisualizer;
483  }
484  }
485 }
override void OnDestroy()
Base OnDestroy method that destroys the Singleton&#39;s unique instance. Called by Unity when destroying ...
This script spawns a specific GameObject when a controller is detected and animates the controller po...
This script keeps track of the GameObjects for each button on the controller. It also keeps track of ...
override void Awake()
Base Awake method that sets the Singleton&#39;s unique instance. Called by Unity when initializing a Mono...
Singleton behaviour class, used for components that should only have one instance.
Definition: Singleton.cs:14