// Copyright HTC Corporation All Rights Reserved. using System.Text; using UnityEngine; using UnityEngine.EventSystems; namespace VIVE.OpenXR.Raycast { [RequireComponent(typeof(LineRenderer))] public class RaycastPointer : RaycastImpl { const string LOG_TAG = "VIVE.OpenXR.Raycast.RaycastPointer"; void DEBUG(StringBuilder msg) { Debug.LogFormat("{0} {1}", LOG_TAG, msg); } #region Inspector [Tooltip("To show the ray which presents the casting direction.")] [SerializeField] private bool m_ShowRay = true; public bool ShowRay { get { return m_ShowRay; } set { m_ShowRay = value; } } [SerializeField] private float m_RayStartWidth = 0.01f; public float RayStartWidth { get { return m_RayStartWidth; } set { m_RayStartWidth = value; } } [SerializeField] private float m_RayEndWidth = 0.01f; public float RayEndWidth { get { return m_RayEndWidth; } set { m_RayEndWidth = value; } } [SerializeField] private Material m_RayMaterial = null; public Material RayMaterial { get { return m_RayMaterial; } set { m_RayMaterial = value; } } [SerializeField] private GameObject m_Pointer = null; public GameObject Pointer { get { return m_Pointer; } set { m_Pointer = value; } } #endregion LineRenderer m_Ray = null; Vector3 m_PointerScale = new Vector3(.15f, .15f, .15f); #region MonoBehaviour overrides protected override void OnEnable() { sb.Clear().Append("OnEnable()"); DEBUG(sb); base.OnEnable(); if (m_Ray == null) { m_Ray = GetComponent(); } if (m_Pointer != null) { m_PointerScale = m_Pointer.transform.localScale; sb.Clear().Append("OnEnable() Get default pointer scale (").Append(m_PointerScale.x).Append(", ").Append(m_PointerScale.y).Append(", ").Append(m_PointerScale.z).Append(")"); DEBUG(sb); } } protected override void OnDisable() { sb.Clear().Append("OnDisable()"); DEBUG(sb); base.OnDisable(); ActivatePointer(false); ActivateRay(false); } protected override void Update() { /// Raycast base.Update(); if (printIntervalLog) { if (m_Ray != null) sb.Clear().Append("Update() ").Append(gameObject.name).Append(", m_Ray enabled: ").Append(m_Ray.enabled); DEBUG(sb); if (m_Pointer != null) sb.Clear().Append("Update() ").Append(gameObject.name).Append(", m_Pointer enabled: ").Append(m_Pointer.activeSelf); DEBUG(sb); } if (!IsInteractable()) { return; } /// Draw the ray and pointer. DrawRayPointer(); } #endregion #region Ray and Pointer private void ActivatePointer(bool active) { if (m_Pointer != null) { if (m_Pointer.activeSelf != active) { sb.Clear().Append("ActivatePointer() ").Append(gameObject.name).Append(" ").Append(active); DEBUG(sb); } m_Pointer.SetActive(active); } } private void ActivateRay(bool active) { if (m_Ray != null) { if (m_Ray.enabled != active) { sb.Clear().Append("ActivateRay() ").Append(gameObject.name).Append(" ").Append(active); DEBUG(sb); } m_Ray.enabled = active; } } private Vector3 GetIntersectionPosition(Camera cam, RaycastResult raycastResult) { if (cam == null) return Vector3.zero; float intersectionDistance = raycastResult.distance + cam.nearClipPlane; Vector3 intersectionPosition = cam.transform.forward * intersectionDistance + cam.transform.position; return intersectionPosition; } Vector3 rayStart = Vector3.zero, rayEnd = Vector3.zero; const float kRayLengthMin = 0.5f; private float m_RayLength = 10; protected Vector3 pointerPosition = Vector3.zero; private void DrawRayPointer() { Vector3 hit = GetIntersectionPosition(eventCamera, pointerData.pointerCurrentRaycast); rayStart = transform.position; if (raycastObject != null) { m_RayLength = Vector3.Distance(hit, rayStart); m_RayLength = m_RayLength > kRayLengthMin ? m_RayLength : kRayLengthMin; } if (LockPointer()) { Vector3 middle = new Vector3(0, 0, (m_RayLength - 0.2f) / 4); DrawCurveRay(rayStart, middle, rayEnd, m_RayStartWidth, m_RayEndWidth, m_RayMaterial); } else { rayEnd = rayStart + (transform.forward * (m_RayLength - 0.2f)); pointerPosition = rayStart + (transform.forward * m_RayLength); DrawRay(rayStart, rayEnd, m_RayStartWidth, m_RayEndWidth, m_RayMaterial); } DrawPointer(pointerPosition); } const float kPointerDistance = 10; private void DrawPointer(Vector3 position) { if (m_Pointer == null) { return; } m_Pointer.transform.position = position; m_Pointer.transform.rotation = Camera.main.transform.rotation; float distance = Vector3.Distance(position, Camera.main.transform.position); m_Pointer.transform.localScale = m_PointerScale * (distance / kPointerDistance); } private void DrawRay(Vector3 start, Vector3 end, float startWidth, float endWidth, Material material) { if (m_Ray == null) { return; } Vector3[] positions = new Vector3[] { start, end }; m_Ray.positionCount = positions.Length; m_Ray.SetPositions(positions); m_Ray.startWidth = startWidth; m_Ray.endWidth = endWidth; m_Ray.material = material; m_Ray.useWorldSpace = true; } private void DrawCurveRay(Vector3 start, Vector3 middle, Vector3 end, float startWidth, float endWidth, Material material) { if (m_Ray == null) { m_Ray = GetComponent(); } Vector3[] positions = GenerateBezierCurve3(50, start, middle, end); m_Ray.positionCount = positions.Length; m_Ray.SetPositions(positions); m_Ray.startWidth = startWidth; m_Ray.endWidth = endWidth; m_Ray.material = material; m_Ray.useWorldSpace = true; } Vector3[] GenerateBezierCurve2(int iteration, Vector3 start, Vector3 end) { Vector3[] points = new Vector3[iteration + 1]; for (int i = 0; i < iteration + 1; i++) { points.SetValue(start + ((end - start).normalized * (end - start).magnitude * i / iteration), i); } return points; } Vector3[] GenerateBezierCurve3(int iteration, Vector3 start, Vector3 middle, Vector3 end) { Vector3[] points1 = GenerateBezierCurve2(iteration, start, middle); Vector3[] points2 = GenerateBezierCurve2(iteration, start, end); Vector3[] points = new Vector3[iteration + 1]; for (int i = 0; i < iteration + 1; i++) { points.SetValue(points1[i] + ((points2[i] - points1[i]).normalized * (points2[i] - points1[i]).magnitude * i / iteration), i); } return points; } #endregion private bool IsInteractable() { ActivatePointer(m_Interactable); ActivateRay(m_Interactable && m_ShowRay); return m_Interactable; } /// For DrawRayPointer(), controls whether locking the pointer or not. protected virtual bool LockPointer() { return false; } } }