647 lines
23 KiB
C#
647 lines
23 KiB
C#
/*
|
|
* @author Valentin Simonov / http://va.lent.in/
|
|
*/
|
|
|
|
using System;
|
|
using TouchScript.Hit;
|
|
using UnityEngine;
|
|
using System.Collections.Generic;
|
|
using TouchScript.Layers.UI;
|
|
using TouchScript.Pointers;
|
|
using UnityEngine.EventSystems;
|
|
using UnityEngine.UI;
|
|
using System.Collections;
|
|
using TouchScript.Utils.Attributes;
|
|
|
|
namespace TouchScript.Layers
|
|
{
|
|
/// <summary>
|
|
/// A layer which combines all types of hit recognition into one: UI (Screen Space and World), 3D and 2D.
|
|
/// </summary>
|
|
/// <seealso cref="TouchScript.Layers.TouchLayer" />
|
|
[AddComponentMenu("TouchScript/Layers/Standard Layer")]
|
|
[HelpURL("http://touchscript.github.io/docs/html/T_TouchScript_Layers_StandardLayer.htm")]
|
|
public class StandardLayer : TouchLayer
|
|
{
|
|
#region Public properties
|
|
|
|
/// <summary>
|
|
/// Indicates that the layer should look for 3D objects in the scene. Set this to <c>false</c> to optimize hit processing.
|
|
/// </summary>
|
|
public bool Hit3DObjects
|
|
{
|
|
get { return hit3DObjects; }
|
|
set
|
|
{
|
|
hit3DObjects = value;
|
|
updateVariants();
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Indicates that the layer should look for 2D objects in the scene. Set this to <c>false</c> to optimize hit processing.
|
|
/// </summary>
|
|
public bool Hit2DObjects
|
|
{
|
|
get { return hit2DObjects; }
|
|
set
|
|
{
|
|
hit2DObjects = value;
|
|
updateVariants();
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Indicates that the layer should look for World UI objects in the scene. Set this to <c>false</c> to optimize hit processing.
|
|
/// </summary>
|
|
public bool HitWorldSpaceUI
|
|
{
|
|
get { return hitWorldSpaceUI; }
|
|
set
|
|
{
|
|
hitWorldSpaceUI = value;
|
|
setupInputModule();
|
|
updateVariants();
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Indicates that the layer should look for Screen Space UI objects in the scene. Set this to <c>false</c> to optimize hit processing.
|
|
/// </summary>
|
|
public bool HitScreenSpaceUI
|
|
{
|
|
get { return hitScreenSpaceUI; }
|
|
set
|
|
{
|
|
hitScreenSpaceUI = value;
|
|
setupInputModule();
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Indicates that the layer should query for <see cref="HitTest"/> components on target objects. Set this to <c>false</c> to optimize hit processing.
|
|
/// </summary>
|
|
public bool UseHitFilters
|
|
{
|
|
get { return useHitFilters; }
|
|
set { useHitFilters = value; }
|
|
}
|
|
|
|
/// <summary>
|
|
/// Gets or sets the layer mask which is used to select layers which should be touchable from this layer.
|
|
/// </summary>
|
|
/// <value>A mask to exclude objects from possibly touchable list.</value>
|
|
public LayerMask LayerMask
|
|
{
|
|
get { return layerMask; }
|
|
set { layerMask = value; }
|
|
}
|
|
|
|
/// <inheritdoc />
|
|
public override Vector3 WorldProjectionNormal
|
|
{
|
|
get
|
|
{
|
|
if (_camera == null) return Vector3.forward;
|
|
return _camera.transform.forward;
|
|
}
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region Private variables
|
|
|
|
private static Comparison<RaycastHitUI> _raycastHitUIComparerFunc = raycastHitUIComparerFunc;
|
|
private static Comparison<RaycastHit> _raycastHitComparerFunc = raycastHitComparerFunc;
|
|
private static Comparison<HitData> _hitDataComparerFunc = hitDataComparerFunc;
|
|
|
|
private static Dictionary<int, ProjectionParams> projectionParamsCache = new Dictionary<int, ProjectionParams>();
|
|
private static List<BaseRaycaster> raycasters;
|
|
|
|
private static List<RaycastHitUI> raycastHitUIList = new List<RaycastHitUI>(20);
|
|
private static List<RaycastHit> raycastHitList = new List<RaycastHit>(20);
|
|
private static List<HitData> hitList = new List<HitData>(20);
|
|
#if UNITY_5_3_OR_NEWER
|
|
private static RaycastHit[] raycastHits = new RaycastHit[20];
|
|
#endif
|
|
private static RaycastHit2D[] raycastHits2D = new RaycastHit2D[20];
|
|
|
|
#pragma warning disable CS0414
|
|
|
|
[SerializeField]
|
|
[HideInInspector]
|
|
private bool basicEditor = true;
|
|
|
|
[SerializeField]
|
|
[HideInInspector]
|
|
private bool advancedProps; // is used to save if advanced properties are opened or closed
|
|
|
|
#pragma warning restore CS0414
|
|
|
|
[SerializeField]
|
|
[HideInInspector]
|
|
private bool hitProps;
|
|
|
|
[SerializeField]
|
|
[ToggleLeft]
|
|
private bool hit3DObjects = true;
|
|
|
|
[SerializeField]
|
|
[ToggleLeft]
|
|
private bool hit2DObjects = true;
|
|
|
|
[SerializeField]
|
|
[ToggleLeft]
|
|
private bool hitWorldSpaceUI = true;
|
|
|
|
[SerializeField]
|
|
[ToggleLeft]
|
|
private bool hitScreenSpaceUI = true;
|
|
|
|
[SerializeField]
|
|
private LayerMask layerMask = -1;
|
|
|
|
[SerializeField]
|
|
[ToggleLeft]
|
|
private bool useHitFilters = false;
|
|
|
|
private bool lookForCameraObjects = false;
|
|
private TouchScriptInputModule inputModule;
|
|
|
|
/// <summary>
|
|
/// Camera.
|
|
/// </summary>
|
|
protected Camera _camera;
|
|
|
|
#endregion
|
|
|
|
#region Public methods
|
|
|
|
/// <inheritdoc />
|
|
public override HitResult Hit(IPointer pointer, out HitData hit)
|
|
{
|
|
if (base.Hit(pointer, out hit) != HitResult.Hit) return HitResult.Miss;
|
|
|
|
var result = HitResult.Miss;
|
|
|
|
if (hitScreenSpaceUI)
|
|
{
|
|
result = performSSUISearch(pointer, out hit);
|
|
switch (result)
|
|
{
|
|
case HitResult.Hit:
|
|
return result;
|
|
case HitResult.Discard:
|
|
hit = default(HitData);
|
|
return result;
|
|
}
|
|
}
|
|
|
|
if (lookForCameraObjects)
|
|
{
|
|
result = performWorldSearch(pointer, out hit);
|
|
switch (result)
|
|
{
|
|
case HitResult.Hit:
|
|
return result;
|
|
case HitResult.Discard:
|
|
hit = default(HitData);
|
|
return result;
|
|
}
|
|
}
|
|
|
|
return HitResult.Miss;
|
|
}
|
|
|
|
/// <inheritdoc />
|
|
public override ProjectionParams GetProjectionParams(Pointer pointer)
|
|
{
|
|
var press = pointer.GetPressData();
|
|
if ((press.Type == HitData.HitType.World2D) ||
|
|
(press.Type == HitData.HitType.World3D))
|
|
return layerProjectionParams;
|
|
|
|
var graphic = press.RaycastHitUI.Graphic;
|
|
if (graphic == null) return layerProjectionParams;
|
|
var canvas = graphic.canvas;
|
|
if (canvas == null) return layerProjectionParams;
|
|
|
|
ProjectionParams pp;
|
|
if (!projectionParamsCache.TryGetValue(canvas.GetInstanceID(), out pp))
|
|
{
|
|
// TODO: memory leak
|
|
pp = new WorldSpaceCanvasProjectionParams(canvas);
|
|
projectionParamsCache.Add(canvas.GetInstanceID(), pp);
|
|
}
|
|
return pp;
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region Unity methods
|
|
|
|
/// <inheritdoc />
|
|
protected override void Awake()
|
|
{
|
|
updateCamera();
|
|
updateVariants();
|
|
base.Awake();
|
|
}
|
|
|
|
private void OnEnable()
|
|
{
|
|
if (!Application.isPlaying) return;
|
|
TouchManager.Instance.FrameStarted += frameStartedHandler;
|
|
StartCoroutine(lateEnable());
|
|
}
|
|
|
|
private IEnumerator lateEnable()
|
|
{
|
|
// Need to wait while EventSystem initializes
|
|
yield return new WaitForEndOfFrame();
|
|
setupInputModule();
|
|
}
|
|
|
|
private void OnDisable()
|
|
{
|
|
if (!Application.isPlaying) return;
|
|
if (inputModule != null) inputModule.INTERNAL_Release();
|
|
if (TouchManager.Instance != null) TouchManager.Instance.FrameStarted -= frameStartedHandler;
|
|
}
|
|
|
|
[ContextMenu("Basic Editor")]
|
|
private void switchToBasicEditor()
|
|
{
|
|
basicEditor = true;
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region Protected functions
|
|
|
|
/// <summary>
|
|
/// Finds a camera.
|
|
/// </summary>
|
|
protected virtual void updateCamera()
|
|
{
|
|
_camera = GetComponent<Camera>();
|
|
}
|
|
|
|
/// <inheritdoc />
|
|
protected override ProjectionParams createProjectionParams()
|
|
{
|
|
return new CameraProjectionParams(_camera);
|
|
}
|
|
|
|
/// <inheritdoc />
|
|
protected override void setName()
|
|
{
|
|
if (string.IsNullOrEmpty(Name))
|
|
if (_camera != null) Name = _camera.name;
|
|
else Name = "Layer";
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region Private functions
|
|
|
|
private void setupInputModule()
|
|
{
|
|
if (inputModule == null)
|
|
{
|
|
if (!hitWorldSpaceUI && !hitScreenSpaceUI) return;
|
|
inputModule = TouchScriptInputModule.Instance;
|
|
if (inputModule != null) TouchScriptInputModule.Instance.INTERNAL_Retain();
|
|
}
|
|
else
|
|
{
|
|
if (hitWorldSpaceUI || hitScreenSpaceUI) return;
|
|
inputModule.INTERNAL_Release();
|
|
inputModule = null;
|
|
}
|
|
}
|
|
|
|
private HitResult performWorldSearch(IPointer pointer, out HitData hit)
|
|
{
|
|
hit = default(HitData);
|
|
|
|
if (_camera == null) return HitResult.Miss;
|
|
if ((_camera.enabled == false) || (_camera.gameObject.activeInHierarchy == false)) return HitResult.Miss;
|
|
var position = pointer.Position;
|
|
if (!_camera.pixelRect.Contains(position)) return HitResult.Miss;
|
|
|
|
hitList.Clear();
|
|
var ray = _camera.ScreenPointToRay(position);
|
|
|
|
int count;
|
|
bool exclusiveSet = manager.HasExclusive;
|
|
|
|
if (hit3DObjects)
|
|
{
|
|
#if UNITY_5_3_OR_NEWER
|
|
count = Physics.RaycastNonAlloc(ray, raycastHits, float.PositiveInfinity, layerMask);
|
|
#else
|
|
var raycastHits = Physics.RaycastAll(ray, float.PositiveInfinity, layerMask);
|
|
var count = raycastHits.Length;
|
|
#endif
|
|
|
|
// Try to do some optimizations if 2D and WS UI are not required
|
|
if (!hit2DObjects && !hitWorldSpaceUI)
|
|
{
|
|
RaycastHit raycast;
|
|
|
|
if (count == 0) return HitResult.Miss;
|
|
if (count > 1)
|
|
{
|
|
raycastHitList.Clear();
|
|
for (var i = 0; i < count; i++)
|
|
{
|
|
raycast = raycastHits[i];
|
|
if (exclusiveSet && !manager.IsExclusive(raycast.transform)) continue;
|
|
raycastHitList.Add(raycast);
|
|
}
|
|
if (raycastHitList.Count == 0) return HitResult.Miss;
|
|
|
|
raycastHitList.Sort(_raycastHitComparerFunc);
|
|
if (useHitFilters)
|
|
{
|
|
for (var i = 0; i < count; i++)
|
|
{
|
|
var result = doHit(pointer, raycastHitList[i], out hit);
|
|
if (result != HitResult.Miss) return result;
|
|
}
|
|
return HitResult.Miss;
|
|
}
|
|
hit = new HitData(raycastHitList[0], this);
|
|
return HitResult.Hit;
|
|
}
|
|
|
|
raycast = raycastHits[0];
|
|
if (exclusiveSet && !manager.IsExclusive(raycast.transform)) return HitResult.Miss;
|
|
if (useHitFilters) return doHit(pointer, raycast, out hit);
|
|
hit = new HitData(raycast, this);
|
|
return HitResult.Hit;
|
|
}
|
|
for (var i = 0; i < count; i++)
|
|
{
|
|
var raycast = raycastHits[i];
|
|
if (exclusiveSet && !manager.IsExclusive(raycast.transform)) continue;
|
|
hitList.Add(new HitData(raycastHits[i], this));
|
|
}
|
|
}
|
|
|
|
if (hit2DObjects)
|
|
{
|
|
count = Physics2D.GetRayIntersectionNonAlloc(ray, raycastHits2D, float.MaxValue, layerMask);
|
|
for (var i = 0; i < count; i++)
|
|
{
|
|
var raycast = raycastHits2D[i];
|
|
if (exclusiveSet && !manager.IsExclusive(raycast.transform)) continue;
|
|
hitList.Add(new HitData(raycast, this));
|
|
}
|
|
}
|
|
|
|
if (hitWorldSpaceUI)
|
|
{
|
|
raycastHitUIList.Clear();
|
|
if (raycasters == null) raycasters = TouchScriptInputModule.Instance.GetRaycasters();
|
|
count = raycasters.Count;
|
|
|
|
for (var i = 0; i < count; i++)
|
|
{
|
|
var raycaster = raycasters[i] as GraphicRaycaster;
|
|
if (raycaster == null) continue;
|
|
var canvas = TouchScriptInputModule.Instance.GetCanvasForRaycaster(raycaster);
|
|
if ((canvas == null) || (canvas.renderMode == RenderMode.ScreenSpaceOverlay) || (canvas.worldCamera != _camera)) continue;
|
|
performUISearchForCanvas(pointer, canvas, raycaster, _camera, float.MaxValue, ray);
|
|
}
|
|
|
|
count = raycastHitUIList.Count;
|
|
for (var i = 0; i < count; i++) hitList.Add(new HitData(raycastHitUIList[i], this));
|
|
}
|
|
|
|
count = hitList.Count;
|
|
if (hitList.Count == 0) return HitResult.Miss;
|
|
if (count > 1)
|
|
{
|
|
hitList.Sort(_hitDataComparerFunc);
|
|
if (useHitFilters)
|
|
{
|
|
for (var i = 0; i < count; ++i)
|
|
{
|
|
hit = hitList[i];
|
|
var result = checkHitFilters(pointer, hit);
|
|
if (result != HitResult.Miss) return result;
|
|
}
|
|
return HitResult.Miss;
|
|
}
|
|
else
|
|
{
|
|
hit = hitList[0];
|
|
return HitResult.Hit;
|
|
}
|
|
}
|
|
hit = hitList[0];
|
|
if (useHitFilters) return checkHitFilters(pointer, hit);
|
|
return HitResult.Hit;
|
|
}
|
|
|
|
private HitResult performSSUISearch(IPointer pointer, out HitData hit)
|
|
{
|
|
hit = default(HitData);
|
|
raycastHitUIList.Clear();
|
|
|
|
if (raycasters == null) raycasters = TouchScriptInputModule.Instance.GetRaycasters();
|
|
var count = raycasters.Count;
|
|
|
|
for (var i = 0; i < count; i++)
|
|
{
|
|
var raycaster = raycasters[i] as GraphicRaycaster;
|
|
if (raycaster == null) continue;
|
|
var canvas = TouchScriptInputModule.Instance.GetCanvasForRaycaster(raycaster);
|
|
if ((canvas == null) || (canvas.renderMode != RenderMode.ScreenSpaceOverlay)) continue;
|
|
performUISearchForCanvas(pointer, canvas, raycaster);
|
|
}
|
|
|
|
count = raycastHitUIList.Count;
|
|
if (count == 0) return HitResult.Miss;
|
|
if (count > 1)
|
|
{
|
|
raycastHitUIList.Sort(_raycastHitUIComparerFunc);
|
|
if (useHitFilters)
|
|
{
|
|
for (var i = 0; i < count; ++i)
|
|
{
|
|
var result = doHit(pointer, raycastHitUIList[i], out hit);
|
|
if (result != HitResult.Miss) return result;
|
|
}
|
|
return HitResult.Miss;
|
|
}
|
|
|
|
hit = new HitData(raycastHitUIList[0], this, true);
|
|
return HitResult.Hit;
|
|
}
|
|
|
|
if (useHitFilters) return doHit(pointer, raycastHitUIList[0], out hit);
|
|
hit = new HitData(raycastHitUIList[0], this, true);
|
|
return HitResult.Hit;
|
|
}
|
|
|
|
private void performUISearchForCanvas(IPointer pointer, Canvas canvas, GraphicRaycaster raycaster, Camera eventCamera = null, float maxDistance = float.MaxValue, Ray ray = default(Ray))
|
|
{
|
|
var position = pointer.Position;
|
|
var foundGraphics = GraphicRegistry.GetGraphicsForCanvas(canvas);
|
|
var count = foundGraphics.Count;
|
|
var exclusiveSet = manager.HasExclusive;
|
|
|
|
for (var i = 0; i < count; i++)
|
|
{
|
|
var graphic = foundGraphics[i];
|
|
var t = graphic.transform;
|
|
|
|
if (exclusiveSet && !manager.IsExclusive(t)) continue;
|
|
|
|
if ((layerMask.value != -1) && ((layerMask.value & (1 << graphic.gameObject.layer)) == 0)) continue;
|
|
|
|
// -1 means it hasn't been processed by the canvas, which means it isn't actually drawn
|
|
if ((graphic.depth == -1) || !graphic.raycastTarget)
|
|
continue;
|
|
|
|
if (!RectTransformUtility.RectangleContainsScreenPoint(graphic.rectTransform, position, eventCamera))
|
|
continue;
|
|
|
|
if (graphic.Raycast(position, eventCamera))
|
|
{
|
|
if (raycaster.ignoreReversedGraphics)
|
|
if (eventCamera == null)
|
|
{
|
|
// If we dont have a camera we know that we should always be facing forward
|
|
var dir = t.rotation * Vector3.forward;
|
|
if (Vector3.Dot(Vector3.forward, dir) <= 0) continue;
|
|
}
|
|
else
|
|
{
|
|
// If we have a camera compare the direction against the cameras forward.
|
|
var cameraFoward = eventCamera.transform.rotation * Vector3.forward;
|
|
var dir = t.rotation * Vector3.forward;
|
|
if (Vector3.Dot(cameraFoward, dir) <= 0) continue;
|
|
}
|
|
|
|
float distance = 0;
|
|
|
|
if ((eventCamera == null) || (canvas.renderMode == RenderMode.ScreenSpaceOverlay)) {}
|
|
else
|
|
{
|
|
var transForward = t.forward;
|
|
// http://geomalgorithms.com/a06-_intersect-2.html
|
|
distance = Vector3.Dot(transForward, t.position - ray.origin) / Vector3.Dot(transForward, ray.direction);
|
|
|
|
// Check to see if the go is behind the camera.
|
|
if (distance < 0) continue;
|
|
if (distance >= maxDistance) continue;
|
|
}
|
|
|
|
raycastHitUIList.Add(
|
|
new RaycastHitUI()
|
|
{
|
|
Target = graphic.transform,
|
|
Raycaster = raycaster,
|
|
Graphic = graphic,
|
|
GraphicIndex = raycastHitUIList.Count,
|
|
Depth = graphic.depth,
|
|
SortingLayer = canvas.sortingLayerID,
|
|
SortingOrder = canvas.sortingOrder,
|
|
Distance = distance
|
|
});
|
|
}
|
|
}
|
|
}
|
|
|
|
private HitResult doHit(IPointer pointer, RaycastHitUI raycastHit, out HitData hit)
|
|
{
|
|
hit = new HitData(raycastHit, this, true);
|
|
return checkHitFilters(pointer, hit);
|
|
}
|
|
|
|
private HitResult doHit(IPointer pointer, RaycastHit raycastHit, out HitData hit)
|
|
{
|
|
hit = new HitData(raycastHit, this);
|
|
return checkHitFilters(pointer, hit);
|
|
}
|
|
|
|
private void updateVariants()
|
|
{
|
|
lookForCameraObjects = _camera != null && (hit3DObjects || hit2DObjects || hitWorldSpaceUI);
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region Compare functions
|
|
|
|
private static int raycastHitUIComparerFunc(RaycastHitUI lhs, RaycastHitUI rhs)
|
|
{
|
|
if (lhs.SortingLayer != rhs.SortingLayer)
|
|
{
|
|
// Uses the layer value to properly compare the relative order of the layers.
|
|
var rid = SortingLayer.GetLayerValueFromID(rhs.SortingLayer);
|
|
var lid = SortingLayer.GetLayerValueFromID(lhs.SortingLayer);
|
|
return rid.CompareTo(lid);
|
|
}
|
|
|
|
if (lhs.SortingOrder != rhs.SortingOrder)
|
|
return rhs.SortingOrder.CompareTo(lhs.SortingOrder);
|
|
|
|
if (lhs.Depth != rhs.Depth)
|
|
return rhs.Depth.CompareTo(lhs.Depth);
|
|
|
|
if (!Mathf.Approximately(lhs.Distance, rhs.Distance))
|
|
return lhs.Distance.CompareTo(rhs.Distance);
|
|
|
|
return lhs.GraphicIndex.CompareTo(rhs.GraphicIndex);
|
|
}
|
|
|
|
private static int raycastHitComparerFunc(RaycastHit lhs, RaycastHit rhs)
|
|
{
|
|
return lhs.distance < rhs.distance ? -1 : 1;
|
|
}
|
|
|
|
private static int hitDataComparerFunc(HitData lhs, HitData rhs)
|
|
{
|
|
if (lhs.SortingLayer != rhs.SortingLayer)
|
|
{
|
|
// Uses the layer value to properly compare the relative order of the layers.
|
|
var rid = SortingLayer.GetLayerValueFromID(rhs.SortingLayer);
|
|
var lid = SortingLayer.GetLayerValueFromID(lhs.SortingLayer);
|
|
return rid.CompareTo(lid);
|
|
}
|
|
|
|
if (lhs.SortingOrder != rhs.SortingOrder)
|
|
return rhs.SortingOrder.CompareTo(lhs.SortingOrder);
|
|
|
|
if ((lhs.Type == HitData.HitType.UI) && (rhs.Type == HitData.HitType.UI))
|
|
{
|
|
if (lhs.RaycastHitUI.Depth != rhs.RaycastHitUI.Depth)
|
|
return rhs.RaycastHitUI.Depth.CompareTo(lhs.RaycastHitUI.Depth);
|
|
|
|
if (!Mathf.Approximately(lhs.Distance, rhs.Distance))
|
|
return lhs.Distance.CompareTo(rhs.Distance);
|
|
|
|
if (lhs.RaycastHitUI.GraphicIndex != rhs.RaycastHitUI.GraphicIndex)
|
|
return rhs.RaycastHitUI.GraphicIndex.CompareTo(lhs.RaycastHitUI.GraphicIndex);
|
|
}
|
|
|
|
return lhs.Distance < rhs.Distance ? -1 : 1;
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region Event handlers
|
|
|
|
private void frameStartedHandler(object sender, EventArgs eventArgs)
|
|
{
|
|
raycasters = null;
|
|
}
|
|
|
|
#endregion
|
|
}
|
|
} |