File size: 9,685 Bytes
18a519f | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 | #if PACKAGE_DOCS_GENERATION || UNITY_INPUT_SYSTEM_ENABLE_UI
using System;
using System.Collections.Generic;
using UnityEngine.EventSystems;
using UnityEngine.InputSystem.Utilities;
using UnityEngine.Serialization;
using UnityEngine.UI;
namespace UnityEngine.InputSystem.UI
{
/// <summary>
/// Raycasting implementation for use with <see cref="TrackedDevice"/>s.
/// </summary>
/// <remarks>
/// This component needs to be added alongside the <c>Canvas</c> component. Usually, raycasting is
/// performed by the <c>GraphicRaycaster</c> component found there but for 3D raycasting necessary for
/// tracked devices, this component is required.
/// </remarks>
[AddComponentMenu("Event/Tracked Device Raycaster")]
[RequireComponent(typeof(Canvas))]
public class TrackedDeviceRaycaster : BaseRaycaster
{
private struct RaycastHitData
{
public RaycastHitData(Graphic graphic, Vector3 worldHitPosition, Vector2 screenPosition, float distance)
{
this.graphic = graphic;
this.worldHitPosition = worldHitPosition;
this.screenPosition = screenPosition;
this.distance = distance;
}
public Graphic graphic { get; }
public Vector3 worldHitPosition { get; }
public Vector2 screenPosition { get; }
public float distance { get; }
}
public override Camera eventCamera
{
get
{
var myCanvas = canvas;
return myCanvas != null ? myCanvas.worldCamera : null;
}
}
public LayerMask blockingMask
{
get => m_BlockingMask;
set => m_BlockingMask = value;
}
public bool checkFor3DOcclusion
{
get => m_CheckFor3DOcclusion;
set => m_CheckFor3DOcclusion = value;
}
public bool checkFor2DOcclusion
{
get => m_CheckFor2DOcclusion;
set => m_CheckFor2DOcclusion = value;
}
public bool ignoreReversedGraphics
{
get => m_IgnoreReversedGraphics;
set => m_IgnoreReversedGraphics = value;
}
public float maxDistance
{
get => m_MaxDistance;
set => m_MaxDistance = value;
}
protected override void OnEnable()
{
base.OnEnable();
s_Instances.AppendWithCapacity(this);
}
protected override void OnDisable()
{
var index = s_Instances.IndexOfReference(this);
if (index != -1)
s_Instances.RemoveAtByMovingTailWithCapacity(index);
base.OnDisable();
}
public override void Raycast(PointerEventData eventData, List<RaycastResult> resultAppendList)
{
if (eventData is ExtendedPointerEventData trackedEventData && trackedEventData.pointerType == UIPointerType.Tracked)
PerformRaycast(trackedEventData, resultAppendList);
}
// Cached instances for raycasts hits to minimize GC.
[NonSerialized] private List<RaycastHitData> m_RaycastResultsCache = new List<RaycastHitData>();
internal void PerformRaycast(ExtendedPointerEventData eventData, List<RaycastResult> resultAppendList)
{
if (canvas == null)
return;
if (eventCamera == null)
return;
var ray = new Ray(eventData.trackedDevicePosition, eventData.trackedDeviceOrientation * Vector3.forward);
var hitDistance = m_MaxDistance;
#if UNITY_INPUT_SYSTEM_ENABLE_PHYSICS
if (m_CheckFor3DOcclusion)
{
if (Physics.Raycast(ray, out var hit, maxDistance: hitDistance, layerMask: m_BlockingMask))
hitDistance = hit.distance;
}
#endif
#if UNITY_INPUT_SYSTEM_ENABLE_PHYSICS2D
if (m_CheckFor2DOcclusion)
{
var raycastDistance = hitDistance;
var hits = Physics2D.GetRayIntersection(ray, raycastDistance, m_BlockingMask);
if (hits.collider != null)
hitDistance = hits.distance;
}
#endif
m_RaycastResultsCache.Clear();
SortedRaycastGraphics(canvas, ray, m_RaycastResultsCache);
// Now that we have a list of sorted hits, process any extra settings and filters.
for (var i = 0; i < m_RaycastResultsCache.Count; i++)
{
var validHit = true;
var hitData = m_RaycastResultsCache[i];
var go = hitData.graphic.gameObject;
if (m_IgnoreReversedGraphics)
{
var forward = ray.direction;
var goDirection = go.transform.rotation * Vector3.forward;
validHit = Vector3.Dot(forward, goDirection) > 0;
}
validHit &= hitData.distance < hitDistance;
if (validHit)
{
var castResult = new RaycastResult
{
gameObject = go,
module = this,
distance = hitData.distance,
index = resultAppendList.Count,
depth = hitData.graphic.depth,
worldPosition = hitData.worldHitPosition,
screenPosition = hitData.screenPosition,
};
resultAppendList.Add(castResult);
}
}
}
internal static InlinedArray<TrackedDeviceRaycaster> s_Instances;
private static readonly List<RaycastHitData> s_SortedGraphics = new List<RaycastHitData>();
private void SortedRaycastGraphics(Canvas canvas, Ray ray, List<RaycastHitData> results)
{
var graphics = GraphicRegistry.GetGraphicsForCanvas(canvas);
s_SortedGraphics.Clear();
for (var i = 0; i < graphics.Count; ++i)
{
var graphic = graphics[i];
if (graphic.depth == -1)
continue;
Vector3 worldPos;
float distance;
if (RayIntersectsRectTransform(graphic.rectTransform, ray, out worldPos, out distance))
{
Vector2 screenPos = eventCamera.WorldToScreenPoint(worldPos);
// mask/image intersection - See Unity docs on eventAlphaThreshold for when this does anything
if (graphic.Raycast(screenPos, eventCamera))
{
s_SortedGraphics.Add(new RaycastHitData(graphic, worldPos, screenPos, distance));
}
}
}
s_SortedGraphics.Sort((g1, g2) => g2.graphic.depth.CompareTo(g1.graphic.depth));
results.AddRange(s_SortedGraphics);
}
private static bool RayIntersectsRectTransform(RectTransform transform, Ray ray, out Vector3 worldPosition, out float distance)
{
var corners = new Vector3[4];
transform.GetWorldCorners(corners);
var plane = new Plane(corners[0], corners[1], corners[2]);
float enter;
if (plane.Raycast(ray, out enter))
{
var intersection = ray.GetPoint(enter);
var bottomEdge = corners[3] - corners[0];
var leftEdge = corners[1] - corners[0];
var bottomDot = Vector3.Dot(intersection - corners[0], bottomEdge);
var leftDot = Vector3.Dot(intersection - corners[0], leftEdge);
// If the intersection is right of the left edge and above the bottom edge.
if (leftDot >= 0 && bottomDot >= 0)
{
var topEdge = corners[1] - corners[2];
var rightEdge = corners[3] - corners[2];
var topDot = Vector3.Dot(intersection - corners[2], topEdge);
var rightDot = Vector3.Dot(intersection - corners[2], rightEdge);
//If the intersection is left of the right edge, and below the top edge
if (topDot >= 0 && rightDot >= 0)
{
worldPosition = intersection;
distance = enter;
return true;
}
}
}
worldPosition = Vector3.zero;
distance = 0;
return false;
}
[FormerlySerializedAs("ignoreReversedGraphics")]
[SerializeField]
private bool m_IgnoreReversedGraphics;
[FormerlySerializedAs("checkFor2DOcclusion")]
[SerializeField]
private bool m_CheckFor2DOcclusion;
[FormerlySerializedAs("checkFor3DOcclusion")]
[SerializeField]
private bool m_CheckFor3DOcclusion;
[Tooltip("Maximum distance (in 3D world space) that rays are traced to find a hit.")]
[SerializeField] private float m_MaxDistance = 1000;
[SerializeField]
private LayerMask m_BlockingMask;
[NonSerialized]
private Canvas m_Canvas;
private Canvas canvas
{
get
{
if (m_Canvas != null)
return m_Canvas;
m_Canvas = GetComponent<Canvas>();
return m_Canvas;
}
}
}
}
#endif
|