1 using System;
2 using System.Collections;
3 using UnityEngine;
4 #if UNITY_EDITOR
5 using UnityEditor;
6
7 #endif
8
9 namespace UnityStandardAssets.Utility
10 {
11 public class WaypointCircuit : MonoBehaviour
12 {
13 public WaypointList waypointList = new WaypointList();
14 [SerializeField] private bool smoothRoute = true;
15 private int numPoints;
16 private Vector3[] points;
17 private float[] distances;
18
19 public float editorVisualisationSubsteps = 100;
20 public float Length { get; private set; }
21
22 public Transform[] Waypoints
23 {
24 get { return waypointList.items; }
25 }
26
27 //this being here will save GC allocs
28 private int p0n;
29 private int p1n;
30 private int p2n;
31 private int p3n;
32
33 private float i;
34 private Vector3 P0;
35 private Vector3 P1;
36 private Vector3 P2;
37 private Vector3 P3;
38
39 // Use this for initialization
40 private void Awake()
41 {
42 if (Waypoints.Length > 1)
43 {
44 CachePositionsAndDistances();
45 }
46 numPoints = Waypoints.Length;
47 }
48
49
50 public RoutePoint GetRoutePoint(float dist)
51 {
52 // position and direction
53 Vector3 p1 = GetRoutePosition(dist);
54 Vector3 p2 = GetRoutePosition(dist + 0.1f);
55 Vector3 delta = p2 - p1;
56 return new RoutePoint(p1, delta.normalized);
57 }
58
59
60 public Vector3 GetRoutePosition(float dist)
61 {
62 int point = 0;
63
64 if (Length == 0)
65 {
66 Length = distances[distances.Length - 1];
67 }
68
69 dist = Mathf.Repeat(dist, Length);
70
71 while (distances[point] < dist)
72 {
73 ++point;
74 }
75
76
77 // get nearest two points, ensuring points wrap-around start & end of circuit
78 p1n = ((point - 1) + numPoints)%numPoints;
79 p2n = point;
80
81 // found point numbers, now find interpolation value between the two middle points
82
83 i = Mathf.InverseLerp(distances[p1n], distances[p2n], dist);
84
85 if (smoothRoute)
86 {
87 // smooth catmull-rom calculation between the two relevant points
88
89
90 // get indices for the surrounding 2 points, because
91 // four points are required by the catmull-rom function
92 p0n = ((point - 2) + numPoints)%numPoints;
93 p3n = (point + 1)%numPoints;
94
95 // 2nd point may have been the 'last' point - a dupe of the first,
96 // (to give a value of max track distance instead of zero)
97 // but now it must be wrapped back to zero if that was the case.
98 p2n = p2n%numPoints;
99
100 P0 = points[p0n];
101 P1 = points[p1n];
102 P2 = points[p2n];
103 P3 = points[p3n];
104
105 return CatmullRom(P0, P1, P2, P3, i);
106 }
107 else
108 {
109 // simple linear lerp between the two points:
110
111 p1n = ((point - 1) + numPoints)%numPoints;
112 p2n = point;
113
114 return Vector3.Lerp(points[p1n], points[p2n], i);
115 }
116 }
117
118
119 private Vector3 CatmullRom(Vector3 p0, Vector3 p1, Vector3 p2, Vector3 p3, float i)
120 {
121 // comments are no use here... it's the catmull-rom equation.
122 // Un-magic this, lord vector!
123 return 0.5f*
124 ((2*p1) + (-p0 + p2)*i + (2*p0 - 5*p1 + 4*p2 - p3)*i*i +
125 (-p0 + 3*p1 - 3*p2 + p3)*i*i*i);
126 }
127
128
129 private void CachePositionsAndDistances()
130 {
131 // transfer the position of each point and distances between points to arrays for
132 // speed of lookup at runtime
133 points = new Vector3[Waypoints.Length + 1];
134 distances = new float[Waypoints.Length + 1];
135
136 float accumulateDistance = 0;
137 for (int i = 0; i < points.Length; ++i)
138 {
139 var t1 = Waypoints[(i)%Waypoints.Length];
140 var t2 = Waypoints[(i + 1)%Waypoints.Length];
141 if (t1 != null && t2 != null)
142 {
143 Vector3 p1 = t1.position;
144 Vector3 p2 = t2.position;
145 points[i] = Waypoints[i%Waypoints.Length].position;
146 distances[i] = accumulateDistance;
147 accumulateDistance += (p1 - p2).magnitude;
148 }
149 }
150 }
151
152
153 private void OnDrawGizmos()
154 {
155 DrawGizmos(false);
156 }
157
158
159 private void OnDrawGizmosSelected()
160 {
161 DrawGizmos(true);
162 }
163
164
165 private void DrawGizmos(bool selected)
166 {
167 waypointList.circuit = this;
168 if (Waypoints.Length > 1)
169 {
170 numPoints = Waypoints.Length;
171
172 CachePositionsAndDistances();
173 Length = distances[distances.Length - 1];
174
175 Gizmos.color = selected ? Color.yellow : new Color(1, 1, 0, 0.5f);
176 Vector3 prev = Waypoints[0].position;
177 if (smoothRoute)
178 {
179 for (float dist = 0; dist < Length; dist += Length/editorVisualisationSubsteps)
180 {
181 Vector3 next = GetRoutePosition(dist + 1);
182 Gizmos.DrawLine(prev, next);
183 prev = next;
184 }
185 Gizmos.DrawLine(prev, Waypoints[0].position);
186 }
187 else
188 {
189 for (int n = 0; n < Waypoints.Length; ++n)
190 {
191 Vector3 next = Waypoints[(n + 1)%Waypoints.Length].position;
192 Gizmos.DrawLine(prev, next);
193 prev = next;
194 }
195 }
196 }
197 }
198
199
200 [Serializable]
201 public class WaypointList
202 {
203 public WaypointCircuit circuit;
204 public Transform[] items = new Transform[0];
205 }
206
207 public struct RoutePoint
208 {
209 public Vector3 position;
210 public Vector3 direction;
211
212
213 public RoutePoint(Vector3 position, Vector3 direction)
214 {
215 this.position = position;
216 this.direction = direction;
217 }
218 }
219 }
220 }
221
222 namespace UnityStandardAssets.Utility.Inspector
223 {
224 #if UNITY_EDITOR
225 [CustomPropertyDrawer(typeof (WaypointCircuit.WaypointList))]
226 public class WaypointListDrawer : PropertyDrawer
227 {
228 private float lineHeight = 18;
229 private float spacing = 4;
230
231
232 public override void OnGUI(Rect position, SerializedProperty property, GUIContent label)
233 {
234 EditorGUI.BeginProperty(position, label, property);
235
236 float x = position.x;
237 float y = position.y;
238 float inspectorWidth = position.width;
239
240 // Draw label
241
242
243 // Don't make child fields be indented
244 var indent = EditorGUI.indentLevel;
245 EditorGUI.indentLevel = 0;
246
247 var items = property.FindPropertyRelative("items");
248 var titles = new string[] {"Transform", "", "", ""};
249 var props = new string[] {"transform", "^", "v", "-"};
250 var widths = new float[] {.7f, .1f, .1f, .1f};
251 float lineHeight = 18;
252 bool changedLength = false;
253 if (items.arraySize > 0)
254 {
255 for (int i = -1; i < items.arraySize; ++i)
256 {
257 var item = items.GetArrayElementAtIndex(i);
258
259 float rowX = x;
260 for (int n = 0; n < props.Length; ++n)
261 {
262 float w = widths[n]*inspectorWidth;
263
264 // Calculate rects
265 Rect rect = new Rect(rowX, y, w, lineHeight);
266 rowX += w;
267
268 if (i == -1)
269 {
270 EditorGUI.LabelField(rect, titles[n]);
271 }
272 else
273 {
274 if (n == 0)
275 {
276 EditorGUI.ObjectField(rect, item.objectReferenceValue, typeof (Transform), true);
277 }
278 else
279 {
280 if (GUI.Button(rect, props[n]))
281 {
282 switch (props[n])
283 {
284 case "-":
285 items.DeleteArrayElementAtIndex(i);
286 items.DeleteArrayElementAtIndex(i);
287 changedLength = true;
288 break;
289 case "v":
290 if (i > 0)
291 {
292 items.MoveArrayElement(i, i + 1);
293 }
294 break;
295 case "^":
296 if (i < items.arraySize - 1)
297 {
298 items.MoveArrayElement(i, i - 1);
299 }
300 break;
301 }
302 }
303 }
304 }
305 }
306
307 y += lineHeight + spacing;
308 if (changedLength)
309 {
310 break;
311 }
312 }
313 }
314 else
315 {
316 // add button
317 var addButtonRect = new Rect((x + position.width) - widths[widths.Length - 1]*inspectorWidth, y,
318 widths[widths.Length - 1]*inspectorWidth, lineHeight);
319 if (GUI.Button(addButtonRect, "+"))
320 {
321 items.InsertArrayElementAtIndex(items.arraySize);
322 }
323
324 y += lineHeight + spacing;
325 }
326
327 // add all button
328 var addAllButtonRect = new Rect(x, y, inspectorWidth, lineHeight);
329 if (GUI.Button(addAllButtonRect, "Assign using all child objects"))
330 {
331 var circuit = property.FindPropertyRelative("circuit").objectReferenceValue as WaypointCircuit;
332 var children = new Transform[circuit.transform.childCount];
333 int n = 0;
334 foreach (Transform child in circuit.transform)
335 {
336 children[n++] = child;
337 }
338 Array.Sort(children, new TransformNameComparer());
339 circuit.waypointList.items = new Transform[children.Length];
340 for (n = 0; n < children.Length; ++n)
341 {
342 circuit.waypointList.items[n] = children[n];
343 }
344 }
345 y += lineHeight + spacing;
346
347 // rename all button
348 var renameButtonRect = new Rect(x, y, inspectorWidth, lineHeight);
349 if (GUI.Button(renameButtonRect, "Auto Rename numerically from this order"))
350 {
351 var circuit = property.FindPropertyRelative("circuit").objectReferenceValue as WaypointCircuit;
352 int n = 0;
353 foreach (Transform child in circuit.waypointList.items)
354 {
355 child.name = "Waypoint " + (n++).ToString("000");
356 }
357 }
358 y += lineHeight + spacing;
359
360 // Set indent back to what it was
361 EditorGUI.indentLevel = indent;
362 EditorGUI.EndProperty();
363 }
364
365
366 public override float GetPropertyHeight(SerializedProperty property, GUIContent label)
367 {
368 SerializedProperty items = property.FindPropertyRelative("items");
369 float lineAndSpace = lineHeight + spacing;
370 return 40 + (items.arraySize*lineAndSpace) + lineAndSpace;
371 }
372
373
374 // comparer for check distances in ray cast hits
375 public class TransformNameComparer : IComparer
376 {
377 public int Compare(object x, object y)
378 {
379 return ((Transform) x).name.CompareTo(((Transform) y).name);
380 }
381 }
382 }
383 #endif
384 }
this being here will save GC allocs
Use this for initialization
position and direction
get nearest two points, ensuring points wrap-around start & end of circuit
found point numbers, now find interpolation value between the two middle points
smooth catmull-rom calculation between the two relevant points
get indices for the surrounding 2 points, because
four points are required by the catmull-rom function
2nd point may have been the 'last' point - a dupe of the first,
(to give a value of max track distance instead of zero)
but now it must be wrapped back to zero if that was the case.
simple linear lerp between the two points:
comments are no use here... it's the catmull-rom equation.
Un-magic this, lord vector!
transfer the position of each point and distances between points to arrays for
speed of lookup at runtime
Draw label
Don't make child fields be indented
Calculate rects
add button
add all button
rename all button
Set indent back to what it was
comparer for check distances in ray cast hits