AR Design
UBC EML collab with UBC SALA - visualizing IoT data in AR
BoundingBox.cs
Go to the documentation of this file.
1 //
2 // Copyright (c) Microsoft Corporation. All rights reserved.
3 // Licensed under the MIT License. See LICENSE in the project root for license information.
4 //
5 using System;
6 using System.Collections.Generic;
7 using UnityEngine;
8 
9 namespace HoloToolkit.Unity.UX
10 {
14  public class BoundingBox : MonoBehaviour
15  {
17  {
18  MeshFilterBounds, // Better for flattened objects - this mode also treats RectTransforms as quad meshes
19  RendererBounds, // Better for objects with non-mesh renderers
20  Colliders, // Better if you want precise control
21  Default, // Use the default method (RendererBounds)
22  }
23 
24  public enum FlattenModeEnum
25  {
26  DoNotFlatten, // Always use XYZ axis
27  FlattenX, // Flatten the X axis
28  FlattenY, // Flatten the Y axis
29  FlattenZ, // Flatten the Z axis
30  FlattenAuto, // Flatten the smallest relative axis if it falls below threshold
31  }
32 
36  [Header("Objects")]
37  [Tooltip("The target object")]
38  [SerializeField]
39  protected GameObject target;
40 
44  [Tooltip("The transform used to scale the bounding box (will be auto-generated)")]
45  [SerializeField]
46  protected Transform scaleTransform = null;
47 
51  [Header("Flattening & Padding")]
52  [Tooltip("Flattening behavior setting.")]
53  [SerializeField]
54  protected FlattenModeEnum flattenPreference = FlattenModeEnum.FlattenAuto;
55 
59  public virtual FlattenModeEnum FlattenPreference
60  {
61  get
62  {
63  return flattenPreference;
64  }
65  set
66  {
67  flattenPreference = value;
68  }
69  }
70 
74  [Tooltip("The relative % size of an axis must meet before being auto-flattened")]
75  [SerializeField]
76  protected float flattenAxisThreshold = 0.025f;
77 
81  [Tooltip("The relative % size of a flattened axis")]
82  [SerializeField]
83  protected float flattenedAxisThickness = 0.01f;
84 
88  [Tooltip("How much to pad the scale of the box to fit around objects (as % of largest dimension)")]
89  [SerializeField]
90  protected float scalePadding = 0.05f;
91 
95  [Tooltip("How much to pad the scale of the box on an axis that's flattened")]
96  [SerializeField]
97  protected float flattenedScalePadding = 0f;
98 
102  [Header("Bounds Calculation")]
103  [Tooltip("Method used to calculate the bounds of the object.")]
104  [SerializeField]
105  protected BoundsCalculationMethodEnum boundsCalculationMethod = BoundsCalculationMethodEnum.MeshFilterBounds;
106 
110  [Tooltip("Any renderers on this layer will be ignored when calculating object bounds")]
111  [SerializeField]
112  protected LayerMask ignoreLayers = (1 << 2); // Ignore Raycast Layer
113 
114  protected Vector3 targetBoundsWorldCenter = Vector3.zero;
115 
116  protected Vector3 targetBoundsLocalScale = Vector3.zero;
117 
118  protected Bounds localTargetBounds = new Bounds();
119 
120  protected List<Vector3> boundsPoints = new List<Vector3>();
121 
122  protected FlattenModeEnum flattenedAxis = FlattenModeEnum.DoNotFlatten;
123 
124  protected bool isVisible = true;
125 
126  protected Renderer rendererForVisibility;
127 
131  public Action OnFlattenedAxisChange;
132 
137  public virtual BoundsCalculationMethodEnum BoundsCalculationMethod
138  {
139  get { return boundsCalculationMethod; }
140  set { boundsCalculationMethod = value; }
141  }
142 
146  public virtual GameObject Target
147  {
148  get
149  {
150  return target;
151  }
152  set
153  {
154  if (target != value)
155  {
156  // Send a message to the new / old targets
157  // TODO send OnTargetSelected / Deselected events
158  target = value;
159  }
160 
161  if (!isActiveAndEnabled)
162  {
163  return;
164  }
165 
166  if (target != null)
167  {
168  CreateTransforms();
169  // Set our transforms to the target immediately
170  RefreshTargetBounds();
171  }
172  }
173  }
174 
178  public Vector3 TargetBoundsCenter
179  {
180  get
181  {
182  return targetBoundsWorldCenter;
183  }
184  }
185 
190  public Vector3 TargetBoundsScale
191  {
192  get
193  {
194  return scaleTransform.localScale;
195  }
196  }
197 
201  public Vector3 TargetBoundsLocalScale
202  {
203  get
204  {
205  return targetBoundsLocalScale;
206  }
207  }
208 
213  public bool IsVisible
214  {
215  get
216  {
217  return isVisible;
218  }
219  set
220  {
221  if (rendererForVisibility == null)
222  {
223  Transform scale = transform.GetChild(0);
224  Transform rig = scale.GetChild(0);
225  GameObject rigobject = rig.gameObject;
226  rendererForVisibility = rigobject.gameObject.GetComponent<Renderer>();
227  }
228 
229  rendererForVisibility.enabled = value;
230  isVisible = value;
231  }
232  }
233 
237  public virtual FlattenModeEnum FlattenedAxis
238  {
239  get
240  {
241  return flattenedAxis;
242  }
243  protected set
244  {
245  if (flattenedAxis != value)
246  {
247  flattenedAxis = value;
248  if (OnFlattenedAxisChange != null)
249  {
250  OnFlattenedAxisChange();
251  }
252  }
253  }
254  }
255 
256  #region
257 #if UNITY_EDITOR
261  protected virtual void OnDrawGizmos()
262  {
263  // nothing
264  if (!Application.isPlaying)
265  {
266  // Do this here to ensure continuous updates in editor
267  CreateTransforms();
268  RefreshTargetBounds();
269  UpdateScaleTransform();
270  }
271 
272  if (target != null)
273  {
274  foreach (Vector3 point in boundsPoints)
275  {
276  Gizmos.DrawSphere(target.transform.TransformPoint(point), 0.01f);
277  }
278  }
279  }
280 #endif
281 
285  protected virtual void Update()
286  {
287  CreateTransforms();
288  RefreshTargetBounds();
289  UpdateScaleTransform();
290  }
291 
302  protected virtual void CreateTransforms()
303  {
304  if (scaleTransform == null)
305  {
306  scaleTransform = transform;
307  }
308 
309  if (scaleTransform == null)
310  {
311  scaleTransform = new GameObject("Scale").transform;
312  }
313 
314  scaleTransform.parent = transform;
315  }
316 
322  protected virtual void RefreshTargetBounds()
323  {
324  if (target == null)
325  {
326  targetBoundsWorldCenter = Vector3.zero;
327  targetBoundsLocalScale = Vector3.one;
328  return;
329  }
330 
331  // Get the new target bounds
332  boundsPoints.Clear();
333 
334  switch (boundsCalculationMethod)
335  {
336  case BoundsCalculationMethodEnum.RendererBounds:
337  default:
338  GetRenderBoundsPoints(target, boundsPoints, ignoreLayers);
339  break;
340 
341  case BoundsCalculationMethodEnum.Colliders:
342  GetColliderBoundsPoints(target, boundsPoints, ignoreLayers);
343  break;
344 
345  case BoundsCalculationMethodEnum.MeshFilterBounds:
346  GetMeshFilterBoundsPoints(target, boundsPoints, ignoreLayers);
347  break;
348  }
349 
350  if (boundsPoints.Count > 0)
351  {
352  // We now have a list of all points in world space
353  // Translate them all to local space
354  for (int i = 0; i < boundsPoints.Count; i++)
355  {
356  boundsPoints[i] = target.transform.InverseTransformPoint(boundsPoints[i]);
357  }
358 
359  // Encapsulate the points with a local bounds
360  localTargetBounds.center = boundsPoints[0];
361  localTargetBounds.size = Vector3.zero;
362  foreach (Vector3 point in boundsPoints)
363  {
364  localTargetBounds.Encapsulate(point);
365  }
366  }
367 
368  // Store the world center of the target bb
369  targetBoundsWorldCenter = target.transform.TransformPoint(localTargetBounds.center);
370 
371  // Store the local scale of the target bb
372  targetBoundsLocalScale = localTargetBounds.size;
373  targetBoundsLocalScale.Scale(target.transform.localScale);
374 
375  // Now check our flatten behavior
376  UpdateFlattenedAxis();
377  }
378 
382  protected virtual void UpdateFlattenedAxis()
383  {
384  // Find the maximum size of the new bounds
385  float maxAxisThickness = Mathf.Max(Mathf.Max(targetBoundsLocalScale.x, targetBoundsLocalScale.y), targetBoundsLocalScale.z);
386 
387  FlattenModeEnum newFlattenedAxis = FlattenModeEnum.DoNotFlatten;
388  switch (flattenPreference)
389  {
390  case FlattenModeEnum.DoNotFlatten:
391  // Do nothing
392  break;
393 
394  case FlattenModeEnum.FlattenAuto:
395  // Flattening order of preference - z, y, x
396  if (Mathf.Abs(targetBoundsLocalScale.z / maxAxisThickness) < flattenAxisThreshold)
397  {
398  newFlattenedAxis = FlattenModeEnum.FlattenZ;
399  targetBoundsLocalScale.z = flattenedAxisThickness * maxAxisThickness;
400  }
401  else if (Mathf.Abs(targetBoundsLocalScale.y / maxAxisThickness) < flattenAxisThreshold)
402  {
403  newFlattenedAxis = FlattenModeEnum.FlattenY;
404  targetBoundsLocalScale.y = flattenedAxisThickness * maxAxisThickness;
405  }
406  else if (Mathf.Abs(targetBoundsLocalScale.x / maxAxisThickness) < flattenAxisThreshold)
407  {
408  newFlattenedAxis = FlattenModeEnum.FlattenX;
409  targetBoundsLocalScale.x = flattenedAxisThickness * maxAxisThickness;
410  }
411  break;
412 
413  case FlattenModeEnum.FlattenX:
414  newFlattenedAxis = FlattenModeEnum.FlattenX;
415  targetBoundsLocalScale.x = flattenedAxisThickness * maxAxisThickness;
416  break;
417 
418  case FlattenModeEnum.FlattenY:
419  newFlattenedAxis = FlattenModeEnum.FlattenY;
420  targetBoundsLocalScale.y = flattenedAxisThickness * maxAxisThickness;
421  break;
422 
423  case FlattenModeEnum.FlattenZ:
424  newFlattenedAxis = FlattenModeEnum.FlattenZ;
425  targetBoundsLocalScale.z = flattenedAxisThickness * maxAxisThickness;
426  break;
427  }
428 
429  FlattenedAxis = newFlattenedAxis;
430  }
431 
435  protected virtual void UpdateScaleTransform()
436  {
437  // If we don't have a target, nothing to do here
438  if (target == null)
439  {
440  return;
441  }
442  // Get position of object based on renderers
443  transform.position = targetBoundsWorldCenter;
444  Vector3 scale = targetBoundsLocalScale;
445 
446  // Use absolute value when determining smallest axis
447  // so we don't get fooled by inverted scales
448  float largestDimension = Mathf.Max(Mathf.Max(
449  Mathf.Abs(scale.x),
450  Mathf.Abs(scale.y)),
451  Mathf.Abs(scale.z));
452 
453  switch (flattenedAxis)
454  {
455  case BoundingBox.FlattenModeEnum.DoNotFlatten:
456  default:
457  scale.x += (largestDimension * scalePadding);
458  scale.y += (largestDimension * scalePadding);
459  scale.z += (largestDimension * scalePadding);
460  break;
461 
462  case BoundingBox.FlattenModeEnum.FlattenX:
463  scale.x += (largestDimension * flattenedScalePadding);
464  scale.y += (largestDimension * scalePadding);
465  scale.z += (largestDimension * scalePadding);
466  break;
467 
468  case BoundingBox.FlattenModeEnum.FlattenY:
469  scale.x += (largestDimension * scalePadding);
470  scale.y += (largestDimension * flattenedScalePadding);
471  scale.z += (largestDimension * scalePadding);
472  break;
473 
474  case BoundingBox.FlattenModeEnum.FlattenZ:
475  scale.x += (largestDimension * scalePadding);
476  scale.y += (largestDimension * scalePadding);
477  scale.z += (largestDimension * flattenedScalePadding);
478  break;
479  }
480  scaleTransform.localScale = scale;
481  Vector3 rotation = target.transform.eulerAngles;
482  transform.eulerAngles = rotation;
483  }
484  #endregion
485 
486  #region static utility functions
487  public static void GetColliderBoundsPoints(GameObject target, List<Vector3> boundsPoints, LayerMask ignoreLayers)
494  {
495  Collider[] colliders = target.GetComponentsInChildren<Collider>();
496  for (int i = 0; i < colliders.Length; i++)
497  {
498  if (ignoreLayers == (1 << colliders[i].gameObject.layer | ignoreLayers))
499  {
500  continue;
501  }
502 
503  switch (colliders[i].GetType().Name)
504  {
505  case "SphereCollider":
506  SphereCollider sc = colliders[i] as SphereCollider;
507  Bounds sphereBounds = new Bounds(sc.center, Vector3.one * sc.radius * 2);
508  sphereBounds.GetFacePositions(sc.transform, ref corners);
509  boundsPoints.AddRange(corners);
510  break;
511 
512  case "BoxCollider":
513  BoxCollider bc = colliders[i] as BoxCollider;
514  Bounds boxBounds = new Bounds(bc.center, bc.size);
515  boxBounds.GetCornerPositions(bc.transform, ref corners);
516  boundsPoints.AddRange(corners);
517  break;
518 
519  case "MeshCollider":
520  MeshCollider mc = colliders[i] as MeshCollider;
521  Bounds meshBounds = mc.sharedMesh.bounds;
522  meshBounds.GetCornerPositions(mc.transform, ref corners);
523  boundsPoints.AddRange(corners);
524  break;
525 
526  case "CapsuleCollider":
527  CapsuleCollider cc = colliders[i] as CapsuleCollider;
528  Bounds capsuleBounds = new Bounds(cc.center, Vector3.zero);
529  switch (cc.direction)
530  {
531  case 0:
532  capsuleBounds.size = new Vector3(cc.height, cc.radius * 2, cc.radius * 2);
533  break;
534 
535  case 1:
536  capsuleBounds.size = new Vector3(cc.radius * 2, cc.height, cc.radius * 2);
537  break;
538 
539  case 2:
540  capsuleBounds.size = new Vector3(cc.radius * 2, cc.radius * 2, cc.height);
541  break;
542  }
543  capsuleBounds.GetFacePositions(cc.transform, ref corners);
544  boundsPoints.AddRange(corners);
545  break;
546 
547  default:
548  break;
549  }
550  }
551  }
552 
559  public static void GetRenderBoundsPoints(GameObject target, List<Vector3> boundsPoints, LayerMask ignoreLayers)
560  {
561  Renderer[] renderers = target.GetComponentsInChildren<Renderer>();
562  for (int i = 0; i < renderers.Length; ++i)
563  {
564  var rendererObj = renderers[i];
565  if (ignoreLayers == (1 << rendererObj.gameObject.layer | ignoreLayers))
566  {
567  continue;
568  }
569 
570  rendererObj.bounds.GetCornerPositionsFromRendererBounds(ref corners);
571  boundsPoints.AddRange(corners);
572  }
573  }
574 
575  public static Bounds GetRenderBounds(GameObject target)
576  {
577  Bounds bounds = new Bounds();
578  Renderer[] renderers = target.GetComponentsInChildren<Renderer>();
579  for (int i = 0; i < renderers.Length; ++i)
580  {
581  var rendererObj = renderers[i];
582  bounds.ExpandToContain(rendererObj.bounds);
583  }
584 
585  return bounds;
586  }
587 
594  public static void GetMeshFilterBoundsPoints(GameObject target, List<Vector3> boundsPoints, LayerMask ignoreLayers)
595  {
596  MeshFilter[] meshFilters = target.GetComponentsInChildren<MeshFilter>();
597  for (int i = 0; i < meshFilters.Length; i++)
598  {
599  var meshFilterObj = meshFilters[i];
600  if (ignoreLayers == (1 << meshFilterObj.gameObject.layer | ignoreLayers))
601  {
602  continue;
603  }
604 
605  Bounds meshBounds = meshFilterObj.sharedMesh.bounds;
606  meshBounds.GetCornerPositions(meshFilterObj.transform, ref corners);
607  boundsPoints.AddRange(corners);
608  }
609  RectTransform[] rectTransforms = target.GetComponentsInChildren<RectTransform>();
610  for (int i = 0; i < rectTransforms.Length; i++)
611  {
612  rectTransforms[i].GetWorldCorners(rectTransformCorners);
613  boundsPoints.AddRange(rectTransformCorners);
614  }
615  }
616 
617  public static void GetNonAxisAlignedBB_Corners(GameObject target, List<Vector3> boundsPoints)
618  {
619  LayerMask mask = new LayerMask();
620 
621  GameObject clone = GameObject.Instantiate(target);
622  clone.transform.localRotation = Quaternion.identity;
623  clone.transform.position = Vector3.zero;
624  clone.transform.localScale = Vector3.one;
625  BoundingBox.GetMeshFilterBoundsPoints(clone, boundsPoints, mask);
626  Vector3 centroid = target.transform.position;
627  GameObject.Destroy(clone);
628 
629 #if UNITY_2017_1_OR_NEWER
630  for (int i = 0; i < boundsPoints.Count; ++i)
631  {
632  boundsPoints[i] = target.transform.localToWorldMatrix.MultiplyPoint(boundsPoints[i]);
633  }
634 #endif // UNITY_2017_1_OR_NEWER
635  }
636 
637  private static Vector3[] corners = null;
638  private static Vector3[] rectTransformCorners = new Vector3[4];
639  #endregion
640  }
641 }
Action OnFlattenedAxisChange
Event Handler- called when the FlattenedAxis property is changed.
Definition: BoundingBox.cs:131
static Bounds GetRenderBounds(GameObject target)
Definition: BoundingBox.cs:575
Base class for bounding box objects
Definition: BoundingBox.cs:14
virtual void UpdateScaleTransform()
Updates bounding box to match target position etc
Definition: BoundingBox.cs:435
static void GetRenderBoundsPoints(GameObject target, List< Vector3 > boundsPoints, LayerMask ignoreLayers)
GetRenderBoundsPoints gets bounding box points using RenderBounds
Definition: BoundingBox.cs:559
GameObject target
The target object. GameObject that the BoundingBox surrounds.
Definition: BoundingBox.cs:39
static void GetMeshFilterBoundsPoints(GameObject target, List< Vector3 > boundsPoints, LayerMask ignoreLayers)
GetMeshFilterBoundsPoints - gets boundingbox points using MeshFilter method.
Definition: BoundingBox.cs:594
virtual void UpdateFlattenedAxis()
recomputes flattening if axis to be flattened has changed.
Definition: BoundingBox.cs:382
virtual void RefreshTargetBounds()
re-calculates the Bounding box extrema (corners) of the axis aligned cube that bounds the Target game...
Definition: BoundingBox.cs:322
virtual void Update()
Override so we&#39;re not overwhelmed by button gizmos
Definition: BoundingBox.cs:285
static void GetNonAxisAlignedBB_Corners(GameObject target, List< Vector3 > boundsPoints)
Definition: BoundingBox.cs:617
virtual void CreateTransforms()
Method which instantiates new Transforms if they have not been declared earlier. Method assigns the v...
Definition: BoundingBox.cs:302