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
|
using UnityEngine;
using System.Collections.Generic;
namespace Pathfinding {
[System.Serializable]
/// <summary>
/// Adjusts start and end points of a path.
///
/// This modifier is included in the <see cref="Pathfinding.Seeker"/> component and is always used if you are using a Seeker.
/// When a path is calculated the resulting path will only be the positions of the nodes it passes through.
/// However often you may not want to navigate to the center of a specific node but instead to a point on the surface of a node.
/// This modifier will adjust the endpoints of the path.
///
/// [Open online documentation to see images]
/// </summary>
public class StartEndModifier : PathModifier {
public override int Order { get { return 0; } }
/// <summary>
/// Add points to the path instead of replacing them.
/// If for example <see cref="exactEndPoint"/> is set to ClosestOnNode then the path will be modified so that
/// the path goes first to the center of the last node in the path and then goes to the closest point
/// on the node to the end point in the path request.
///
/// If this is false however then the relevant points in the path will simply be replaced.
/// In the above example the path would go directly to the closest point on the node without passing
/// through the center of the node.
/// </summary>
public bool addPoints;
/// <summary>
/// How the start point of the path will be determined.
/// See: <see cref="Exactness"/>
/// </summary>
public Exactness exactStartPoint = Exactness.ClosestOnNode;
/// <summary>
/// How the end point of the path will be determined.
/// See: <see cref="Exactness"/>
/// </summary>
public Exactness exactEndPoint = Exactness.ClosestOnNode;
/// <summary>
/// Will be called when a path is processed.
/// The value which is returned will be used as the start point of the path
/// and potentially clamped depending on the value of the <see cref="exactStartPoint"/> field.
/// Only used for the Original, Interpolate and NodeConnection modes.
/// </summary>
public System.Func<Vector3> adjustStartPoint;
/// <summary>
/// Sets where the start and end points of a path should be placed.
///
/// Here is a legend showing what the different items in the above images represent.
/// The images above show a path coming in from the top left corner and ending at a node next to an obstacle as well as 2 different possible end points of the path and how they would be modified.
/// [Open online documentation to see images]
/// </summary>
public enum Exactness {
/// <summary>
/// The point is snapped to the position of the first/last node in the path.
/// Use this if your game is very tile based and you want your agents to stop precisely at the center of the nodes.
/// If you recalculate the path while the agent is moving you may want the start point snapping to be ClosestOnNode and the end point snapping to be SnapToNode however
/// as while an agent is moving it will likely not be right at the center of a node.
///
/// [Open online documentation to see images]
/// </summary>
SnapToNode,
/// <summary>
/// The point is set to the exact point which was passed when creating the path request.
/// Note that if a path was for example requested to a point inside an obstacle, then the last point of the path will be inside that obstacle, which is usually not what you want.
/// Consider using the <see cref="Exactness.ClosestOnNode"/> option instead.
///
/// [Open online documentation to see images]
/// </summary>
Original,
/// <summary>
/// The point is set to the closest point on the line between either the two first points or the two last points.
/// Usually you will want to use the NodeConnection mode instead since that is usually the behaviour that you really want.
/// This mode exists mostly for compatibility reasons.
/// [Open online documentation to see images]
/// Deprecated: Use NodeConnection instead.
/// </summary>
Interpolate,
/// <summary>
/// The point is set to the closest point on the surface of the node. Note that some node types (point nodes) do not have a surface, so the "closest point" is simply the node's position which makes this identical to <see cref="Exactness.SnapToNode"/>.
/// This is the mode that you almost always want to use in a free movement 3D world.
/// [Open online documentation to see images]
/// </summary>
ClosestOnNode,
/// <summary>
/// The point is set to the closest point on one of the connections from the start/end node.
/// This mode may be useful in a grid based or point graph based world when using the AILerp script.
///
/// Note: If you are using this mode with a <see cref="Pathfinding.PointGraph"/> you probably also want to use the <see cref="Pathfinding.PointGraph.NodeDistanceMode Connection"/> for <see cref="PointGraph.nearestNodeDistanceMode"/>.
///
/// [Open online documentation to see images]
/// </summary>
NodeConnection,
}
/// <summary>
/// Do a straight line check from the node's center to the point determined by the <see cref="Exactness"/>.
/// There are very few cases where you will want to use this. It is mostly here for
/// backwards compatibility reasons.
///
/// Version: Since 4.1 this field only has an effect for the <see cref="Exactness"/> mode Original because that's the only one where it makes sense.
/// </summary>
public bool useRaycasting;
public LayerMask mask = -1;
/// <summary>
/// Do a straight line check from the node's center to the point determined by the <see cref="Exactness"/>.
/// See: <see cref="useRaycasting"/>
///
/// Version: Since 4.1 this field only has an effect for the <see cref="Exactness"/> mode Original because that's the only one where it makes sense.
/// </summary>
public bool useGraphRaycasting;
List<GraphNode> connectionBuffer;
System.Action<GraphNode> connectionBufferAddDelegate;
public override void Apply (Path _p) {
var p = _p as ABPath;
// This modifier only supports ABPaths (doesn't make much sense for other paths anyway)
if (p == null || p.vectorPath.Count == 0) return;
bool singleNode = false;
if (p.vectorPath.Count == 1 && !addPoints) {
// Duplicate first point
p.vectorPath.Add(p.vectorPath[0]);
singleNode = true;
}
// Add instead of replacing points
bool forceAddStartPoint, forceAddEndPoint;
// Which connection the start/end point was on (only used for the Connection mode)
int closestStartConnection, closestEndConnection;
Vector3 pStart = Snap(p, exactStartPoint, true, out forceAddStartPoint, out closestStartConnection);
Vector3 pEnd = Snap(p, exactEndPoint, false, out forceAddEndPoint, out closestEndConnection);
// This is a special case when the path is only a single node and the Connection mode is used.
// (forceAddStartPoint/forceAddEndPoint is only used for the Connection mode)
// In this case the start and end points lie on the connections of the node.
// There are two cases:
// 1. If the start and end points lie on the same connection we do *not* want
// the path to pass through the node center but instead go directly from point to point.
// This is the case of closestStartConnection == closestEndConnection.
// 2. If the start and end points lie on different connections we *want*
// the path to pass through the node center as it goes from one connection to another one.
// However in any case we only want the node center to be added once to the path
// so we set forceAddStartPoint to false anyway.
if (singleNode) {
if (closestStartConnection == closestEndConnection) {
forceAddStartPoint = false;
forceAddEndPoint = false;
} else {
forceAddStartPoint = false;
}
}
// Add or replace the start point
// Disable adding of points if the mode is SnapToNode since then
// the first item in vectorPath will very likely be the same as the
// position of the first node
if ((forceAddStartPoint || addPoints) && exactStartPoint != Exactness.SnapToNode) {
p.vectorPath.Insert(0, pStart);
} else {
p.vectorPath[0] = pStart;
}
if ((forceAddEndPoint || addPoints) && exactEndPoint != Exactness.SnapToNode) {
p.vectorPath.Add(pEnd);
} else {
p.vectorPath[p.vectorPath.Count-1] = pEnd;
}
}
Vector3 Snap (ABPath path, Exactness mode, bool start, out bool forceAddPoint, out int closestConnectionIndex) {
var index = start ? 0 : path.path.Count - 1;
var node = path.path[index];
var nodePos = (Vector3)node.position;
closestConnectionIndex = 0;
forceAddPoint = false;
switch (mode) {
case Exactness.ClosestOnNode:
return start ? path.startPoint : path.endPoint;
case Exactness.SnapToNode:
return nodePos;
case Exactness.Original:
case Exactness.Interpolate:
case Exactness.NodeConnection:
Vector3 relevantPoint;
if (start) {
relevantPoint = adjustStartPoint != null? adjustStartPoint() : path.originalStartPoint;
} else {
relevantPoint = path.originalEndPoint;
}
switch (mode) {
case Exactness.Original:
return GetClampedPoint(nodePos, relevantPoint, node);
case Exactness.Interpolate:
// Adjacent node to either the start node or the end node in the path
var adjacentNode = path.path[Mathf.Clamp(index + (start ? 1 : -1), 0, path.path.Count-1)];
return VectorMath.ClosestPointOnSegment(nodePos, (Vector3)adjacentNode.position, relevantPoint);
case Exactness.NodeConnection:
// This code uses some tricks to avoid allocations
// even though it uses delegates heavily
// The connectionBufferAddDelegate delegate simply adds whatever node
// it is called with to the connectionBuffer
connectionBuffer = connectionBuffer ?? new List<GraphNode>();
connectionBufferAddDelegate = connectionBufferAddDelegate ?? (System.Action<GraphNode>)connectionBuffer.Add;
// Adjacent node to either the start node or the end node in the path
adjacentNode = path.path[Mathf.Clamp(index + (start ? 1 : -1), 0, path.path.Count-1)];
// Add all neighbours of #node to the connectionBuffer
node.GetConnections(connectionBufferAddDelegate);
var bestPos = nodePos;
var bestDist = float.PositiveInfinity;
// Loop through all neighbours
// Do it in reverse order because the length of the connectionBuffer
// will change during iteration
for (int i = connectionBuffer.Count - 1; i >= 0; i--) {
var neighbour = connectionBuffer[i];
if (!path.CanTraverse(neighbour)) continue;
// Find the closest point on the connection between the nodes
// and check if the distance to that point is lower than the previous best
var closest = VectorMath.ClosestPointOnSegment(nodePos, (Vector3)neighbour.position, relevantPoint);
var dist = (closest - relevantPoint).sqrMagnitude;
if (dist < bestDist) {
bestPos = closest;
bestDist = dist;
closestConnectionIndex = i;
// If this node is not the adjacent node
// then the path should go through the start node as well
forceAddPoint = neighbour != adjacentNode;
}
}
connectionBuffer.Clear();
return bestPos;
default:
throw new System.ArgumentException("Cannot reach this point, but the compiler is not smart enough to realize that.");
}
default:
throw new System.ArgumentException("Invalid mode");
}
}
protected Vector3 GetClampedPoint (Vector3 from, Vector3 to, GraphNode hint) {
Vector3 point = to;
RaycastHit hit;
if (useRaycasting && Physics.Linecast(from, to, out hit, mask)) {
point = hit.point;
}
if (useGraphRaycasting && hint != null) {
var rayGraph = AstarData.GetGraph(hint) as IRaycastableGraph;
if (rayGraph != null) {
GraphHitInfo graphHit;
if (rayGraph.Linecast(from, point, out graphHit)) {
point = graphHit.point;
}
}
}
return point;
}
}
}
|