//========= Copyright 2016-2018, HTC Corporation. All rights reserved. =========== using HTC.UnityPlugin.Utility; using System; using System.Collections.Generic; using System.Collections.ObjectModel; using UnityEngine; using UnityEngine.EventSystems; namespace HTC.UnityPlugin.Pointer3D { public enum RaycastMode { DefaultRaycast, Projection, Projectile, } // Contains and handles Pointer3DEventDatas, derived class should implement their own Pointer3DEventData // and add into buttonEventDataList, Pointer3DInputModule then will send eventData to the raycast target. public class Pointer3DRaycaster : BaseFallbackCamRaycaster { public const float MIN_SEGMENT_DISTANCE = 0.01f; private ReadOnlyCollection buttonEventDataListReadOnly; private ReadOnlyCollection sortedRaycastResultsReadOnly; private ReadOnlyCollection breakPointsReadOnly; protected readonly List buttonEventDataList = new List(); protected readonly List sortedRaycastResults = new List(); protected readonly List breakPoints = new List(); public float dragThreshold = 0.02f; public float clickInterval = 0.3f; public bool showDebugRay = true; public Pointer3DEventData HoverEventData { get { return buttonEventDataList.Count > 0 ? buttonEventDataList[0] : null; } } public ReadOnlyCollection ButtonEventDataList { get { return buttonEventDataListReadOnly ?? (buttonEventDataListReadOnly = buttonEventDataList.AsReadOnly()); } } public ReadOnlyCollection SortedRaycastResults { get { return sortedRaycastResultsReadOnly ?? (sortedRaycastResultsReadOnly = sortedRaycastResults.AsReadOnly()); } } public ReadOnlyCollection BreakPoints { get { return breakPointsReadOnly ?? (breakPointsReadOnly = breakPoints.AsReadOnly()); } } public virtual Vector2 GetScrollDelta() { return Vector2.zero; } #if UNITY_EDITOR protected override void OnValidate() { base.OnValidate(); // auto create RaySegmentGenerator if raycast mode is set when using previous version // and reset to DefaultRaycast since raycastMode is obsoleted if (m_raycastMode != RaycastMode.DefaultRaycast) { if (Application.isPlaying) { SetLagacyRaycastMode(m_raycastMode); m_raycastMode = RaycastMode.DefaultRaycast; } else { // force saving changes of raycastMode // use delayCall because sometimes scene is not loaded and can't be marked dirty at this time UnityEditor.EditorApplication.delayCall += new UnityEditor.EditorApplication.CallbackFunction(() => { Debug.LogWarning("The RaycastMode." + m_raycastMode + " setting has been replaced by adding " + (m_raycastMode == RaycastMode.Projection ? "ProjectionGenerator" : "ProjectileGenerator") + " component. Please save the scene to preserve this changes."); SetLagacyRaycastMode(m_raycastMode); m_raycastMode = RaycastMode.DefaultRaycast; UnityEditor.SceneManagement.EditorSceneManager.MarkSceneDirty(gameObject.scene); UnityEditor.EditorUtility.SetDirty(this); }); } } } #endif protected override void Start() { base.Start(); if (m_raycastMode != RaycastMode.DefaultRaycast) { SetLagacyRaycastMode(m_raycastMode); m_raycastMode = RaycastMode.DefaultRaycast; } } // override OnEnable & OnDisable on purpose so that this BaseRaycaster won't be registered into RaycasterManager protected override void OnEnable() { //base.OnEnable(); Pointer3DInputModule.AddRaycaster(this); } protected override void OnDisable() { //base.OnDisable(); Pointer3DInputModule.RemoveRaycaster(this); } public virtual void CleanUpRaycast() { sortedRaycastResults.Clear(); breakPoints.Clear(); } // invoke by Pointer3DInputModule public virtual void Raycast() { sortedRaycastResults.Clear(); breakPoints.Clear(); var zScale = transform.lossyScale.z; var amountDistance = (FarDistance - NearDistance) * zScale; var origin = transform.TransformPoint(0f, 0f, NearDistance); breakPoints.Add(origin); bool hasNext = true; Vector3 direction; float distance; Ray ray; RaycastResult firstHit = default(RaycastResult); var generator = CurrentSegmentGenerator(); if (ReferenceEquals(generator, null)) { // process default raycast direction = transform.forward; distance = amountDistance; ray = new Ray(origin, direction); // move event camera in place eventCamera.farClipPlane = eventCamera.nearClipPlane + distance; eventCamera.transform.position = ray.origin - (ray.direction * eventCamera.nearClipPlane); eventCamera.transform.rotation = Quaternion.LookRotation(ray.direction, transform.up); ForeachRaycastMethods(ray, distance, sortedRaycastResults); firstHit = FirstRaycastResult(); breakPoints.Add(firstHit.isValid ? firstHit.worldPosition : ray.GetPoint(distance)); #if UNITY_EDITOR if (showDebugRay) { Debug.DrawLine(breakPoints[0], breakPoints[1], firstHit.isValid ? Color.green : Color.red); } #endif } else { generator.ResetSegments(); do { hasNext = generator.NextSegment(out direction, out distance); if (distance < MIN_SEGMENT_DISTANCE) { Debug.LogWarning("RaySegment.distance cannot smaller than " + MIN_SEGMENT_DISTANCE + "! distance=" + distance.ToString("0.000")); break; } distance *= zScale; if (distance < amountDistance) { amountDistance -= distance; } else { distance = amountDistance; amountDistance = 0f; } ray = new Ray(origin, direction); // move event camera in place eventCamera.farClipPlane = eventCamera.nearClipPlane + distance; eventCamera.transform.position = ray.origin - (ray.direction * eventCamera.nearClipPlane); eventCamera.transform.rotation = Quaternion.LookRotation(ray.direction, transform.up); ForeachRaycastMethods(ray, distance, sortedRaycastResults); firstHit = FirstRaycastResult(); // end loop if raycast hit if (firstHit.isValid) { breakPoints.Add(firstHit.worldPosition); #if UNITY_EDITOR if (showDebugRay) { Debug.DrawLine(breakPoints[breakPoints.Count - 2], breakPoints[breakPoints.Count - 1], Color.green); } #endif break; } // otherwise, shift to next iteration origin = ray.GetPoint(distance); breakPoints.Add(origin); #if UNITY_EDITOR if (showDebugRay) { Debug.DrawLine(breakPoints[breakPoints.Count - 2], breakPoints[breakPoints.Count - 1], Color.red); } #endif } while (hasNext && amountDistance > 0f); } } // called by StandaloneInputModule, not supported public override void Raycast(PointerEventData eventData, List resultAppendList) { } public RaycastResult FirstRaycastResult() { for (int i = 0, imax = sortedRaycastResults.Count; i < imax; ++i) { if (!sortedRaycastResults[i].isValid) { continue; } return sortedRaycastResults[i]; } return default(RaycastResult); } #region managing segment generators private IndexedSet generators = new IndexedSet(); public void AddGenerator(IRaySegmentGenerator generator) { generators.AddUnique(generator); } public void RemoveGenerator(IRaySegmentGenerator generator) { generators.Remove(generator); } // returns first enabled generator in generators, doesn't gaurantee any orders public IRaySegmentGenerator CurrentSegmentGenerator() { for (int i = 0, imax = generators.Count; i < imax; ++i) { if (generators[i].enabled) { return generators[i]; } } return null; } // enable or create TGenerator, disable others public TGenerator ForceEnableSegmentGenerator() where TGenerator : MonoBehaviour, IRaySegmentGenerator { var gen = default(TGenerator); var genFound = false; var gens = ListPool.Get(); GetComponents(gens); for (int i = 0, imax = gens.Count; i < imax; ++i) { if (genFound || !(gens[i] is TGenerator)) { gens[i].enabled = false; } else { gens[i].enabled = true; gen = gens[i] as TGenerator; genFound = true; } } ListPool.Release(gens); if (!genFound) { gen = gameObject.AddComponent(); } return gen; } public void ForceDisableSegmentGenerators() { var gens = ListPool.Get(); GetComponents(gens); for (int i = 0, imax = gens.Count; i < imax; ++i) { gens[i].enabled = false; } ListPool.Release(gens); } #endregion #region obsolete interfaces [HideInInspector] [SerializeField] private RaycastMode m_raycastMode = RaycastMode.DefaultRaycast; [HideInInspector] [SerializeField] private float m_velocity = 2f; [HideInInspector] [SerializeField] private Vector3 m_gravity = Vector3.down; [Obsolete("Please use component ProjectionGenerator / ProjectileGenerator")] public RaycastMode raycastMode { get { return m_raycastMode; } set { m_raycastMode = value; SetLagacyRaycastMode(value); } } [Obsolete("Please use ProjectionGenerator.velocity / ProjectileGenerator.velocity")] public float velocity { get { return m_velocity; } set { m_velocity = value; var gen = CurrentSegmentGenerator(); if (gen != null) { if (gen is ProjectionGenerator) { ((ProjectionGenerator)gen).velocity = value; } else if (gen is ProjectileGenerator) { ((ProjectileGenerator)gen).velocity = value; } } } } [Obsolete("Please use ProjectionGenerator.gravity / ProjectileGenerator.gravity")] public Vector3 gravity { get { return m_gravity; } set { m_gravity = value; var gen = CurrentSegmentGenerator(); if (gen != null) { if (gen is ProjectionGenerator) { ((ProjectionGenerator)gen).gravity = value; } else if (gen is ProjectileGenerator) { ((ProjectileGenerator)gen).gravity = value; } } } } [Obsolete("Please use component ProjectionGenerator / ProjectileGenerator")] protected virtual void InitSegmentGenerator() { } // for backward compatible private void SetLagacyRaycastMode(RaycastMode mode) { switch (mode) { case RaycastMode.Projection: { var gen = ForceEnableSegmentGenerator(); gen.velocity = m_velocity; gen.gravity = m_gravity; break; } case RaycastMode.Projectile: { var gen = ForceEnableSegmentGenerator(); gen.velocity = m_velocity; gen.gravity = m_gravity; break; } default: { ForceDisableSegmentGenerators(); break; } } } #endregion public override string ToString() { var str = string.Empty; str += "Raycaster path: " + Pointer3DInputModule.PrintGOPath(gameObject) + "(" + GetType().Name + ")\n"; str += "Raycaster transform: " + "pos" + transform.position.ToString("0.00") + " " + "rot" + transform.eulerAngles.ToString("0.0") + "\n"; for (int i = 0, imax = buttonEventDataList.Count; i < imax; ++i) { var eventData = buttonEventDataList[i]; if (eventData == null) { continue; } if (eventData.eligibleForClick || (i == 0 && eventData.pointerEnter != null)) // is hover event? { str += "EventData: [" + i + "]\n"; str += eventData.ToString(); } } return str; } } }