AR Design
UBC EML collab with UBC SALA - visualizing IoT data in AR
TapToPlace.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.Collections.Generic;
5 using UnityEngine;
7 
8 namespace HoloToolkit.Unity.SpatialMapping
9 {
17  [RequireComponent(typeof(Collider))]
18  [RequireComponent(typeof(Interpolator))]
19  public class TapToPlace : MonoBehaviour, IInputClickHandler
20  {
21  [Tooltip("Distance from camera to keep the object while placing it.")]
22  public float DefaultGazeDistance = 2.0f;
23 
24  [Tooltip("Place parent on tap instead of current game object.")]
25  public bool PlaceParentOnTap;
26 
27  [Tooltip("Specify the parent game object to be moved on tap, if the immediate parent is not desired.")]
28  public GameObject ParentGameObjectToPlace;
29 
35  [Tooltip("Setting this to true will enable the user to move and place the object in the scene without needing to tap on the object. Useful when you want to place an object immediately.")]
36  public bool IsBeingPlaced;
37 
38  [Tooltip("Setting this to true will allow this behavior to control the DrawMesh property on the spatial mapping.")]
39  public bool AllowMeshVisualizationControl = true;
40 
41  [Tooltip("Should the center of the Collider be used instead of the gameObjects world transform.")]
42  public bool UseColliderCenter;
43 
44  private Interpolator interpolator;
45 
49  private const int IgnoreRaycastLayer = 2;
50 
51  private Dictionary<GameObject, int> layerCache = new Dictionary<GameObject, int>();
52  private Vector3 PlacementPosOffset;
53 
54  protected virtual void Start()
55  {
56  if (PlaceParentOnTap)
57  {
58  ParentGameObjectToPlace = GetParentToPlace();
59  PlaceParentOnTap = ParentGameObjectToPlace != null;
60  }
61 
62  interpolator = EnsureInterpolator();
63 
64  if (IsBeingPlaced)
65  {
66  StartPlacing();
67  }
68  else // If we are not starting out with actively placing the object, give it a World Anchor
69  {
70  AttachWorldAnchor();
71  }
72  }
73 
74  private void OnEnable()
75  {
76  Bounds bounds = transform.GetColliderBounds();
77  PlacementPosOffset = transform.position - bounds.center;
78  }
79 
84  private GameObject GetParentToPlace()
85  {
86  if (ParentGameObjectToPlace)
87  {
88  return ParentGameObjectToPlace;
89  }
90 
91  return gameObject.transform.parent ? gameObject.transform.parent.gameObject : null;
92  }
93 
97  private Interpolator EnsureInterpolator()
98  {
99  var interpolatorHolder = PlaceParentOnTap ? ParentGameObjectToPlace : gameObject;
100  return interpolatorHolder.EnsureComponent<Interpolator>();
101  }
102 
103  protected virtual void Update()
104  {
105  if (!IsBeingPlaced) { return; }
106  Transform cameraTransform = CameraCache.Main.transform;
107 
108  Vector3 placementPosition = GetPlacementPosition(cameraTransform.position, cameraTransform.forward, DefaultGazeDistance);
109 
110  if (UseColliderCenter)
111  {
112  placementPosition += PlacementPosOffset;
113  }
114 
115  // Here is where you might consider adding intelligence
116  // to how the object is placed. For example, consider
117  // placing based on the bottom of the object's
118  // collider so it sits properly on surfaces.
119 
120  if (PlaceParentOnTap)
121  {
122  placementPosition = ParentGameObjectToPlace.transform.position + (placementPosition - gameObject.transform.position);
123  }
124 
125  // update the placement to match the user's gaze.
126  interpolator.SetTargetPosition(placementPosition);
127 
128  // Rotate this object to face the user.
129  interpolator.SetTargetRotation(Quaternion.Euler(0, cameraTransform.localEulerAngles.y, 0));
130  }
131 
132  public virtual void OnInputClicked(InputClickedEventData eventData)
133  {
134  // On each tap gesture, toggle whether the user is in placing mode.
135  IsBeingPlaced = !IsBeingPlaced;
136  HandlePlacement();
137  eventData.Use();
138  }
139 
140  private void HandlePlacement()
141  {
142  if (IsBeingPlaced)
143  {
144  StartPlacing();
145  }
146  else
147  {
148  StopPlacing();
149  }
150  }
151  private void StartPlacing()
152  {
153  var layerCacheTarget = PlaceParentOnTap ? ParentGameObjectToPlace : gameObject;
154  layerCacheTarget.SetLayerRecursively(IgnoreRaycastLayer, out layerCache);
155  InputManager.Instance.PushModalInputHandler(gameObject);
156 
157  ToggleSpatialMesh();
158  RemoveWorldAnchor();
159  }
160 
161  private void StopPlacing()
162  {
163  var layerCacheTarget = PlaceParentOnTap ? ParentGameObjectToPlace : gameObject;
164  layerCacheTarget.ApplyLayerCacheRecursively(layerCache);
165  InputManager.Instance.PopModalInputHandler();
166 
167  ToggleSpatialMesh();
168  AttachWorldAnchor();
169  }
170 
171  private void AttachWorldAnchor()
172  {
173  if (WorldAnchorManager.Instance != null)
174  {
175  // Add world anchor when object placement is done.
176  WorldAnchorManager.Instance.AttachAnchor(PlaceParentOnTap ? ParentGameObjectToPlace : gameObject);
177  }
178  }
179 
180  private void RemoveWorldAnchor()
181  {
182  if (WorldAnchorManager.Instance != null)
183  {
184  //Removes existing world anchor if any exist.
185  WorldAnchorManager.Instance.RemoveAnchor(PlaceParentOnTap ? ParentGameObjectToPlace : gameObject);
186  }
187  }
188 
192  private void ToggleSpatialMesh()
193  {
194  if (SpatialMappingManager.Instance != null && AllowMeshVisualizationControl)
195  {
196  SpatialMappingManager.Instance.DrawVisualMeshes = IsBeingPlaced;
197  }
198  }
199 
204  private static Vector3 GetPlacementPosition(Vector3 headPosition, Vector3 gazeDirection, float defaultGazeDistance)
205  {
206  RaycastHit hitInfo;
207  if (SpatialMappingRaycast(headPosition, gazeDirection, out hitInfo))
208  {
209  return hitInfo.point;
210  }
211  return GetGazePlacementPosition(headPosition, gazeDirection, defaultGazeDistance);
212  }
213 
221  private static bool SpatialMappingRaycast(Vector3 origin, Vector3 direction, out RaycastHit spatialMapHit)
222  {
223  if (SpatialMappingManager.Instance != null)
224  {
225  RaycastHit hitInfo;
226  if (Physics.Raycast(origin, direction, out hitInfo, 30.0f, SpatialMappingManager.Instance.LayerMask))
227  {
228  spatialMapHit = hitInfo;
229  return true;
230  }
231  }
232  spatialMapHit = new RaycastHit();
233  return false;
234  }
235 
243  private static Vector3 GetGazePlacementPosition(Vector3 headPosition, Vector3 gazeDirection, float defaultGazeDistance)
244  {
245  if (GazeManager.Instance.HitObject != null)
246  {
247  return GazeManager.Instance.HitPosition;
248  }
249  return headPosition + gazeDirection * defaultGazeDistance;
250  }
251  }
252 }
The gaze manager manages everything related to a gaze ray that can interact with other objects...
Definition: GazeManager.cs:13
void SetTargetPosition(Vector3 target)
Sets the target position for the transform and if position wasn&#39;t already animating, fires the InterpolationStarted event.
Input Manager is responsible for managing input sources and dispatching relevant events to the approp...
Definition: InputManager.cs:19
Wrapper around world anchor store to streamline some of the persistence API busy work.
The TapToPlace class is a basic way to enable users to move objects and place them on real world surf...
Definition: TapToPlace.cs:19
Interface to implement to react to simple click input.
The purpose of this class is to provide a cached reference to the main camera. Calling Camera...
Definition: CameraCache.cs:12
static T Instance
Returns the Singleton instance of the classes type. If no instance is found, then we search for an in...
Definition: Singleton.cs:26
A MonoBehaviour that interpolates a transform&#39;s position, rotation or scale.
Definition: Interpolator.cs:11
static Camera Main
Returns a cached reference to the main camera and uses Camera.main if it hasn&#39;t been cached yet...
Definition: CameraCache.cs:20
void SetTargetRotation(Quaternion target)
Sets the target rotation for the transform and if rotation wasn&#39;t already animating, fires the InterpolationStarted event.
The SpatialMappingManager class allows applications to use a SurfaceObserver or a stored Spatial Mapp...
Describes an input event that involves a tap.
virtual void OnInputClicked(InputClickedEventData eventData)
Definition: TapToPlace.cs:132
bool IsBeingPlaced
Keeps track of if the user is moving the object or not. Setting this to true will enable the user to ...
Definition: TapToPlace.cs:36