using System; using System.Collections; using System.Collections.Generic; using System.Linq; using Unity.Mathematics; using UnityEditor; using UnityEditor.EditorTools; using UnityEditor.Splines; using UnityEngine; using UnityEngine.Splines; #if UNITY_2022_1_OR_NEWER using UnityEditor.Overlays; #else using System.Reflection; using UnityEditor.Toolbars; using UnityEngine.UIElements; #endif using Interpolators = UnityEngine.Splines.Interpolators; namespace Unity.Splines.Examples { [CustomEditor(typeof(SpeedTiltTool))] #if UNITY_2022_1_OR_NEWER class SplineDataPointToolSettings : UnityEditor.Editor, ICreateToolbar #else class SplineDataPointToolSettings : UnityEditor.Editor #endif { public virtual IEnumerable toolbarElements { get { yield return "Tool Settings/Pivot Mode"; yield return "Tool Settings/Pivot Rotation"; yield return "SpeedTiltTool/SplineDataType"; } } #if !UNITY_2022_1_OR_NEWER const string k_ElementClassName = "unity-editor-toolbar-element"; const string k_StyleSheetsPath = "StyleSheets/Toolbars/"; static VisualElement CreateToolbar() { var target = new VisualElement(); var path = k_StyleSheetsPath + "EditorToolbar"; var common = EditorGUIUtility.Load($"{path}Common.uss") as StyleSheet; if (common != null) target.styleSheets.Add(common); var themeSpecificName = EditorGUIUtility.isProSkin ? "Dark" : "Light"; var themeSpecific = EditorGUIUtility.Load($"{path}{themeSpecificName}.uss") as StyleSheet; if (themeSpecific != null) target.styleSheets.Add(themeSpecific); target.AddToClassList("unity-toolbar-overlay"); target.style.flexDirection = FlexDirection.Row; return target; } public override VisualElement CreateInspectorGUI() { var root = CreateToolbar(); var elements = TypeCache.GetTypesWithAttribute(typeof(EditorToolbarElementAttribute)); foreach (var element in toolbarElements) { var type = elements.FirstOrDefault(x => { var attrib = x.GetCustomAttribute(); return attrib != null && attrib.id == element; }); if (type != null) { try { const BindingFlags flags = BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.CreateInstance; var ve = (VisualElement)Activator.CreateInstance(type, flags, null, null, null, null); ve.AddToClassList(k_ElementClassName); root.Add(ve); } catch (Exception e) { Debug.LogError($"Failed creating toolbar element from ID \"{element}\".\n{e}"); } } } EditorToolbarUtility.SetupChildrenAsButtonStrip(root); return root; } #endif } [EditorTool("Speed & Tilt Tool", typeof(AnimateCarAlongSpline))] public class SpeedTiltTool : EditorTool, IDrawSelectedHandles { internal enum SplineDataType { SpeedData, TiltData }; //Speed handles parameters const float k_SpeedScaleFactor = 10f; const float k_DisplaySpace = 0.5f; //Tilt handles parameters Quaternion m_StartingRotation; const float k_HandleSize = 0.15f; Color[] m_HandlesColors = {Color.red, new (1f,0.6f,0f)}; List m_LineSegments = new List(); static SplineDataType s_SelectedSplineData = SplineDataType.SpeedData; internal static SplineDataType selectedSplineData { get => s_SelectedSplineData; set => s_SelectedSplineData = value; } GUIContent m_IconContent; public override GUIContent toolbarIcon => m_IconContent; bool m_DisableHandles; bool m_SpeedInUse; bool m_TiltInUse; void OnEnable() { m_IconContent = new GUIContent() { image = Resources.Load("Icons/SpeedTiltTool"), text = "Speed & Tilt Tool", tooltip = "Adjust the vehicle speed and tilt DataPoints along the spline." }; } public override void OnToolGUI(EditorWindow window) { var splineDataTarget = target as AnimateCarAlongSpline; if(splineDataTarget == null || splineDataTarget.splineContainer == null) return; var nativeSpline = new NativeSpline(splineDataTarget.splineContainer.Spline, splineDataTarget.splineContainer.transform.localToWorldMatrix); Undo.RecordObject(splineDataTarget, "Modifying Speed and Tilt SplineData"); m_DisableHandles = false; //Speed handles section Handles.color = m_HandlesColors[(int)SplineDataType.SpeedData]; //User defined : Handles to manipulate Speed data m_SpeedInUse = DrawSpeedDataPoints(nativeSpline, splineDataTarget.speed, splineDataTarget.m_MaxSpeed, true); //Use defined : Draws a line along the whole Speed SplineData DrawSpeedSplineData(nativeSpline, splineDataTarget.speed); //Tilt handles section Handles.color = m_HandlesColors[(int)SplineDataType.TiltData]; //User defined : Handles to manipulate Tilt data m_TiltInUse = DrawTiltDataPoints(nativeSpline, splineDataTarget.tilt); //Use defined : Draws a line along the whole Tilt SplineData DrawTiltSplineData(nativeSpline, splineDataTarget.tilt); //Draw DataPoint default Manipulation handles Handles.color = m_HandlesColors[(int)s_SelectedSplineData]; if(s_SelectedSplineData == SplineDataType.SpeedData) nativeSpline.DataPointHandles(splineDataTarget.speed); else nativeSpline.DataPointHandles(splineDataTarget.tilt); } public void OnDrawHandles() { var splineDataTarget = target as AnimateCarAlongSpline; if(ToolManager.IsActiveTool(this) || splineDataTarget.splineContainer == null) return; if(Event.current.type != EventType.Repaint) return; m_DisableHandles = true; var nativeSpline = new NativeSpline(splineDataTarget.splineContainer.Spline, splineDataTarget.splineContainer.transform.localToWorldMatrix); Color color = m_HandlesColors[(int)SplineDataType.SpeedData]; color.a = 0.5f; Handles.color = color; DrawSpeedDataPoints(nativeSpline, splineDataTarget.speed, splineDataTarget.m_MaxSpeed, false); DrawSpeedSplineData(nativeSpline, splineDataTarget.speed); color = m_HandlesColors[(int)SplineDataType.TiltData]; color.a = 0.5f; Handles.color = color; DrawTiltSplineData(nativeSpline,splineDataTarget.tilt); } bool DrawSpeedDataPoints(NativeSpline spline, SplineData speedSplineData, float maxSpeed, bool drawLabel) { var inUse = false; for(int dataFrameIndex = 0; dataFrameIndex < speedSplineData.Count; dataFrameIndex++) { var dataPoint = speedSplineData[dataFrameIndex]; var normalizedT = SplineUtility.GetNormalizedInterpolation(spline, dataPoint.Index, speedSplineData.PathIndexUnit); var position = spline.EvaluatePosition(normalizedT); var speedValue = dataPoint.Value; if(speedValue > maxSpeed) { speedValue = maxSpeed; dataPoint.Value = maxSpeed; speedSplineData[dataFrameIndex] = dataPoint; } var id = m_DisableHandles ? -1 : GUIUtility.GetControlID(FocusType.Passive); if(DrawSpeedDataPoint(id, position, speedValue, drawLabel, out var result)) { dataPoint.Value = Mathf.Clamp(result, 0.01f, maxSpeed); speedSplineData[dataFrameIndex] = dataPoint; inUse = true; } } return inUse; } bool DrawSpeedDataPoint( int controlID, Vector3 position, float inValue, bool drawLabel, out float outValue) { outValue = 0f; var handleColor = Handles.color; if(GUIUtility.hotControl == controlID) handleColor = Handles.selectedColor; else if(GUIUtility.hotControl == 0 && HandleUtility.nearestControl==controlID) handleColor = Handles.preselectionColor; var extremity = position + (inValue / k_SpeedScaleFactor) * Vector3.up; using(new Handles.DrawingScope(handleColor)) { var size = k_HandleSize * HandleUtility.GetHandleSize(position); Handles.DrawLine(position, extremity); var val = Handles.Slider(controlID, extremity, Vector3.up, size, Handles.SphereHandleCap, 0); if(drawLabel) Handles.Label(extremity + 2f * size * Vector3.up, inValue.ToString()); if(GUIUtility.hotControl == controlID) { outValue = k_SpeedScaleFactor * (val - position).magnitude * math.sign(math.dot(val - position, Vector3.up)); return true; } } return false; } bool DrawTiltDataPoints(NativeSpline spline, SplineData tiltSplineData) { var inUse = false; for(int dataFrameIndex = 0; dataFrameIndex < tiltSplineData.Count; dataFrameIndex++) { var dataPoint = tiltSplineData[dataFrameIndex]; var normalizedT = SplineUtility.GetNormalizedInterpolation(spline, dataPoint.Index, tiltSplineData.PathIndexUnit); spline.Evaluate(normalizedT, out var position, out var tangent, out var up); var id = m_DisableHandles ? -1 : GUIUtility.GetControlID(FocusType.Passive); if(DrawTiltDataPoint(id, position, tangent, up, dataPoint.Value, out var result)) { dataPoint.Value = result; tiltSplineData[dataFrameIndex] = dataPoint; inUse = true; } } return inUse; } bool DrawTiltDataPoint( int controlID, Vector3 position, Vector3 tangent, Vector3 up, float3 inValue, out float3 outValue) { outValue = float3.zero; if(tangent == Vector3.zero) return false; Matrix4x4 localMatrix = Matrix4x4.identity; localMatrix.SetTRS(position, Quaternion.LookRotation(tangent, up), Vector3.one); var matrix = Handles.matrix * localMatrix; using(new Handles.DrawingScope(matrix)) { var dataPointRotation = Quaternion.FromToRotation(Vector3.up, inValue); if(GUIUtility.hotControl == 0) m_StartingRotation = dataPointRotation; var color = Handles.color; if(!m_TiltInUse) color.a = 0.33f; using(new Handles.DrawingScope(color)) Handles.ArrowHandleCap(-1, Vector3.zero, Quaternion.FromToRotation(Vector3.forward, inValue), 1f, EventType.Repaint); var rotation = Handles.Disc(controlID, dataPointRotation, Vector3.zero, Vector3.forward, 1, false, 0); if(GUIUtility.hotControl == controlID) { var deltaRot = Quaternion.Inverse(m_StartingRotation) * rotation; outValue = deltaRot * m_StartingRotation * Vector3.up; return true; } } return false; } void DrawSpeedSplineData(NativeSpline spline, SplineData splineData) { m_LineSegments.Clear(); if(GUIUtility.hotControl == 0 || m_SpeedInUse || !ToolManager.IsActiveTool(this) || Tools.viewToolActive) { var data = splineData.Evaluate(spline, 0, PathIndexUnit.Distance, new Interpolators.LerpFloat()); var position = spline.EvaluatePosition(0); var previousExtremity = (Vector3)position + ( data / k_SpeedScaleFactor ) * Vector3.up; var currentOffset = k_DisplaySpace; while(currentOffset < spline.GetLength()) { var t = currentOffset / spline.GetLength(); position = spline.EvaluatePosition(t); data = splineData.Evaluate(spline, currentOffset, PathIndexUnit.Distance, new Interpolators.LerpFloat()); var extremity = (Vector3)position + ( data / k_SpeedScaleFactor ) * Vector3.up; m_LineSegments.Add(previousExtremity); m_LineSegments.Add(extremity); currentOffset += k_DisplaySpace; previousExtremity = extremity; } position = spline.EvaluatePosition(1); data = splineData.Evaluate(spline, spline.GetLength(), PathIndexUnit.Distance, new Interpolators.LerpFloat()); var lastExtremity = (Vector3)position + ( data / k_SpeedScaleFactor ) * Vector3.up; m_LineSegments.Add(previousExtremity); m_LineSegments.Add(lastExtremity); } Handles.DrawLines(m_LineSegments.ToArray()); } void DrawTiltSplineData(NativeSpline spline, SplineData splineData) { m_LineSegments.Clear(); if(GUIUtility.hotControl == 0 || m_TiltInUse || !ToolManager.IsActiveTool(this) || Tools.viewToolActive) { var currentOffset = k_DisplaySpace; while(currentOffset < spline.GetLength()) { var t = currentOffset / spline.GetLength(); spline.Evaluate(t, out float3 position, out float3 direction, out float3 up); var data = splineData.Evaluate(spline, t, PathIndexUnit.Normalized, new Interpolators.LerpFloat3()); Matrix4x4 localMatrix = Matrix4x4.identity; localMatrix.SetTRS(position, Quaternion.LookRotation(direction, up), Vector3.one); m_LineSegments.Add(localMatrix.GetPosition()); m_LineSegments.Add(localMatrix.MultiplyPoint(math.normalize(data))); currentOffset += k_DisplaySpace; } } var color = Handles.color; if(!m_TiltInUse) color.a = 0.33f; using(new Handles.DrawingScope(color)) Handles.DrawLines(m_LineSegments.ToArray()); } } }