public void OnSceneGUI() { MultiSpline ms = (MultiSpline)target; if (!ms || ms.m_Type == GenType.Uninitialized || s_CurrentlyEditing != ms) { return; } InitializeStyles(); ResetUndo(); switch (ms.m_Type) { case GenType.Simple: SceneGUI_Simple(ms); break; case GenType.Linear: SceneGUI_Linear(ms); break; case GenType.CSpline: SceneGUI_CSpline(ms); break; case GenType.Cardinal: SceneGUI_Cardinal(ms); break; } }
static private void ConvertTo(MultiSpline inSpline, GenType inType) { switch (inType) { case GenType.Simple: ConvertToSimple(inSpline); break; case GenType.Linear: ConvertToLinear(inSpline); break; case GenType.CSpline: ConvertToCSpline(inSpline); break; case GenType.Cardinal: ConvertToCardinal(inSpline); break; default: Debug.LogError("[MultiSpline] Type " + inType.ToString() + " not yet supported in editor."); return; } inSpline.m_Type = inType; inSpline.m_Dirty = true; }
static private void RenderGizmo_CSplinePreview(MultiSpline inSpline, Transform inTransform) { int vertCount = inSpline.m_Vertices.Length; if (!inSpline.m_Looped) { --vertCount; } for (int i = 0; i < inSpline.m_Vertices.Length; ++i) { if (i < vertCount) { RenderGizmo_PathPreview(inSpline, (float)i / vertCount, (float)(i + 1) / vertCount, 24); } RenderGizmo_PathNode(inTransform, inSpline.m_Vertices[i].Point, i == 0); if (inSpline.m_Looped || i > 0) { RenderGizmo_TangentLine(inTransform, inSpline.m_Vertices[i].Point, -inSpline.m_Vertices[i].InTangent, false); RenderGizmo_TangentPoint(inTransform, inSpline.m_Vertices[i].Point, -inSpline.m_Vertices[i].InTangent, false); } if (i < vertCount) { RenderGizmo_TangentLine(inTransform, inSpline.m_Vertices[i].Point, inSpline.m_Vertices[i].OutTangent, true); RenderGizmo_TangentPoint(inTransform, inSpline.m_Vertices[i].Point, inSpline.m_Vertices[i].OutTangent, true); } } }
private void InspectorGUI_Cardinal(MultiSpline inSpline) { HeaderText("Catmull-Rom / Cardinal Spline", 200); ToggleField("Looped", ref inSpline.m_Looped); SliderField("Tension", ref inSpline.m_CRTension, -3, 1); }
private void InspectorGUI_Simple(MultiSpline inSpline) { HeaderText("Simple Spline"); Vector3Field("Start Position", ref inSpline.m_Vertices[0].Point); Vector3Field("End Position", ref inSpline.m_Vertices[1].Point); Vector3Field("Control", ref inSpline.m_ControlPointA); }
private void OnDisable() { if (s_CurrentlyEditing == target) { s_CurrentlyEditing = null; } Undo.undoRedoPerformed += MarkSplineAsDirty; }
private void MarkSplineAsDirty() { MultiSpline ms = (MultiSpline)target; if (ms != null) { ms.m_Dirty = true; } }
static private void RenderGizmo_SimplePreview(MultiSpline inSpline, Transform inTransform) { RenderGizmo_PathPreview(inSpline, 0, 1, 24); RenderGizmo_PathNode(inTransform, inSpline.m_Vertices[0].Point, true); RenderGizmo_PathNode(inTransform, inSpline.m_Vertices[1].Point, false); RenderGizmo_ControlPoint(inTransform, inSpline.m_ControlPointA); RenderGizmo_ControlLine(inTransform, inSpline.m_Vertices[0].Point, inSpline.m_ControlPointA); RenderGizmo_ControlLine(inTransform, inSpline.m_Vertices[1].Point, inSpline.m_ControlPointA); }
static private void DeleteVertex(MultiSpline inSpline, int inIndex) { if (inSpline.m_Vertices == null || inSpline.m_Vertices.Length < 3 || inSpline.m_Type == GenType.Uninitialized || inSpline.m_Type == GenType.Simple) { return; } UnityEditor.ArrayUtility.RemoveAt(ref inSpline.m_Vertices, inIndex); inSpline.m_Dirty = true; }
static private void ConvertToCardinal(MultiSpline inSpline) { switch (inSpline.m_Type) { case GenType.Uninitialized: default: { // Default values inSpline.m_CRTension = 0; inSpline.m_Looped = false; Array.Resize(ref inSpline.m_Vertices, 4); inSpline.m_Vertices[0].Point = new Vector3(-1, -1, 0); inSpline.m_Vertices[1].Point = new Vector3(-1, 0, 0); inSpline.m_Vertices[2].Point = new Vector3(1, 0, 0); inSpline.m_Vertices[3].Point = new Vector3(1, -1, 0); break; } case GenType.Simple: { inSpline.m_Looped = false; Vector3 start = inSpline.m_Vertices[0].Point; Vector3 end = inSpline.m_Vertices[1].Point; Vector3 control = inSpline.m_ControlPointA; Vector3 startTangent = 0.5f * (start - control); Vector3 endTangent = 0.5f * (end - control); Array.Resize(ref inSpline.m_Vertices, 2); inSpline.m_ControlPointA = start + startTangent; inSpline.m_Vertices[0].Point = start; inSpline.m_Vertices[1].Point = end; inSpline.m_ControlPointB = end + endTangent; break; } case GenType.Linear: case GenType.CSpline: { // Points are identical, just need to set control points properly inSpline.m_ControlPointA = inSpline.m_Vertices[0].Point; inSpline.m_ControlPointB = inSpline.m_Vertices[inSpline.m_Vertices.Length - 1].Point; break; } case GenType.Cardinal: { // Converts directly return; } } }
static private void RenderGizmo_PathPreview(MultiSpline inSpline, float inStart = 0, float inEnd = 1, int inSegments = 64) { Transform tr = inSpline.transform; int numSamples = Spline.Sample(inSpline.m_TransformWrapper, s_CachedSamples, inStart, inEnd, 0, inSegments); for (int i = 0; i < numSamples - 1; ++i) { Vector3 start = s_CachedSamples[i]; Vector3 next = s_CachedSamples[i + 1]; Gizmos.color = Color.Lerp(GizmoConfig.LineColorNormalStart, GizmoConfig.LineColorNormalEnd, inStart + (inEnd - inStart) * ((float)i / (numSamples - 1))); Gizmos.DrawLine(start, next); } }
private void SceneGUI_Cardinal(MultiSpline inSpline) { Transform tr = inSpline.transform; if (Tools.current == Tool.Move) { for (int i = 0; i < inSpline.m_Vertices.Length; ++i) { EditorGUI.BeginChangeCheck(); Vector3 pos = Handles.PositionHandle( tr.TransformPoint(inSpline.m_Vertices[i].Point), Quaternion.identity); if (EditorGUI.EndChangeCheck()) { MarkDirty("Changed node position"); inSpline.m_Vertices[i].Point = tr.InverseTransformPoint(pos); } } if (!inSpline.m_Looped) { // Control point A { EditorGUI.BeginChangeCheck(); Vector3 pos = Handles.PositionHandle( tr.TransformPoint(inSpline.m_ControlPointA), Quaternion.identity); if (EditorGUI.EndChangeCheck()) { MarkDirty("Changed control point"); inSpline.m_ControlPointA = tr.InverseTransformPoint(pos); } } // Control point B { EditorGUI.BeginChangeCheck(); Vector3 pos = Handles.PositionHandle( tr.TransformPoint(inSpline.m_ControlPointB), Quaternion.identity); if (EditorGUI.EndChangeCheck()) { MarkDirty("Changed control point"); inSpline.m_ControlPointB = tr.InverseTransformPoint(pos); } } } } }
static private void RenderGizmo_LinearPreview(MultiSpline inSpline, Transform inTransform) { int vertCount = inSpline.m_Vertices.Length; if (!inSpline.m_Looped) { --vertCount; } RenderGizmo_PathPreview(inSpline, 0, 1, 1 + vertCount * 8); for (int i = 0; i < inSpline.m_Vertices.Length; ++i) { RenderGizmo_PathNode(inTransform, inSpline.m_Vertices[i].Point, i == 0); } }
static private void ConvertToLinear(MultiSpline inSpline) { switch (inSpline.m_Type) { case GenType.Uninitialized: default: { // Default values inSpline.m_Looped = false; Array.Resize(ref inSpline.m_Vertices, 2); inSpline.m_Vertices[0] = new CSplineVertex(Vector3.left); inSpline.m_Vertices[1] = new CSplineVertex(Vector3.right); break; } case GenType.Simple: { // Swap control and end points for linear order Vector3 control = inSpline.m_ControlPointA; Array.Resize(ref inSpline.m_Vertices, 3); inSpline.m_Vertices[2] = inSpline.m_Vertices[1]; inSpline.m_Vertices[1] = new CSplineVertex(control); // Default to not closed inSpline.m_Looped = false; break; } case GenType.Linear: { // Already linear, no need to do anything return; } case GenType.CSpline: case GenType.Cardinal: { // Copy CSpline points over Array.Resize(ref inSpline.m_Vertices, inSpline.m_Vertices.Length); for (int i = 0; i < inSpline.m_Vertices.Length; ++i) { inSpline.m_Vertices[i] = inSpline.m_Vertices[i]; } break; } } }
static private void RenderGizmo_CardinalPreview(MultiSpline inSpline, Transform inTransform) { for (int i = 0; i < inSpline.m_Vertices.Length; ++i) { RenderGizmo_PathPreview(inSpline, (float)i / inSpline.m_Vertices.Length, (float)(i + 1) / inSpline.m_Vertices.Length, 24); RenderGizmo_PathNode(inTransform, inSpline.m_Vertices[i].Point, i == 0); } if (!inSpline.m_Looped) { RenderGizmo_ControlLine(inTransform, inSpline.m_Vertices[0].Point, inSpline.m_ControlPointA); RenderGizmo_ControlPoint(inTransform, inSpline.m_ControlPointA); RenderGizmo_ControlLine(inTransform, inSpline.m_Vertices[inSpline.m_Vertices.Length - 1].Point, inSpline.m_ControlPointB); RenderGizmo_ControlPoint(inTransform, inSpline.m_ControlPointB); } }
static private void InsertVertexAfter(MultiSpline inSpline, int inIndex) { if (inSpline.m_Vertices == null || inSpline.m_Type == GenType.Uninitialized || inSpline.m_Type == GenType.Simple) { return; } int currentIndex = inIndex; float lerp = 0.5f; bool bAddToEnd = false; int segCount = inSpline.m_Vertices.Length; if (!inSpline.m_Looped) { --segCount; } if (currentIndex == inSpline.m_Vertices.Length - 1) { bAddToEnd = true; if (!inSpline.m_Looped) { --currentIndex; lerp = 1.5f; } } float realLerp = (float)(currentIndex + lerp) / segCount; Vector3 next = inSpline.GetPoint(realLerp); Vector3 dir = inSpline.GetDirection(realLerp); CSplineVertex vert; vert.Point = next; vert.InTangent = vert.OutTangent = inSpline.m_Type == GenType.CSpline ? dir : Vector3.zero; if (bAddToEnd) { UnityEditor.ArrayUtility.Add(ref inSpline.m_Vertices, vert); } else { UnityEditor.ArrayUtility.Insert(ref inSpline.m_Vertices, currentIndex + 1, vert); } }
private void SceneGUI_Simple(MultiSpline inSpline) { Transform tr = inSpline.transform; if (Tools.current == Tool.Move) { // Starting position { EditorGUI.BeginChangeCheck(); Vector3 newStartPos = Handles.PositionHandle( tr.TransformPoint(inSpline.m_Vertices[0].Point), Quaternion.identity); if (EditorGUI.EndChangeCheck()) { MarkDirty("Changed starting position"); inSpline.m_Vertices[0].Point = tr.InverseTransformPoint(newStartPos); } } // Ending position { EditorGUI.BeginChangeCheck(); Vector3 newEndPos = Handles.PositionHandle( tr.TransformPoint(inSpline.m_Vertices[1].Point), Quaternion.identity); if (EditorGUI.EndChangeCheck()) { MarkDirty("Changed ending position"); inSpline.m_Vertices[1].Point = tr.InverseTransformPoint(newEndPos); } } // Control position { EditorGUI.BeginChangeCheck(); Vector3 newControlPos = Handles.PositionHandle( tr.TransformPoint(inSpline.m_ControlPointA), Quaternion.identity); if (EditorGUI.EndChangeCheck()) { MarkDirty("Changed control position"); inSpline.m_ControlPointA = tr.InverseTransformPoint(newControlPos); } } } }
private void SceneGUI_Linear(MultiSpline inSpline) { Transform tr = inSpline.transform; if (Tools.current == Tool.Move) { for (int i = 0; i < inSpline.m_Vertices.Length; ++i) { EditorGUI.BeginChangeCheck(); Vector3 pos = Handles.PositionHandle( tr.TransformPoint(inSpline.m_Vertices[i].Point), Quaternion.identity); if (EditorGUI.EndChangeCheck()) { MarkDirty("Changed node position"); inSpline.m_Vertices[i].Point = tr.InverseTransformPoint(pos); } } } }
static private void RecenterVertices(MultiSpline inSpline, Vector3 inZero) { // Center the vertices if (inSpline.m_Vertices != null) { Vector3 currentCenter = Vector3.zero; for (int i = 0; i < inSpline.m_Vertices.Length; ++i) { currentCenter += inSpline.m_Vertices[i].Point; } currentCenter /= inSpline.m_Vertices.Length; Vector3 offset = inZero - currentCenter; for (int i = 0; i < inSpline.m_Vertices.Length; ++i) { inSpline.m_Vertices[i].Point += offset; } inSpline.m_ControlPointA += offset; inSpline.m_ControlPointB += offset; } }
static private void InsertVertexBefore(MultiSpline inSpline, int inIndex) { if (inSpline.m_Vertices == null || inSpline.m_Type == GenType.Uninitialized || inSpline.m_Type == GenType.Simple) { return; } int currentIndex = inIndex; float lerp = -0.5f; int segCount = inSpline.m_Vertices.Length; if (!inSpline.m_Looped) { --segCount; } if (currentIndex == 0) { if (!inSpline.m_Looped) { ++currentIndex; lerp = -1.5f; } } float realLerp = (float)(currentIndex + lerp) / segCount; Vector3 next = inSpline.GetPoint(realLerp); Vector3 dir = inSpline.GetDirection(realLerp); CSplineVertex vert; vert.Point = next; vert.InTangent = vert.OutTangent = inSpline.m_Type == GenType.CSpline ? dir : Vector3.zero; UnityEditor.ArrayUtility.Insert(ref inSpline.m_Vertices, currentIndex, vert); inSpline.m_Dirty = true; }
static private void ConvertToCSpline(MultiSpline inSpline) { //TODO(Alex): Implement }
public override void OnInspectorGUI() { MultiSpline ms = (MultiSpline)target; if (!ms) { return; } InitializeStyles(); ResetUndo(); // Editing controls if (ms.m_Type != GenType.Uninitialized) { HeaderText("Edit Controls"); if (s_CurrentlyEditing == ms) { if (GUILayout.Button("Stop Editing", s_ActivatedButton)) { s_CurrentlyEditing = null; } } else { if (GUILayout.Button("Edit", EditorStyles.miniButton)) { s_CurrentlyEditing = ms; } } } using (new EnableGUI(s_CurrentlyEditing == ms || ms.m_Type == GenType.Uninitialized)) { HeaderText("Spline Types"); if (ms.m_Type == GenType.Uninitialized) { EditorGUILayout.HelpBox("This spline has not been initialized. Select one of the following types before starting.", MessageType.Warning); } EditorGUILayout.BeginHorizontal(); { if (GUILayout.Button("Simple Spline", ms.m_Type == GenType.Simple ? s_ActivatedButton : EditorStyles.miniButton)) { MarkDirty("Converted to Simple"); ConvertTo(ms, GenType.Simple); s_CurrentlyEditing = ms; } if (GUILayout.Button("Linear Spline", ms.m_Type == GenType.Linear ? s_ActivatedButton : EditorStyles.miniButton)) { MarkDirty("Converted to Linear"); ConvertTo(ms, GenType.Linear); s_CurrentlyEditing = ms; } } EditorGUILayout.EndHorizontal(); EditorGUILayout.BeginHorizontal(); { if (GUILayout.Button("C-Spline", ms.m_Type == GenType.CSpline ? s_ActivatedButton : EditorStyles.miniButton)) { MarkDirty("Converted to CSpline"); ConvertTo(ms, GenType.CSpline); s_CurrentlyEditing = ms; } if (GUILayout.Button("Catmull-Rom / Cardinal", ms.m_Type == GenType.Cardinal ? s_ActivatedButton : EditorStyles.miniButton)) { MarkDirty("Converted to Cardinal"); ConvertTo(ms, GenType.Cardinal); s_CurrentlyEditing = ms; } } EditorGUILayout.EndHorizontal(); if (ms.m_Type != GenType.Uninitialized) { GUILayout.Space(8); switch (ms.m_Type) { case GenType.Simple: InspectorGUI_Simple(ms); break; case GenType.Linear: InspectorGUI_Linear(ms); break; case GenType.CSpline: InspectorGUI_CSpline(ms); break; case GenType.Cardinal: InspectorGUI_Cardinal(ms); break; } GUILayout.Space(8); HeaderText("Utilities", 80); if (GUILayout.Button("Re-center Vertices")) { MarkDirty("Recenter Vertices"); RecenterVertices(ms, Vector3.zero); } } } }
private void InspectorGUI_Linear(MultiSpline inSpline) { HeaderText("Linear Spline"); ToggleField("Looped", ref inSpline.m_Looped); }
private void InspectorGUI_CSpline(MultiSpline inSpline) { HeaderText("C-Spline", 80); ToggleField("Looped", ref inSpline.m_Looped); }
static private void ConvertToSimple(MultiSpline inSpline) { Vector3 start, end, control; switch (inSpline.m_Type) { case GenType.Uninitialized: default: { // initialize with default values inSpline.m_Looped = false; start = Vector3.left; end = Vector3.right; control = Vector3.up; break; } case GenType.Simple: { // Already a simple spline return; } case GenType.Linear: { // take start and end int endIdx = inSpline.m_Vertices.Length - 1; start = inSpline.m_Vertices[0].Point; end = inSpline.m_Vertices[endIdx].Point; // average the mid vertices to get control point if (endIdx > 1) { Vector3 vSum = Vector3.zero; for (int i = 1; i < endIdx; ++i) { vSum += inSpline.m_Vertices[i].Point; } vSum.x /= (endIdx - 1); vSum.y /= (endIdx - 1); vSum.z /= (endIdx - 1); control = vSum; } else { control = (start + end) * 0.5f; } break; } case GenType.CSpline: case GenType.Cardinal: { // take start and end int endIdx = inSpline.m_Vertices.Length - 1; start = inSpline.m_Vertices[0].Point; end = inSpline.m_Vertices[endIdx].Point; // Average the out/in control points control = ((start - inSpline.m_Vertices[0].OutTangent) + (end + inSpline.m_Vertices[endIdx].InTangent)) * 0.5f; break; } } Array.Resize(ref inSpline.m_Vertices, 2); inSpline.m_Vertices[0] = new CSplineVertex(start); inSpline.m_Vertices[1] = new CSplineVertex(end); inSpline.m_ControlPointA = control; inSpline.m_Looped = false; }