AR Design
UBC EML collab with UBC SALA - visualizing IoT data in AR
Tagalong.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 UnityEngine;
5 
6 namespace HoloToolkit.Unity
7 {
14  public class Tagalong : SimpleTagalong
15  {
16  // These members allow for specifying target and minimum percentage in
17  // the FOV.
18  [Range(0.0f, 1.0f), Tooltip("The minimum horizontal percentage visible before the object starts tagging along.")]
19  public float MinimumHorizontalOverlap = 0.1f;
20  [Range(0.0f, 1.0f), Tooltip("The target horizontal percentage the Tagalong attempts to achieve.")]
21  public float TargetHorizontalOverlap = 1.0f;
22  [Range(0.0f, 1.0f), Tooltip("The minimum vertical percentage visible before the object starts tagging along.")]
23  public float MinimumVerticalOverlap = 0.1f;
24  [Range(0.0f, 1.0f), Tooltip("The target vertical percentage the Tagalong attempts to achieve.")]
25  public float TargetVerticalOverlap = 1.0f;
26 
27  // These members control how many rays to cast when looking for
28  // collisions with other holograms.
29  [Range(3, 11), Tooltip("The number of rays to cast horizontally across the Tagalong.")]
30  public int HorizontalRayCount = 3;
31  [Range(3, 11), Tooltip("The number of rays to cast vertically across the Tagalong.")]
32  public int VerticalRayCount = 3;
33 
34  [Tooltip("Don't allow the Tagalong to come closer than this distance.")]
35  public float MinimumTagalongDistance = 1.0f;
36  [Tooltip("When true, the Tagalong object maintains a fixed angular size.")]
37  public bool MaintainFixedSize = true;
38 
39  [Tooltip("The speed to update the Tagalong's distance when compensating for depth (meters/second).")]
40  public float DepthUpdateSpeed = 4.0f;
41 
42  private float defaultTagalongDistance;
43 
44  // These members are useful for debugging the Tagalong in Unity's
45  // editor or the HoloLens.
46  [Tooltip("Set to true to draw lines of interest in Unity's scene view during play-mode.")]
47  public bool DebugDrawLines = false;
48  [Tooltip("Useful for visualizing the Raycasts used for determining the depth to place the Tagalong. Set to 'None' to disable.")]
49  public Light DebugPointLight;
50 
51  protected override void Start()
52  {
53  base.Start();
54 
55  // Remember the default for distance.
56  defaultTagalongDistance = TagalongDistance;
57 
58  // If the specified minimum distance for the tagalong would be within the
59  // camera's near clipping plane, adjust it to be 10% beyond the near
60  // clipping plane.
61  if (CameraCache.Main.nearClipPlane > MinimumTagalongDistance)
62  {
63  MinimumTagalongDistance = CameraCache.Main.nearClipPlane * 1.1f;
64  }
65 
66  // The EnforceDistance functionality of the SimmpleTagalong has a
67  // detrimental effect on this Tagalong's desired behavior.
68  // Disable that behavior here.
69  EnforceDistance = false;
70 
71  // Add the FixedAngularSize script if MaintainFixedSize is true.
72  if (MaintainFixedSize)
73  {
74  gameObject.AddComponent<FixedAngularSize>();
75  }
76  }
77 
78  protected override void Update()
79  {
80  base.Update();
81 
82  if (!interpolator.AnimatingPosition)
83  {
84  // If we aren't animating towards a new position, check to see if
85  // we need to update the Tagalong's position because it is behind
86  // some other hologram or the Spatial Mapping mesh.
87  Vector3 newPosition;
88  if (AdjustTagalongDistance(CameraCache.Main.transform.position, out newPosition))
89  {
90  interpolator.PositionPerSecond = DepthUpdateSpeed;
91  interpolator.SetTargetPosition(newPosition);
92  TagalongDistance = Mathf.Min(defaultTagalongDistance, Vector3.Distance(CameraCache.Main.transform.position, newPosition));
93  }
94  }
95  }
96 
97  protected override bool CalculateTagalongTargetPosition(Vector3 fromPosition, out Vector3 toPosition)
98  {
99  bool needsToMoveX = false;
100  bool needsToMoveY = false;
101  toPosition = fromPosition;
102 
103  // Cache some things that we will need later.
104  Transform cameraTransform = CameraCache.Main.transform;
105  Vector3 cameraPosition = cameraTransform.position;
106 
107  // Get the bounds of the Tagalong's collider.
108  Bounds colliderBounds = tagalongCollider.bounds;
109 
110  // Default the new position to be the current position.
111  Vector3 newToPosition = tagalongCollider.bounds.center;
112 
113  // Adjust the center of the bounds to be TagalongDistance away from
114  // the camera. We will use this point instead of tranform.position for
115  // the rest of our calculations.
116  Ray rayTemp = new Ray(cameraPosition, colliderBounds.center - cameraPosition);
117  colliderBounds.center = rayTemp.GetPoint(TagalongDistance);
118 
119 #if UNITY_EDITOR
120  DebugDrawColliderBox(DebugDrawLines, colliderBounds);
121 #endif // UNITY_EDITOR
122 
123  // Get the actual width and height of the Tagalong's BoxCollider.
124  float width = tagalongCollider.size.x * transform.lossyScale.x;
125  float height = tagalongCollider.size.y * transform.lossyScale.y;
126 
127  // Determine if the Tagalong is to the left or right of the Camera's
128  // forward vector.
129  Plane verticalCenterPlane = new Plane(cameraTransform.right, cameraPosition + cameraTransform.forward);
130  bool tagalongIsRightOfCenter = verticalCenterPlane.GetDistanceToPoint(colliderBounds.center) > 0;
131 
132  // Based on left/right of center choose the appropriate directional
133  // vector and frustum plane for the rest of our horizontal calculations.
134  Vector3 horizontalTowardCenter = tagalongIsRightOfCenter ? -transform.right : transform.right;
135  Plane verticalFrustumPlane = tagalongIsRightOfCenter ? frustumPlanes[frustumRight] : frustumPlanes[frustumLeft];
136 
137  // Find the edge of the collider that is closest to the center plane.
138  Vector3 centermostHorizontalEdge = colliderBounds.center + (horizontalTowardCenter * (width / 2f));
139 
140  // Find the point on the collider that is the MinimumHorizontalOverlap
141  // as percentage away from the centermostHorizontalEdge.
142  Vector3 targetPoint = centermostHorizontalEdge + (-horizontalTowardCenter * (width * MinimumHorizontalOverlap));
143 
144  // If the calculated targetPoint is outside the verticalFrustumPlane
145  // of interest, we need to move the tagalong so it is at least
146  // TargetHorizontalOverlap inside the view frustum.
147  needsToMoveX = verticalFrustumPlane.GetDistanceToPoint(targetPoint) < 0;
148  if (needsToMoveX || DebugDrawLines)
149  {
150  // Calculate the new target position, ignoring the vertical.
151  Vector3 newCalculatedTargetPosition =
152  CalculateTargetPosition(true, centermostHorizontalEdge, horizontalTowardCenter, width,
153  colliderBounds.center, verticalFrustumPlane, tagalongIsRightOfCenter);
154 
155  if (needsToMoveX)
156  {
157  newToPosition.x = newCalculatedTargetPosition.x;
158  newToPosition.z = newCalculatedTargetPosition.z;
159  }
160  }
161 
162  // Repeat everything we did above, but for the vertical dimension.
163  // Comments will be abbreviated.
164 
165  colliderBounds = tagalongCollider.bounds;
166  rayTemp = new Ray(cameraPosition, colliderBounds.center - cameraPosition);
167  colliderBounds.center = rayTemp.GetPoint(TagalongDistance);
168  Plane horizontalCenterPlane = new Plane(cameraTransform.up, cameraPosition + cameraTransform.forward);
169  bool tagalongIsAboveCenter = horizontalCenterPlane.GetDistanceToPoint(colliderBounds.center) > 0;
170  Vector3 verticalTowardCenter = tagalongIsAboveCenter ? -transform.up : transform.up;
171  Plane horizontalFrustumPlane = tagalongIsAboveCenter ? frustumPlanes[frustumTop] : frustumPlanes[frustumBottom];
172  Vector3 centermostVerticalEdge = colliderBounds.center + (verticalTowardCenter * (height / 2f));
173  targetPoint = centermostVerticalEdge + (-verticalTowardCenter * (height * MinimumVerticalOverlap));
174  // We've determined the Tagalong needs to move in the YZ plane.
175  needsToMoveY = horizontalFrustumPlane.GetDistanceToPoint(targetPoint) < 0;
176  if (needsToMoveY || DebugDrawLines)
177  {
178  // Calculate the new target position, ignoring the vertical.
179  Vector3 newCalculatedTargetPosition =
180  CalculateTargetPosition(false, centermostVerticalEdge, verticalTowardCenter, height,
181  colliderBounds.center, horizontalFrustumPlane, !tagalongIsAboveCenter);
182  if (needsToMoveY)
183  {
184  newToPosition.y = newCalculatedTargetPosition.y;
185  newToPosition.z = newCalculatedTargetPosition.z;
186  }
187  }
188 
189  if (needsToMoveX || needsToMoveY)
190  {
191  Ray ray = new Ray(cameraPosition, newToPosition - cameraPosition);
192  toPosition = ray.GetPoint(TagalongDistance);
193  }
194 
195  return needsToMoveX || needsToMoveY;
196  }
197 
209  private Vector3 CalculateTargetPosition(bool isHorizontal, Vector3 centermostEdge, Vector3 vectorTowardCenter, float width,
210  Vector3 center, Plane frustumPlane, bool invertAngle)
211  {
212  Transform cameraTransform = CameraCache.Main.transform;
213  Vector3 cameraPosition = cameraTransform.position;
214 
215  // The target overlap can't be less than the minimum overlap. Pick
216  // the bigger of the two.
217  float desiredOverlap = isHorizontal
218  ? Mathf.Max(MinimumHorizontalOverlap, TargetHorizontalOverlap)
219  : Mathf.Max(MinimumVerticalOverlap, TargetVerticalOverlap);
220 
221  // Recalculate the targetPoint so it has the desired overlap.
222  Vector3 targetPoint = centermostEdge + (-vectorTowardCenter * (width * desiredOverlap));
223 
224  // Find a point on the frustum we care about. Start with a point
225  // in front of the camera and cast a ray from there to the frustum.
226  Vector3 centeredPoint = cameraPosition + cameraTransform.forward * TagalongDistance;
227  Ray rayTemp = new Ray(centeredPoint, (invertAngle ? 1 : -1) * (isHorizontal ? cameraTransform.right : cameraTransform.up));
228  float distToFrustum = 0.0f;
229  frustumPlane.Raycast(rayTemp, out distToFrustum);
230  Vector3 pointOnFrustum = rayTemp.GetPoint(distToFrustum);
231 
232  // Adjust the point found on the frustum plane to be the same
233  // distance from the camera as targetPoint is, but still on the
234  // frustum plane.
235  rayTemp = new Ray(cameraPosition, pointOnFrustum - cameraPosition);
236  float distanceToTarget = Vector3.Distance(cameraPosition, targetPoint);
237  Vector3 recalculatedPointOnFrustum = rayTemp.GetPoint(distanceToTarget);
238 
239  // Find the new calculated target position. First get the rotation
240  // between the target and center of the collider.
241  Quaternion rotQuat = Quaternion.FromToRotation(targetPoint - cameraPosition, center - cameraPosition);
242  // Create the vector we want to rotate.
243  Vector3 vectorToRotate = recalculatedPointOnFrustum - cameraPosition;
244  // Create the new target position.
245  Vector3 newCalculatedTargetPosition = cameraPosition + rotQuat * vectorToRotate;
246 
247 #if UNITY_EDITOR
248  DebugDrawDebuggingLines(DebugDrawLines, center, cameraPosition,
249  cameraPosition + (targetPoint - cameraPosition),
250  centeredPoint, pointOnFrustum, recalculatedPointOnFrustum,
251  newCalculatedTargetPosition);
252 #endif // UNITY_EDITOR
253 
254  return newCalculatedTargetPosition;
255  }
256 
257  private bool AdjustTagalongDistance(Vector3 cameraPosition, out Vector3 newPosition)
258  {
259  bool needsUpdating = false;
260 
261  // Get the actual width and height of the Tagalong's BoxCollider.
262  float width = tagalongCollider.size.x * transform.lossyScale.x;
263  float height = tagalongCollider.size.y * transform.lossyScale.y;
264 
265  // Find the lower-left corner of the Tagalong's BoxCollider.
266  Vector3 lowerLeftCorner = transform.position - (transform.right * (width / 2)) - (transform.up * (height / 2));
267 
268  // Cast a grid of rays across the Tagalong's collider. Keep track of
269  // of the closest hit, ignoring collisions with ourselves and those
270  // that are closer than MinimumColliderDistance.
271  RaycastHit closestHit = new RaycastHit();
272  float closestHitDistance = float.PositiveInfinity;
273  RaycastHit[] allHits;
274  for (int x = 0; x < HorizontalRayCount; x++)
275  {
276  Vector3 xCoord = lowerLeftCorner + transform.right * (x * width / (HorizontalRayCount - 1));
277  for (int y = 0; y < VerticalRayCount; y++)
278  {
279  Vector3 targetCoord = xCoord + transform.up * (y * height / (VerticalRayCount - 1));
280 
281  allHits = Physics.RaycastAll(cameraPosition, targetCoord - cameraPosition, defaultTagalongDistance * 1.5f);
282  for (int h = 0; h < allHits.Length; h++)
283  {
284  if (allHits[h].distance >= MinimumTagalongDistance &&
285  allHits[h].distance < closestHitDistance &&
286  !allHits[h].transform.IsChildOf(transform))
287  {
288  closestHit = allHits[h];
289  closestHitDistance = closestHit.distance;
290  if (DebugPointLight != null)
291  {
292  Light clonedLight = Instantiate(DebugPointLight, closestHit.point, Quaternion.identity) as Light;
293  clonedLight.color = Color.red;
294  Destroy(clonedLight, 1.0f);
295  }
296 #if UNITY_EDITOR
297  DebugDrawLine(DebugDrawLines, cameraPosition, targetCoord, Color.red);
298 #endif // UNITY_EDITOR
299  }
300  }
301  }
302  }
303 
304  // If we hit something, the closestHitDistance will be < infinity.
305  needsUpdating = closestHitDistance < float.PositiveInfinity;
306  if (needsUpdating)
307  {
308  // The closestHitDistance is a straight-line from the camera to the
309  // point on the collider that was hit. Unless the closest hit was
310  // encountered on the center Raycast, using the distance found will
311  // actually push the tagalong too far away, and part of the object
312  // that was hit will show through the Tagalong. We can fix that
313  // with a little thing we like to call Trigonometry.
314  Vector3 cameraToTransformPosition = transform.position - cameraPosition;
315  Vector3 cameraToClosestHitPoint = closestHit.point - cameraPosition;
316  float angleBetween = Vector3.Angle(cameraToTransformPosition, cameraToClosestHitPoint);
317  closestHitDistance = closestHitDistance * Mathf.Cos(angleBetween * Mathf.Deg2Rad);
318 
319  // Make sure we aren't trying to move too close.
320  closestHitDistance = Mathf.Max(closestHitDistance, MinimumTagalongDistance);
321  }
322  else if (TagalongDistance != defaultTagalongDistance)
323  {
324  // If we didn't hit anything but the TagalongDistance is different
325  // from the defaultTagalongDistance, we still need to update.
326  needsUpdating = true;
327  closestHitDistance = defaultTagalongDistance;
328  }
329 
330  newPosition = cameraPosition + (transform.position - cameraPosition).normalized * closestHitDistance;
331  return needsUpdating;
332  }
333 
334 #if UNITY_EDITOR
335  protected void DebugDrawLine(bool draw, Vector3 start, Vector3 end)
336  {
337  DebugDrawLine(draw, start, end, Color.white);
338  }
339 
340  protected void DebugDrawLine(bool draw, Vector3 start, Vector3 end, Color color)
341  {
342  if (draw)
343  {
344  Debug.DrawLine(start, end, color);
345  }
346  }
347 
353  void DebugDrawColliderBox(bool draw, Bounds colliderBounds)
354  {
355  Vector3 extents = colliderBounds.extents;
356 
357  Vector3 frontUpperLeft, backUpperLeft, backUpperRight, frontUpperRight;
358  frontUpperLeft = colliderBounds.center + new Vector3(-extents.x, extents.y, -extents.z);
359  backUpperLeft = colliderBounds.center + new Vector3(-extents.x, extents.y, extents.z);
360  backUpperRight = colliderBounds.center + new Vector3(extents.x, extents.y, extents.z);
361  frontUpperRight = colliderBounds.center + new Vector3(extents.x, extents.y, -extents.z);
362 
363  DebugDrawLine(draw, frontUpperLeft, backUpperLeft, Color.blue);
364  DebugDrawLine(draw, backUpperLeft, backUpperRight, Color.red);
365  DebugDrawLine(draw, backUpperRight, frontUpperRight, Color.blue);
366  DebugDrawLine(draw, frontUpperRight, frontUpperLeft, Color.red);
367 
368  Vector3 frontLowerLeft, backLowerLeft, backLowerRight, frontLowerRight;
369  frontLowerLeft = colliderBounds.center + new Vector3(-extents.x, -extents.y, -extents.z);
370  backLowerLeft = colliderBounds.center + new Vector3(-extents.x, -extents.y, extents.z);
371  backLowerRight = colliderBounds.center + new Vector3(extents.x, -extents.y, extents.z);
372  frontLowerRight = colliderBounds.center + new Vector3(extents.x, -extents.y, -extents.z);
373 
374  DebugDrawLine(draw, frontLowerLeft, backLowerLeft, Color.blue);
375  DebugDrawLine(draw, backLowerLeft, backLowerRight, Color.red);
376  DebugDrawLine(draw, backLowerRight, frontLowerRight, Color.blue);
377  DebugDrawLine(draw, frontLowerRight, frontLowerLeft, Color.red);
378 
379  DebugDrawLine(draw, frontUpperLeft, frontLowerLeft, Color.green);
380  DebugDrawLine(draw, backUpperLeft, backLowerLeft, Color.green);
381  DebugDrawLine(draw, backUpperRight, backLowerRight, Color.green);
382  DebugDrawLine(draw, frontUpperRight, frontLowerRight, Color.green);
383  }
384 
385  void DebugDrawDebuggingLines(bool draw, Vector3 center, Vector3 cameraPosition,
386  Vector3 cameraToTarget,
387  Vector3 centeredPoint, Vector3 pointOnFrustum, Vector3 recalculatedPointOnFrustum,
388  Vector3 calculatedPosition)
389  {
390  DebugDrawLine(draw, cameraPosition, center, Color.blue);
391  DebugDrawLine(draw, cameraPosition, cameraToTarget, Color.yellow);
392  DebugDrawLine(draw, cameraPosition, centeredPoint, Color.red);
393  DebugDrawLine(draw, centeredPoint, pointOnFrustum, Color.red);
394  DebugDrawLine(draw, cameraPosition, recalculatedPointOnFrustum, Color.red);
395  DebugDrawLine(draw, cameraPosition, calculatedPosition, Color.cyan);
396  }
397 #endif // UNITY_EDITOR
398  }
399 }
override void Start()
Definition: Tagalong.cs:51
override bool CalculateTagalongTargetPosition(Vector3 fromPosition, out Vector3 toPosition)
Determines if the Tagalong needs to move based on the provided position.
Definition: Tagalong.cs:97
A Tagalong that stays at a fixed distance from the camera and always seeks to have a part of itself i...
Causes a Hologram to maintain a fixed angular size, which is to say it occupies the same pixels in th...
A Tagalong that extends SimpleTagalong that allows for specifying the minimum and target percentage o...
Definition: Tagalong.cs:14
The purpose of this class is to provide a cached reference to the main camera. Calling Camera...
Definition: CameraCache.cs:12
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
override void Update()
Definition: Tagalong.cs:78