using System.Collections; using System.Collections.Generic; using UnityEngine; using UnityEngine.EventSystems; using SC.XR.Unity.Module_InputSystem; using UnityEngine.Events; using System; using SC.XR.Unity; [AddComponentMenu("SDK/BoundingBox")] public class BoundingBox : MonoBehaviour, IPointerEnterHandler, IPointerExitHandler, IPointerDownHandler, IPointerUpHandler { [SerializeField] [Tooltip("Type of activation method for showing/hiding bounding box handles and controls")] private BoundingBoxActivationType activation = BoundingBoxActivationType.ActivateOnStart; public BoundingBoxActivationType ActivationType { get { return activation; } } [SerializeField] [Tooltip("Flatten bounds in the specified axis or flatten the smallest one if 'auto' is selected")] private FlattenModeType flattenAxis = FlattenModeType.DoNotFlatten; public FlattenModeType FlattenAxis { get { return flattenAxis; } set { flattenAxis = value; Redraw(); } } [SerializeField] private HandleType activeHandle = ~HandleType.None; public HandleType ActiveHandle { get { return activeHandle; } set { activeHandle = value; Redraw(); } } [SerializeField] private BoundingBoxAssets m_HandlerAssets; public BoundingBoxAssets BoundingBoxAssets { get { if (m_HandlerAssets == null) { m_HandlerAssets = Resources.Load("HandlerAssets/DefaultAssets"); } return m_HandlerAssets; } } //[SerializeField] //[AssetPreAssign("Assets/SDK/Common/StandardAssets/Materials/BoundingBox.mat", typeof(Material))] private Material m_boxFocusDisplayMat; public Material boxFocusDisplayMat { get { if (m_boxFocusDisplayMat == null) { if (BoundingBoxAssets) { m_boxFocusDisplayMat = BoundingBoxAssets.boxFocusDisplayMat; } } return m_boxFocusDisplayMat; } } //[SerializeField] //[AssetPreAssign("Assets/SDK/Common/StandardAssets/Materials/BoundingBoxGrabbed.mat", typeof(Material))] private Material m_boxGrabDisplayMat; public Material boxGrabDisplayMat { get { if (m_boxGrabDisplayMat == null) { if (BoundingBoxAssets) { m_boxGrabDisplayMat = BoundingBoxAssets.boxGrabDisplayMat; } } return m_boxGrabDisplayMat; } } //[SerializeField] //[AssetPreAssign("Assets/SDK/Common/StandardAssets/Materials/BoundingBoxHandleWhite.mat", typeof(Material))] private Material m_HandleMaterial; public Material HandleMaterial { get { if (m_HandleMaterial == null) { if (BoundingBoxAssets) { m_HandleMaterial = BoundingBoxAssets.HandleMaterial; } } return m_HandleMaterial; } } //[SerializeField] //[AssetPreAssign("Assets/SDK/Common/StandardAssets/Materials/BoundingBoxHandleBlueGrabbed.mat", typeof(Material))] private Material m_HandleGrabMaterial; public Material HandleGrabMaterial { get { if (m_HandleGrabMaterial == null) { if (BoundingBoxAssets) { m_HandleGrabMaterial = BoundingBoxAssets.HandleGrabMaterial; } } return m_HandleGrabMaterial; } } //[SerializeField] //[Header("Scale Handles")] //[AssetPreAssign("Assets/SDK/Common/StandardAssets/Prefabs/BoundingBox_ScaleHandle.prefab", typeof(GameObject))] private GameObject m_CornerPrefab; public GameObject CornerPrefab { get { if (m_CornerPrefab == null) { if (BoundingBoxAssets) { m_CornerPrefab = BoundingBoxAssets.CornerPrefab; } } return m_CornerPrefab; } } //[SerializeField] //[AssetPreAssign("Assets/SDK/Common/StandardAssets/Prefabs/BoundingBox_ScaleHandle_Slate.prefab", typeof(GameObject))] private GameObject m_CornerSlatePrefab; public GameObject CornerSlatePrefab { get { if (m_CornerSlatePrefab == null) { if (BoundingBoxAssets) { m_CornerSlatePrefab = BoundingBoxAssets.CornerSlatePrefab; } } return m_CornerSlatePrefab; } } public bool activeScaleMinRestrict = true; [Tooltip("Minimum scaling allowed relative to the world size Unit of m")] public float scaleMinimum = 0.05f; public bool activeScaleMaxRestrict = false; [Tooltip("Maximum scaling allowed relative to the world size Unit of m")] public float scaleMaximum = 2.0f; public Vector3 boundingBoxInitScale { get; private set; } public Vector3 calculateMinScale { get { Vector3 temp = (CurrentBoundsExtents * 2 / scaleMinimum); return new Vector3(boundingBoxInitScale.x / temp.x, boundingBoxInitScale.y / temp.y, boundingBoxInitScale.z / temp.z); } } public Vector3 calculateMaxScale { get { Vector3 temp = (CurrentBoundsExtents * 2 / scaleMaximum); return new Vector3(boundingBoxInitScale.x / temp.x, boundingBoxInitScale.y / temp.y, boundingBoxInitScale.z / temp.z); } } //[SerializeField] //[Tooltip("Size of the cube collidable used in scale handles")] //private float scaleHandleSize = 0.016f; // 1.6cm default handle size //public float ScaleHandleSize //{ // get // { // return scaleHandleSize; // } // set // { // scaleHandleSize = value; // Redraw(); // } //} [HideInInspector] public BoxCollider BoundBoxCollider; // Half the size of the current bounds private Vector3 currentBoundsExtents; public Vector3 CurrentBoundsExtents { get { return currentBoundsExtents; } } //[SerializeField] //[Header("Rotation Handles")] //[AssetPreAssign("Assets/SDK/Common/StandardAssets/Prefabs/BoundingBox_RotateHandle.prefab", typeof(GameObject))] private GameObject m_SidePrefab; public GameObject SidePrefab { get { if (m_SidePrefab == null) { if (BoundingBoxAssets) { m_SidePrefab = BoundingBoxAssets.SidePrefab; } } return m_SidePrefab; } } //[SerializeField] //[Tooltip("Radius of the handle geometry of rotation handles")] //private float rotationHandleSize = 0.016f; // 1.6cm default handle size //public float RotationHandleSize //{ // get // { // return rotationHandleSize; // } // set // { // rotationHandleSize = value; // Redraw(); // } //} [SerializeField] [Tooltip("Check to show rotation handles for the X axis")] private bool showRotationHandleForX = true; public bool ShowRotationHandleForX { get { return showRotationHandleForX; } set { showRotationHandleForX = value; Redraw(); } } [SerializeField] [Tooltip("Check to show rotation handles for the Y axis")] private bool showRotationHandleForY = true; public bool ShowRotationHandleForY { get { return showRotationHandleForY; } set { showRotationHandleForY = value; Redraw(); } } [SerializeField] [Tooltip("Check to show rotation handles for the Z axis")] private bool showRotationHandleForZ = true; public bool ShowRotationHandleForZ { get { return showRotationHandleForZ; } set { showRotationHandleForZ = value; Redraw(); } } //[SerializeField] //[Header("AxisScale Handles")] private GameObject m_facePrefab; public GameObject facePrefab { get { if (m_facePrefab == null) { if (BoundingBoxAssets) { m_facePrefab = BoundingBoxAssets.facePrefab; } } return m_facePrefab; } } //[SerializeField] //[Tooltip("Radius of the handle geometry of rotation handles")] //private float axisScaleHandleSize = 0.016f; // 1.6cm default handle size //public float AxisScaleHandleSize //{ // get // { // return axisScaleHandleSize; // } // set // { // axisScaleHandleSize = value; // Redraw(); // } //} [SerializeField] private AxisType activeAxis = ~AxisType.None; public AxisType ActiveAxis { get { return activeAxis; } set { activeAxis = value; Redraw(); } } public Transform BoundingBoxContainer { get; set; } public BoundingBoxRoot BoundingBoxRoot { get; private set; } public CornerBoundingBoxRoot CornerBoundingBoxRoot { get; private set; } public SideBoundingBoxRoot SideBoundingBoxRoot { get; set; } private FaceBoundingBoxRoot FaceBoundingBoxRoot { get; set; } private List BoundingBoxRootList { get; set; } [Header("Audio")] [SerializeField] public SCAudiosConfig.AudioType RotateStartAudio = SCAudiosConfig.AudioType.Manipulation_Start; [SerializeField] public SCAudiosConfig.AudioType RotateStopAudio = SCAudiosConfig.AudioType.Manipulation_End; [SerializeField] public SCAudiosConfig.AudioType ScaleStartAudio = SCAudiosConfig.AudioType.Manipulation_Start; [SerializeField] public SCAudiosConfig.AudioType ScaleStopAudio = SCAudiosConfig.AudioType.Manipulation_End; [Header("Events")] /// /// Event that gets fired when interaction with a rotation handle starts. /// public UnityEvent RotateStarted = new UnityEvent(); /// /// Event that gets fired when interaction with a rotation handle stops. /// public UnityEvent RotateStopped = new UnityEvent(); /// /// Event that gets fired when interaction with a scale handle starts. /// public UnityEvent ScaleStarted = new UnityEvent(); /// /// Event that gets fired when interaction with a scale handle stops. /// public UnityEvent ScaleStopped = new UnityEvent(); public UnityEvent Rotating = new UnityEvent(); public UnityEvent Scaling = new UnityEvent(); #region Class & Enum Define /// /// Enum which describes whether a BoundingBox handle which has been grabbed, is /// a Rotation Handle (sphere) or a Scale Handle( cube) /// [Flags] public enum HandleType { None = 1 << 0, Rotation = 1 << 1, Scale = 1 << 2, AxisScale = 1 << 3, } [Flags] public enum AxisType { None = 1 << 0, X = 1 << 1, Y = 1 << 2, Z = 1 << 3, NX = 1 << 4, NY = 1 << 5, NZ = 1 << 6, } /// /// Enum which describes how an object's BoundingBox is to be flattened. /// public enum FlattenModeType { DoNotFlatten = 0, /// /// Flatten the X axis /// FlattenX, /// /// Flatten the Y axis /// FlattenY, /// /// Flatten the Z axis /// FlattenZ, /// /// Flatten the smallest relative axis if it falls below threshold /// FlattenAuto, } /// /// This enum defines how the BoundingBox gets activated /// public enum BoundingBoxActivationType { ActivateOnStart = 0, ActivateByPointer, } public class Handle { /// /// Handle Type /// public HandleType type; /// /// Handle Root Gameobject /// public Transform root; /// /// Handle bounds /// public Bounds bounds; public Vector3 localPosition; public Transform visualsScale; public GameObject visual; public void SetActive(bool active) { root.gameObject.SetActive(active); } } #endregion #region Unity Lifecycle Function // Start is called before the first frame update void Start() { Init(); } private void OnValidate() { Redraw(); } #endregion private void Init() { CreatBoundingBoxRoot(flattenAxis); BoundingBoxRoot = new BoundingBoxRoot(this); CornerBoundingBoxRoot = new CornerBoundingBoxRoot(this); SideBoundingBoxRoot = new SideBoundingBoxRoot(this); FaceBoundingBoxRoot = new FaceBoundingBoxRoot(this); BoundingBoxRoot.Init(); CornerBoundingBoxRoot.Init(); SideBoundingBoxRoot.Init(); FaceBoundingBoxRoot.Init(); if (BoundingBoxRootList == null) { BoundingBoxRootList = new List(); BoundingBoxRootList.Add(BoundingBoxRoot); BoundingBoxRootList.Add(CornerBoundingBoxRoot); BoundingBoxRootList.Add(SideBoundingBoxRoot); BoundingBoxRootList.Add(FaceBoundingBoxRoot); } if (ActivationType == BoundingBoxActivationType.ActivateOnStart) { SetVisibility(true); } else { SetVisibility(false); } } private void CreatBoundingBoxRoot(FlattenModeType flattenMode) { RecaculateBounds(); } public void Redraw() { if (BoundingBoxRootList == null) { return; } RecaculateBounds(); for (int i = 0; i < BoundingBoxRootList.Count; i++) { IBoundingBoxRoot boundingBoxRoot = BoundingBoxRootList[i]; boundingBoxRoot.ReDraw(); } } private void RecaculateBounds() { // Make sure that the bounds of all child objects are up to date before we compute bounds Physics.SyncTransforms(); BoundBoxCollider = GetComponentInChildren(); if (BoundBoxCollider == null) { Debug.Log("Error! Please Add BoxCollider And Adjust Size For BoundingBoxGameobject"); return; } // Store current rotation then zero out the rotation so that the bounds // are computed when the object is in its 'axis aligned orientation'. Quaternion currentRotation = transform.rotation; transform.rotation = Quaternion.identity; Physics.SyncTransforms(); // Update collider bounds currentBoundsExtents = BoundBoxCollider.bounds.extents; boundingBoxInitScale = BoundBoxCollider.transform.lossyScale; // After bounds are computed, restore rotation... transform.rotation = currentRotation; Physics.SyncTransforms(); if (currentBoundsExtents != Vector3.zero) { if (FlattenAxis == FlattenModeType.FlattenAuto) { float min = Mathf.Min(currentBoundsExtents.x, Mathf.Min(currentBoundsExtents.y, currentBoundsExtents.z)); flattenAxis = (min == currentBoundsExtents.x) ? FlattenModeType.FlattenX : ((min == currentBoundsExtents.y) ? FlattenModeType.FlattenY : FlattenModeType.FlattenZ); } currentBoundsExtents.x = (flattenAxis == FlattenModeType.FlattenX) ? 0.0f : currentBoundsExtents.x; currentBoundsExtents.y = (flattenAxis == FlattenModeType.FlattenY) ? 0.0f : currentBoundsExtents.y; currentBoundsExtents.z = (flattenAxis == FlattenModeType.FlattenZ) ? 0.0f : currentBoundsExtents.z; Transform existContainerTransform = this.transform.Find(GetType().ToString()); if (existContainerTransform != null) { #if UNITY_EDITOR GameObject.Destroy(existContainerTransform.gameObject); #else GameObject.DestroyImmediate(existContainerTransform.gameObject); #endif } BoundingBoxContainer = new GameObject(GetType().ToString()).transform; BoundingBoxContainer.parent = transform; BoundingBoxContainer.position = BoundBoxCollider.bounds.center; BoundingBoxContainer.localRotation = Quaternion.identity; } } public void SetVisibility(bool isVisible) { for (int i = 0; i < BoundingBoxRootList.Count; i++) { IBoundingBoxRoot boundingBoxRoot = BoundingBoxRootList[i]; boundingBoxRoot.SetVisible(isVisible); } } public void SetHighLight(Transform activeHandle, bool hideOtherHandle) { for (int i = 0; i < BoundingBoxRootList.Count; i++) { IBoundingBoxRoot boundingBoxRoot = BoundingBoxRootList[i]; boundingBoxRoot.SetHighLight(activeHandle, hideOtherHandle); } } #region BoundingBox PointerEvent public void OnPointerEnter(PointerEventData eventData) { SCPointEventData scPointEventData = eventData as SCPointEventData; if (scPointEventData == null) { return; } if (scPointEventData.DownPressGameObject == null) { SetVisibility(true); } } public void OnPointerExit(PointerEventData eventData) { SCPointEventData scPointEventData = eventData as SCPointEventData; if (scPointEventData == null) { return; } if (scPointEventData.DownPressGameObject == null) { if (activation == BoundingBoxActivationType.ActivateOnStart) { SetVisibility(true); } else { SetVisibility(false); } } } public void OnPointerDown(PointerEventData eventData) { SCPointEventData scPointEventData = eventData as SCPointEventData; if (scPointEventData == null) { return; } SetHighLight(eventData.pointerCurrentRaycast.gameObject.transform, true); } public void OnPointerUp(PointerEventData eventData) { SCPointEventData scPointEventData = eventData as SCPointEventData; if (scPointEventData == null) { return; } if (activation == BoundingBoxActivationType.ActivateOnStart) { SetVisibility(true); } else { SetVisibility(true); } } #endregion }