ARPlaneManager.cs
12.5 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
using System;
using System.Collections.Generic;
using Unity.Collections;
using UnityEngine.Serialization;
using UnityEngine.XR.ARSubsystems;
namespace UnityEngine.XR.ARFoundation
{
/// <summary>
/// A manager for <see cref="ARPlane"/>s. Creates, updates, and removes
/// <c>GameObject</c>s in response to detected surfaces in the physical
/// environment.
/// </summary>
[DefaultExecutionOrder(ARUpdateOrder.k_PlaneManager)]
[DisallowMultipleComponent]
[RequireComponent(typeof(ARSessionOrigin))]
[HelpURL(HelpUrls.ApiWithNamespace + nameof(ARPlaneManager) + ".html")]
public sealed class ARPlaneManager : ARTrackableManager<
XRPlaneSubsystem,
XRPlaneSubsystemDescriptor,
#if UNITY_2020_2_OR_NEWER
XRPlaneSubsystem.Provider,
#endif
BoundedPlane,
ARPlane>, IRaycaster
{
[SerializeField]
[Tooltip("If not null, instantiates this prefab for each created plane.")]
GameObject m_PlanePrefab;
/// <summary>
/// Getter/setter for the Plane Prefab.
/// </summary>
public GameObject planePrefab
{
get => m_PlanePrefab;
set => m_PlanePrefab = value;
}
[SerializeField, PlaneDetectionModeMask]
[Tooltip("The types of planes to detect.")]
PlaneDetectionMode m_DetectionMode = (PlaneDetectionMode)(-1);
/// <summary>
/// Get or set the <c>PlaneDetectionMode</c> to use for plane detection.
/// This property is obsolete.
/// Use <see cref="requestedDetectionMode"/> or <see cref="currentDetectionMode"/> instead.
/// </summary>
[Obsolete("Use requestedDetectionMode or currentDetectionMode instead")]
public PlaneDetectionMode detectionMode
{
get => m_DetectionMode;
set => requestedDetectionMode = value;
}
/// <summary>
/// Get or set the requested plane detection mode.
/// </summary>
public PlaneDetectionMode requestedDetectionMode
{
get => subsystem?.requestedPlaneDetectionMode ?? m_DetectionMode;
set
{
m_DetectionMode = value;
if (enabled && subsystem != null)
{
subsystem.requestedPlaneDetectionMode = value;
}
}
}
/// <summary>
/// Get the current plane detection mode in use by the subsystem.
/// </summary>
public PlaneDetectionMode currentDetectionMode => subsystem?.currentPlaneDetectionMode ?? PlaneDetectionMode.None;
/// <summary>
/// Invoked when planes have changed (been added, updated, or removed).
/// </summary>
public event Action<ARPlanesChangedEventArgs> planesChanged;
/// <summary>
/// Attempt to retrieve an existing <see cref="ARPlane"/> by <paramref name="trackableId"/>.
/// </summary>
/// <param name="trackableId">The <see cref="TrackableId"/> of the plane to retrieve.</param>
/// <returns>The <see cref="ARPlane"/> with <paramref name="trackableId"/>, or <c>null</c> if it does not exist.</returns>
public ARPlane GetPlane(TrackableId trackableId) => m_Trackables.TryGetValue(trackableId, out ARPlane plane) ? plane : null;
/// <summary>
/// Performs a raycast against all currently tracked planes.
/// </summary>
/// <param name="ray">The ray, in Unity world space, to cast.</param>
/// <param name="trackableTypeMask">A mask of raycast types to perform.</param>
/// <param name="allocator">The <c>Allocator</c> to use when creating the returned <c>NativeArray</c>.</param>
/// <returns>
/// A new <c>NativeArray</c> of raycast results allocated with <paramref name="allocator"/>.
/// The caller owns the memory and is responsible for calling <c>Dispose</c> on the <c>NativeArray</c>.
/// </returns>
/// <seealso cref="ARRaycastManager.Raycast(Ray, List{ARRaycastHit}, TrackableType)"/>
/// <seealso cref="ARRaycastManager.Raycast(Vector2, List{ARRaycastHit}, TrackableType)"/>
public NativeArray<XRRaycastHit> Raycast(
Ray ray,
TrackableType trackableTypeMask,
Allocator allocator)
{
// No plane types requested; early out.
if ((trackableTypeMask & TrackableType.Planes) == TrackableType.None)
return new NativeArray<XRRaycastHit>(0, allocator);
var trackableCollection = trackables;
// Allocate a buffer that is at least large enough to contain a hit against every plane
var hitBuffer = new NativeArray<XRRaycastHit>(trackableCollection.count, Allocator.Temp);
try
{
int count = 0;
foreach (var plane in trackableCollection)
{
TrackableType trackableTypes = TrackableType.None;
var normal = plane.transform.localRotation * Vector3.up;
var infinitePlane = new Plane(normal, plane.transform.localPosition);
float distance;
if (!infinitePlane.Raycast(ray, out distance))
continue;
// Pose in session space
var pose = new Pose(
ray.origin + ray.direction * distance,
plane.transform.localRotation);
if ((trackableTypeMask & TrackableType.PlaneWithinInfinity) != TrackableType.None)
trackableTypes |= TrackableType.PlaneWithinInfinity;
// To test the rest, we need the intersection point in plane space
var hitPositionPlaneSpace3d = Quaternion.Inverse(plane.transform.localRotation) * (pose.position - plane.transform.localPosition);
var hitPositionPlaneSpace = new Vector2(hitPositionPlaneSpace3d.x, hitPositionPlaneSpace3d.z);
var estimatedOrWithinBounds = TrackableType.PlaneWithinBounds | TrackableType.PlaneEstimated;
if ((trackableTypeMask & estimatedOrWithinBounds) != TrackableType.None)
{
var differenceFromCenter = hitPositionPlaneSpace - plane.centerInPlaneSpace;
if ((Mathf.Abs(differenceFromCenter.x) <= plane.extents.x) &&
(Mathf.Abs(differenceFromCenter.y) <= plane.extents.y))
{
trackableTypes |= (estimatedOrWithinBounds & trackableTypeMask);
}
}
if ((trackableTypeMask & TrackableType.PlaneWithinPolygon) != TrackableType.None)
{
if (WindingNumber(hitPositionPlaneSpace, plane.boundary) != 0)
trackableTypes |= TrackableType.PlaneWithinPolygon;
}
if (trackableTypes != TrackableType.None)
{
hitBuffer[count++] = new XRRaycastHit(
plane.trackableId,
pose,
distance,
trackableTypes);
}
}
// Finally, copy to return value
var hitResults = new NativeArray<XRRaycastHit>(count, allocator);
NativeArray<XRRaycastHit>.Copy(hitBuffer, hitResults, count);
return hitResults;
}
finally
{
hitBuffer.Dispose();
}
}
static float GetCrossDirection(Vector2 a, Vector2 b)
{
return a.x * b.y - a.y * b.x;
}
// See http://geomalgorithms.com/a03-_inclusion.html
static int WindingNumber(
Vector2 positionInPlaneSpace,
NativeArray<Vector2> boundaryInPlaneSpace)
{
int windingNumber = 0;
Vector2 point = positionInPlaneSpace;
for (int i = 0; i < boundaryInPlaneSpace.Length; ++i)
{
int j = (i + 1) % boundaryInPlaneSpace.Length;
Vector2 vi = boundaryInPlaneSpace[i];
Vector2 vj = boundaryInPlaneSpace[j];
if (vi.y <= point.y)
{
if (vj.y > point.y) // an upward crossing
{
if (GetCrossDirection(vj - vi, point - vi) < 0f) // P left of edge
++windingNumber;
}
// have a valid up intersect
}
else
{ // y > P.y (no test needed)
if (vj.y <= point.y) // a downward crossing
{
if (GetCrossDirection(vj - vi, point - vi) > 0f) // P right of edge
--windingNumber;
}
// have a valid down intersect
}
}
return windingNumber;
}
/// <summary>
/// Get the prefab which will be instantiated for each <see cref="ARPlane"/>. May be `null`.
/// </summary>
/// <returns>The prefab which will be instantiated for each <see cref="ARPlane"/>.</returns>
protected override GameObject GetPrefab() => m_PlanePrefab;
/// <summary>
/// Invoked just before `Start`ing the plane subsystem. Used to set the subsystem's
/// `requestedPlaneDetectionMode`.
/// </summary>
protected override void OnBeforeStart()
{
subsystem.requestedPlaneDetectionMode = m_DetectionMode;
}
/// <summary>
/// Invoked just after each <see cref="ARPlane"/> is updated.
/// </summary>
/// <param name="plane">The <see cref="ARPlane"/> being updated.</param>
/// <param name="sessionRelativeData">The new data associated with the plane. All spatial
/// data is is session-relative space.</param>
protected override void OnAfterSetSessionRelativeData(
ARPlane plane,
BoundedPlane sessionRelativeData)
{
ARPlane subsumedByPlane;
if (m_Trackables.TryGetValue(sessionRelativeData.subsumedById, out subsumedByPlane))
{
plane.subsumedBy = subsumedByPlane;
}
else
{
plane.subsumedBy = null;
}
plane.UpdateBoundary(subsystem);
}
/// <summary>
/// Invoked when the base class detects trackable changes.
/// </summary>
/// <param name="added">The list of added <see cref="ARPlane"/>s.</param>
/// <param name="updated">The list of updated <see cref="ARPlane"/>s.</param>
/// <param name="removed">The list of removed <see cref="ARPlane"/>s.</param>
protected override void OnTrackablesChanged(
List<ARPlane> added,
List<ARPlane> updated,
List<ARPlane> removed)
{
if (planesChanged != null)
{
using (new ScopedProfiler("OnPlanesChanged"))
planesChanged(
new ARPlanesChangedEventArgs(
added,
updated,
removed));
}
}
/// <summary>
/// The name to be used for the <c>GameObject</c> whenever a new plane is detected.
/// </summary>
protected override string gameObjectName => "ARPlane";
/// <summary>
/// Invoked when Unity enables this `MonoBehaviour`. Used to register with the <see cref="ARRaycastManager"/>.
/// </summary>
protected override void OnEnable()
{
base.OnEnable();
if (subsystem != null)
{
var raycastManager = GetComponent<ARRaycastManager>();
if (raycastManager != null)
raycastManager.RegisterRaycaster(this);
}
}
/// <summary>
/// Invoked when Unity disables this `MonoBehaviour`. Used to unregister with the <see cref="ARRaycastManager"/>.
/// </summary>
protected override void OnDisable()
{
base.OnDisable();
var raycastManager = GetComponent<ARRaycastManager>();
if (raycastManager != null)
raycastManager.UnregisterRaycaster(this);
}
}
}