407 lines
16 KiB
C#
407 lines
16 KiB
C#
|
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<string> 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<EditorToolbarElementAttribute>();
|
||
|
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<Vector3> m_LineSegments = new List<Vector3>();
|
||
|
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<Texture2D>("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<float> 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<float3> 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<float> 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<float3> 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());
|
||
|
|
||
|
}
|
||
|
}
|
||
|
}
|