// Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. See LICENSE in the project root for license information. using UnityEngine; namespace HoloToolkit.Unity { /// <summary> /// DirectionIndicator creates an indicator around the cursor showing /// what direction to turn to find this GameObject. /// </summary> public class DirectionIndicator : MonoBehaviour { [Tooltip("The Cursor object the direction indicator will be positioned around.")] public GameObject Cursor; [Tooltip("Model to display the direction to the object this script is attached to.")] public GameObject DirectionIndicatorObject; [Tooltip("Color to shade the direction indicator.")] public Color DirectionIndicatorColor = Color.blue; [Tooltip("Allowable percentage inside the holographic frame to continue to show a directional indicator.")] [Range(-0.3f, 0.3f)] public float VisibilitySafeFactor = 0.1f; [Tooltip("Multiplier to decrease the distance from the cursor center an object is rendered to keep it in view.")] [Range(0.1f, 1.0f)] public float MetersFromCursor = 0.3f; // The default rotation of the cursor direction indicator. private Quaternion directionIndicatorDefaultRotation = Quaternion.identity; // Cache the MeshRenderer for the on-cursor indicator since it will be enabled and disabled frequently. private Renderer directionIndicatorRenderer; // Cache the Material to prevent material leak. private Material indicatorMaterial; // Check if the cursor direction indicator is visible. private bool isDirectionIndicatorVisible; public void Awake() { if (Cursor == null) { Debug.LogError("Please include a GameObject for the cursor."); } if (DirectionIndicatorObject == null) { Debug.LogError("Please include a GameObject for the Direction Indicator."); } // Instantiate the direction indicator. DirectionIndicatorObject = InstantiateDirectionIndicator(DirectionIndicatorObject); if (DirectionIndicatorObject == null) { Debug.LogError("Direction Indicator failed to instantiate."); } } public void OnDestroy() { DestroyImmediate(indicatorMaterial); Destroy(DirectionIndicatorObject); } private GameObject InstantiateDirectionIndicator(GameObject directionIndicator) { if (directionIndicator == null) { return null; } GameObject indicator = Instantiate(directionIndicator); // Set local variables for the indicator. directionIndicatorDefaultRotation = indicator.transform.rotation; directionIndicatorRenderer = indicator.GetComponent<Renderer>(); // Start with the indicator disabled. directionIndicatorRenderer.enabled = false; // Remove any colliders and rigidbodies so the indicators do not interfere with Unity's physics system. foreach (Collider indicatorCollider in indicator.GetComponents<Collider>()) { Destroy(indicatorCollider); } foreach (Rigidbody rigidBody in indicator.GetComponents<Rigidbody>()) { Destroy(rigidBody); } indicatorMaterial = directionIndicatorRenderer.material; indicatorMaterial.color = DirectionIndicatorColor; indicatorMaterial.SetColor("_TintColor", DirectionIndicatorColor); return indicator; } public void Update() { if (DirectionIndicatorObject == null) { return; } Camera mainCamera = CameraCache.Main; // Direction from the Main Camera to this script's parent gameObject. Vector3 camToObjectDirection = gameObject.transform.position - mainCamera.transform.position; camToObjectDirection.Normalize(); // The cursor indicator should only be visible if the target is not visible. isDirectionIndicatorVisible = !IsTargetVisible(mainCamera); directionIndicatorRenderer.enabled = isDirectionIndicatorVisible; if (isDirectionIndicatorVisible) { Vector3 position; Quaternion rotation; GetDirectionIndicatorPositionAndRotation( camToObjectDirection, mainCamera.transform, out position, out rotation); DirectionIndicatorObject.transform.position = position; DirectionIndicatorObject.transform.rotation = rotation; } } private bool IsTargetVisible(Camera mainCamera) { // This will return true if the target's mesh is within the Main Camera's view frustums. Vector3 targetViewportPosition = mainCamera.WorldToViewportPoint(gameObject.transform.position); return (targetViewportPosition.x > VisibilitySafeFactor && targetViewportPosition.x < 1 - VisibilitySafeFactor && targetViewportPosition.y > VisibilitySafeFactor && targetViewportPosition.y < 1 - VisibilitySafeFactor && targetViewportPosition.z > 0); } private void GetDirectionIndicatorPositionAndRotation(Vector3 camToObjectDirection, Transform cameraTransform, out Vector3 position, out Quaternion rotation) { // Find position: // Save the cursor transform position in a variable. Vector3 origin = Cursor.transform.position; // Project the camera to target direction onto the screen plane. Vector3 cursorIndicatorDirection = Vector3.ProjectOnPlane(camToObjectDirection, -1 * cameraTransform.forward); cursorIndicatorDirection.Normalize(); // If the direction is 0, set the direction to the right. // This will only happen if the camera is facing directly away from the target. if (cursorIndicatorDirection == Vector3.zero) { cursorIndicatorDirection = cameraTransform.right; } // The final position is translated from the center of the screen along this direction vector. position = origin + cursorIndicatorDirection * MetersFromCursor; // Find the rotation from the facing direction to the target object. rotation = Quaternion.LookRotation(cameraTransform.forward, cursorIndicatorDirection) * directionIndicatorDefaultRotation; } } }