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
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
|
using UnityEngine;
using System.Collections.Generic;
using Unity.Collections;
namespace Pathfinding.Graphs.Grid {
using Pathfinding.Util;
using Pathfinding.Graphs.Grid.Jobs;
using Pathfinding.Jobs;
/// <summary>
/// Handles collision checking for graphs.
/// Mostly used by grid based graphs
/// </summary>
[System.Serializable]
public class GraphCollision {
/// <summary>
/// Collision shape to use.
/// See: <see cref="ColliderType"/>
/// </summary>
public ColliderType type = ColliderType.Capsule;
/// <summary>
/// Diameter of capsule or sphere when checking for collision.
/// When checking for collisions the system will check if any colliders
/// overlap a specific shape at the node's position. The shape is determined
/// by the <see cref="type"/> field.
///
/// A diameter of 1 means that the shape has a diameter equal to the node's width,
/// or in other words it is equal to <see cref="Pathfinding.GridGraph.nodeSize"/>.
///
/// If <see cref="type"/> is set to Ray, this does not affect anything.
///
/// [Open online documentation to see images]
/// </summary>
public float diameter = 1F;
/// <summary>
/// Height of capsule or length of ray when checking for collision.
/// If <see cref="type"/> is set to Sphere, this does not affect anything.
///
/// [Open online documentation to see images]
///
/// Warning: In contrast to Unity's capsule collider and character controller this height does not include the end spheres of the capsule, but only the cylinder part.
/// This is mostly for historical reasons.
/// </summary>
public float height = 2F;
/// <summary>
/// Height above the ground that collision checks should be done.
/// For example, if the ground was found at y=0, collisionOffset = 2
/// type = Capsule and height = 3 then the physics system
/// will be queried to see if there are any colliders in a capsule
/// for which the bottom sphere that is made up of is centered at y=2
/// and the top sphere has its center at y=2+3=5.
///
/// If type = Sphere then the sphere's center would be at y=2 in this case.
/// </summary>
public float collisionOffset;
/// <summary>
/// Direction of the ray when checking for collision.
/// If <see cref="type"/> is not Ray, this does not affect anything
///
/// Deprecated: Only the Both mode is supported now.
/// </summary>
[System.Obsolete("Only the Both mode is supported now")]
public RayDirection rayDirection = RayDirection.Both;
/// <summary>Layers to be treated as obstacles.</summary>
public LayerMask mask;
/// <summary>Layers to be included in the height check.</summary>
public LayerMask heightMask = -1;
/// <summary>
/// The height to check from when checking height ('ray length' in the inspector).
///
/// As the image below visualizes, different ray lengths can make the ray hit different things.
/// The distance is measured up from the graph plane.
///
/// [Open online documentation to see images]
/// </summary>
public float fromHeight = 100;
/// <summary>
/// Toggles thick raycast.
/// See: https://docs.unity3d.com/ScriptReference/Physics.SphereCast.html
/// </summary>
public bool thickRaycast;
/// <summary>
/// Diameter of the thick raycast in nodes.
/// 1 equals <see cref="Pathfinding.GridGraph.nodeSize"/>
/// </summary>
public float thickRaycastDiameter = 1;
/// <summary>Make nodes unwalkable when no ground was found with the height raycast. If height raycast is turned off, this doesn't affect anything.</summary>
public bool unwalkableWhenNoGround = true;
/// <summary>
/// Use Unity 2D Physics API.
///
/// If enabled, the 2D Physics API will be used, and if disabled, the 3D Physics API will be used.
///
/// This changes the collider types (see <see cref="type)"/> from 3D versions to their corresponding 2D versions. For example the sphere shape becomes a circle.
///
/// The <see cref="heightCheck"/> setting will be ignored when 2D physics is used.
///
/// See: http://docs.unity3d.com/ScriptReference/Physics2D.html
/// </summary>
public bool use2D;
/// <summary>Toggle collision check</summary>
public bool collisionCheck = true;
/// <summary>
/// Toggle height check. If false, the grid will be flat.
///
/// This setting will be ignored when 2D physics is used.
/// </summary>
public bool heightCheck = true;
/// <summary>
/// Direction to use as UP.
/// See: Initialize
/// </summary>
public Vector3 up;
/// <summary>
/// <see cref="up"/> * <see cref="height"/>.
/// See: Initialize
/// </summary>
private Vector3 upheight;
/// <summary>Used for 2D collision queries</summary>
private ContactFilter2D contactFilter;
/// <summary>
/// Just so that the Physics2D.OverlapPoint method has some buffer to store things in.
/// We never actually read from this array, so we don't even care if this is thread safe.
/// </summary>
private static Collider2D[] dummyArray = new Collider2D[1];
/// <summary>
/// <see cref="diameter"/> * scale * 0.5.
/// Where scale usually is <see cref="Pathfinding.GridGraph.nodeSize"/>
/// See: Initialize
/// </summary>
private float finalRadius;
/// <summary>
/// <see cref="thickRaycastDiameter"/> * scale * 0.5.
/// Where scale usually is <see cref="Pathfinding.GridGraph.nodeSize"/> See: Initialize
/// </summary>
private float finalRaycastRadius;
/// <summary>Offset to apply after each raycast to make sure we don't hit the same point again in CheckHeightAll</summary>
public const float RaycastErrorMargin = 0.005F;
/// <summary>
/// Sets up several variables using the specified matrix and scale.
/// See: GraphCollision.up
/// See: GraphCollision.upheight
/// See: GraphCollision.finalRadius
/// See: GraphCollision.finalRaycastRadius
/// </summary>
public void Initialize (GraphTransform transform, float scale) {
up = (transform.Transform(Vector3.up) - transform.Transform(Vector3.zero)).normalized;
upheight = up*height;
finalRadius = diameter*scale*0.5F;
finalRaycastRadius = thickRaycastDiameter*scale*0.5F;
contactFilter = new ContactFilter2D { layerMask = mask, useDepth = false, useLayerMask = true, useNormalAngle = false, useTriggers = false };
}
/// <summary>
/// Returns true if the position is not obstructed.
/// If <see cref="collisionCheck"/> is false, this will always return true.
/// </summary>
public bool Check (Vector3 position) {
if (!collisionCheck) {
return true;
}
if (use2D) {
switch (type) {
case ColliderType.Capsule:
case ColliderType.Sphere:
return Physics2D.OverlapCircle(position, finalRadius, contactFilter, dummyArray) == 0;
default:
return Physics2D.OverlapPoint(position, contactFilter, dummyArray) == 0;
}
}
position += up*collisionOffset;
switch (type) {
case ColliderType.Capsule:
return !Physics.CheckCapsule(position, position+upheight, finalRadius, mask, QueryTriggerInteraction.Ignore);
case ColliderType.Sphere:
return !Physics.CheckSphere(position, finalRadius, mask, QueryTriggerInteraction.Ignore);
default:
return !Physics.Raycast(position, up, height, mask, QueryTriggerInteraction.Ignore) && !Physics.Raycast(position+upheight, -up, height, mask, QueryTriggerInteraction.Ignore);
}
}
/// <summary>
/// Returns the position with the correct height.
/// If <see cref="heightCheck"/> is false, this will return position.
/// </summary>
public Vector3 CheckHeight (Vector3 position) {
RaycastHit hit;
bool walkable;
return CheckHeight(position, out hit, out walkable);
}
/// <summary>
/// Returns the position with the correct height.
/// If <see cref="heightCheck"/> is false, this will return position.
/// walkable will be set to false if nothing was hit.
/// The ray will check a tiny bit further than to the grids base to avoid floating point errors when the ground is exactly at the base of the grid
/// </summary>
public Vector3 CheckHeight (Vector3 position, out RaycastHit hit, out bool walkable) {
walkable = true;
if (!heightCheck || use2D) {
hit = new RaycastHit();
return position;
}
if (thickRaycast) {
var ray = new Ray(position+up*fromHeight, -up);
if (Physics.SphereCast(ray, finalRaycastRadius, out hit, fromHeight+0.005F, heightMask, QueryTriggerInteraction.Ignore)) {
return VectorMath.ClosestPointOnLine(ray.origin, ray.origin+ray.direction, hit.point);
}
walkable &= !unwalkableWhenNoGround;
} else {
// Cast a ray from above downwards to try to find the ground
if (Physics.Raycast(position+up*fromHeight, -up, out hit, fromHeight+0.005F, heightMask, QueryTriggerInteraction.Ignore)) {
return hit.point;
}
walkable &= !unwalkableWhenNoGround;
}
return position;
}
/// <summary>Internal buffer used by <see cref="CheckHeightAll"/></summary>
RaycastHit[] hitBuffer = new RaycastHit[8];
/// <summary>
/// Returns all hits when checking height for position.
/// Warning: Does not work well with thick raycast, will only return an object a single time
///
/// Warning: The returned array is ephermal. It will be invalidated when this method is called again.
/// If you need persistent results you should copy it.
///
/// The returned array may be larger than the actual number of hits, the numHits out parameter indicates how many hits there actually were.
/// </summary>
public RaycastHit[] CheckHeightAll (Vector3 position, out int numHits) {
if (!heightCheck || use2D) {
hitBuffer[0] = new RaycastHit {
point = position,
distance = 0,
};
numHits = 1;
return hitBuffer;
}
// Cast a ray from above downwards to try to find the ground
#if UNITY_2017_1_OR_NEWER
numHits = Physics.RaycastNonAlloc(position+up*fromHeight, -up, hitBuffer, fromHeight+0.005F, heightMask, QueryTriggerInteraction.Ignore);
if (numHits == hitBuffer.Length) {
// Try again with a larger buffer
hitBuffer = new RaycastHit[hitBuffer.Length*2];
return CheckHeightAll(position, out numHits);
}
return hitBuffer;
#else
var result = Physics.RaycastAll(position+up*fromHeight, -up, fromHeight+0.005F, heightMask, QueryTriggerInteraction.Ignore);
numHits = result.Length;
return result;
#endif
}
/// <summary>
/// Returns if the position is obstructed for all nodes using the Ray collision checking method.
/// collisionCheckResult[i] = true if there were no obstructions, false otherwise
/// </summary>
public void JobCollisionRay (NativeArray<Vector3> nodePositions, NativeArray<bool> collisionCheckResult, Vector3 up, Allocator allocationMethod, JobDependencyTracker dependencyTracker) {
var collisionRaycastCommands1 = dependencyTracker.NewNativeArray<RaycastCommand>(nodePositions.Length, allocationMethod);
var collisionRaycastCommands2 = dependencyTracker.NewNativeArray<RaycastCommand>(nodePositions.Length, allocationMethod);
var collisionHits1 = dependencyTracker.NewNativeArray<RaycastHit>(nodePositions.Length, allocationMethod);
var collisionHits2 = dependencyTracker.NewNativeArray<RaycastHit>(nodePositions.Length, allocationMethod);
// Fire rays from above down to the nodes' positions
new JobPrepareRaycasts {
origins = nodePositions,
originOffset = up * (height + collisionOffset),
direction = -up,
distance = height,
mask = mask,
physicsScene = Physics.defaultPhysicsScene,
raycastCommands = collisionRaycastCommands1,
}.Schedule(dependencyTracker);
// Fire rays from the node up towards the sky
new JobPrepareRaycasts {
origins = nodePositions,
originOffset = up * collisionOffset,
direction = up,
distance = height,
mask = mask,
physicsScene = Physics.defaultPhysicsScene,
raycastCommands = collisionRaycastCommands2,
}.Schedule(dependencyTracker);
dependencyTracker.ScheduleBatch(collisionRaycastCommands1, collisionHits1, 2048);
dependencyTracker.ScheduleBatch(collisionRaycastCommands2, collisionHits2, 2048);
new JobMergeRaycastCollisionHits {
hit1 = collisionHits1,
hit2 = collisionHits2,
result = collisionCheckResult,
}.Schedule(dependencyTracker);
}
#if UNITY_2022_2_OR_NEWER
public void JobCollisionCapsule (NativeArray<Vector3> nodePositions, NativeArray<bool> collisionCheckResult, Vector3 up, Allocator allocationMethod, JobDependencyTracker dependencyTracker) {
var commands = dependencyTracker.NewNativeArray<OverlapCapsuleCommand>(nodePositions.Length, allocationMethod);
var collisionHits = dependencyTracker.NewNativeArray<ColliderHit>(nodePositions.Length, allocationMethod);
new JobPrepareCapsuleCommands {
origins = nodePositions,
originOffset = up * collisionOffset,
direction = up * height,
radius = finalRadius,
mask = mask,
commands = commands,
physicsScene = Physics.defaultPhysicsScene,
}.Schedule(dependencyTracker);
dependencyTracker.ScheduleBatch(commands, collisionHits, 2048);
new JobColliderHitsToBooleans {
hits = collisionHits,
result = collisionCheckResult,
}.Schedule(dependencyTracker);
}
public void JobCollisionSphere (NativeArray<Vector3> nodePositions, NativeArray<bool> collisionCheckResult, Vector3 up, Allocator allocationMethod, JobDependencyTracker dependencyTracker) {
var commands = dependencyTracker.NewNativeArray<OverlapSphereCommand>(nodePositions.Length, allocationMethod);
var collisionHits = dependencyTracker.NewNativeArray<ColliderHit>(nodePositions.Length, allocationMethod);
new JobPrepareSphereCommands {
origins = nodePositions,
originOffset = up * collisionOffset,
radius = finalRadius,
mask = mask,
commands = commands,
physicsScene = Physics.defaultPhysicsScene,
}.Schedule(dependencyTracker);
dependencyTracker.ScheduleBatch(commands, collisionHits, 2048);
new JobColliderHitsToBooleans {
hits = collisionHits,
result = collisionCheckResult,
}.Schedule(dependencyTracker);
}
#endif
}
/// <summary>
/// Determines collision check shape.
/// See: <see cref="GraphCollision"/>
/// </summary>
public enum ColliderType {
/// <summary>Uses a Sphere, Physics.CheckSphere. In 2D this is a circle instead.</summary>
Sphere,
/// <summary>Uses a Capsule, Physics.CheckCapsule. This will behave identically to the Sphere mode in 2D.</summary>
Capsule,
/// <summary>Uses a Ray, Physics.Linecast. In 2D this is a single point instead.</summary>
Ray
}
/// <summary>Determines collision check ray direction</summary>
public enum RayDirection {
Up, /// <summary>< Casts the ray from the bottom upwards</summary>
Down, /// <summary>< Casts the ray from the top downwards</summary>
Both /// <summary>< Casts two rays in both directions</summary>
}
}
|