ARRaycastManager.cs
13 KB
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
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
using System;
using System.Collections.Generic;
using Unity.Collections;
using UnityEngine.XR.ARSubsystems;
namespace UnityEngine.XR.ARFoundation
{
/// <summary>
/// Manages an <c>XRRaycastSubsystem</c>, exposing raycast functionality in ARFoundation. Use this component
/// to raycast against trackables (i.e., detected features in the physical environment) when they do not have
/// a presence in the Physics world.
/// </summary>
[DefaultExecutionOrder(ARUpdateOrder.k_RaycastManager)]
[DisallowMultipleComponent]
[RequireComponent(typeof(ARSessionOrigin))]
[HelpURL(HelpUrls.ApiWithNamespace + nameof(ARRaycastManager) + ".html")]
public sealed class ARRaycastManager : ARTrackableManager<
XRRaycastSubsystem, XRRaycastSubsystemDescriptor,
#if UNITY_2020_2_OR_NEWER
XRRaycastSubsystem.Provider,
#endif
XRRaycast, ARRaycast>
{
[SerializeField]
[Tooltip("If not null, instantiates this prefab for each raycast.")]
GameObject m_RaycastPrefab;
/// <summary>
/// If not null, this prefab will be instantiated for each <see cref="ARRaycast"/>.
/// </summary>
public GameObject raycastPrefab
{
get => m_RaycastPrefab;
set => m_RaycastPrefab = value;
}
/// <summary>
/// Cast a ray from a point in screen space against trackables, i.e., detected features such as planes.
/// </summary>
/// <param name="screenPoint">The point, in device screen pixels, from which to cast.</param>
/// <param name="hitResults">Contents are replaced with the raycast results, if successful.</param>
/// <param name="trackableTypes">(Optional) The types of trackables to cast against.</param>
/// <returns>True if the raycast hit a trackable in the <paramref name="trackableTypes"/></returns>
#region ARRaycastManager_Raycast_screenPoint
public bool Raycast(
Vector2 screenPoint,
List<ARRaycastHit> hitResults,
TrackableType trackableTypes = TrackableType.All)
#endregion
{
if (subsystem == null)
return false;
if (hitResults == null)
throw new ArgumentNullException("hitResults");
var nativeHits = m_RaycastViewportDelegate(screenPoint, trackableTypes, Allocator.Temp);
var originTransform = sessionOrigin.camera != null ? sessionOrigin.camera.transform : sessionOrigin.trackablesParent;
return TransformAndDisposeNativeHitResults(nativeHits, hitResults, originTransform.position);
}
/// <summary>
/// Cast a <c>Ray</c> against trackables, i.e., detected features such as planes.
/// </summary>
/// <param name="ray">The <c>Ray</c>, in Unity world space, to cast.</param>
/// <param name="hitResults">Contents are replaced with the raycast results, if successful.</param>
/// <param name="trackableTypes">(Optional) The types of trackables to cast against.</param>
/// <returns>True if the raycast hit a trackable in the <paramref name="trackableTypes"/></returns>
#region ARRaycastManager_Raycast_ray
public bool Raycast(
Ray ray,
List<ARRaycastHit> hitResults,
TrackableType trackableTypes = TrackableType.All)
#endregion
{
if (subsystem == null)
return false;
if (hitResults == null)
throw new ArgumentNullException(nameof(hitResults));
var sessionSpaceRay = sessionOrigin.trackablesParent.InverseTransformRay(ray);
var nativeHits = m_RaycastRayDelegate(sessionSpaceRay, trackableTypes, Allocator.Temp);
return TransformAndDisposeNativeHitResults(nativeHits, hitResults, ray.origin);
}
/// <summary>
/// Creates an <see cref="ARRaycast"/> that updates automatically. <see cref="ARRaycast"/>s will
/// continue to update until you remove them with <see cref="RemoveRaycast(ARRaycast)"/> or disable
/// this component.
/// </summary>
/// <param name="screenPoint">A point on the screen, in pixels.</param>
/// <param name="estimatedDistance">The estimated distance to the intersection point.
/// This can be used to determine a potential intersection before the environment has been fully mapped.</param>
/// <returns>A new <see cref="ARRaycast"/> if successful; otherwise `null`.</returns>
#region ARRaycastManager_AddRaycast_screenPoint
public ARRaycast AddRaycast(Vector2 screenPoint, float estimatedDistance)
#endregion
{
if (subsystem == null)
return null;
var normalizedScreenPoint = new Vector2(
Mathf.Clamp01(screenPoint.x / Screen.width),
Mathf.Clamp01(screenPoint.y / Screen.height));
if (subsystem.TryAddRaycast(normalizedScreenPoint, estimatedDistance, out XRRaycast sessionRelativeData))
{
return CreateTrackableImmediate(sessionRelativeData);
}
if (sessionOrigin.camera && subsystem.TryAddRaycast(ScreenPointToSessionSpaceRay(screenPoint), estimatedDistance, out sessionRelativeData))
{
return CreateTrackableImmediate(sessionRelativeData);
}
return null;
}
/// <summary>
/// Removes an existing <see cref="ARRaycast"/>.
/// </summary>
/// <param name="raycast">The <see cref="ARRaycast"/> to remove.</param>
/// <exception cref="ArgumentNullException">Thrown is <paramref name="raycast"/> is `null`.</exception>
public void RemoveRaycast(ARRaycast raycast)
{
if (raycast == null)
throw new ArgumentNullException(nameof(raycast));
subsystem?.RemoveRaycast(raycast.trackableId);
}
/// <summary>
/// Gets the prefab that should be instantiated for each <see cref="ARRaycast"/>. May be `null`.
/// </summary>
/// <returns>The prefab that should be instantiated for each <see cref="ARRaycast"/>.</returns>
protected override GameObject GetPrefab() => m_RaycastPrefab;
static void TransformAndSortRaycastResults(
Transform transform,
NativeArray<XRRaycastHit> nativeHits,
List<ARRaycastHit> managedHits,
Vector3 rayOrigin)
{
foreach (var nativeHit in nativeHits)
{
float distanceInWorldSpace = (nativeHit.pose.position - rayOrigin).magnitude;
managedHits.Add(new ARRaycastHit(nativeHit, distanceInWorldSpace, transform));
}
}
/// <summary>
/// Allows AR managers to register themselves as a raycaster.
/// Raycasters be used as a fallback method if the AR platform does
/// not support raycasting using arbitrary <c>Ray</c>s.
/// </summary>
/// <param name="raycaster">A raycaster implementing the IRaycast interface.</param>
internal void RegisterRaycaster(IRaycaster raycaster)
{
if (!m_Raycasters.Contains(raycaster))
m_Raycasters.Add(raycaster);
}
/// <summary>
/// Unregisters a raycaster previously registered with <see cref="RegisterRaycaster(IRaycaster)"/>.
/// </summary>
/// <param name="raycaster">A raycaster to use in the fallback case.</param>
internal void UnregisterRaycaster(IRaycaster raycaster)
{
if (m_Raycasters != null)
m_Raycasters.Remove(raycaster);
}
/// <summary>
/// Invoked just after the subsystem has been `Start`ed. Used to set raycast delegates internally.
/// </summary>
protected override void OnAfterStart()
{
var desc =
#if UNITY_2020_2_OR_NEWER
subsystem.subsystemDescriptor;
#else
subsystem.SubsystemDescriptor;
#endif
if (desc.supportsViewportBasedRaycast)
{
m_RaycastViewportDelegate = RaycastViewport;
}
else
{
m_RaycastViewportDelegate = RaycastViewportAsRay;
}
if (desc.supportsWorldBasedRaycast)
{
m_RaycastRayDelegate = RaycastRay;
}
else
{
m_RaycastRayDelegate = RaycastFallback;
}
var raycasters = GetComponents(typeof(IRaycaster));
foreach (var raycaster in raycasters)
RegisterRaycaster((IRaycaster)raycaster);
}
Ray ScreenPointToSessionSpaceRay(Vector2 screenPoint)
{
var worldSpaceRay = sessionOrigin.camera.ScreenPointToRay(screenPoint);
return sessionOrigin.trackablesParent.InverseTransformRay(worldSpaceRay);
}
NativeArray<XRRaycastHit> RaycastViewportAsRay(
Vector2 screenPoint,
TrackableType trackableTypeMask,
Allocator allocator)
{
if (sessionOrigin.camera == null)
return new NativeArray<XRRaycastHit>(0, allocator);
return m_RaycastRayDelegate(ScreenPointToSessionSpaceRay(screenPoint), trackableTypeMask, allocator);
}
NativeArray<XRRaycastHit> RaycastViewport(
Vector2 screenPoint,
TrackableType trackableTypeMask,
Allocator allocator)
{
screenPoint.x = Mathf.Clamp01(screenPoint.x / Screen.width);
screenPoint.y = Mathf.Clamp01(screenPoint.y / Screen.height);
return subsystem.Raycast(screenPoint, trackableTypeMask, allocator);
}
NativeArray<XRRaycastHit> RaycastRay(
Ray ray,
TrackableType trackableTypeMask,
Allocator allocator)
{
return subsystem.Raycast(ray, trackableTypeMask, allocator);
}
static int RaycastHitComparer(ARRaycastHit lhs, ARRaycastHit rhs)
{
return lhs.CompareTo(rhs);
}
NativeArray<XRRaycastHit> RaycastFallback(
Ray ray,
TrackableType trackableTypeMask,
Allocator allocator)
{
s_NativeRaycastHits.Clear();
int count = 0;
foreach (var raycaster in m_Raycasters)
{
var hits = raycaster.Raycast(ray, trackableTypeMask, Allocator.Temp);
if (hits.IsCreated)
{
s_NativeRaycastHits.Add(hits);
count += hits.Length;
}
}
var allHits = new NativeArray<XRRaycastHit>(count, allocator);
int dstIndex = 0;
foreach (var hitArray in s_NativeRaycastHits)
{
NativeArray<XRRaycastHit>.Copy(hitArray, 0, allHits, dstIndex, hitArray.Length);
hitArray.Dispose();
dstIndex += hitArray.Length;
}
return allHits;
}
bool TransformAndDisposeNativeHitResults(
NativeArray<XRRaycastHit> nativeHits,
List<ARRaycastHit> managedHits,
Vector3 rayOrigin)
{
managedHits.Clear();
if (!nativeHits.IsCreated)
return false;
try
{
// Results are in "trackables space", so transform results back into world space
TransformAndSortRaycastResults(sessionOrigin.trackablesParent, nativeHits, managedHits, rayOrigin);
managedHits.Sort(s_RaycastHitComparer);
return managedHits.Count > 0;
}
finally
{
nativeHits.Dispose();
}
}
/// <summary>
/// Invoked just after a <see cref="ARRaycast"/> has been updated.
/// </summary>
/// <param name="raycast">The <see cref="ARRaycast"/> being updated.</param>
/// <param name="sessionRelativeData">The new data associated with the raycast. All spatial
/// data is is session-relative space.</param>
protected override void OnAfterSetSessionRelativeData(ARRaycast raycast, XRRaycast sessionRelativeData)
{
var planeManager = GetComponent<ARPlaneManager>();
raycast.plane = planeManager ? planeManager.GetPlane(sessionRelativeData.hitTrackableId) : null;
}
/// <summary>
/// The name of the `GameObject` for each instantiated <see cref="ARRaycast"/>.
/// </summary>
protected override string gameObjectName => "ARRaycast";
static Comparison<ARRaycastHit> s_RaycastHitComparer = RaycastHitComparer;
static List<NativeArray<XRRaycastHit>> s_NativeRaycastHits = new List<NativeArray<XRRaycastHit>>();
Func<Vector2, TrackableType, Allocator, NativeArray<XRRaycastHit>> m_RaycastViewportDelegate;
Func<Ray, TrackableType, Allocator, NativeArray<XRRaycastHit>> m_RaycastRayDelegate;
List<IRaycaster> m_Raycasters = new List<IRaycaster>();
}
}