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
|
using UnityEngine;
namespace Pathfinding {
using Pathfinding.Drawing;
using Pathfinding.Graphs.Util;
/// <summary>
/// Adds new geometry to a recast graph.
///
/// This component will add new geometry to a recast graph similar
/// to how a NavmeshCut component removes it.
///
/// There are quite a few limitations to this component though.
/// This navmesh geometry will not be connected to the rest of the navmesh
/// in the same tile unless very exactly positioned so that the
/// triangles line up exactly.
/// It will be connected to neighbouring tiles if positioned so that
/// it lines up with the tile border.
///
/// This component has a few very specific use-cases.
/// For example if you have a tiled recast graph
/// this component could be used to add bridges
/// in that world.
/// You would create a NavmeshCut object cutting out a hole for the bridge.
/// then add a NavmeshAdd object which fills that space.
/// Make sure NavmeshCut.CutsAddedGeom is disabled on the NavmeshCut, otherwise it will
/// cut away the NavmeshAdd object.
/// Then you can add links between the added geometry and the rest of the world, preferably using NodeLink3.
/// </summary>
[HelpURL("https://arongranberg.com/astar/documentation/stable/navmeshadd.html")]
public class NavmeshAdd : NavmeshClipper {
public enum MeshType {
Rectangle,
CustomMesh
}
public MeshType type;
/// <summary>
/// Custom mesh to use.
/// The contour(s) of the mesh will be extracted.
/// If you get the "max perturbations" error when cutting with this, check the normals on the mesh.
/// They should all point in the same direction. Try flipping them if that does not help.
/// </summary>
public Mesh mesh;
/// <summary>Cached vertices</summary>
Vector3[] verts;
/// <summary>Cached triangles</summary>
int[] tris;
/// <summary>Size of the rectangle</summary>
public Vector2 rectangleSize = new Vector2(1, 1);
public float meshScale = 1;
public Vector3 center;
/// <summary>
/// Includes rotation and scale in calculations.
/// This is slower since a lot more matrix multiplications are needed but gives more flexibility.
/// </summary>
[UnityEngine.Serialization.FormerlySerializedAsAttribute("useRotation")]
public bool useRotationAndScale;
/// <summary>
/// Distance between positions to require an update of the navmesh.
/// A smaller distance gives better accuracy, but requires more updates when moving the object over time,
/// so it is often slower.
/// </summary>
[Tooltip("Distance between positions to require an update of the navmesh\nA smaller distance gives better accuracy, but requires more updates when moving the object over time, so it is often slower.")]
public float updateDistance = 0.4f;
/// <summary>
/// How many degrees rotation that is required for an update to the navmesh.
/// Should be between 0 and 180.
/// </summary>
[Tooltip("How many degrees rotation that is required for an update to the navmesh. Should be between 0 and 180.")]
public float updateRotationDistance = 10;
/// <summary>cached transform component</summary>
protected Transform tr;
/// <summary>
/// Returns true if this object has moved so much that it requires an update.
/// When an update to the navmesh has been done, call NotifyUpdated to be able to get
/// relavant output from this method again.
/// </summary>
public override bool RequiresUpdate (GridLookup<NavmeshClipper>.Root previousState) {
return (tr.position-previousState.previousPosition).sqrMagnitude > updateDistance*updateDistance || (useRotationAndScale && (Quaternion.Angle(previousState.previousRotation, tr.rotation) > updateRotationDistance));
}
/// <summary>
/// Forces this navmesh add to update the navmesh.
///
/// This update is not instant, it is done the next time it is checked if it needs updating.
/// See: <see cref="NavmeshUpdates.updateInterval"/>
/// See: <see cref="NavmeshUpdates.ForceUpdate"/>
/// </summary>
public override void ForceUpdate () {
AstarPath.active.navmeshUpdates.ForceUpdateAround(this);
}
protected override void Awake () {
base.Awake();
tr = transform;
}
/// <summary>Internal method to notify the NavmeshAdd that it has just been used to update the navmesh</summary>
internal override void NotifyUpdated (GridLookup<NavmeshClipper>.Root previousState) {
previousState.previousPosition = tr.position;
if (useRotationAndScale) {
previousState.previousRotation = tr.rotation;
}
}
public Vector3 Center {
get {
return tr.position + (useRotationAndScale ? tr.TransformPoint(center) : center);
}
}
[ContextMenu("Rebuild Mesh")]
public void RebuildMesh () {
if (type == MeshType.CustomMesh) {
if (mesh == null) {
verts = null;
tris = null;
} else {
verts = mesh.vertices;
tris = mesh.triangles;
}
} else { // Rectangle
if (verts == null || verts.Length != 4 || tris == null || tris.Length != 6) {
verts = new Vector3[4];
tris = new int[6];
}
tris[0] = 0;
tris[1] = 1;
tris[2] = 2;
tris[3] = 0;
tris[4] = 2;
tris[5] = 3;
verts[0] = new Vector3(-rectangleSize.x*0.5f, 0, -rectangleSize.y*0.5f);
verts[1] = new Vector3(rectangleSize.x*0.5f, 0, -rectangleSize.y*0.5f);
verts[2] = new Vector3(rectangleSize.x*0.5f, 0, rectangleSize.y*0.5f);
verts[3] = new Vector3(-rectangleSize.x*0.5f, 0, rectangleSize.y*0.5f);
}
}
/// <summary>
/// Bounds in XZ space after transforming using the *inverse* transform of the inverseTransform parameter.
/// The transformation will typically transform the vertices to graph space and this is used to
/// figure out which tiles the add intersects.
/// </summary>
public override Rect GetBounds (Pathfinding.Util.GraphTransform inverseTransform, float radiusMargin) {
if (this.verts == null) RebuildMesh();
var verts = Pathfinding.Util.ArrayPool<Int3>.Claim(this.verts != null? this.verts.Length : 0);
int[] tris;
GetMesh(ref verts, out tris, inverseTransform);
Rect r = new Rect();
for (int i = 0; i < tris.Length; i++) {
var p = (Vector3)verts[tris[i]];
if (i == 0) {
r = new Rect(p.x, p.z, 0, 0);
} else {
r.xMax = System.Math.Max(r.xMax, p.x);
r.yMax = System.Math.Max(r.yMax, p.z);
r.xMin = System.Math.Min(r.xMin, p.x);
r.yMin = System.Math.Min(r.yMin, p.z);
}
}
Pathfinding.Util.ArrayPool<Int3>.Release(ref verts);
return r;
}
/// <summary>Copy the mesh to the vertex and triangle buffers after the vertices have been transformed using the inverse of the inverseTransform parameter.</summary>
/// <param name="vbuffer">Assumed to be either null or an array which has a length of zero or a power of two. If this mesh has more
/// vertices than can fit in the buffer then the buffer will be pooled using Pathfinding.Util.ArrayPool.Release and
/// a new sufficiently large buffer will be taken from the pool.</param>
/// <param name="tbuffer">This will be set to the internal triangle buffer. You must not modify this array.</param>
/// <param name="inverseTransform">All vertices will be transformed using the #Pathfinding.GraphTransform.InverseTransform method.
/// This is typically used to transform from world space to graph space.</param>
public void GetMesh (ref Int3[] vbuffer, out int[] tbuffer, Pathfinding.Util.GraphTransform inverseTransform = null) {
if (verts == null) RebuildMesh();
if (verts == null) {
tbuffer = Util.ArrayPool<int>.Claim(0);
return;
}
if (vbuffer == null || vbuffer.Length < verts.Length) {
if (vbuffer != null) Util.ArrayPool<Int3>.Release(ref vbuffer);
vbuffer = Util.ArrayPool<Int3>.Claim(verts.Length);
}
tbuffer = tris;
if (useRotationAndScale) {
Matrix4x4 m = Matrix4x4.TRS(tr.position + center, tr.rotation, tr.localScale * meshScale);
for (int i = 0; i < verts.Length; i++) {
var v = m.MultiplyPoint3x4(verts[i]);
if (inverseTransform != null) v = inverseTransform.InverseTransform(v);
vbuffer[i] = (Int3)v;
}
} else {
Vector3 voffset = tr.position + center;
for (int i = 0; i < verts.Length; i++) {
var v = voffset + verts[i]*meshScale;
if (inverseTransform != null) v = inverseTransform.InverseTransform(v);
vbuffer[i] = (Int3)v;
}
}
}
public static readonly Color GizmoColor = new Color(154.0f/255, 35.0f/255, 239.0f/255);
#if UNITY_EDITOR
public static Int3[] gizmoBuffer;
public override void DrawGizmos () {
if (tr == null) tr = transform;
int[] tbuffer;
GetMesh(ref gizmoBuffer, out tbuffer);
for (int i = 0; i < tbuffer.Length; i += 3) {
var v1 = (Vector3)gizmoBuffer[tbuffer[i+0]];
var v2 = (Vector3)gizmoBuffer[tbuffer[i+1]];
var v3 = (Vector3)gizmoBuffer[tbuffer[i+2]];
Draw.Line(v1, v2, GizmoColor);
Draw.Line(v2, v3, GizmoColor);
Draw.Line(v3, v1, GizmoColor);
}
}
#endif
}
}
|