AR Design
UBC EML collab with UBC SALA - visualizing IoT data in AR
Spline.cs
Go to the documentation of this file.
1 // Copyright (c) Microsoft Corporation. All rights reserved.
2 // Licensed under the MIT License. See LICENSE in the project root for license information.
3 
4 using System;
5 using System.Collections.Generic;
6 using UnityEngine;
7 
8 namespace HoloToolkit.Unity.UX
9 {
10  public class Spline : LineBase
11  {
12  [Header("Spline Settings")]
13  [SerializeField]
14  private SplinePoint[] points = new SplinePoint[4];
15 
16  [SerializeField]
17  private bool alignControlPoints = true;
18 
19  public bool AlignControlPoints
20  {
21  get { return alignControlPoints; }
22  set
23  {
24  if (alignControlPoints != value)
25  {
26  alignControlPoints = value;
27  ForceUpdateAlignment();
28  }
29  }
30  }
31 
32  public override int NumPoints
33  {
34  get
35  {
36  return points.Length;
37  }
38  }
39 
40  public void ForceUpdateAlignment()
41  {
42  if (AlignControlPoints)
43  {
44  for (int i = 0; i < NumPoints; i++)
45  {
46  ForceUpdateAlignment(i);
47  }
48  }
49  }
50 
51  private void ForceUpdateAlignment(int pointIndex)
52  {
53  if (AlignControlPoints)
54  {
55  int prevControlPoint = 0;
56  int changedControlPoint = 0;
57  int midPointIndex = ((pointIndex + 1) / 3) * 3;
58 
59  if (pointIndex <= midPointIndex)
60  {
61  prevControlPoint = midPointIndex - 1;
62  changedControlPoint = midPointIndex + 1;
63  }
64  else
65  {
66  prevControlPoint = midPointIndex + 1;
67  changedControlPoint = midPointIndex - 1;
68  }
69 
70  if (loops)
71  {
72  if (changedControlPoint < 0)
73  {
74  changedControlPoint = (NumPoints - 1) + changedControlPoint;
75  }
76  else if (changedControlPoint >= NumPoints)
77  {
78  changedControlPoint = changedControlPoint % (NumPoints - 1);
79  }
80 
81  if (prevControlPoint < 0)
82  {
83  prevControlPoint = (NumPoints - 1) + prevControlPoint;
84  }
85  else if (prevControlPoint >= NumPoints)
86  {
87  prevControlPoint = prevControlPoint % (NumPoints - 1);
88  }
89 
90  Vector3 midPoint = points[midPointIndex].Point;
91  Vector3 tangent = midPoint - points[prevControlPoint].Point;
92  tangent = tangent.normalized * Vector3.Distance(midPoint, points[changedControlPoint].Point);
93  points[changedControlPoint].Point = midPoint + tangent;
94 
95  }
96  else if (changedControlPoint >= 0 && changedControlPoint < NumPoints && prevControlPoint >= 0 && prevControlPoint < NumPoints)
97  {
98  Vector3 midPoint = points[midPointIndex].Point;
99  Vector3 tangent = midPoint - points[prevControlPoint].Point;
100  tangent = tangent.normalized * Vector3.Distance(midPoint, points[changedControlPoint].Point);
101  points[changedControlPoint].Point = midPoint + tangent;
102  }
103  }
104  }
105 
106  public override void AppendPoint(Vector3 point)
107  {
108  int pointIndex = points.Length;
109  Array.Resize<SplinePoint>(ref points, points.Length + 1);
110  SetPoint(pointIndex, point);
111  }
112 
113  protected override Vector3 GetPointInternal(float normalizedDistance)
114  {
115  float totalDistance = normalizedDistance * (NumPoints - 1);
116 
117  int point1Index = Mathf.FloorToInt(totalDistance);
118  point1Index -= (point1Index % 3);
119  float subDistance = (totalDistance - point1Index) / 3;
120 
121  int point2Index = 0;
122  int point3Index = 0;
123  int point4Index = 0;
124 
125  if (!loops)
126  {
127  if (point1Index + 3 >= NumPoints)
128  {
129  return points[NumPoints - 1].Point;
130  }
131  if (point1Index < 0)
132  {
133  return points[0].Point;
134  }
135 
136  point2Index = point1Index + 1;
137  point3Index = point1Index + 2;
138  point4Index = point1Index + 3;
139 
140  }
141  else
142  {
143  point2Index = (point1Index + 1) % (NumPoints - 1);
144  point3Index = (point1Index + 2) % (NumPoints - 1);
145  point4Index = (point1Index + 3) % (NumPoints - 1);
146  }
147 
148  Vector3 point1 = points[point1Index].Point;
149  Vector3 point2 = points[point2Index].Point;
150  Vector3 point3 = points[point3Index].Point;
151  Vector3 point4 = points[point4Index].Point;
152 
153  return LineUtils.InterpolateBezeirPoints(point1, point2, point3, point4, subDistance);
154  }
155 
156  protected override Vector3 GetPointInternal(int pointIndex)
157  {
158  if (pointIndex < 0 || pointIndex >= points.Length)
159  {
160  throw new IndexOutOfRangeException();
161  }
162 
163  if (loops && pointIndex == NumPoints - 1)
164  {
165  points[pointIndex] = points[0];
166  pointIndex = 0;
167  }
168 
169  return points[pointIndex].Point;
170  }
171 
172  protected override void SetPointInternal(int pointIndex, Vector3 point)
173  {
174  if (pointIndex < 0 || pointIndex >= points.Length)
175  {
176  throw new IndexOutOfRangeException();
177  }
178 
179  if (loops && pointIndex == NumPoints - 1)
180  {
181  points[pointIndex] = points[0];
182  pointIndex = 0;
183  }
184 
185  if (AlignControlPoints)
186  {
187  if (pointIndex % 3 == 0)
188  {
189  Vector3 delta = point - points[pointIndex].Point;
190  if (loops)
191  {
192  if (pointIndex == 0)
193  {
194  points[1].Point += delta;
195  points[NumPoints - 2].Point += delta;
196  points[NumPoints - 1].Point = point;
197  }
198  else if (pointIndex == NumPoints)
199  {
200  points[0].Point = point;
201  points[1].Point += delta;
202  points[pointIndex - 1].Point += delta;
203  }
204  else
205  {
206  points[pointIndex - 1].Point += delta;
207  points[pointIndex + 1].Point += delta;
208  }
209  }
210  else
211  {
212  if (pointIndex > 0)
213  {
214  points[pointIndex - 1].Point += delta;
215  }
216  if (pointIndex + 1 < points.Length)
217  {
218  points[pointIndex + 1].Point += delta;
219  }
220  }
221  }
222  }
223 
224  points[pointIndex].Point = point;
225 
226  ForceUpdateAlignment(pointIndex);
227  }
228 
229  protected override Vector3 GetUpVectorInternal(float normalizedLength)
230  {
231 
232  float arrayValueLength = 1f / points.Length;
233  int indexA = Mathf.FloorToInt(normalizedLength * points.Length);
234  if (indexA >= points.Length)
235  {
236  indexA = 0;
237  }
238 
239  int indexB = indexA + 1;
240  if (indexB >= points.Length)
241  {
242  indexB = 0;
243  }
244 
245  float blendAmount = (normalizedLength - (arrayValueLength * indexA)) / arrayValueLength;
246  Quaternion rotation = Quaternion.Lerp(points[indexA].Rotation, points[indexB].Rotation, blendAmount);
247  return rotation * transform.up;
248  }
249 
250  protected override float GetUnclampedWorldLengthInternal()
251  {
252  // Crude approximation
253  // TODO optimize
254  float distance = 0f;
255  Vector3 last = GetPoint(0f);
256  for (int i = 1; i < 10; i++)
257  {
258  Vector3 current = GetPoint((float)i / 10);
259  distance += Vector3.Distance(last, current);
260  }
261  return distance;
262  }
263 
264 #if UNITY_EDITOR
265  [UnityEditor.CustomEditor(typeof(Spline))]
266  public class CustomEditor : LineBaseEditor
267  {
268  private static bool editPositions = true;
269  private static bool editRotations = false;
270  private static List<SplinePoint> pointsList = new List<SplinePoint>();
271  private static HashSet<int> overlappingPointIndexes = new HashSet<int>();
272 
273  private const float overlappingPointThreshold = 0.015f;
274 
275  // Convenience buttons for adding / removing points
276  protected override void DrawCustomFooter()
277  {
278  base.DrawCustomFooter();
279 
280  Spline line = (Spline)target;
281 
282  overlappingPointIndexes.Clear();
283 
284  if (DrawSectionStart(line.name + " Points", "Point Editing"))
285  {
286  if (GUILayout.Button(" + Add Points to Start"))
287  {
288  pointsList.Clear();
289  SplinePoint[] newPoints = new SplinePoint[3];
290  Vector3 direction = line.GetVelocity(0.01f);
291  float distance = Mathf.Max(line.UnclampedWorldLength * 0.05f, overlappingPointThreshold * 5);
292  newPoints[2].Point = line.FirstPoint - (direction * distance);
293  newPoints[1].Point = newPoints[2].Point - (direction * distance);
294  newPoints[0].Point = newPoints[1].Point - (direction * distance);
295  pointsList.AddRange(newPoints);
296  pointsList.AddRange(line.points);
297  line.points = pointsList.ToArray();
298  }
299  if (line.NumPoints > 4)
300  {
301  if (GUILayout.Button(" - Remove Points From Start"))
302  {
303  // Using lists for maximum clarity
304  pointsList.Clear();
305  pointsList.AddRange(line.points);
306  pointsList.RemoveAt(0);
307  pointsList.RemoveAt(0);
308  pointsList.RemoveAt(0);
309  line.points = pointsList.ToArray();
310  }
311  }
312 
313  // Points list
314  UnityEditor.EditorGUILayout.BeginVertical(UnityEditor.EditorStyles.helpBox);
315  bool wideModeSetting = UnityEditor.EditorGUIUtility.wideMode;
316  UnityEditor.EditorGUIUtility.wideMode = false;
317  UnityEditor.EditorGUILayout.BeginHorizontal();
318 
319  // Positions
320  UnityEditor.EditorGUILayout.BeginVertical();
321  GUI.color = (editPositions ? defaultColor : disabledColor);
322  editPositions = UnityEditor.EditorGUILayout.Toggle("Edit Positions", editPositions);
323  for (int i = 0; i < line.points.Length; i++)
324  {
325  GUI.color = (i % 3 == 0) ? handleColorCircle : handleColorSquare;
326  if (editPositions)
327  {
328  GUI.color = Color.Lerp(GUI.color, defaultColor, 0.75f);
329  // highlight points that are overlapping
330  for (int j = 0; j < line.points.Length; j++)
331  {
332  if (j == i)
333  {
334  continue;
335  }
336 
337  if (Vector3.Distance(line.points[j].Point, line.points[i].Point) < overlappingPointThreshold)
338  {
339  overlappingPointIndexes.Add(i);
340  overlappingPointIndexes.Add(j);
341  GUI.color = errorColor;
342  break;
343  }
344  }
345  line.points[i].Point = UnityEditor.EditorGUILayout.Vector3Field(string.Empty, line.points[i].Point);
346  }
347  else
348  {
349  GUI.color = Color.Lerp(GUI.color, disabledColor, 0.75f);
350  UnityEditor.EditorGUILayout.Vector3Field(string.Empty, line.points[i].Point);
351  }
352  }
353  UnityEditor.EditorGUILayout.EndVertical();
354 
355  // Rotations
356  GUI.color = defaultColor;
357  UnityEditor.EditorGUILayout.BeginVertical();
358  editRotations = UnityEditor.EditorGUILayout.Toggle("Edit Rotations", editRotations);
359  GUI.color = (editRotations ? defaultColor : disabledColor);
360  for (int i = 0; i < line.points.Length; i++)
361  {
362  GUI.color = (i % 3 == 0) ? handleColorCircle : handleColorSquare;
363  if (editRotations)
364  {
365  GUI.color = Color.Lerp(GUI.color, defaultColor, 0.75f);
366  line.points[i].Rotation = Quaternion.Euler(UnityEditor.EditorGUILayout.Vector3Field(string.Empty, line.points[i].Rotation.eulerAngles));
367  }
368  else
369  {
370  GUI.color = Color.Lerp(GUI.color, disabledColor, 0.75f);
371  UnityEditor.EditorGUILayout.Vector3Field(string.Empty, line.points[i].Rotation.eulerAngles);
372  }
373  }
374  UnityEditor.EditorGUILayout.EndVertical();
375 
376  UnityEditor.EditorGUILayout.EndHorizontal();
377  UnityEditor.EditorGUIUtility.wideMode = wideModeSetting;
378 
379  GUI.color = defaultColor;
380  // If we found overlapping points, provide an option to auto-separate them
381  if (overlappingPointIndexes.Count > 0)
382  {
383  GUI.color = errorColor;
384  if (GUILayout.Button("Fix overlapping points"))
385  {
386  // Move them slightly out of the way
387  foreach (int overlappoingPointIndex in overlappingPointIndexes)
388  {
389  line.points[overlappoingPointIndex].Point += (UnityEngine.Random.onUnitSphere * overlappingPointThreshold * 2);
390  }
391  }
392  }
393 
394  UnityEditor.EditorGUILayout.EndVertical();
395 
396  GUI.color = defaultColor;
397  if (GUILayout.Button(" + Add Points To End"))
398  {
399  // Using lists for maximum clarity
400  pointsList.Clear();
401  SplinePoint[] newPoints = new SplinePoint[3];
402  Vector3 direction = line.GetVelocity(0.99f);
403  float distance = Mathf.Max(line.UnclampedWorldLength * 0.05f, overlappingPointThreshold * 5);
404  newPoints[0].Point = line.LastPoint + (direction * distance);
405  newPoints[1].Point = newPoints[0].Point + (direction * distance);
406  newPoints[2].Point = newPoints[1].Point + (direction * distance);
407  pointsList.AddRange(line.points);
408  pointsList.AddRange(newPoints);
409  line.points = pointsList.ToArray();
410  }
411  if (line.NumPoints > 4)
412  {
413  if (GUILayout.Button(" - Remove Points From End"))
414  {
415  // Using lists for maximum clarity
416  pointsList.Clear();
417  pointsList.AddRange(line.points);
418  pointsList.RemoveAt(pointsList.Count - 1);
419  pointsList.RemoveAt(pointsList.Count - 1);
420  pointsList.RemoveAt(pointsList.Count - 1);
421  line.points = pointsList.ToArray();
422  }
423  }
424  }
425  DrawSectionEnd();
426  }
427 
428  protected override void DrawCustomSceneGUI()
429  {
430  Spline line = (Spline)target;
431 
432  base.DrawCustomSceneGUI();
433 
434  for (int i = 0; i < line.NumPoints; i++)
435  {
436  if (editPositions)
437  {
438  if (i % 3 == 0)
439  {
440  if (i == 0)
441  {
442  line.SetPoint(i, SphereMoveHandle(line.GetPoint(i)));
443  UnityEditor.Handles.color = handleColorTangent;
444  UnityEditor.Handles.DrawLine(line.GetPoint(i), line.GetPoint(i + 1));
445  }
446  else if (i == line.NumPoints - 1)
447  {
448  line.SetPoint(i, SphereMoveHandle(line.GetPoint(i)));
449  UnityEditor.Handles.color = handleColorTangent;
450  UnityEditor.Handles.DrawLine(line.GetPoint(i), line.GetPoint(i - 1));
451  }
452  else
453  {
454  line.SetPoint(i, CircleMoveHandle(line.GetPoint(i)));
455  UnityEditor.Handles.color = handleColorTangent;
456  UnityEditor.Handles.DrawLine(line.GetPoint(i), line.GetPoint(i + 1));
457  UnityEditor.Handles.DrawLine(line.GetPoint(i), line.GetPoint(i - 1));
458  }
459 
460  }
461  else
462  {
463  line.SetPoint(i, SquareMoveHandle(line.GetPoint(i)));
464  }
465  }
466 
467  if (editRotations)
468  {
469  line.points[i].Rotation = RotationHandle(line.GetPoint(i), line.points[i].Rotation);
470  }
471  }
472  }
473  }
474 #endif
475 
476  }
477 }
Vector3 GetVelocity(float normalizedLength)
Gets the velocity along the line
Definition: LineBase.cs:208
override Vector3 GetPointInternal(int pointIndex)
Get a point based on point index Point index will be pre-clamped
Definition: Spline.cs:156
static Vector3 InterpolateBezeirPoints(Vector3 point1, Vector3 point2, Vector3 point3, Vector3 point4, float normalizedLength)
Definition: LineUtility.cs:225
override void SetPointInternal(int pointIndex, Vector3 point)
Definition: Spline.cs:172
override void AppendPoint(Vector3 point)
Definition: Spline.cs:106
override Vector3 GetUpVectorInternal(float normalizedLength)
Gets the up vector at a normalized length along line (used for rotation)
Definition: Spline.cs:229
override float GetUnclampedWorldLengthInternal()
Get the UNCLAMPED world length of the line
Definition: Spline.cs:250
override int NumPoints
Definition: Spline.cs:33
override Vector3 GetPointInternal(float normalizedDistance)
Get a point based on normalized distance along line Normalized distance will be pre-clamped ...
Definition: Spline.cs:113
void SetPoint(int pointIndex, Vector3 point)
Sets a point in the line This function is not guaranteed to have an effect
Definition: LineBase.cs:329
Vector3 GetPoint(float normalizedLength)
Gets a point along the line at the specified length
Definition: LineBase.cs:291