initial commit
This commit is contained in:
parent
6715289efe
commit
788c3389af
37645 changed files with 2526849 additions and 80 deletions
|
|
@ -0,0 +1,114 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using Unity.Mathematics;
|
||||
using UnityEngine;
|
||||
using UnityEngine.UIElements;
|
||||
|
||||
namespace UnityEditor.Splines
|
||||
{
|
||||
sealed class ButtonStripField : VisualElement
|
||||
{
|
||||
static readonly StyleSheet s_StyleSheet;
|
||||
|
||||
static ButtonStripField()
|
||||
{
|
||||
s_StyleSheet = AssetDatabase.LoadAssetAtPath<StyleSheet>("Packages/com.unity.splines/Editor/Stylesheets/ButtonStripField.uss");
|
||||
}
|
||||
|
||||
const string k_ButtonStripClass = "button-strip";
|
||||
const string k_ButtonClass = "button-strip-button";
|
||||
const string k_ButtonIconClass = "button-strip-button__icon";
|
||||
const string k_LeftButtonClass = k_ButtonClass + "--left";
|
||||
const string k_MiddleButtonClass = k_ButtonClass + "--middle";
|
||||
const string k_RightButtonClass = k_ButtonClass + "--right";
|
||||
const string k_AloneButtonClass = k_ButtonClass + "--alone";
|
||||
const string k_CheckedButtonClass = k_ButtonClass + "--checked";
|
||||
|
||||
GUIContent[] m_Choices = new GUIContent[0];
|
||||
readonly VisualElement m_ButtonStrip;
|
||||
|
||||
public GUIContent[] choices
|
||||
{
|
||||
get => m_Choices;
|
||||
set
|
||||
{
|
||||
m_Choices = value ?? new GUIContent[0];
|
||||
RebuildButtonStrip();
|
||||
}
|
||||
}
|
||||
|
||||
int m_Value;
|
||||
|
||||
public int value
|
||||
{
|
||||
get => m_Value;
|
||||
set
|
||||
{
|
||||
m_Value = value;
|
||||
UpdateButtonsState(m_Value);
|
||||
OnValueChanged?.Invoke(m_Value);
|
||||
}
|
||||
}
|
||||
|
||||
public event Action<int> OnValueChanged;
|
||||
|
||||
public ButtonStripField()
|
||||
{
|
||||
styleSheets.Add(s_StyleSheet);
|
||||
|
||||
m_ButtonStrip = this;
|
||||
m_ButtonStrip.AddToClassList(k_ButtonStripClass);
|
||||
}
|
||||
|
||||
Button CreateButton(GUIContent content)
|
||||
{
|
||||
var button = new Button();
|
||||
button.displayTooltipWhenElided = false;
|
||||
button.tooltip = L10n.Tr(content.tooltip);
|
||||
var icon = new VisualElement { name = content.text };
|
||||
icon.AddToClassList(k_ButtonIconClass);
|
||||
button.AddToClassList(k_ButtonClass);
|
||||
button.Add(icon);
|
||||
return button;
|
||||
}
|
||||
|
||||
//public override void SetValueWithoutNotify(int newValue)
|
||||
public void SetValueWithoutNotify(int newValue)
|
||||
{
|
||||
m_Value = math.clamp(newValue, 0, choices.Length - 1);
|
||||
UpdateButtonsState(m_Value);
|
||||
}
|
||||
|
||||
void UpdateButtonsState(int value)
|
||||
{
|
||||
List<Button> buttons = m_ButtonStrip.Query<Button>().ToList();
|
||||
for (int i = 0; i < buttons.Count; ++i)
|
||||
{
|
||||
buttons[i].EnableInClassList(k_CheckedButtonClass, value == i);
|
||||
}
|
||||
}
|
||||
|
||||
void RebuildButtonStrip()
|
||||
{
|
||||
m_ButtonStrip.Clear();
|
||||
for (int i = 0, count = choices.Length; i < count; ++i)
|
||||
{
|
||||
var button = CreateButton(choices[i]);
|
||||
var targetValue = i;
|
||||
button.clicked += () => { value = targetValue; };
|
||||
if (choices.Length == 1)
|
||||
button.AddToClassList(k_AloneButtonClass);
|
||||
else if (i == 0)
|
||||
button.AddToClassList(k_LeftButtonClass);
|
||||
else if (i == count - 1)
|
||||
button.AddToClassList(k_RightButtonClass);
|
||||
else
|
||||
button.AddToClassList(k_MiddleButtonClass);
|
||||
|
||||
m_ButtonStrip.Add(button);
|
||||
}
|
||||
|
||||
UpdateButtonsState(value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,11 @@
|
|||
fileFormatVersion: 2
|
||||
guid: d25ad062b459643449c2494b5237dc00
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
|
|
@ -0,0 +1,26 @@
|
|||
using UnityEditor.EditorTools;
|
||||
using UnityEngine;
|
||||
using UnityEngine.Splines;
|
||||
using UObject = UnityEngine.Object;
|
||||
|
||||
namespace UnityEditor.Splines
|
||||
{
|
||||
static class EditorSplineGizmos
|
||||
{
|
||||
[DrawGizmo(GizmoType.Active | GizmoType.NonSelected | GizmoType.Selected | GizmoType.Pickable)]
|
||||
static void DrawUnselectedSplineGizmos(ISplineProvider provider, GizmoType gizmoType)
|
||||
{
|
||||
//Skip if tool engaged is a spline tool
|
||||
if (typeof(SplineTool).IsAssignableFrom(ToolManager.activeToolType) &&
|
||||
(provider is UObject objectProvider) && EditableSplineManager.TryGetTargetData(objectProvider, out _))
|
||||
return;
|
||||
|
||||
var prev = Gizmos.color;
|
||||
Gizmos.color = (gizmoType & (GizmoType.Selected | GizmoType.Active)) > 0
|
||||
? Handles.selectedColor
|
||||
: SplineGizmoUtility.s_GizmosLineColor.value;
|
||||
SplineGizmoUtility.DrawGizmos(provider);
|
||||
Gizmos.color = prev;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,3 @@
|
|||
fileFormatVersion: 2
|
||||
guid: a0357b2fb04a410d8dc62740f4fe4cbb
|
||||
timeCreated: 1618421082
|
||||
|
|
@ -0,0 +1,94 @@
|
|||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
using UnityEngine.UIElements;
|
||||
using UnityEditor.Toolbars;
|
||||
|
||||
namespace UnityEditor.Splines
|
||||
{
|
||||
[EditorToolbarElement("Spline Tool Settings/Handle Rotation")]
|
||||
class HandleRotationDropdown : EditorToolbarDropdown
|
||||
{
|
||||
const string k_ParentRotationIconPath = "Packages/com.unity.splines/Editor/Resources/Icons/ToolHandleParent.png";
|
||||
const string k_ElementRotationIconPath = "Packages/com.unity.splines/Editor/Resources/Icons/ToolHandleElement.png";
|
||||
|
||||
readonly List<GUIContent> m_OptionContents = new List<GUIContent>();
|
||||
|
||||
public HandleRotationDropdown()
|
||||
{
|
||||
name = "Handle Rotation";
|
||||
|
||||
var content = EditorGUIUtility.TrTextContent("Local",
|
||||
"Toggle Tool Handle Rotation\n\nTool handles are in the active object's rotation.",
|
||||
"ToolHandleLocal");
|
||||
m_OptionContents.Add(content);
|
||||
|
||||
content = EditorGUIUtility.TrTextContent("Global",
|
||||
"Toggle Tool Handle Rotation\n\nTool handles are in global rotation.",
|
||||
"ToolHandleGlobal");
|
||||
m_OptionContents.Add(content);
|
||||
|
||||
content = EditorGUIUtility.TrTextContent("Parent",
|
||||
"Toggle Tool Handle Rotation\n\nTool handles are in active element's parent's rotation.",
|
||||
k_ParentRotationIconPath);
|
||||
m_OptionContents.Add(content);
|
||||
|
||||
content = EditorGUIUtility.TrTextContent("Element",
|
||||
"Toggle Tool Handle Rotation\n\nTool handles are in active element's rotation.",
|
||||
k_ElementRotationIconPath);
|
||||
m_OptionContents.Add(content);
|
||||
|
||||
RegisterCallback<AttachToPanelEvent>(AttachedToPanel);
|
||||
RegisterCallback<DetachFromPanelEvent>(DetachedFromPanel);
|
||||
|
||||
clicked += OpenContextMenu;
|
||||
|
||||
RefreshElementContent();
|
||||
}
|
||||
|
||||
void OpenContextMenu()
|
||||
{
|
||||
var menu = new GenericMenu();
|
||||
menu.AddItem(m_OptionContents[(int)HandleOrientation.Global], SplineTool.handleOrientation == HandleOrientation.Global,
|
||||
() => SetHandleOrientationIfNeeded(HandleOrientation.Global));
|
||||
|
||||
menu.AddItem(m_OptionContents[(int)HandleOrientation.Local], SplineTool.handleOrientation == HandleOrientation.Local,
|
||||
() => SetHandleOrientationIfNeeded(HandleOrientation.Local));
|
||||
|
||||
menu.AddItem(m_OptionContents[(int)HandleOrientation.Parent], SplineTool.handleOrientation == HandleOrientation.Parent,
|
||||
() => SetHandleOrientationIfNeeded(HandleOrientation.Parent));
|
||||
|
||||
menu.AddItem(m_OptionContents[(int)HandleOrientation.Element], SplineTool.handleOrientation == HandleOrientation.Element,
|
||||
() => SetHandleOrientationIfNeeded(HandleOrientation.Element));
|
||||
|
||||
menu.DropDown(worldBound);
|
||||
}
|
||||
|
||||
void SetHandleOrientationIfNeeded(HandleOrientation handleOrientation)
|
||||
{
|
||||
if (SplineTool.handleOrientation != handleOrientation)
|
||||
{
|
||||
SplineTool.handleOrientation = handleOrientation;
|
||||
RefreshElementContent();
|
||||
}
|
||||
}
|
||||
|
||||
void RefreshElementContent()
|
||||
{
|
||||
var content = m_OptionContents[(int)SplineTool.handleOrientation];
|
||||
|
||||
text = content.text;
|
||||
tooltip = content.tooltip;
|
||||
icon = content.image as Texture2D;
|
||||
}
|
||||
|
||||
void AttachedToPanel(AttachToPanelEvent evt)
|
||||
{
|
||||
SplineTool.handleOrientationChanged += RefreshElementContent;
|
||||
}
|
||||
|
||||
void DetachedFromPanel(DetachFromPanelEvent evt)
|
||||
{
|
||||
SplineTool.handleOrientationChanged -= RefreshElementContent;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,11 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 29ae16933bbb94210b13408d3f86a072
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
|
|
@ -0,0 +1,8 @@
|
|||
fileFormatVersion: 2
|
||||
guid: eaa3cd6309cba3244b2a227f3e8e7eb3
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
|
|
@ -0,0 +1,157 @@
|
|||
using Unity.Mathematics;
|
||||
using UnityEngine;
|
||||
using UnityEngine.UIElements;
|
||||
|
||||
#if !UNITY_2022_1_OR_NEWER
|
||||
using UnityEditor.UIElements;
|
||||
#endif
|
||||
|
||||
namespace UnityEditor.Splines
|
||||
{
|
||||
sealed class BezierKnotDrawer : KnotDrawer<BezierEditableKnot>
|
||||
{
|
||||
const string k_TangentFoldoutStyle = "tangent-drawer";
|
||||
|
||||
readonly TangentModeStrip m_Mode;
|
||||
|
||||
readonly FloatField m_InMagnitude;
|
||||
readonly Vector3Field m_In;
|
||||
readonly FloatField m_InX;
|
||||
readonly FloatField m_InY;
|
||||
readonly FloatField m_InZ;
|
||||
readonly FloatField m_OutMagnitude;
|
||||
readonly Vector3Field m_Out;
|
||||
readonly FloatField m_OutX;
|
||||
readonly FloatField m_OutY;
|
||||
readonly FloatField m_OutZ;
|
||||
|
||||
public BezierKnotDrawer()
|
||||
{
|
||||
Add(m_Mode = new TangentModeStrip());
|
||||
|
||||
( m_InMagnitude, m_In ) = CreateTangentFoldout("Tangent In", "TangentIn");
|
||||
m_InX = m_In.Q<FloatField>("unity-x-input");
|
||||
m_InY = m_In.Q<FloatField>("unity-y-input");
|
||||
m_InZ = m_In.Q<FloatField>("unity-z-input");
|
||||
|
||||
( m_OutMagnitude, m_Out ) = CreateTangentFoldout("Tangent Out", "TangentOut");
|
||||
m_OutX = m_Out.Q<FloatField>("unity-x-input");
|
||||
m_OutY = m_Out.Q<FloatField>("unity-y-input");
|
||||
m_OutZ = m_Out.Q<FloatField>("unity-z-input");
|
||||
|
||||
m_InMagnitude.RegisterValueChangedCallback((evt) =>
|
||||
{
|
||||
UpdateTangentMagnitude(target.tangentIn, m_InMagnitude, evt.newValue, -1f);
|
||||
m_In.SetValueWithoutNotify(target.tangentIn.localPosition);
|
||||
m_Out.SetValueWithoutNotify(target.tangentOut.localPosition);
|
||||
m_OutMagnitude.SetValueWithoutNotify(Round(math.length(target.tangentOut.localPosition)));
|
||||
RoundFloatFieldsValues();
|
||||
});
|
||||
|
||||
m_In.RegisterValueChangedCallback((evt) =>
|
||||
{
|
||||
IgnoreKnotCallbacks(true);
|
||||
target.tangentIn.localPosition = evt.newValue;
|
||||
IgnoreKnotCallbacks(false);
|
||||
m_InMagnitude.SetValueWithoutNotify(Round(math.length(target.tangentIn.localPosition)));
|
||||
});
|
||||
|
||||
m_OutMagnitude.RegisterValueChangedCallback((evt) =>
|
||||
{
|
||||
UpdateTangentMagnitude(target.tangentOut, m_OutMagnitude, evt.newValue, 1f);
|
||||
m_Out.SetValueWithoutNotify(target.tangentOut.localPosition);
|
||||
m_In.SetValueWithoutNotify(target.tangentIn.localPosition);
|
||||
m_InMagnitude.SetValueWithoutNotify(Round(math.length(target.tangentIn.localPosition)));
|
||||
RoundFloatFieldsValues();
|
||||
});
|
||||
|
||||
m_Out.RegisterValueChangedCallback((evt) =>
|
||||
{
|
||||
IgnoreKnotCallbacks(true);
|
||||
target.tangentOut.localPosition = evt.newValue;
|
||||
IgnoreKnotCallbacks(false);
|
||||
m_OutMagnitude.SetValueWithoutNotify(Round(math.length(target.tangentOut.localPosition)));
|
||||
});
|
||||
}
|
||||
|
||||
public override void Update()
|
||||
{
|
||||
base.Update();
|
||||
|
||||
m_Mode.SetElement(target);
|
||||
m_In.SetValueWithoutNotify(target.tangentIn.localPosition);
|
||||
m_Out.SetValueWithoutNotify(target.tangentOut.localPosition);
|
||||
m_InMagnitude.SetValueWithoutNotify(math.length(target.tangentIn.localPosition));
|
||||
m_OutMagnitude.SetValueWithoutNotify(math.length(target.tangentOut.localPosition));
|
||||
|
||||
RoundFloatFieldsValues();
|
||||
//Disabling edition when using linear tangents
|
||||
EnableElements(target.mode);
|
||||
}
|
||||
|
||||
void UpdateTangentMagnitude(EditableTangent tangent, FloatField magnitudeField, float value, float directionSign)
|
||||
{
|
||||
if (value < 0f)
|
||||
{
|
||||
magnitudeField.SetValueWithoutNotify(0f);
|
||||
value = 0f;
|
||||
}
|
||||
|
||||
var direction = new float3(0, 0, directionSign);
|
||||
if(math.length(tangent.localPosition) > 0)
|
||||
direction = math.normalize(tangent.localPosition);
|
||||
|
||||
IgnoreKnotCallbacks(true);
|
||||
tangent.localPosition = value * direction;
|
||||
IgnoreKnotCallbacks(false);
|
||||
}
|
||||
|
||||
void RoundFloatFieldsValues()
|
||||
{
|
||||
m_InMagnitude.SetValueWithoutNotify(Round(m_InMagnitude.value));
|
||||
m_InX.SetValueWithoutNotify(Round(m_InX.value));
|
||||
m_InY.SetValueWithoutNotify(Round(m_InY.value));
|
||||
m_InZ.SetValueWithoutNotify(Round(m_InZ.value));
|
||||
m_OutMagnitude.SetValueWithoutNotify(Round(m_OutMagnitude.value));
|
||||
m_OutX.SetValueWithoutNotify(Round(m_OutX.value));
|
||||
m_OutY.SetValueWithoutNotify(Round(m_OutY.value));
|
||||
m_OutZ.SetValueWithoutNotify(Round(m_OutZ.value));
|
||||
}
|
||||
|
||||
void EnableElements(BezierEditableKnot.Mode mode)
|
||||
{
|
||||
var bezierTangent = mode != BezierEditableKnot.Mode.Linear;
|
||||
var brokenTangents = mode == BezierEditableKnot.Mode.Broken;
|
||||
m_InMagnitude.SetEnabled(bezierTangent);
|
||||
m_OutMagnitude.SetEnabled(bezierTangent);
|
||||
m_In.SetEnabled(brokenTangents);
|
||||
m_Out.SetEnabled(brokenTangents);
|
||||
}
|
||||
|
||||
(FloatField,Vector3Field) CreateTangentFoldout(string text, string vect3name)
|
||||
{
|
||||
//Create Elements
|
||||
var foldoutRoot = new VisualElement();
|
||||
foldoutRoot.AddToClassList(k_TangentFoldoutStyle);
|
||||
|
||||
var foldout = new Foldout() { value = false };
|
||||
var foldoutToggle = foldout.Q<Toggle>();
|
||||
var magnitude = new FloatField(L10n.Tr(text), 3);
|
||||
var vector3Field = new Vector3Field() { name = vect3name };
|
||||
|
||||
//Build UI Hierarchy
|
||||
Add(foldoutRoot);
|
||||
foldoutRoot.Add(foldout);
|
||||
foldoutToggle.Add(magnitude);
|
||||
foldout.Add(vector3Field);
|
||||
|
||||
return (magnitude, vector3Field);
|
||||
}
|
||||
|
||||
public override void OnTargetSet()
|
||||
{
|
||||
m_In.parent.SetEnabled(SplineSelectionUtility.IsSelectable(target.spline, target.index, target.tangentIn));
|
||||
m_Out.parent.SetEnabled(SplineSelectionUtility.IsSelectable(target.spline, target.index, target.tangentOut));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,11 @@
|
|||
fileFormatVersion: 2
|
||||
guid: e1dec5e4d3c4dd84aab4dfbb3c8e6b48
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
|
|
@ -0,0 +1,41 @@
|
|||
using UnityEngine;
|
||||
using UnityEngine.UIElements;
|
||||
|
||||
namespace UnityEditor.Splines
|
||||
{
|
||||
interface IElementDrawer
|
||||
{
|
||||
void SetTarget(ISplineElement element);
|
||||
void Update();
|
||||
void OnTargetSet();
|
||||
}
|
||||
|
||||
abstract class ElementDrawer<T> : VisualElement, IElementDrawer where T : ISplineElement
|
||||
{
|
||||
const int k_FloatFieldsDigits = 3;
|
||||
|
||||
public T target { get; private set; }
|
||||
|
||||
public virtual void Update() {}
|
||||
|
||||
public void SetTarget(ISplineElement element)
|
||||
{
|
||||
target = (T) element;
|
||||
OnTargetSet();
|
||||
}
|
||||
|
||||
public static float Round(float value)
|
||||
{
|
||||
float mult = Mathf.Pow(10.0f, k_FloatFieldsDigits);
|
||||
return Mathf.Round(value * mult) / mult;
|
||||
}
|
||||
|
||||
public virtual void OnTargetSet() { }
|
||||
|
||||
protected void IgnoreKnotCallbacks(bool ignore)
|
||||
{
|
||||
if (parent is ElementInspector inspector)
|
||||
inspector.ignoreKnotCallbacks = ignore;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,11 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 95245c7aa2899544b8fe0daa4947e0be
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
|
|
@ -0,0 +1,140 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using UnityEngine.UIElements;
|
||||
|
||||
namespace UnityEditor.Splines
|
||||
{
|
||||
sealed class ElementInspector : VisualElement, IDisposable
|
||||
{
|
||||
static readonly string k_NoSelectionMessage = L10n.Tr("No element selected");
|
||||
static readonly string k_MultiSelectNoAllowedMessage = L10n.Tr(" - not supported");
|
||||
|
||||
readonly Label m_ErrorMessage;
|
||||
|
||||
Type m_InspectedType;
|
||||
EditableKnot m_TargetKnot;
|
||||
IElementDrawer m_ElementDrawer;
|
||||
|
||||
static StyleSheet s_CommonStyleSheet;
|
||||
static StyleSheet s_ThemeStyleSheet;
|
||||
|
||||
bool m_InspectorDirty;
|
||||
public bool ignoreKnotCallbacks { get; set; }
|
||||
|
||||
public ElementInspector()
|
||||
{
|
||||
if (s_CommonStyleSheet == null)
|
||||
s_CommonStyleSheet = AssetDatabase.LoadAssetAtPath<StyleSheet>("Packages/com.unity.splines/Editor/Stylesheets/SplineInspectorCommon.uss");
|
||||
if (s_ThemeStyleSheet == null)
|
||||
s_ThemeStyleSheet = AssetDatabase.LoadAssetAtPath<StyleSheet>($"Packages/com.unity.splines/Editor/Stylesheets/SplineInspector{(EditorGUIUtility.isProSkin ? "Dark" : "Light")}.uss");
|
||||
|
||||
styleSheets.Add(s_CommonStyleSheet);
|
||||
styleSheets.Add(s_ThemeStyleSheet);
|
||||
|
||||
m_ErrorMessage = new Label();
|
||||
Add(m_ErrorMessage);
|
||||
|
||||
EditableKnot.knotModified += OnKnotModified;
|
||||
EditorApplication.update += UpdateIfDirty;
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
EditableKnot.knotModified -= OnKnotModified;
|
||||
}
|
||||
|
||||
void OnKnotModified(EditableKnot knot)
|
||||
{
|
||||
if (!ignoreKnotCallbacks && m_TargetKnot == knot)
|
||||
m_InspectorDirty = true;
|
||||
}
|
||||
|
||||
void UpdateIfDirty()
|
||||
{
|
||||
if(m_InspectorDirty)
|
||||
{
|
||||
m_ElementDrawer?.Update();
|
||||
m_InspectorDirty = false;
|
||||
}
|
||||
}
|
||||
|
||||
public void SetElement(ISplineElement element, int selectCount)
|
||||
{
|
||||
UpdateDrawerForElementType(selectCount > 1 ? null : element?.GetType());
|
||||
|
||||
if (selectCount > 1)
|
||||
ShowErrorMessage(BuildMultiSelectError(selectCount)+k_MultiSelectNoAllowedMessage);
|
||||
else if (element == null || m_ElementDrawer == null)
|
||||
ShowErrorMessage(k_NoSelectionMessage);
|
||||
else
|
||||
{
|
||||
if (element is EditableKnot knot)
|
||||
m_TargetKnot = knot;
|
||||
else if (element is EditableTangent tangent)
|
||||
m_TargetKnot = tangent.owner;
|
||||
|
||||
HideErrorMessage();
|
||||
m_ElementDrawer.SetTarget(element);
|
||||
m_ElementDrawer.Update();
|
||||
}
|
||||
}
|
||||
|
||||
string BuildMultiSelectError(int selectCount)
|
||||
{
|
||||
string message = "(" + selectCount + ") ";
|
||||
var selectionList = new List<ISplineElement>();
|
||||
SplineSelection.GetSelectedElements(selectionList);
|
||||
var isLookingForKnots = selectionList.FirstOrDefault() is EditableKnot;
|
||||
foreach(var element in selectionList)
|
||||
{
|
||||
if(isLookingForKnots && element is EditableKnot)
|
||||
continue;
|
||||
if(!isLookingForKnots && element is EditableTangent)
|
||||
continue;
|
||||
|
||||
message += "Elements selected";
|
||||
return message;
|
||||
}
|
||||
|
||||
message += isLookingForKnots ? "Knots selected" : "Tangents selected";
|
||||
return message;
|
||||
}
|
||||
|
||||
void ShowErrorMessage(string error)
|
||||
{
|
||||
m_ErrorMessage.style.display = DisplayStyle.Flex;
|
||||
m_ErrorMessage.text = error;
|
||||
}
|
||||
|
||||
void HideErrorMessage()
|
||||
{
|
||||
m_ErrorMessage.style.display = DisplayStyle.None;
|
||||
}
|
||||
|
||||
void UpdateDrawerForElementType(Type targetType)
|
||||
{
|
||||
if (m_InspectedType == targetType)
|
||||
return;
|
||||
|
||||
if (m_ElementDrawer != null)
|
||||
((VisualElement)m_ElementDrawer).RemoveFromHierarchy();
|
||||
|
||||
if (targetType == null)
|
||||
m_ElementDrawer = null;
|
||||
else if (typeof(BezierEditableKnot).IsAssignableFrom(targetType))
|
||||
m_ElementDrawer = new BezierKnotDrawer();
|
||||
else if (typeof(EditableKnot).IsAssignableFrom(targetType))
|
||||
m_ElementDrawer = new KnotDrawer();
|
||||
else if (typeof(EditableTangent).IsAssignableFrom(targetType))
|
||||
m_ElementDrawer = new TangentDrawer();
|
||||
else
|
||||
m_ElementDrawer = null;
|
||||
|
||||
if (m_ElementDrawer != null)
|
||||
Add((VisualElement)m_ElementDrawer);
|
||||
|
||||
m_InspectedType = targetType;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,11 @@
|
|||
fileFormatVersion: 2
|
||||
guid: b5d8a953f9ef52441bd58f4c1e5853a0
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
|
|
@ -0,0 +1,83 @@
|
|||
using UnityEditor.UIElements;
|
||||
using UnityEngine;
|
||||
using UnityEngine.UIElements;
|
||||
|
||||
namespace UnityEditor.Splines
|
||||
{
|
||||
sealed class KnotDrawer : KnotDrawer<EditableKnot> {}
|
||||
|
||||
class KnotDrawer<T> : ElementDrawer<T> where T : EditableKnot
|
||||
{
|
||||
readonly Label m_KnotLabel;
|
||||
readonly Vector3Field m_Position;
|
||||
readonly FloatField m_PositionX;
|
||||
readonly FloatField m_PositionY;
|
||||
readonly FloatField m_PositionZ;
|
||||
readonly Vector3Field m_Rotation;
|
||||
readonly FloatField m_RotationX;
|
||||
readonly FloatField m_RotationY;
|
||||
readonly FloatField m_RotationZ;
|
||||
|
||||
public KnotDrawer()
|
||||
{
|
||||
Add(m_KnotLabel = new Label());
|
||||
m_KnotLabel.style.height = 24;
|
||||
m_KnotLabel.style.unityTextAlign = TextAnchor.MiddleLeft;
|
||||
|
||||
VisualElement row;
|
||||
Add(row = new VisualElement(){name = "Vector3WithIcon"});
|
||||
row.style.flexDirection = FlexDirection.Row;
|
||||
row.Add(new VisualElement(){name = "PositionIcon"});
|
||||
row.Add(m_Position = new Vector3Field() { name = "Position" });
|
||||
|
||||
m_Position.style.flexGrow = 1;
|
||||
m_PositionX = m_Position.Q<FloatField>("unity-x-input");
|
||||
m_PositionY = m_Position.Q<FloatField>("unity-y-input");
|
||||
m_PositionZ = m_Position.Q<FloatField>("unity-z-input");
|
||||
|
||||
Add(row = new VisualElement(){name = "Vector3WithIcon"});
|
||||
row.style.flexDirection = FlexDirection.Row;
|
||||
row.Add(new VisualElement(){name = "RotationIcon"});
|
||||
row.Add(m_Rotation = new Vector3Field() { name = "Rotation" });;
|
||||
|
||||
m_Rotation.style.flexGrow = 1;
|
||||
m_RotationX = m_Rotation.Q<FloatField>("unity-x-input");
|
||||
m_RotationY = m_Rotation.Q<FloatField>("unity-y-input");
|
||||
m_RotationZ = m_Rotation.Q<FloatField>("unity-z-input");
|
||||
|
||||
m_Position.RegisterValueChangedCallback((evt) =>
|
||||
{
|
||||
IgnoreKnotCallbacks(true);
|
||||
target.localPosition = evt.newValue;
|
||||
IgnoreKnotCallbacks(false);
|
||||
});
|
||||
m_Rotation.RegisterValueChangedCallback((evt) =>
|
||||
{
|
||||
IgnoreKnotCallbacks(true);
|
||||
target.localRotation = Quaternion.Euler(evt.newValue);
|
||||
IgnoreKnotCallbacks(false);
|
||||
});
|
||||
}
|
||||
|
||||
public override void Update()
|
||||
{
|
||||
base.Update();
|
||||
|
||||
m_KnotLabel.text = "Knot "+target.index.ToString()+" selected";
|
||||
m_Position.SetValueWithoutNotify(target.localPosition);
|
||||
m_Rotation.SetValueWithoutNotify(((Quaternion)target.localRotation).eulerAngles);
|
||||
|
||||
RoundFloatFieldsValues();
|
||||
}
|
||||
|
||||
void RoundFloatFieldsValues()
|
||||
{
|
||||
m_PositionX.SetValueWithoutNotify(Round(m_PositionX.value));
|
||||
m_PositionY.SetValueWithoutNotify(Round(m_PositionY.value));
|
||||
m_PositionZ.SetValueWithoutNotify(Round(m_PositionZ.value));
|
||||
m_RotationX.SetValueWithoutNotify(Round(m_RotationX.value));
|
||||
m_RotationY.SetValueWithoutNotify(Round(m_RotationY.value));
|
||||
m_RotationZ.SetValueWithoutNotify(Round(m_RotationZ.value));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,11 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 3018d9f76949a7f4c8ad97e48caff165
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
|
|
@ -0,0 +1,24 @@
|
|||
using UnityEngine;
|
||||
using UnityEngine.UIElements;
|
||||
|
||||
namespace UnityEditor.Splines
|
||||
{
|
||||
sealed class ReadOnlyField : BaseField<string>
|
||||
{
|
||||
readonly Label m_IndexField;
|
||||
|
||||
public ReadOnlyField(string label) : base(label, new Label() { name = "ReadOnlyValue" })
|
||||
{
|
||||
style.flexDirection = FlexDirection.Row;
|
||||
|
||||
m_IndexField = this.Q<Label>("ReadOnlyValue");
|
||||
m_IndexField.text = value;
|
||||
m_IndexField.style.unityTextAlign = TextAnchor.MiddleLeft;
|
||||
}
|
||||
|
||||
public override void SetValueWithoutNotify(string newValue)
|
||||
{
|
||||
m_IndexField.text = newValue;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,11 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 37aa8727811c9e04785d9c0e4cafc5d3
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
|
|
@ -0,0 +1,125 @@
|
|||
using Unity.Mathematics;
|
||||
using UnityEngine;
|
||||
using UnityEngine.UIElements;
|
||||
|
||||
#if !UNITY_2022_1_OR_NEWER
|
||||
using UnityEditor.UIElements;
|
||||
#endif
|
||||
|
||||
namespace UnityEditor.Splines
|
||||
{
|
||||
sealed class TangentDrawer : ElementDrawer<EditableTangent>
|
||||
{
|
||||
const string k_TangentDrawerStyle = "tangent-drawer";
|
||||
const string k_TangentLabelStyle = "tangent-label";
|
||||
const string k_TangentFillerStyle = "tangent-filler";
|
||||
|
||||
readonly Label m_TangentLabel;
|
||||
readonly TangentModeStrip m_Mode;
|
||||
|
||||
FloatField m_Magnitude;
|
||||
Label m_DirectionLabel;
|
||||
Vector3Field m_Direction;
|
||||
FloatField m_DirectionX;
|
||||
FloatField m_DirectionY;
|
||||
FloatField m_DirectionZ;
|
||||
|
||||
public TangentDrawer()
|
||||
{
|
||||
AddToClassList(k_TangentDrawerStyle);
|
||||
Add(m_TangentLabel = new Label());
|
||||
m_TangentLabel.style.height = 24;
|
||||
m_TangentLabel.style.unityTextAlign = TextAnchor.MiddleLeft;
|
||||
Add(m_Mode = new TangentModeStrip());
|
||||
|
||||
CreateTangentFields();
|
||||
|
||||
m_Magnitude.RegisterValueChangedCallback((evt) =>
|
||||
{
|
||||
UpdateTangentMagnitude(evt.newValue);
|
||||
m_Direction.SetValueWithoutNotify(target.localPosition);
|
||||
RoundFloatFieldsValues();
|
||||
});
|
||||
|
||||
m_Direction.RegisterValueChangedCallback((evt) =>
|
||||
{
|
||||
IgnoreKnotCallbacks(true);
|
||||
target.localPosition = evt.newValue;
|
||||
IgnoreKnotCallbacks(false);
|
||||
m_Magnitude.SetValueWithoutNotify(Round(math.length(target.localPosition)));
|
||||
});
|
||||
}
|
||||
|
||||
public override void Update()
|
||||
{
|
||||
base.Update();
|
||||
|
||||
m_TangentLabel.text = GetTangentLabel();
|
||||
m_Mode.SetElement(target);
|
||||
m_Magnitude.SetValueWithoutNotify(math.length(target.localPosition));
|
||||
m_Direction.SetValueWithoutNotify(target.localPosition);
|
||||
|
||||
RoundFloatFieldsValues();
|
||||
//Disabling edition when using linear, mirrored or continuous tangents
|
||||
EnableElements(m_Mode.GetMode());
|
||||
}
|
||||
|
||||
void CreateTangentFields()
|
||||
{
|
||||
m_Magnitude = new FloatField("Magnitude",6);
|
||||
|
||||
m_DirectionLabel = new Label("Direction");
|
||||
m_DirectionLabel.AddToClassList(k_TangentLabelStyle);
|
||||
|
||||
var filler = new VisualElement();
|
||||
filler.AddToClassList(k_TangentFillerStyle);
|
||||
|
||||
m_Direction = new Vector3Field(){name = "direction"};
|
||||
m_DirectionX = m_Direction.Q<FloatField>("unity-x-input");
|
||||
m_DirectionY = m_Direction.Q<FloatField>("unity-y-input");
|
||||
m_DirectionZ = m_Direction.Q<FloatField>("unity-z-input");
|
||||
|
||||
//Build UI Hierarchy
|
||||
Add(m_Magnitude);
|
||||
Add(m_DirectionLabel);
|
||||
Add(filler);
|
||||
filler.Add(m_Direction);
|
||||
}
|
||||
|
||||
string GetTangentLabel()
|
||||
{
|
||||
var inOutLabel = target.tangentIndex == 0 ? "In" : "Out";
|
||||
string label = "Tangent "+inOutLabel+" selected (Knot "+target.owner.index+")";
|
||||
return label;
|
||||
}
|
||||
|
||||
void UpdateTangentMagnitude(float value)
|
||||
{
|
||||
var direction = new float3(0, 0, 1);
|
||||
|
||||
if(math.length(target.localPosition) > 0)
|
||||
direction = math.normalize(target.localPosition);
|
||||
|
||||
IgnoreKnotCallbacks(true);
|
||||
target.localPosition = value * direction;
|
||||
IgnoreKnotCallbacks(false);
|
||||
}
|
||||
|
||||
void RoundFloatFieldsValues()
|
||||
{
|
||||
m_Magnitude.SetValueWithoutNotify(Round(m_Magnitude.value));
|
||||
m_DirectionX.SetValueWithoutNotify(Round(m_DirectionX.value));
|
||||
m_DirectionY.SetValueWithoutNotify(Round(m_DirectionY.value));
|
||||
m_DirectionZ.SetValueWithoutNotify(Round(m_DirectionZ.value));
|
||||
}
|
||||
|
||||
void EnableElements(BezierEditableKnot.Mode mode)
|
||||
{
|
||||
var bezierTangent = m_Mode.GetMode() != BezierEditableKnot.Mode.Linear;
|
||||
var brokenMode = m_Mode.GetMode() == BezierEditableKnot.Mode.Broken;
|
||||
m_Magnitude.SetEnabled(bezierTangent);
|
||||
m_DirectionLabel.SetEnabled(brokenMode);
|
||||
m_Direction.SetEnabled(brokenMode);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,11 @@
|
|||
fileFormatVersion: 2
|
||||
guid: d01d6b9007f24984692322edf5415826
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
|
|
@ -0,0 +1,88 @@
|
|||
using Unity.Mathematics;
|
||||
using UnityEngine;
|
||||
using UnityEngine.UIElements;
|
||||
|
||||
namespace UnityEditor.Splines
|
||||
{
|
||||
sealed class TangentModeStrip : VisualElement
|
||||
{
|
||||
readonly GUIContent[] modes = new[]
|
||||
{
|
||||
EditorGUIUtility.TrTextContent("Linear", "Linear Tangents:\nTangents are pointing to the previous/next spline knot."),
|
||||
EditorGUIUtility.TrTextContent("Mirrored", "Mirrored Tangents:\nIf Knot or InTangent is selected, OutTangent will be mirrored on InTangent. Else, InTangent will be mirrored on OutTangent."),
|
||||
EditorGUIUtility.TrTextContent("Continuous", "Continuous Tangents:\nInTangent and OutTangent are always aligned."),
|
||||
EditorGUIUtility.TrTextContent("Broken", "Broken Tangents:\nInTangent and OutTangent are dissociated.")
|
||||
};
|
||||
readonly ButtonStripField m_ModeStrip;
|
||||
|
||||
ISplineElement m_Target;
|
||||
|
||||
internal TangentModeStrip()
|
||||
{
|
||||
Add(m_ModeStrip = new ButtonStripField() { name = "TangentMode" });
|
||||
m_ModeStrip.choices = modes;
|
||||
}
|
||||
|
||||
internal BezierEditableKnot.Mode GetMode()
|
||||
{
|
||||
return (BezierEditableKnot.Mode)m_ModeStrip.value;
|
||||
}
|
||||
|
||||
internal void SetElement(ISplineElement target)
|
||||
{
|
||||
if(m_Target != target)
|
||||
{
|
||||
m_Target = target;
|
||||
BezierEditableKnot knot = null;
|
||||
if(target is BezierEditableKnot targetedKnot)
|
||||
knot = targetedKnot;
|
||||
else if(m_Target is EditableTangent targetedTangent && targetedTangent.owner is BezierEditableKnot tangentOwner)
|
||||
knot = tangentOwner;
|
||||
|
||||
m_ModeStrip.OnValueChanged += ((newMode) => UpdateMode(knot, (BezierEditableKnot.Mode) newMode));
|
||||
}
|
||||
|
||||
if(m_Target is BezierEditableKnot tKnot)
|
||||
UpdateValue((int)tKnot.mode);
|
||||
else if(m_Target is EditableTangent tTangent && tTangent.owner is BezierEditableKnot tangentOwner)
|
||||
UpdateValue((int)tangentOwner.mode);
|
||||
}
|
||||
|
||||
void UpdateMode(BezierEditableKnot knot, BezierEditableKnot.Mode mode)
|
||||
{
|
||||
if(knot.mode == mode)
|
||||
return;
|
||||
|
||||
if(mode is BezierEditableKnot.Mode.Mirrored or BezierEditableKnot.Mode.Continuous)
|
||||
{
|
||||
// m_Target is the knot "knot", use the InTangent to resolve the new mode
|
||||
var refTangent = knot.GetTangent(0);
|
||||
var otherTangent = knot.GetTangent(1);
|
||||
|
||||
//Else if target is a tangent, update the values regarding the selected tangent
|
||||
if(m_Target is EditableTangent { owner: BezierEditableKnot owner } target)
|
||||
{
|
||||
refTangent = target;
|
||||
for(int i = 0; i < owner.tangentCount; ++i)
|
||||
{
|
||||
var tangent = owner.GetTangent(i);
|
||||
if(tangent != target)
|
||||
otherTangent = tangent;
|
||||
}
|
||||
}
|
||||
|
||||
if(mode == BezierEditableKnot.Mode.Mirrored)
|
||||
otherTangent.SetLocalPositionNoNotify(-refTangent.localPosition);
|
||||
else //Continuous mode
|
||||
otherTangent.SetLocalPositionNoNotify(-math.normalize(refTangent.localPosition) * math.length(otherTangent.localPosition));
|
||||
}
|
||||
|
||||
knot.SetMode(mode);
|
||||
}
|
||||
|
||||
internal void UpdateValue(int modeValue)
|
||||
{
|
||||
m_ModeStrip.SetValueWithoutNotify(modeValue);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,3 @@
|
|||
fileFormatVersion: 2
|
||||
guid: c74ddef7e6e74f529a51b5ad71552fbd
|
||||
timeCreated: 1637605071
|
||||
|
|
@ -0,0 +1,60 @@
|
|||
using System;
|
||||
using UnityEngine;
|
||||
using UnityEditor;
|
||||
|
||||
class SplineComponentEditor : Editor
|
||||
{
|
||||
static GUIStyle s_HorizontalLine;
|
||||
static GUIStyle s_FoldoutStyle;
|
||||
|
||||
protected virtual void OnEnable()
|
||||
{
|
||||
if (s_HorizontalLine == null)
|
||||
{
|
||||
s_HorizontalLine = new GUIStyle();
|
||||
s_HorizontalLine.normal.background = EditorGUIUtility.whiteTexture;
|
||||
s_HorizontalLine.margin = new RectOffset(0, 0, 3, 3);
|
||||
s_HorizontalLine.fixedHeight = 1;
|
||||
}
|
||||
}
|
||||
|
||||
protected void HorizontalLine(Color color)
|
||||
{
|
||||
var c = GUI.color;
|
||||
GUI.color = color;
|
||||
GUILayout.Box( GUIContent.none, s_HorizontalLine );
|
||||
GUI.color = c;
|
||||
}
|
||||
|
||||
protected bool Foldout(bool foldout, GUIContent content)
|
||||
{
|
||||
return Foldout(foldout, content, false);
|
||||
}
|
||||
|
||||
public static bool Foldout(bool foldout, GUIContent content, bool toggleOnLabelClick)
|
||||
{
|
||||
if (s_FoldoutStyle == null)
|
||||
{
|
||||
s_FoldoutStyle = new GUIStyle(EditorStyles.foldout);
|
||||
s_FoldoutStyle.fontStyle = FontStyle.Bold;
|
||||
}
|
||||
|
||||
return EditorGUILayout.Foldout(foldout, content, toggleOnLabelClick, s_FoldoutStyle);
|
||||
}
|
||||
|
||||
internal struct LabelWidthScope : IDisposable
|
||||
{
|
||||
float previousWidth;
|
||||
|
||||
public LabelWidthScope(float width)
|
||||
{
|
||||
previousWidth = EditorGUIUtility.labelWidth;
|
||||
EditorGUIUtility.labelWidth = width;
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
EditorGUIUtility.labelWidth = previousWidth;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,11 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 9103eb7dcd041455886438d75625d19c
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
|
|
@ -0,0 +1,100 @@
|
|||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using UnityEngine;
|
||||
using UnityEngine.Splines;
|
||||
|
||||
namespace UnityEditor.Splines.Editor.GUI
|
||||
{
|
||||
class SplineDataConversionWindow : EditorWindow
|
||||
{
|
||||
readonly static string k_SplineDataConversionMessage = L10n.Tr("Select a reference spline to convert your SplineData with no data loss. Otherwise data won't be converted.");
|
||||
|
||||
SerializedProperty m_SplineDataProperty;
|
||||
SerializedProperty m_SplineDataUnitProperty;
|
||||
FieldInfo m_FieldInfo;
|
||||
SplineContainer m_TargetSpline;
|
||||
int m_NewValue;
|
||||
|
||||
public static void DoConfirmWindow(SerializedProperty property, SerializedProperty unitProperty, FieldInfo fieldInfo, Component targetComponent, int newValue)
|
||||
{
|
||||
// Get existing open window or if none, make a new one:
|
||||
var window = (SplineDataConversionWindow)GetWindow(typeof(SplineDataConversionWindow));
|
||||
window.m_SplineDataProperty = property;
|
||||
window.m_SplineDataUnitProperty = unitProperty;
|
||||
window.m_FieldInfo = fieldInfo;
|
||||
window.m_TargetSpline = FindPlausibleSplineContainer(targetComponent);
|
||||
window.m_NewValue = newValue;
|
||||
window.minSize = new Vector2(400, 100);
|
||||
window.maxSize = new Vector2(400, 100);
|
||||
window.Show();
|
||||
}
|
||||
|
||||
void OnGUI()
|
||||
{
|
||||
GUILayout.Label(L10n.Tr("Spline Data Conversion"), EditorStyles.boldLabel);
|
||||
if(m_TargetSpline == null)
|
||||
EditorGUILayout.HelpBox(k_SplineDataConversionMessage,MessageType.Warning);
|
||||
else
|
||||
EditorGUILayout.HelpBox(L10n.Tr($"The spline {m_TargetSpline} will be used for data conversion."),MessageType.Info);
|
||||
|
||||
m_TargetSpline = (SplineContainer)EditorGUILayout.ObjectField(
|
||||
"Reference Spline",
|
||||
(Object)m_TargetSpline,
|
||||
typeof(SplineContainer),
|
||||
true);
|
||||
|
||||
EditorGUILayout.BeginHorizontal();
|
||||
if(GUILayout.Button(new GUIContent(L10n.Tr("Convert"), L10n.Tr("Convert data indexes to the new Unit."))))
|
||||
{
|
||||
if(m_TargetSpline != null)
|
||||
ApplyConversion();
|
||||
else
|
||||
ApplyWithNoConversion();
|
||||
|
||||
Close();
|
||||
}
|
||||
if(GUILayout.Button(new GUIContent(L10n.Tr("Don't Convert"), L10n.Tr("Do not convert data indexes."))))
|
||||
{
|
||||
ApplyWithNoConversion();
|
||||
Close();
|
||||
}
|
||||
if(GUILayout.Button(L10n.Tr("Cancel")))
|
||||
Close();
|
||||
EditorGUILayout.EndHorizontal();
|
||||
}
|
||||
|
||||
static SplineContainer FindPlausibleSplineContainer(Component targetComponent)
|
||||
{
|
||||
SplineContainer container = null;
|
||||
var fieldInfos = targetComponent.GetType().GetFields(BindingFlags.Instance|BindingFlags.Public|BindingFlags.NonPublic);
|
||||
var providerFieldInfo = fieldInfos.FirstOrDefault(field => field.FieldType == typeof(SplineContainer));
|
||||
if(providerFieldInfo != null && providerFieldInfo.FieldType == typeof(SplineContainer))
|
||||
container = (SplineContainer)providerFieldInfo.GetValue(targetComponent);
|
||||
|
||||
if(container == null)
|
||||
container = targetComponent.gameObject.GetComponent<SplineContainer>();
|
||||
|
||||
return container;
|
||||
}
|
||||
|
||||
void ApplyWithNoConversion()
|
||||
{
|
||||
m_SplineDataUnitProperty.intValue = m_NewValue;
|
||||
m_SplineDataUnitProperty.serializedObject.ApplyModifiedProperties();
|
||||
}
|
||||
|
||||
void ApplyConversion()
|
||||
{
|
||||
var targetObject = m_FieldInfo.GetValue(m_SplineDataProperty.serializedObject.targetObject);
|
||||
var convertMethod = targetObject.GetType().GetMethod("ConvertPathUnit", BindingFlags.Instance | BindingFlags.Public)?
|
||||
.MakeGenericMethod(typeof(NativeSpline));
|
||||
|
||||
convertMethod?.Invoke(targetObject, new object[]
|
||||
{
|
||||
new NativeSpline(m_TargetSpline.Spline, m_TargetSpline.transform.localToWorldMatrix),
|
||||
(PathIndexUnit)m_NewValue
|
||||
});
|
||||
m_SplineDataProperty.serializedObject.ApplyModifiedProperties();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,3 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 97db6026b4154fbf97b957dd2137810d
|
||||
timeCreated: 1634919075
|
||||
|
|
@ -0,0 +1,286 @@
|
|||
using System;
|
||||
using System.Linq;
|
||||
using Unity.Mathematics;
|
||||
using UnityEngine;
|
||||
using UnityEngine.Splines;
|
||||
|
||||
namespace UnityEditor.Splines
|
||||
{
|
||||
/// <summary>
|
||||
/// Provides methods for drawing <see cref="SplineData"/> manipulation handles.
|
||||
/// </summary>
|
||||
public static class SplineDataHandles
|
||||
{
|
||||
const float k_HandleSize = 0.15f;
|
||||
const int k_PickRes = 2;
|
||||
|
||||
static int[] s_DataPointsIDs;
|
||||
|
||||
static int s_NewDataPointIndex = -1;
|
||||
static bool s_AddingDataPoint = false;
|
||||
|
||||
static bool m_ShowAddHandle;
|
||||
static float3 m_Position;
|
||||
static float m_T;
|
||||
|
||||
/// <summary>
|
||||
/// Draw default manipulation handles that enables adding, removing and moving
|
||||
/// DataPoints of the targeted SplineData along a Spline. Left click on an empty location
|
||||
/// on the spline adds a new DataPoint in the SplineData. Left click on an existing DataPoint
|
||||
/// allows to move this point along the Spline while a right click on it allows to delete that DataPoint.
|
||||
/// </summary>
|
||||
/// <param name="spline">The Spline to use to interpret the SplineData.</param>
|
||||
/// <param name="splineData">The SplineData for which the handles are drawn.</param>
|
||||
/// <typeparam name="TSpline">The Spline type.</typeparam>
|
||||
/// <typeparam name="TData">The type of data this data point stores.</typeparam>
|
||||
public static void DataPointHandles<TSpline, TData>(
|
||||
this TSpline spline,
|
||||
SplineData<TData> splineData)
|
||||
where TSpline : ISpline
|
||||
{
|
||||
var evt = Event.current;
|
||||
if(evt.type == EventType.MouseMove)
|
||||
{
|
||||
//Compute distance to spline and closest point
|
||||
var ray = HandleUtility.GUIPointToWorldRay(evt.mousePosition);
|
||||
var distance = SplineUtility.GetNearestPoint(spline, ray, out m_Position, out m_T);
|
||||
m_ShowAddHandle = distance < HandleUtility.GetHandleSize(m_Position);
|
||||
}
|
||||
|
||||
//Id has to be consistent no matter the distance test
|
||||
var id = GUIUtility.GetControlID(FocusType.Passive);
|
||||
|
||||
//Only activating the tooling when close enough from the spline
|
||||
if(m_ShowAddHandle)
|
||||
DataPointAddHandle(id, spline, splineData, m_Position, m_T);
|
||||
|
||||
//Remove DataPoint functionality
|
||||
TryRemoveDataPoint(splineData);
|
||||
|
||||
//Draw Default manipulation handles
|
||||
DataPointMoveHandles(spline, splineData);
|
||||
}
|
||||
|
||||
static void TryRemoveDataPoint<TData>(SplineData<TData> splineData)
|
||||
{
|
||||
var evt = Event.current;
|
||||
//Remove data point only when not adding one and when using right click button
|
||||
if(!s_AddingDataPoint && GUIUtility.hotControl == 0
|
||||
&& evt.type == EventType.MouseDown && evt.button == 1
|
||||
&& s_DataPointsIDs.Contains(HandleUtility.nearestControl))
|
||||
{
|
||||
var dataPointIndex = splineData.Indexes.ElementAt(Array.IndexOf(s_DataPointsIDs, HandleUtility.nearestControl));
|
||||
splineData.RemoveDataPoint(dataPointIndex);
|
||||
evt.Use();
|
||||
}
|
||||
}
|
||||
|
||||
static void DataPointAddHandle<TSpline, TData>(
|
||||
int controlID,
|
||||
TSpline spline,
|
||||
SplineData<TData> splineData,
|
||||
float3 pos,
|
||||
float t)
|
||||
where TSpline : ISpline
|
||||
{
|
||||
Event evt = Event.current;
|
||||
EventType eventType = evt.GetTypeForControl(controlID);
|
||||
|
||||
switch (eventType)
|
||||
{
|
||||
case EventType.Layout:
|
||||
{
|
||||
if(!Tools.viewToolActive)
|
||||
HandleUtility.AddControl(controlID, HandleUtility.DistanceToCircle(pos, 0.1f));
|
||||
break;
|
||||
}
|
||||
|
||||
case EventType.Repaint:
|
||||
if(HandleUtility.nearestControl == controlID && GUIUtility.hotControl == 0 || s_AddingDataPoint)
|
||||
{
|
||||
var upDir = spline.EvaluateUpVector(t);
|
||||
Handles.CircleHandleCap(controlID, pos, Quaternion.LookRotation(upDir), 0.15f * HandleUtility.GetHandleSize(pos), EventType.Repaint);
|
||||
}
|
||||
break;
|
||||
|
||||
case EventType.MouseDown:
|
||||
if (evt.button == 0
|
||||
&& HandleUtility.nearestControl == controlID
|
||||
&& GUIUtility.hotControl == 0)
|
||||
{
|
||||
s_AddingDataPoint = true;
|
||||
var index = SplineUtility.ConvertIndexUnit(
|
||||
spline, t,
|
||||
splineData.PathIndexUnit);
|
||||
|
||||
s_NewDataPointIndex = splineData.AddDataPointWithDefaultValue(index);
|
||||
evt.Use();
|
||||
}
|
||||
break;
|
||||
|
||||
case EventType.MouseDrag:
|
||||
if (evt.button == 0 && s_AddingDataPoint)
|
||||
{
|
||||
GUIUtility.hotControl = 0;
|
||||
var index = SplineUtility.ConvertIndexUnit(
|
||||
spline, t,
|
||||
splineData.PathIndexUnit);
|
||||
s_NewDataPointIndex = splineData.MoveDataPoint(s_NewDataPointIndex, index);
|
||||
evt.Use();
|
||||
}
|
||||
break;
|
||||
|
||||
case EventType.MouseUp:
|
||||
if (evt.button == 0 && s_AddingDataPoint)
|
||||
{
|
||||
s_AddingDataPoint = false;
|
||||
s_NewDataPointIndex = -1;
|
||||
GUIUtility.hotControl = 0;
|
||||
evt.Use();
|
||||
}
|
||||
break;
|
||||
|
||||
case EventType.MouseMove:
|
||||
HandleUtility.Repaint();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
static void DataPointMoveHandles<TSpline, TData>(TSpline spline, SplineData<TData> splineData)
|
||||
where TSpline : ISpline
|
||||
{
|
||||
if(s_DataPointsIDs == null || s_DataPointsIDs.Length != splineData.Count)
|
||||
s_DataPointsIDs = new int[splineData.Count];
|
||||
|
||||
//Cache all data point IDs
|
||||
for(int dataIndex = 0; dataIndex < splineData.Count; dataIndex++)
|
||||
s_DataPointsIDs[dataIndex] = GUIUtility.GetControlID(FocusType.Passive);
|
||||
|
||||
//Draw all data points handles on the spline
|
||||
for(int dataIndex = 0; dataIndex < splineData.Count; dataIndex++)
|
||||
{
|
||||
var id = GUIUtility.GetControlID(FocusType.Passive);
|
||||
|
||||
var index = splineData.Indexes.ElementAt(dataIndex);
|
||||
SplineDataHandle(
|
||||
s_DataPointsIDs[dataIndex],
|
||||
spline,
|
||||
splineData,
|
||||
index,
|
||||
k_HandleSize,
|
||||
out float newIndex);
|
||||
|
||||
if(GUIUtility.hotControl == s_DataPointsIDs[dataIndex])
|
||||
{
|
||||
var newDataIndex = splineData.MoveDataPoint(dataIndex, newIndex);
|
||||
//If the current DataPoint is moved across another DataPoint, then update the hotControl ID
|
||||
if(newDataIndex - index != 0)
|
||||
GUIUtility.hotControl = s_DataPointsIDs[newDataIndex];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void SplineDataHandle<TSpline, TData>(
|
||||
int controlID,
|
||||
TSpline spline,
|
||||
SplineData<TData> splineData,
|
||||
float dataPointIndex,
|
||||
float size,
|
||||
out float newTime) where TSpline : ISpline
|
||||
{
|
||||
newTime = dataPointIndex;
|
||||
|
||||
Event evt = Event.current;
|
||||
EventType eventType = evt.GetTypeForControl(controlID);
|
||||
|
||||
var normalizedT = SplineUtility.GetNormalizedInterpolation(spline, dataPointIndex, splineData.PathIndexUnit);
|
||||
var dataPosition = SplineUtility.EvaluatePosition(spline, normalizedT);
|
||||
|
||||
switch (eventType)
|
||||
{
|
||||
case EventType.Layout:
|
||||
var dist = HandleUtility.DistanceToCircle(dataPosition, size * HandleUtility.GetHandleSize(dataPosition));
|
||||
HandleUtility.AddControl(controlID, dist);
|
||||
break;
|
||||
|
||||
case EventType.Repaint:
|
||||
DrawSplineDataHandle(controlID, dataPosition, size);
|
||||
break;
|
||||
|
||||
case EventType.MouseDown:
|
||||
if (evt.button == 0
|
||||
&& HandleUtility.nearestControl == controlID
|
||||
&& GUIUtility.hotControl == 0)
|
||||
{
|
||||
GUIUtility.hotControl = controlID;
|
||||
newTime = GetClosestSplineDataT(spline, splineData);
|
||||
evt.Use();
|
||||
}
|
||||
break;
|
||||
|
||||
case EventType.MouseDrag:
|
||||
if (GUIUtility.hotControl == controlID)
|
||||
{
|
||||
newTime = GetClosestSplineDataT(spline, splineData);
|
||||
evt.Use();
|
||||
}
|
||||
break;
|
||||
|
||||
case EventType.MouseUp:
|
||||
if (GUIUtility.hotControl == controlID)
|
||||
{
|
||||
if(evt.button == 0)
|
||||
{
|
||||
GUIUtility.hotControl = 0;
|
||||
newTime = GetClosestSplineDataT(spline, splineData);
|
||||
}
|
||||
evt.Use();
|
||||
}
|
||||
break;
|
||||
|
||||
case EventType.MouseMove:
|
||||
HandleUtility.Repaint();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
static void DrawSplineDataHandle(int controlID, Vector3 position, float size)
|
||||
{
|
||||
var handleColor = Handles.color;
|
||||
if(controlID == GUIUtility.hotControl)
|
||||
handleColor = Handles.selectedColor;
|
||||
else if(GUIUtility.hotControl == 0 && controlID == HandleUtility.nearestControl)
|
||||
handleColor = Handles.preselectionColor;
|
||||
|
||||
// to avoid affecting the sphere dimensions with the handles matrix, we'll just use the position and reset
|
||||
// the matrix to identity when drawing.
|
||||
position = Handles.matrix * position;
|
||||
|
||||
using(new Handles.DrawingScope(handleColor, Matrix4x4.identity))
|
||||
{
|
||||
Handles.SphereHandleCap(
|
||||
controlID,
|
||||
position,
|
||||
Quaternion.identity,
|
||||
size * HandleUtility.GetHandleSize(position),
|
||||
EventType.Repaint
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// Spline must be in world space
|
||||
static float GetClosestSplineDataT<TSpline,TData>(TSpline spline, SplineData<TData> splineData) where TSpline : ISpline
|
||||
{
|
||||
var evt = Event.current;
|
||||
var ray = HandleUtility.GUIPointToWorldRay(evt.mousePosition);
|
||||
|
||||
SplineUtility.GetNearestPoint(spline,
|
||||
ray,
|
||||
out float3 _,
|
||||
out float t,
|
||||
k_PickRes);
|
||||
|
||||
return SplineUtility.ConvertIndexUnit(spline, t, splineData.PathIndexUnit);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,11 @@
|
|||
fileFormatVersion: 2
|
||||
guid: bea6b12ab5854760b485bc57408b9c3e
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
|
|
@ -0,0 +1,97 @@
|
|||
using System;
|
||||
using UnityEditor.Splines.Editor.GUI;
|
||||
using UnityEngine;
|
||||
using UnityEngine.Internal;
|
||||
using UnityEngine.Splines;
|
||||
|
||||
namespace UnityEditor.Splines
|
||||
{
|
||||
[ExcludeFromDocs]
|
||||
[CustomPropertyDrawer(typeof(SplineData<>))]
|
||||
public class SplineDataPropertyDrawer : PropertyDrawer
|
||||
{
|
||||
readonly static string k_MultiSplineEditMessage = L10n.Tr("Multi-selection is not supported for SplineData");
|
||||
readonly static string k_DataUnitTooltip = L10n.Tr("The unit Data Points are using to be associated to the spline. 'Spline Distance' is " +
|
||||
"using the distance in Unity Units from the spline origin, 'Normalized Distance' is using a normalized value of the spline " +
|
||||
"length between [0,1] and 'Knot Index' is using Spline Knot indeces.");
|
||||
|
||||
readonly static GUIContent[] k_PathUnitIndexLabels = new[]
|
||||
{
|
||||
new GUIContent(L10n.Tr("Spline Distance")),
|
||||
new GUIContent(L10n.Tr("Normalized Distance")),
|
||||
new GUIContent(L10n.Tr("Knot Index"))
|
||||
};
|
||||
|
||||
public override float GetPropertyHeight(SerializedProperty property, GUIContent label)
|
||||
{
|
||||
float height = EditorGUIUtility.singleLineHeight;
|
||||
if(!property.isExpanded || property.serializedObject.isEditingMultipleObjects)
|
||||
return height;
|
||||
|
||||
//Adding space for the object field
|
||||
height += EditorGUIUtility.singleLineHeight + EditorGUIUtility.standardVerticalSpacing;
|
||||
height += EditorGUI.GetPropertyHeight(property.FindPropertyRelative("m_IndexUnit")) + EditorGUIUtility.standardVerticalSpacing;
|
||||
|
||||
var datapointsProperty = property.FindPropertyRelative("m_DataPoints");
|
||||
height += EditorGUIUtility.singleLineHeight;
|
||||
if(datapointsProperty.isExpanded)
|
||||
{
|
||||
height += 2 * EditorGUIUtility.singleLineHeight;
|
||||
var arraySize = datapointsProperty.arraySize;
|
||||
if(arraySize == 0)
|
||||
{
|
||||
height += EditorGUIUtility.singleLineHeight;
|
||||
}
|
||||
else
|
||||
{
|
||||
for(int dataPointIndex = 0; dataPointIndex < arraySize; dataPointIndex++)
|
||||
{
|
||||
height += datapointsProperty.GetArrayElementAtIndex(dataPointIndex).isExpanded
|
||||
? 3 * EditorGUIUtility.singleLineHeight + 2 * EditorGUIUtility.standardVerticalSpacing
|
||||
: EditorGUIUtility.singleLineHeight;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return height;
|
||||
}
|
||||
|
||||
public override void OnGUI(Rect position, SerializedProperty property, GUIContent label)
|
||||
{
|
||||
EditorGUI.BeginProperty(position, label, property);
|
||||
if(property.serializedObject.isEditingMultipleObjects)
|
||||
{
|
||||
EditorGUI.LabelField(position,L10n.Tr(k_MultiSplineEditMessage), EditorStyles.helpBox);
|
||||
return;
|
||||
}
|
||||
|
||||
property.isExpanded = EditorGUI.Foldout(SplineUIManager.ReserveSpace(EditorGUIUtility.singleLineHeight, ref position), property.isExpanded, label);
|
||||
if (property.isExpanded)
|
||||
{
|
||||
EditorGUI.indentLevel++;
|
||||
SplineUIManager.ReserveSpace(EditorGUIUtility.standardVerticalSpacing, ref position);
|
||||
|
||||
var indexProperty = property.FindPropertyRelative("m_IndexUnit");
|
||||
var dataPointsProperty = property.FindPropertyRelative("m_DataPoints");
|
||||
var pathUnit = (PathIndexUnit)indexProperty.intValue;
|
||||
EditorGUI.BeginChangeCheck();
|
||||
var newPathUnit = EditorGUI.Popup(SplineUIManager.ReserveSpace(EditorGUI.GetPropertyHeight(indexProperty), ref position),
|
||||
new GUIContent("Data Index Unit",L10n.Tr(k_DataUnitTooltip)), (int)pathUnit, k_PathUnitIndexLabels);
|
||||
if(EditorGUI.EndChangeCheck())
|
||||
{
|
||||
if(dataPointsProperty.arraySize == 0)
|
||||
indexProperty.intValue = newPathUnit;
|
||||
else
|
||||
SplineDataConversionWindow.DoConfirmWindow(property, indexProperty, fieldInfo, property.serializedObject.targetObject as Component, newPathUnit);
|
||||
}
|
||||
|
||||
SplineUIManager.ReserveSpace(EditorGUIUtility.standardVerticalSpacing, ref position);
|
||||
|
||||
dataPointsProperty.isExpanded = EditorGUI.Foldout(SplineUIManager.ReserveSpace(EditorGUIUtility.singleLineHeight, ref position), dataPointsProperty.isExpanded, new GUIContent("Data Points"));
|
||||
if(dataPointsProperty.isExpanded)
|
||||
SplineDataUIManager.instance.GetDataPointsReorderableList(property, dataPointsProperty, fieldInfo, pathUnit).DoList(position);
|
||||
}
|
||||
EditorGUI.EndProperty();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,11 @@
|
|||
fileFormatVersion: 2
|
||||
guid: db00a1694ad7319449442045b8557a93
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
|
|
@ -0,0 +1,152 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Reflection;
|
||||
using UnityEditorInternal;
|
||||
using UnityEngine;
|
||||
using UnityEngine.Splines;
|
||||
|
||||
namespace UnityEditor.Splines
|
||||
{
|
||||
internal class SplineDataUIManager:ScriptableSingleton<SplineDataUIManager>
|
||||
{
|
||||
readonly static string k_DataIndexTooltip = L10n.Tr("The index of the Data Point along the spline and the unit used");
|
||||
readonly static string k_DataValueTooltip = L10n.Tr("The value of the Data Point.");
|
||||
|
||||
static Dictionary<string, ReorderableList> s_ReorderableLists = new Dictionary<string, ReorderableList>();
|
||||
|
||||
void OnEnable()
|
||||
{
|
||||
Selection.selectionChanged += ClearReorderableLists;
|
||||
}
|
||||
|
||||
void OnDisable()
|
||||
{
|
||||
Selection.selectionChanged -= ClearReorderableLists;
|
||||
}
|
||||
|
||||
string GetDisplayName(PathIndexUnit unit)
|
||||
{
|
||||
switch(unit)
|
||||
{
|
||||
case PathIndexUnit.Distance:
|
||||
return "Dist";
|
||||
case PathIndexUnit.Normalized:
|
||||
return "Path %";
|
||||
case PathIndexUnit.Knot:
|
||||
default:
|
||||
return "Knot";
|
||||
}
|
||||
}
|
||||
|
||||
void ClearReorderableLists()
|
||||
{
|
||||
s_ReorderableLists.Clear();
|
||||
}
|
||||
|
||||
static void SetSplineDataDirty(FieldInfo fieldInfo, SerializedProperty dataPointProperty)
|
||||
{
|
||||
var targetObject = fieldInfo.GetValue(dataPointProperty.serializedObject.targetObject);
|
||||
var dirtyMethod = targetObject.GetType().GetMethod("SetDirty", BindingFlags.Instance | BindingFlags.NonPublic);
|
||||
dirtyMethod?.Invoke(targetObject, null);
|
||||
}
|
||||
|
||||
public ReorderableList GetDataPointsReorderableList(SerializedProperty property, SerializedProperty dataPointProperty, FieldInfo fieldInfo, PathIndexUnit unit)
|
||||
{
|
||||
var key = dataPointProperty.propertyPath + property.serializedObject.targetObject.GetInstanceID();
|
||||
if(s_ReorderableLists.TryGetValue(key, out var list))
|
||||
{
|
||||
try
|
||||
{
|
||||
SerializedProperty.EqualContents(list.serializedProperty, dataPointProperty);
|
||||
return list;
|
||||
}
|
||||
catch (NullReferenceException)
|
||||
{
|
||||
s_ReorderableLists.Remove(key);
|
||||
}
|
||||
}
|
||||
|
||||
list = new ReorderableList(dataPointProperty.serializedObject, dataPointProperty, true, false, true, true);
|
||||
s_ReorderableLists.Add(key, list);
|
||||
|
||||
list.elementHeightCallback = (int index) =>
|
||||
{
|
||||
return dataPointProperty.arraySize > 0 && dataPointProperty.GetArrayElementAtIndex(index).isExpanded
|
||||
? 3 * EditorGUIUtility.singleLineHeight + 2 * EditorGUIUtility.standardVerticalSpacing
|
||||
: EditorGUIUtility.singleLineHeight;
|
||||
};
|
||||
|
||||
list.onChangedCallback = reorderableList =>
|
||||
{
|
||||
SetSplineDataDirty(fieldInfo, dataPointProperty);
|
||||
};
|
||||
|
||||
list.drawElementCallback =
|
||||
(Rect position, int listIndex, bool isActive, bool isFocused) =>
|
||||
{
|
||||
var ppte = dataPointProperty.GetArrayElementAtIndex(listIndex);
|
||||
|
||||
EditorGUI.indentLevel++;
|
||||
var expended = EditorGUI.Foldout(SplineUIManager.ReserveSpace(EditorGUIUtility.singleLineHeight, ref position), ppte.isExpanded, new GUIContent($"Data Point [{listIndex}]"), true);
|
||||
if(expended != ppte.isExpanded)
|
||||
{
|
||||
ppte.isExpanded = expended;
|
||||
if(!isActive)
|
||||
list.index = listIndex;
|
||||
list.GrabKeyboardFocus();
|
||||
}
|
||||
|
||||
if(ppte.isExpanded)
|
||||
{
|
||||
EditorGUI.indentLevel++;
|
||||
SplineUIManager.ReserveSpace(EditorGUIUtility.standardVerticalSpacing, ref position);
|
||||
EditorGUI.BeginChangeCheck();
|
||||
var indexProperty = ppte.FindPropertyRelative("m_Index");
|
||||
EditorGUI.DelayedFloatField(SplineUIManager.ReserveSpace(EditorGUIUtility.singleLineHeight, ref position), indexProperty, new GUIContent($"Data Index ({GetDisplayName(unit)})", L10n.Tr(k_DataIndexTooltip)));
|
||||
if(EditorGUI.EndChangeCheck())
|
||||
{
|
||||
if(!isActive)
|
||||
return;
|
||||
|
||||
dataPointProperty.serializedObject.ApplyModifiedProperties();
|
||||
var newIndex = ppte.FindPropertyRelative("m_Index").floatValue;
|
||||
|
||||
var targetObject = fieldInfo.GetValue(dataPointProperty.serializedObject.targetObject);
|
||||
var sortMethod = targetObject.GetType().GetMethod("ForceSort", BindingFlags.Instance | BindingFlags.NonPublic);
|
||||
EditorApplication.delayCall += () =>
|
||||
{
|
||||
sortMethod?.Invoke(targetObject, null);
|
||||
dataPointProperty.serializedObject.Update();
|
||||
for(int i = 0; i < dataPointProperty.arraySize; i++)
|
||||
{
|
||||
var index = dataPointProperty.GetArrayElementAtIndex(i).FindPropertyRelative("m_Index").floatValue;
|
||||
if(index == newIndex)
|
||||
{
|
||||
list.index = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
SplineUIManager.ReserveSpace(EditorGUIUtility.standardVerticalSpacing, ref position);
|
||||
EditorGUI.BeginChangeCheck();
|
||||
var valueProperty = ppte.FindPropertyRelative("m_Value");
|
||||
EditorGUI.PropertyField(SplineUIManager.ReserveSpace(EditorGUI.GetPropertyHeight(valueProperty), ref position), valueProperty, new GUIContent("Data Value", L10n.Tr(k_DataValueTooltip)));
|
||||
if(EditorGUI.EndChangeCheck())
|
||||
{
|
||||
SetSplineDataDirty(fieldInfo, dataPointProperty);
|
||||
if(!isActive)
|
||||
list.index = listIndex;
|
||||
}
|
||||
|
||||
EditorGUI.indentLevel--;
|
||||
}
|
||||
|
||||
EditorGUI.indentLevel--;
|
||||
};
|
||||
|
||||
return list;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,11 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 5bcefd1f1914dff4dae9fe9c4250b8da
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
|
|
@ -0,0 +1,76 @@
|
|||
using UnityEngine;
|
||||
using UnityEngine.Splines;
|
||||
|
||||
namespace UnityEditor.Splines
|
||||
{
|
||||
//This drawer is used to draw the actual type of the spline (editor version) and not the stored spline which is always bezier
|
||||
[CustomPropertyDrawer(typeof(Spline), true)]
|
||||
class SplineDrawer : PropertyDrawer
|
||||
{
|
||||
const string k_MultiSplineEditMessage = "Multi-selection is not supported for Splines";
|
||||
const string k_SplineFoldoutTitle = "Advanced";
|
||||
|
||||
public override float GetPropertyHeight(SerializedProperty property, GUIContent label)
|
||||
{
|
||||
float height = EditorGUIUtility.singleLineHeight;
|
||||
if(!property.isExpanded || property.serializedObject.isEditingMultipleObjects)
|
||||
return height;
|
||||
|
||||
height += EditorGUI.GetPropertyHeight(property.FindPropertyRelative("m_EditModeType"));
|
||||
|
||||
var proxy = SplineUIManager.instance.GetProxyFromProperty(property);
|
||||
|
||||
var it = proxy.SerializedObject.FindProperty("Spline").Copy();
|
||||
it.NextVisible(true);
|
||||
do
|
||||
{
|
||||
height += EditorGUI.GetPropertyHeight(it);
|
||||
} while(it.NextVisible(false));
|
||||
|
||||
return height;
|
||||
}
|
||||
|
||||
public override void OnGUI(Rect position, SerializedProperty property, GUIContent label)
|
||||
{
|
||||
if(property.serializedObject.isEditingMultipleObjects)
|
||||
{
|
||||
EditorGUI.LabelField(position,k_MultiSplineEditMessage, EditorStyles.helpBox);
|
||||
return;
|
||||
}
|
||||
|
||||
label.text = L10n.Tr(k_SplineFoldoutTitle);
|
||||
property.isExpanded = EditorGUI.Foldout(SplineUIManager.ReserveSpace(EditorGUIUtility.singleLineHeight, ref position), property.isExpanded, label);
|
||||
if (property.isExpanded)
|
||||
{
|
||||
EditorGUI.indentLevel++;
|
||||
var proxy = SplineUIManager.instance.GetProxyFromProperty(property);
|
||||
|
||||
var editTypeProperty = property.FindPropertyRelative("m_EditModeType");
|
||||
EditorGUI.PropertyField(SplineUIManager.ReserveSpace(EditorGUI.GetPropertyHeight(editTypeProperty), ref position), editTypeProperty);
|
||||
|
||||
var pathProperty = proxy.SerializedObject.FindProperty("Spline");
|
||||
|
||||
// HACK to get around the fact that array size change isn't an actual change when applying (bug)
|
||||
var knotsProperty = pathProperty.FindPropertyRelative("m_Knots");
|
||||
var arraySize = knotsProperty.arraySize;
|
||||
|
||||
EditorGUI.BeginChangeCheck();
|
||||
|
||||
var it = pathProperty.Copy();
|
||||
it.NextVisible(true);
|
||||
do
|
||||
{
|
||||
EditorGUI.PropertyField(SplineUIManager.ReserveSpace(EditorGUI.GetPropertyHeight(it), ref position), it, true);
|
||||
} while(it.NextVisible(false));
|
||||
|
||||
|
||||
if(EditorGUI.EndChangeCheck() || arraySize != knotsProperty.arraySize)
|
||||
{
|
||||
SplineUIManager.instance.ApplyProxyToProperty(proxy, property);
|
||||
}
|
||||
|
||||
EditorGUI.indentLevel--;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,11 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 07e0a2238ade23d4e8de8e43b83cf1ad
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
|
|
@ -0,0 +1,185 @@
|
|||
using System;
|
||||
using System.Linq;
|
||||
using UnityEngine;
|
||||
using UnityEngine.Splines;
|
||||
|
||||
namespace UnityEditor.Splines
|
||||
{
|
||||
[CustomEditor(typeof(SplineExtrude))]
|
||||
[CanEditMultipleObjects]
|
||||
class SplineExtrudeEditor : SplineComponentEditor
|
||||
{
|
||||
SerializedProperty m_Container;
|
||||
SerializedProperty m_RebuildOnSplineChange;
|
||||
SerializedProperty m_RebuildFrequency;
|
||||
SerializedProperty m_Sides;
|
||||
SerializedProperty m_SegmentsPerUnit;
|
||||
SerializedProperty m_Capped;
|
||||
SerializedProperty m_Radius;
|
||||
SerializedProperty m_Range;
|
||||
SerializedProperty m_UpdateColliders;
|
||||
|
||||
static readonly GUIContent k_RangeContent = new GUIContent("Range", "The section of the Spline to extrude.");
|
||||
static readonly GUIContent k_AdvancedContent = new GUIContent("Advanced", "Advanced Spline Extrude settings.");
|
||||
static readonly GUIContent k_PercentageContent = new GUIContent("Percentage", "The section of the Spline to extrude in percentages.");
|
||||
|
||||
static readonly string k_Spline = "Spline";
|
||||
static readonly string k_Geometry = L10n.Tr("Geometry");
|
||||
static readonly string k_ProfileEdges = "Profile Edges";
|
||||
static readonly string k_CapEnds = "Cap Ends";
|
||||
static readonly string k_AutoRegenGeo = "Auto-Regen Geometry";
|
||||
static readonly string k_To = L10n.Tr("to");
|
||||
static readonly string k_From = L10n.Tr("from");
|
||||
|
||||
SplineExtrude[] m_Components;
|
||||
bool m_AnyMissingMesh;
|
||||
|
||||
protected override void OnEnable()
|
||||
{
|
||||
base.OnEnable();
|
||||
|
||||
m_Container = serializedObject.FindProperty("m_Container");
|
||||
m_RebuildOnSplineChange = serializedObject.FindProperty("m_RebuildOnSplineChange");
|
||||
m_RebuildFrequency = serializedObject.FindProperty("m_RebuildFrequency");
|
||||
m_Sides = serializedObject.FindProperty("m_Sides");
|
||||
m_SegmentsPerUnit = serializedObject.FindProperty("m_SegmentsPerUnit");
|
||||
m_Capped = serializedObject.FindProperty("m_Capped");
|
||||
m_Radius = serializedObject.FindProperty("m_Radius");
|
||||
m_Range = serializedObject.FindProperty("m_Range");
|
||||
m_UpdateColliders = serializedObject.FindProperty("m_UpdateColliders");
|
||||
|
||||
m_Components = targets.Select(x => x as SplineExtrude).Where(y => y != null).ToArray();
|
||||
m_AnyMissingMesh = m_Components.Any(x => x.TryGetComponent<MeshFilter>(out var filter) && filter.sharedMesh == null);
|
||||
|
||||
EditorSplineUtility.afterSplineWasModified += OnSplineModified;
|
||||
}
|
||||
|
||||
void OnDisable()
|
||||
{
|
||||
EditorSplineUtility.afterSplineWasModified -= OnSplineModified;
|
||||
}
|
||||
|
||||
void OnSplineModified(Spline spline)
|
||||
{
|
||||
if (EditorApplication.isPlayingOrWillChangePlaymode)
|
||||
return;
|
||||
|
||||
foreach (var extrude in m_Components)
|
||||
{
|
||||
if (extrude.container != null && extrude.container.Spline == spline)
|
||||
extrude.Rebuild();
|
||||
}
|
||||
}
|
||||
|
||||
public override void OnInspectorGUI()
|
||||
{
|
||||
serializedObject.Update();
|
||||
|
||||
EditorGUI.BeginChangeCheck();
|
||||
|
||||
EditorGUILayout.PropertyField(m_Container, new GUIContent(k_Spline, m_Container.tooltip));
|
||||
HorizontalLine(Color.grey);
|
||||
EditorGUILayout.LabelField(k_Geometry, EditorStyles.boldLabel);
|
||||
|
||||
if(m_AnyMissingMesh)
|
||||
{
|
||||
GUILayout.BeginHorizontal();
|
||||
EditorGUILayout.PrefixLabel(" ");
|
||||
if(GUILayout.Button("Create Mesh Asset"))
|
||||
CreateMeshAssets(m_Components);
|
||||
GUILayout.EndHorizontal();
|
||||
}
|
||||
|
||||
EditorGUI.indentLevel++;
|
||||
|
||||
EditorGUI.BeginChangeCheck();
|
||||
EditorGUILayout.PropertyField(m_Radius);
|
||||
if(EditorGUI.EndChangeCheck())
|
||||
m_Radius.floatValue = Mathf.Clamp(m_Radius.floatValue, .00001f, 1000f);
|
||||
|
||||
EditorGUI.BeginChangeCheck();
|
||||
EditorGUILayout.PropertyField(m_Sides, new GUIContent(k_ProfileEdges, m_Sides.tooltip));
|
||||
if(EditorGUI.EndChangeCheck())
|
||||
m_Sides.intValue = Mathf.Clamp(m_Sides.intValue, 3, 2048);
|
||||
|
||||
EditorGUI.BeginChangeCheck();
|
||||
EditorGUILayout.PropertyField(m_SegmentsPerUnit);
|
||||
if(EditorGUI.EndChangeCheck())
|
||||
m_SegmentsPerUnit.floatValue = Mathf.Clamp(m_SegmentsPerUnit.floatValue, .00001f, 4096f);
|
||||
|
||||
EditorGUILayout.PropertyField(m_Capped, new GUIContent(k_CapEnds, m_Capped.tooltip));
|
||||
EditorGUI.indentLevel--;
|
||||
|
||||
HorizontalLine(Color.grey);
|
||||
|
||||
m_Range.isExpanded = Foldout(m_Range.isExpanded, k_AdvancedContent);
|
||||
if (m_Range.isExpanded)
|
||||
{
|
||||
EditorGUI.indentLevel++;
|
||||
|
||||
EditorGUI.showMixedValue = m_Range.hasMultipleDifferentValues;
|
||||
var range = m_Range.vector2Value;
|
||||
EditorGUI.BeginChangeCheck();
|
||||
EditorGUILayout.MinMaxSlider(k_RangeContent, ref range.x, ref range.y, 0f, 1f);
|
||||
if (EditorGUI.EndChangeCheck())
|
||||
m_Range.vector2Value = range;
|
||||
|
||||
EditorGUI.indentLevel++;
|
||||
EditorGUILayout.BeginHorizontal();
|
||||
EditorGUILayout.PrefixLabel(k_PercentageContent);
|
||||
EditorGUI.indentLevel--;
|
||||
EditorGUI.indentLevel--;
|
||||
|
||||
EditorGUI.BeginChangeCheck();
|
||||
var newRange = new Vector2(range.x, range.y);
|
||||
using (new LabelWidthScope(30f))
|
||||
newRange.x = EditorGUILayout.FloatField(k_From, range.x * 100f) / 100f;
|
||||
|
||||
using (new LabelWidthScope(15f))
|
||||
newRange.y = EditorGUILayout.FloatField(k_To, range.y * 100f) / 100f;
|
||||
|
||||
if (EditorGUI.EndChangeCheck())
|
||||
{
|
||||
newRange.x = Mathf.Min(Mathf.Clamp(newRange.x, 0f, 1f), range.y);
|
||||
newRange.y = Mathf.Max(newRange.x, Mathf.Clamp(newRange.y, 0f, 1f));
|
||||
m_Range.vector2Value = newRange;
|
||||
}
|
||||
|
||||
EditorGUILayout.EndHorizontal();
|
||||
EditorGUI.showMixedValue = false;
|
||||
|
||||
EditorGUI.indentLevel++;
|
||||
EditorGUILayout.PropertyField(m_RebuildOnSplineChange, new GUIContent(k_AutoRegenGeo, m_RebuildOnSplineChange.tooltip));
|
||||
if (m_RebuildOnSplineChange.boolValue)
|
||||
{
|
||||
EditorGUI.indentLevel++;
|
||||
EditorGUI.BeginDisabledGroup(!m_RebuildOnSplineChange.boolValue);
|
||||
EditorGUILayout.PropertyField(m_RebuildFrequency);
|
||||
EditorGUI.EndDisabledGroup();
|
||||
EditorGUI.indentLevel--;
|
||||
}
|
||||
|
||||
EditorGUILayout.PropertyField(m_UpdateColliders);
|
||||
|
||||
EditorGUI.indentLevel--;
|
||||
}
|
||||
|
||||
serializedObject.ApplyModifiedProperties();
|
||||
|
||||
if(EditorGUI.EndChangeCheck())
|
||||
foreach(var extrude in m_Components)
|
||||
extrude.Rebuild();
|
||||
}
|
||||
|
||||
void CreateMeshAssets(SplineExtrude[] components)
|
||||
{
|
||||
foreach (var extrude in components)
|
||||
{
|
||||
if (!extrude.TryGetComponent<MeshFilter>(out var filter) || filter.sharedMesh != null)
|
||||
filter.sharedMesh = extrude.CreateMeshAsset();
|
||||
}
|
||||
|
||||
m_AnyMissingMesh = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,3 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 8ae5aa24d97048439e76e9392765c364
|
||||
timeCreated: 1637689699
|
||||
|
|
@ -0,0 +1,54 @@
|
|||
using UnityEditor.EditorTools;
|
||||
using UnityEditor.Overlays;
|
||||
using UnityEngine;
|
||||
using UnityEngine.UIElements;
|
||||
|
||||
namespace UnityEditor.Splines
|
||||
{
|
||||
[Icon("UnityEditor.InspectorWindow")]
|
||||
[Overlay(typeof(SceneView), "unity-spline-inspector", "Spline Inspector", "SplineInspector")]
|
||||
sealed class SplineInspectorOverlay : Overlay, ITransientOverlay
|
||||
{
|
||||
static VisualTreeAsset s_VisualTree;
|
||||
|
||||
public bool visible => ToolManager.activeContextType == typeof(SplineToolContext);
|
||||
|
||||
ElementInspector m_ElementInspector;
|
||||
|
||||
public override VisualElement CreatePanelContent()
|
||||
{
|
||||
VisualElement root = new VisualElement();
|
||||
|
||||
m_ElementInspector = new ElementInspector();
|
||||
UpdateInspector();
|
||||
|
||||
root.Add(m_ElementInspector);
|
||||
|
||||
return root;
|
||||
}
|
||||
|
||||
public override void OnCreated()
|
||||
{
|
||||
displayedChanged += OnDisplayedChange;
|
||||
SplineSelection.changed += UpdateInspector;
|
||||
SplineConversionUtility.splineTypeChanged += UpdateInspector;
|
||||
}
|
||||
|
||||
public override void OnWillBeDestroyed()
|
||||
{
|
||||
displayedChanged -= OnDisplayedChange;
|
||||
SplineSelection.changed -= UpdateInspector;
|
||||
SplineConversionUtility.splineTypeChanged -= UpdateInspector;
|
||||
}
|
||||
|
||||
void OnDisplayedChange(bool displayed)
|
||||
{
|
||||
UpdateInspector();
|
||||
}
|
||||
|
||||
void UpdateInspector()
|
||||
{
|
||||
m_ElementInspector?.SetElement(SplineSelection.GetActiveElement(), SplineSelection.count);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,11 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 5737da4b5db2be640adcc9bb3360aa91
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
|
|
@ -0,0 +1,522 @@
|
|||
using System;
|
||||
using UnityEditor;
|
||||
using UnityEditor.Splines;
|
||||
using UnityEngine;
|
||||
using UnityEngine.Splines;
|
||||
|
||||
class SplineInstantiateGizmoDrawer
|
||||
{
|
||||
[DrawGizmo(GizmoType.Selected | GizmoType.Active)]
|
||||
static void DrawSplineInstantiateGizmos(SplineInstantiate scr, GizmoType gizmoType)
|
||||
{
|
||||
var instances = scr.instances;
|
||||
|
||||
foreach(var instance in instances)
|
||||
{
|
||||
var pos = instance.transform.position;
|
||||
Handles.color = Color.red;
|
||||
Handles.DrawAAPolyLine(3f,new []{ pos, pos + 0.25f * instance.transform.right });
|
||||
Handles.color = Color.green;
|
||||
Handles.DrawAAPolyLine(3f,new []{pos, pos + 0.25f * instance.transform.up});
|
||||
Handles.color = Color.blue;
|
||||
Handles.DrawAAPolyLine(3f,new []{pos, pos + 0.25f * instance.transform.forward});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[CustomPropertyDrawer (typeof(SplineInstantiate.InstantiableItem))]
|
||||
class InstantiableItemDrawer : PropertyDrawer
|
||||
{
|
||||
static readonly string k_ProbabilityTooltip = L10n.Tr("Probability for that element to appear.");
|
||||
|
||||
public override float GetPropertyHeight(SerializedProperty property, GUIContent label)
|
||||
{
|
||||
return EditorGUIUtility.singleLineHeight;
|
||||
}
|
||||
|
||||
public override void OnGUI(Rect rect, SerializedProperty property, GUIContent label)
|
||||
{
|
||||
var prefabProperty = property.FindPropertyRelative("prefab");
|
||||
var probaProperty = property.FindPropertyRelative("probability");
|
||||
|
||||
var headerLine = ReserveSpace(EditorGUIUtility.singleLineHeight, ref rect);
|
||||
|
||||
using(new SplineInstantiateEditor.LabelWidthScope(0f))
|
||||
EditorGUI.ObjectField(ReserveLineSpace(headerLine.width - 100, ref headerLine), prefabProperty, new GUIContent(""));
|
||||
|
||||
ReserveLineSpace(10, ref headerLine);
|
||||
EditorGUI.LabelField(ReserveLineSpace(15, ref headerLine), new GUIContent("%", k_ProbabilityTooltip));
|
||||
probaProperty.floatValue = EditorGUI.FloatField(ReserveLineSpace(60, ref headerLine), probaProperty.floatValue);
|
||||
}
|
||||
|
||||
static Rect ReserveSpace(float height, ref Rect total)
|
||||
{
|
||||
Rect current = total;
|
||||
current.height = height;
|
||||
total.y += height;
|
||||
return current;
|
||||
}
|
||||
|
||||
static Rect ReserveLineSpace(float width, ref Rect total)
|
||||
{
|
||||
Rect current = total;
|
||||
current.width = width;
|
||||
total.x += width;
|
||||
return current;
|
||||
}
|
||||
}
|
||||
|
||||
[CustomPropertyDrawer (typeof(SplineInstantiate.AlignAxis))]
|
||||
class ItemAxisDrawer : PropertyDrawer
|
||||
{
|
||||
static int s_LastUpAxis;
|
||||
|
||||
public override void OnGUI(Rect rect, SerializedProperty property, GUIContent label)
|
||||
{
|
||||
var enumValue = property.intValue;
|
||||
|
||||
if(property.name == "m_Up")
|
||||
{
|
||||
property.intValue = (int)( (SplineInstantiate.AlignAxis)EditorGUI.EnumPopup(rect, label, (SplineInstantiate.AlignAxis)enumValue));
|
||||
s_LastUpAxis = property.intValue;
|
||||
}
|
||||
else
|
||||
{
|
||||
property.intValue = (int)((SplineInstantiate.AlignAxis)EditorGUI.EnumPopup(rect, label, (SplineInstantiate.AlignAxis)enumValue,
|
||||
(item) =>
|
||||
{
|
||||
int axisItem = (int)(SplineInstantiate.AlignAxis)item;
|
||||
return !(axisItem == s_LastUpAxis || axisItem == (s_LastUpAxis + 3) % 6);
|
||||
}));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[CustomEditor(typeof(SplineInstantiate),false)]
|
||||
[CanEditMultipleObjects]
|
||||
class SplineInstantiateEditor : SplineComponentEditor
|
||||
{
|
||||
enum SpawnType
|
||||
{
|
||||
Exact,
|
||||
Random
|
||||
}
|
||||
|
||||
SerializedProperty m_SplineContainer;
|
||||
|
||||
SerializedProperty m_ItemsToInstantiate;
|
||||
SerializedProperty m_InstantiateMethod;
|
||||
|
||||
SerializedProperty m_Space;
|
||||
SerializedProperty m_UpAxis;
|
||||
SerializedProperty m_ForwardAxis;
|
||||
SerializedProperty m_Spacing;
|
||||
SerializedProperty m_PositionOffset;
|
||||
SerializedProperty m_RotationOffset;
|
||||
SerializedProperty m_ScaleOffset;
|
||||
SerializedProperty m_AutoRefresh;
|
||||
|
||||
static readonly string[] k_SpacingTypesLabels = new []
|
||||
{
|
||||
L10n.Tr("Count"),
|
||||
L10n.Tr("Spacing (Spline)"),
|
||||
L10n.Tr("Spacing (Linear)")
|
||||
};
|
||||
|
||||
static readonly string k_Helpbox = L10n.Tr("Instantiated Objects need a SplineContainer target to be created.");
|
||||
|
||||
//Setup Section
|
||||
static readonly string k_Setup = L10n.Tr("Instantiated Object Setup");
|
||||
static readonly string k_ObjectUp = L10n.Tr("Up Axis");
|
||||
static readonly string k_ObjectUpTooltip = L10n.Tr("Object axis to use as Up Direction when instantiating on the Spline (default is Y).");
|
||||
static readonly string k_ObjectForward = L10n.Tr("Forward Axis");
|
||||
static readonly string k_ObjectForwardTooltip = L10n.Tr("Object axis to use as Forward Direction when instantiating on the Spline (default is Z).");
|
||||
static readonly string k_AlignTo = L10n.Tr("Align To");
|
||||
static readonly string k_AlignToTooltip = L10n.Tr("Define the space to use to orientate the instantiated object.");
|
||||
|
||||
static readonly string k_Instantiation = L10n.Tr("Instantiation");
|
||||
static readonly string k_Method = L10n.Tr("Instantiate Method");
|
||||
static readonly string k_MethodTooltip = L10n.Tr("How instances are generated along the spline.");
|
||||
static readonly string k_Max = L10n.Tr("Max");
|
||||
static readonly string k_Min = L10n.Tr("Min");
|
||||
|
||||
SpawnType m_SpacingType;
|
||||
|
||||
//Offsets
|
||||
static readonly string k_Offset = L10n.Tr("Offsets");
|
||||
static readonly string k_PositionOffset = L10n.Tr("Position Offset");
|
||||
static readonly string k_PositionOffsetTooltip = L10n.Tr("Whether or not to use a position offset.");
|
||||
static readonly string k_RotationOffset = L10n.Tr("Rotation Offset");
|
||||
static readonly string k_RotationOffsetTooltip = L10n.Tr("Whether or not to use a rotation offset.");
|
||||
static readonly string k_ScaleOffset = L10n.Tr("Scale Offset");
|
||||
static readonly string k_ScaleOffsetTooltip = L10n.Tr("Whether or not to use a scale offset.");
|
||||
|
||||
//Generation
|
||||
static readonly string k_Generation = L10n.Tr("Generation");
|
||||
static readonly string k_AutoRefresh = L10n.Tr("Auto Refresh Generation");
|
||||
static readonly string k_AutoRefreshTooltip = L10n.Tr("Automatically refresh the instances when the spline or the values are changed.");
|
||||
|
||||
static readonly string k_Randomize = L10n.Tr("Randomize");
|
||||
static readonly string k_RandomizeTooltip = L10n.Tr("Compute a new randomization of the instances along the spline.");
|
||||
static readonly string k_Regenerate = L10n.Tr("Regenerate");
|
||||
static readonly string k_RegenerateTooltip = L10n.Tr("Regenerate the instances along the spline (only available when the content is not automatically refreshed).");
|
||||
static readonly string k_Clear = L10n.Tr("Clear");
|
||||
static readonly string k_ClearTooltip = L10n.Tr("Clear the instances along the spline (only available when the content is not automatically refreshed).");
|
||||
|
||||
bool m_PositionFoldout;
|
||||
bool m_RotationFoldout;
|
||||
bool m_ScaleFoldout;
|
||||
|
||||
enum OffsetType
|
||||
{
|
||||
Exact,
|
||||
Random
|
||||
};
|
||||
|
||||
protected override void OnEnable()
|
||||
{
|
||||
base.OnEnable();
|
||||
|
||||
m_SplineContainer = serializedObject.FindProperty("m_Container");
|
||||
|
||||
m_ItemsToInstantiate = serializedObject.FindProperty("m_ItemsToInstantiate");
|
||||
m_InstantiateMethod = serializedObject.FindProperty("m_Method");
|
||||
|
||||
m_Space = serializedObject.FindProperty("m_Space");
|
||||
m_UpAxis = serializedObject.FindProperty("m_Up");
|
||||
m_ForwardAxis = serializedObject.FindProperty("m_Forward");
|
||||
|
||||
m_Spacing = serializedObject.FindProperty("m_Spacing");
|
||||
|
||||
m_PositionOffset = serializedObject.FindProperty("m_PositionOffset");
|
||||
m_RotationOffset = serializedObject.FindProperty("m_RotationOffset");
|
||||
m_ScaleOffset = serializedObject.FindProperty("m_ScaleOffset");
|
||||
|
||||
m_AutoRefresh = serializedObject.FindProperty("m_AutoRefresh");
|
||||
|
||||
m_SpacingType = m_Spacing.vector2Value.x == m_Spacing.vector2Value.y ? SpawnType.Exact : SpawnType.Random;
|
||||
|
||||
EditorSplineUtility.afterSplineWasModified += OnSplineModified;
|
||||
}
|
||||
|
||||
void OnDisable()
|
||||
{
|
||||
EditorSplineUtility.afterSplineWasModified -= OnSplineModified;
|
||||
}
|
||||
|
||||
void OnSplineModified(Spline spline)
|
||||
{
|
||||
if (EditorApplication.isPlayingOrWillChangePlaymode)
|
||||
return;
|
||||
|
||||
( (SplineInstantiate)target ).SetSplineDirty(spline);
|
||||
}
|
||||
|
||||
public override void OnInspectorGUI()
|
||||
{
|
||||
serializedObject.Update();
|
||||
|
||||
var splineInstantiate = ((SplineInstantiate)target);
|
||||
var dirtyInstances = false;
|
||||
var updateInstances = false;
|
||||
|
||||
EditorGUILayout.PropertyField(m_SplineContainer);
|
||||
if(m_SplineContainer.objectReferenceValue == null)
|
||||
EditorGUILayout.HelpBox(k_Helpbox, MessageType.Warning);
|
||||
|
||||
EditorGUI.BeginChangeCheck();
|
||||
EditorGUILayout.PropertyField(m_ItemsToInstantiate);
|
||||
dirtyInstances = EditorGUI.EndChangeCheck();
|
||||
|
||||
DoSetupSection();
|
||||
dirtyInstances |= DoInstantiateSection();
|
||||
|
||||
updateInstances |= DisplayOffsets();
|
||||
|
||||
HorizontalLine( Color.grey );
|
||||
EditorGUILayout.LabelField(k_Generation, EditorStyles.boldLabel);
|
||||
EditorGUI.indentLevel++;
|
||||
EditorGUILayout.PropertyField(m_AutoRefresh, new GUIContent(k_AutoRefresh, k_AutoRefreshTooltip));
|
||||
EditorGUI.indentLevel--;
|
||||
serializedObject.ApplyModifiedProperties();
|
||||
|
||||
EditorGUILayout.Separator();
|
||||
EditorGUILayout.BeginHorizontal();
|
||||
EditorGUILayout.Space();
|
||||
if(GUILayout.Button(new GUIContent(k_Randomize, k_RandomizeTooltip), GUILayout.MaxWidth(100f)))
|
||||
splineInstantiate.Randomize();
|
||||
if(GUILayout.Button(new GUIContent(k_Regenerate, k_RegenerateTooltip), GUILayout.MaxWidth(100f)))
|
||||
updateInstances = true;
|
||||
if(GUILayout.Button(new GUIContent(k_Clear, k_ClearTooltip), GUILayout.MaxWidth(100f)))
|
||||
splineInstantiate.Clear();
|
||||
EditorGUILayout.Space();
|
||||
EditorGUILayout.EndHorizontal();
|
||||
EditorGUILayout.Separator();
|
||||
|
||||
if(dirtyInstances)
|
||||
splineInstantiate.SetDirty();
|
||||
|
||||
if(updateInstances)
|
||||
splineInstantiate.UpdateInstances();
|
||||
}
|
||||
|
||||
void DoSetupSection()
|
||||
{
|
||||
HorizontalLine(Color.grey);
|
||||
EditorGUILayout.LabelField(k_Setup, EditorStyles.boldLabel);
|
||||
GUILayout.Space(5f);
|
||||
EditorGUI.indentLevel++;
|
||||
|
||||
EditorGUI.BeginChangeCheck();
|
||||
|
||||
EditorGUILayout.PropertyField(m_UpAxis, new GUIContent(k_ObjectUp, k_ObjectUpTooltip));
|
||||
EditorGUILayout.PropertyField(m_ForwardAxis, new GUIContent(k_ObjectForward, k_ObjectForwardTooltip));
|
||||
|
||||
if(EditorGUI.EndChangeCheck())
|
||||
{
|
||||
//Insuring axis integrity
|
||||
if(m_ForwardAxis.intValue == m_UpAxis.intValue || m_ForwardAxis.intValue == ( m_UpAxis.intValue + 3 ) % 6)
|
||||
m_ForwardAxis.intValue = ( m_ForwardAxis.intValue + 1 ) % 6;
|
||||
}
|
||||
|
||||
EditorGUILayout.PropertyField(m_Space, new GUIContent(k_AlignTo, k_AlignToTooltip));
|
||||
EditorGUI.indentLevel--;
|
||||
}
|
||||
|
||||
bool DoInstantiateSection()
|
||||
{
|
||||
var dirty = false;
|
||||
Vector2 spacingV2 = m_Spacing.vector2Value;
|
||||
|
||||
HorizontalLine( Color.grey );
|
||||
EditorGUILayout.LabelField(k_Instantiation, EditorStyles.boldLabel);
|
||||
|
||||
EditorGUI.indentLevel++;
|
||||
EditorGUI.BeginChangeCheck();
|
||||
EditorGUILayout.PropertyField(m_InstantiateMethod, new GUIContent(k_Method, k_MethodTooltip), EditorStyles.boldFont );
|
||||
|
||||
if(EditorGUI.EndChangeCheck())
|
||||
{
|
||||
if(m_SpacingType == SpawnType.Random && m_InstantiateMethod.intValue == (int)SplineInstantiate.Method.LinearDistance)
|
||||
m_Spacing.vector2Value = new Vector2(spacingV2.x, float.NaN);
|
||||
dirty = true;
|
||||
}
|
||||
|
||||
EditorGUILayout.BeginHorizontal();
|
||||
EditorGUILayout.PrefixLabel(new GUIContent(k_SpacingTypesLabels[m_InstantiateMethod.intValue]));
|
||||
EditorGUI.indentLevel--;
|
||||
|
||||
GUILayout.Space(2f);
|
||||
|
||||
EditorGUI.BeginChangeCheck();
|
||||
|
||||
float spacingX = m_Spacing.vector2Value.x;
|
||||
var isExact = m_SpacingType == SpawnType.Exact;
|
||||
if(isExact || m_InstantiateMethod.intValue != (int)SplineInstantiate.Method.LinearDistance)
|
||||
{
|
||||
using(new LabelWidthScope(30f))
|
||||
spacingX = (SplineInstantiate.Method)m_InstantiateMethod.intValue == SplineInstantiate.Method.InstanceCount ?
|
||||
EditorGUILayout.IntField(new GUIContent(isExact ? L10n.Tr("Dist") : k_Min), (int)m_Spacing.vector2Value.x, GUILayout.MinWidth(50f)) :
|
||||
EditorGUILayout.FloatField(new GUIContent(isExact ? L10n.Tr("Dist") : k_Min), m_Spacing.vector2Value.x, GUILayout.MinWidth(50f));
|
||||
}
|
||||
if(isExact)
|
||||
{
|
||||
spacingV2 = new Vector2(spacingX, spacingX);
|
||||
}
|
||||
else if(m_InstantiateMethod.intValue != (int)SplineInstantiate.Method.LinearDistance)
|
||||
{
|
||||
using(new LabelWidthScope(30f))
|
||||
{
|
||||
var spacingY = (SplineInstantiate.Method)m_InstantiateMethod.intValue == SplineInstantiate.Method.InstanceCount ?
|
||||
EditorGUILayout.IntField(new GUIContent(k_Max), (int)m_Spacing.vector2Value.y, GUILayout.MinWidth(50f)) :
|
||||
EditorGUILayout.FloatField(new GUIContent(k_Max), m_Spacing.vector2Value.y, GUILayout.MinWidth(50f));
|
||||
|
||||
if(spacingX > m_Spacing.vector2Value.y)
|
||||
spacingY = spacingX;
|
||||
else if(spacingY < m_Spacing.vector2Value.x)
|
||||
spacingX = spacingY;
|
||||
|
||||
spacingV2 = new Vector2(spacingX, spacingY);
|
||||
}
|
||||
}
|
||||
|
||||
if(EditorGUI.EndChangeCheck())
|
||||
m_Spacing.vector2Value = spacingV2;
|
||||
|
||||
EditorGUI.BeginChangeCheck();
|
||||
if(m_InstantiateMethod.intValue != (int)SplineInstantiate.Method.LinearDistance)
|
||||
m_SpacingType = (SpawnType)EditorGUILayout.EnumPopup(m_SpacingType, GUILayout.MinWidth(30f));
|
||||
else
|
||||
m_SpacingType = (SpawnType)EditorGUILayout.Popup(m_SpacingType == SpawnType.Exact ? 0 : 1,
|
||||
new []{"Exact", "Auto"}, GUILayout.MinWidth(30f));
|
||||
|
||||
if(EditorGUI.EndChangeCheck())
|
||||
{
|
||||
if(m_SpacingType == SpawnType.Exact)
|
||||
m_Spacing.vector2Value = new Vector2(spacingV2.x, spacingV2.x);
|
||||
else if(m_InstantiateMethod.intValue == (int)SplineInstantiate.Method.LinearDistance)
|
||||
m_Spacing.vector2Value = new Vector2(spacingV2.x, float.NaN);
|
||||
|
||||
dirty = true;
|
||||
}
|
||||
|
||||
EditorGUILayout.EndHorizontal();
|
||||
|
||||
return dirty;
|
||||
}
|
||||
|
||||
bool DoOffsetProperties(
|
||||
SerializedProperty offsetProperty, GUIContent content, bool foldoutValue, out bool newFoldoutValue)
|
||||
{
|
||||
bool changed = false;
|
||||
newFoldoutValue = foldoutValue;
|
||||
|
||||
EditorGUILayout.BeginHorizontal();
|
||||
|
||||
using(new LabelWidthScope(0f))
|
||||
{
|
||||
var setupProperty = offsetProperty.FindPropertyRelative("setup");
|
||||
var setup = (SplineInstantiate.Vector3Offset.Setup)setupProperty.intValue;
|
||||
|
||||
var hasOffset = ( setup & SplineInstantiate.Vector3Offset.Setup.HasOffset ) != 0;
|
||||
EditorGUI.BeginChangeCheck();
|
||||
hasOffset = EditorGUILayout.Toggle(hasOffset, GUILayout.MaxWidth(20f));
|
||||
if(EditorGUI.EndChangeCheck())
|
||||
{
|
||||
if(hasOffset)
|
||||
setup |= SplineInstantiate.Vector3Offset.Setup.HasOffset;
|
||||
else
|
||||
setup &= ~SplineInstantiate.Vector3Offset.Setup.HasOffset;
|
||||
|
||||
setupProperty.intValue = (int)setup;
|
||||
changed = true;
|
||||
}
|
||||
|
||||
EditorGUILayout.Space(10f);
|
||||
using(new EditorGUI.DisabledScope(!hasOffset))
|
||||
{
|
||||
newFoldoutValue = Foldout(foldoutValue, content, hasOffset) && hasOffset;
|
||||
GUILayout.FlexibleSpace();
|
||||
EditorGUILayout.EndHorizontal();
|
||||
|
||||
if(newFoldoutValue)
|
||||
{
|
||||
EditorGUILayout.BeginHorizontal();
|
||||
|
||||
var hasCustomSpace = ( setup & SplineInstantiate.Vector3Offset.Setup.HasCustomSpace ) != 0;
|
||||
EditorGUI.BeginChangeCheck();
|
||||
var space = m_Space.intValue < 1 ? "Spline Element" : m_Space.intValue == 1 ? "Spline Object" : "World";
|
||||
hasCustomSpace = EditorGUILayout.Toggle(new GUIContent("Override space", L10n.Tr("Override current space (" + space + ")")), hasCustomSpace);
|
||||
if(EditorGUI.EndChangeCheck())
|
||||
{
|
||||
if(hasCustomSpace)
|
||||
setup |= SplineInstantiate.Vector3Offset.Setup.HasCustomSpace;
|
||||
else
|
||||
setup &= ~SplineInstantiate.Vector3Offset.Setup.HasCustomSpace;
|
||||
|
||||
setupProperty.intValue = (int)setup;
|
||||
changed = true;
|
||||
}
|
||||
|
||||
var spaceProperty = offsetProperty.FindPropertyRelative("space");
|
||||
using(new EditorGUI.DisabledScope(!hasCustomSpace))
|
||||
{
|
||||
var type = (SplineInstantiate.OffsetSpace)spaceProperty.intValue;
|
||||
EditorGUI.BeginChangeCheck();
|
||||
type = (SplineInstantiate.OffsetSpace)EditorGUILayout.EnumPopup(type);
|
||||
if(EditorGUI.EndChangeCheck())
|
||||
{
|
||||
spaceProperty.intValue = (int)type;
|
||||
changed = true;
|
||||
}
|
||||
}
|
||||
|
||||
EditorGUILayout.EndHorizontal();
|
||||
|
||||
var minProperty = offsetProperty.FindPropertyRelative("min");
|
||||
var maxProperty = offsetProperty.FindPropertyRelative("max");
|
||||
|
||||
var minPropertyValue = minProperty.vector3Value;
|
||||
var maxPropertyValue = maxProperty.vector3Value;
|
||||
|
||||
float min, max;
|
||||
SerializedProperty randomProperty;
|
||||
for(int i = 0; i < 3; i++)
|
||||
{
|
||||
string label = i == 0 ? "X" : i == 1 ? "Y" : "Z";
|
||||
EditorGUILayout.BeginHorizontal();
|
||||
using(new LabelWidthScope(30f))
|
||||
EditorGUILayout.LabelField(label);
|
||||
randomProperty = offsetProperty.FindPropertyRelative("random"+label);
|
||||
GUILayout.FlexibleSpace();
|
||||
if(randomProperty.boolValue)
|
||||
{
|
||||
EditorGUI.BeginChangeCheck();
|
||||
using(new LabelWidthScope(30f))
|
||||
{
|
||||
min = EditorGUILayout.FloatField("from", minPropertyValue[i], GUILayout.MinWidth(95f), GUILayout.MaxWidth(95f));
|
||||
max = EditorGUILayout.FloatField(" to", maxPropertyValue[i], GUILayout.MinWidth(95f), GUILayout.MaxWidth(95f));
|
||||
}
|
||||
|
||||
if(EditorGUI.EndChangeCheck())
|
||||
{
|
||||
if(min > maxPropertyValue[i])
|
||||
maxPropertyValue[i] = min;
|
||||
if(max < minPropertyValue[i])
|
||||
minPropertyValue[i] = max;
|
||||
|
||||
minPropertyValue[i] = min;
|
||||
maxPropertyValue[i] = max;
|
||||
|
||||
minProperty.vector3Value = minPropertyValue;
|
||||
maxProperty.vector3Value = maxPropertyValue;
|
||||
changed = true;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
EditorGUI.BeginChangeCheck();
|
||||
using(new LabelWidthScope(30f))
|
||||
min = EditorGUILayout.FloatField("is ", minPropertyValue[i], GUILayout.MinWidth(193f), GUILayout.MaxWidth(193f));
|
||||
|
||||
if(EditorGUI.EndChangeCheck())
|
||||
{
|
||||
minPropertyValue[i] = min;
|
||||
if(min > maxPropertyValue[i])
|
||||
maxPropertyValue[i] = min;
|
||||
|
||||
minProperty.vector3Value = minPropertyValue;
|
||||
maxProperty.vector3Value = maxPropertyValue;
|
||||
changed = true;
|
||||
}
|
||||
}
|
||||
|
||||
EditorGUI.BeginChangeCheck();
|
||||
var isOffsetRandom = randomProperty.boolValue ? OffsetType.Random : OffsetType.Exact;
|
||||
using(new LabelWidthScope(0f))
|
||||
isOffsetRandom = (OffsetType)EditorGUILayout.EnumPopup(isOffsetRandom,GUILayout.MinWidth(100f), GUILayout.MaxWidth(200f));
|
||||
if(EditorGUI.EndChangeCheck())
|
||||
{
|
||||
randomProperty.boolValue = isOffsetRandom == OffsetType.Random;
|
||||
changed = true;
|
||||
}
|
||||
|
||||
EditorGUILayout.EndHorizontal();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return changed;
|
||||
}
|
||||
|
||||
bool DisplayOffsets()
|
||||
{
|
||||
HorizontalLine( Color.grey );
|
||||
var updateNeeded = DoOffsetProperties(m_PositionOffset, new GUIContent(k_PositionOffset, k_PositionOffsetTooltip), m_PositionFoldout, out m_PositionFoldout);
|
||||
updateNeeded |= DoOffsetProperties(m_RotationOffset, new GUIContent(k_RotationOffset, k_RotationOffsetTooltip), m_RotationFoldout, out m_RotationFoldout);
|
||||
updateNeeded |= DoOffsetProperties(m_ScaleOffset, new GUIContent(k_ScaleOffset, k_ScaleOffsetTooltip), m_ScaleFoldout, out m_ScaleFoldout);
|
||||
|
||||
return updateNeeded;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,11 @@
|
|||
fileFormatVersion: 2
|
||||
guid: c574a2f698921d1458f6dfba4b5e1229
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
|
|
@ -0,0 +1,77 @@
|
|||
using Unity.Mathematics;
|
||||
using UnityEngine.Splines;
|
||||
using UnityEngine;
|
||||
#if UNITY_2020_2_OR_NEWER
|
||||
using UnityEditor.EditorTools;
|
||||
#else
|
||||
using ToolManager = UnityEditor.EditorTools.EditorTools;
|
||||
#endif
|
||||
|
||||
namespace UnityEditor.Splines
|
||||
{
|
||||
static class SplineMenu
|
||||
{
|
||||
const string k_MenuPath = "GameObject/3D Object/Spline";
|
||||
|
||||
static GameObject CreateSplineGameObject(MenuCommand menuCommand, Spline spline = null)
|
||||
{
|
||||
var name = GameObjectUtility.GetUniqueNameForSibling(null, "Spline");
|
||||
var gameObject = ObjectFactory.CreateGameObject(name, typeof(SplineContainer));
|
||||
|
||||
#if ST_EXPOSE_GO_CREATE_PLACEMENT_LANDED
|
||||
ObjectFactory.PlaceGameObject(gameObject, menuCommand.context as GameObject);
|
||||
#else
|
||||
if (menuCommand.context is GameObject go)
|
||||
{
|
||||
Undo.RecordObject(gameObject.transform, "Re-parenting");
|
||||
gameObject.transform.SetParent(go.transform);
|
||||
}
|
||||
#endif
|
||||
|
||||
if (spline != null)
|
||||
{
|
||||
var container = gameObject.GetComponent<SplineContainer>();
|
||||
container.Spline = spline;
|
||||
Selection.activeGameObject = gameObject;
|
||||
}
|
||||
|
||||
return gameObject;
|
||||
}
|
||||
|
||||
const int k_MenuPriority = 200;
|
||||
|
||||
[MenuItem(k_MenuPath + "/Draw Spline Tool...", false, k_MenuPriority + 0)]
|
||||
static void CreateNewSpline(MenuCommand menuCommand)
|
||||
{
|
||||
var gameObject = CreateSplineGameObject(menuCommand);
|
||||
|
||||
gameObject.transform.localPosition = Vector3.zero;
|
||||
gameObject.transform.localRotation = Quaternion.identity;
|
||||
|
||||
Selection.activeObject = gameObject;
|
||||
ActiveEditorTracker.sharedTracker.RebuildIfNecessary();
|
||||
//Ensuring trackers are rebuilt before changing to SplineContext
|
||||
EditorApplication.delayCall += SetKnotPlacementTool;
|
||||
}
|
||||
|
||||
static void SetKnotPlacementTool()
|
||||
{
|
||||
ToolManager.SetActiveContext<SplineToolContext>();
|
||||
ToolManager.SetActiveTool<KnotPlacementTool>();
|
||||
}
|
||||
|
||||
|
||||
[MenuItem(k_MenuPath + "/Square", false, k_MenuPriority + 11)]
|
||||
static void CreateSquare(MenuCommand command)
|
||||
{
|
||||
CreateSplineGameObject(command, SplineFactory.CreateSquare(1f));
|
||||
}
|
||||
|
||||
[MenuItem(k_MenuPath + "/Circle", false, k_MenuPriority + 12)]
|
||||
static void CreateCircle(MenuCommand command)
|
||||
{
|
||||
// .36 is just an eye-balled approximation
|
||||
CreateSplineGameObject(command, SplineFactory.CreateRoundedSquare(1f, .36f));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,11 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 0b9129ed6a1eebe48b297bed06a9694e
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
|
|
@ -0,0 +1,31 @@
|
|||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
using UnityEngine.Splines;
|
||||
|
||||
namespace UnityEditor.Splines
|
||||
{
|
||||
[CustomPropertyDrawer(typeof(SplineType))]
|
||||
sealed class SplineTypeDrawer : PropertyDrawer
|
||||
{
|
||||
static readonly List<IEditableSpline> s_PathsBuffer = new List<IEditableSpline>();
|
||||
|
||||
public override void OnGUI(Rect position, SerializedProperty property, GUIContent label)
|
||||
{
|
||||
EditorGUI.BeginChangeCheck();
|
||||
var index = EditorGUI.PropertyField(position, property, label);
|
||||
if (EditorGUI.EndChangeCheck())
|
||||
{
|
||||
EditorApplication.delayCall += () =>
|
||||
{
|
||||
//When switching type we do a conversion pass to update the spline data to fit the new type
|
||||
SplineConversionUtility.UpdateEditableSplinesForTargets(property.serializedObject.targetObjects);
|
||||
EditableSplineUtility.GetSelectedSplines(property.serializedObject.targetObjects, s_PathsBuffer);
|
||||
foreach (var path in s_PathsBuffer)
|
||||
path.SetDirty();
|
||||
SplineConversionUtility.ApplyEditableSplinesIfDirty(property.serializedObject.targetObjects);
|
||||
SceneView.RepaintAll();
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,11 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 8f68fdecdda04574a80cf7990a3cebdb
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
|
|
@ -0,0 +1,222 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Reflection;
|
||||
using Unity.Mathematics;
|
||||
using UnityEngine;
|
||||
using UnityEngine.Splines;
|
||||
|
||||
namespace UnityEditor.Splines
|
||||
{
|
||||
sealed class SplineUIProxy : ScriptableObject
|
||||
{
|
||||
[NonSerialized] public SerializedObject SerializedObject;
|
||||
[SerializeReference] public IEditableSpline Spline;
|
||||
[NonSerialized] public int LastFrameCount;
|
||||
}
|
||||
|
||||
class SplineUIManager : ScriptableSingleton<SplineUIManager>
|
||||
{
|
||||
List<BezierKnot> m_KnotsBuffer = new List<BezierKnot>();
|
||||
Dictionary<Spline, SplineUIProxy> m_Proxies = new Dictionary<Spline, SplineUIProxy>();
|
||||
List<Spline> m_ProxiesToDestroy = new List<Spline>();
|
||||
|
||||
void OnEnable()
|
||||
{
|
||||
Selection.selectionChanged += VerifyProxiesAreValid;
|
||||
#if UNITY_EDITOR
|
||||
EditorSplineUtility.afterSplineWasModified += OnSplineUpdated;
|
||||
Undo.undoRedoPerformed += ClearProxies;
|
||||
#endif
|
||||
}
|
||||
|
||||
void OnDisable()
|
||||
{
|
||||
Selection.selectionChanged -= VerifyProxiesAreValid;
|
||||
#if UNITY_EDITOR
|
||||
EditorSplineUtility.afterSplineWasModified -= OnSplineUpdated;
|
||||
Undo.undoRedoPerformed -= ClearProxies;
|
||||
#endif
|
||||
}
|
||||
|
||||
public SplineUIProxy GetProxyFromProperty(SerializedProperty splineProperty)
|
||||
{
|
||||
var targetSpline = GetSplineValue(splineProperty.serializedObject.targetObject, splineProperty.propertyPath);
|
||||
|
||||
if (targetSpline == null || !m_Proxies.TryGetValue(targetSpline, out SplineUIProxy proxy))
|
||||
{
|
||||
proxy = ScriptableObject.CreateInstance<SplineUIProxy>();
|
||||
var editType = splineProperty.FindPropertyRelative("m_EditModeType");
|
||||
|
||||
if (splineProperty.hasMultipleDifferentValues)
|
||||
{
|
||||
proxy.Spline = null;
|
||||
}
|
||||
else
|
||||
{
|
||||
IEditableSpline spline = EditableSplineUtility.CreatePathOfType((SplineType) editType.enumValueIndex);
|
||||
spline.closed = splineProperty.FindPropertyRelative("m_Closed").boolValue;
|
||||
|
||||
var knotsProperty = splineProperty.FindPropertyRelative("m_Knots");
|
||||
m_KnotsBuffer.Clear();
|
||||
for (int i = 0; i < knotsProperty.arraySize; ++i)
|
||||
{
|
||||
var knot = knotsProperty.GetArrayElementAtIndex(i);
|
||||
m_KnotsBuffer.Add(new BezierKnot(
|
||||
GetFloat3FromProperty(knot.FindPropertyRelative("Position")),
|
||||
GetFloat3FromProperty(knot.FindPropertyRelative("TangentIn")),
|
||||
GetFloat3FromProperty(knot.FindPropertyRelative("TangentOut")),
|
||||
GetQuaternionFromProperty(knot.FindPropertyRelative("Rotation"))));
|
||||
}
|
||||
|
||||
spline.FromBezier(m_KnotsBuffer);
|
||||
proxy.Spline = spline;
|
||||
|
||||
var conversionData = spline;
|
||||
conversionData.isDirty = false;
|
||||
conversionData.ValidateData();
|
||||
}
|
||||
|
||||
proxy.SerializedObject = new SerializedObject(proxy);
|
||||
if(targetSpline != null)
|
||||
m_Proxies.Add(targetSpline, proxy);
|
||||
}
|
||||
|
||||
proxy.LastFrameCount = Time.frameCount;
|
||||
return proxy;
|
||||
}
|
||||
|
||||
public void ApplyProxyToProperty(SplineUIProxy proxy, SerializedProperty property)
|
||||
{
|
||||
proxy.SerializedObject.ApplyModifiedPropertiesWithoutUndo();
|
||||
|
||||
var path = proxy.Spline;
|
||||
path.ValidateData();
|
||||
property.FindPropertyRelative("m_EditModeType").enumValueIndex = (int)EditableSplineUtility.GetSplineType(path);
|
||||
property.FindPropertyRelative("m_Closed").boolValue = proxy.SerializedObject.FindProperty("Spline.m_Closed").boolValue;
|
||||
var knotsProperty = property.FindPropertyRelative("m_Knots");
|
||||
|
||||
m_KnotsBuffer.Clear();
|
||||
path.ToBezier(m_KnotsBuffer);
|
||||
knotsProperty.arraySize = m_KnotsBuffer.Count;
|
||||
for (int i = 0; i < m_KnotsBuffer.Count; ++i)
|
||||
{
|
||||
var knotProperty = knotsProperty.GetArrayElementAtIndex(i);
|
||||
var knot = m_KnotsBuffer[i];
|
||||
SetFloat3Property(knotProperty.FindPropertyRelative("Position"), knot.Position);
|
||||
SetFloat3Property(knotProperty.FindPropertyRelative("TangentIn"), knot.TangentIn);
|
||||
SetFloat3Property(knotProperty.FindPropertyRelative("TangentOut"), knot.TangentOut);
|
||||
|
||||
if(math.length(knot.Rotation.value) == 0)
|
||||
{
|
||||
//Update knot rotation with a valid value
|
||||
knot.Rotation = quaternion.identity;
|
||||
m_KnotsBuffer[i] = knot;
|
||||
//Updating proxy
|
||||
path.FromBezier(m_KnotsBuffer);
|
||||
}
|
||||
|
||||
SetQuaternionFromProperty(knotProperty.FindPropertyRelative("Rotation"), knot.Rotation);
|
||||
}
|
||||
|
||||
var targetSpline = GetSplineValue(property.serializedObject.targetObject, property.propertyPath);
|
||||
targetSpline?.SetDirty();
|
||||
property.FindPropertyRelative("m_Length").floatValue = -1;
|
||||
|
||||
EditorApplication.delayCall += () => SplineConversionUtility.UpdateEditableSplinesForTarget(property.serializedObject.targetObject);
|
||||
}
|
||||
|
||||
Spline GetSplineValue(UnityEngine.Object targetObject, string propertyPath)
|
||||
{
|
||||
if(targetObject == null)
|
||||
return null;
|
||||
|
||||
var fieldInfo = targetObject.GetType().GetField(propertyPath, BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public);
|
||||
return (Spline)fieldInfo?.GetValue(targetObject);;
|
||||
}
|
||||
|
||||
static float3 GetFloat3FromProperty(SerializedProperty property)
|
||||
{
|
||||
return new float3(
|
||||
property.FindPropertyRelative("x").floatValue,
|
||||
property.FindPropertyRelative("y").floatValue,
|
||||
property.FindPropertyRelative("z").floatValue);
|
||||
}
|
||||
|
||||
static void SetFloat3Property(SerializedProperty property, float3 value)
|
||||
{
|
||||
property.FindPropertyRelative("x").floatValue = value.x;
|
||||
property.FindPropertyRelative("y").floatValue = value.y;
|
||||
property.FindPropertyRelative("z").floatValue = value.z;
|
||||
}
|
||||
|
||||
static quaternion GetQuaternionFromProperty(SerializedProperty property)
|
||||
{
|
||||
return new quaternion(
|
||||
property.FindPropertyRelative("value.x").floatValue,
|
||||
property.FindPropertyRelative("value.y").floatValue,
|
||||
property.FindPropertyRelative("value.z").floatValue,
|
||||
property.FindPropertyRelative("value.w").floatValue);
|
||||
}
|
||||
|
||||
static void SetQuaternionFromProperty(SerializedProperty property, quaternion quaternion)
|
||||
{
|
||||
property.FindPropertyRelative("value.x").floatValue = quaternion.value.x;
|
||||
property.FindPropertyRelative("value.y").floatValue = quaternion.value.y;
|
||||
property.FindPropertyRelative("value.z").floatValue = quaternion.value.z;
|
||||
property.FindPropertyRelative("value.w").floatValue = quaternion.value.w;
|
||||
}
|
||||
|
||||
void ClearProxies()
|
||||
{
|
||||
foreach(var kvp in m_Proxies)
|
||||
{
|
||||
//Forcing inspector update on spline changes in the scene view
|
||||
DestroyImmediate(kvp.Value);
|
||||
}
|
||||
m_Proxies.Clear();
|
||||
}
|
||||
|
||||
void OnSplineUpdated(Spline spline)
|
||||
{
|
||||
if(m_Proxies.TryGetValue(spline, out SplineUIProxy proxy))
|
||||
{
|
||||
//Forcing inspector update on spline changes in the scene view
|
||||
DestroyImmediate(proxy);
|
||||
m_Proxies.Remove(spline);
|
||||
}
|
||||
}
|
||||
|
||||
void VerifyProxiesAreValid()
|
||||
{
|
||||
if(m_Proxies.Count == 0)
|
||||
return;
|
||||
|
||||
m_ProxiesToDestroy.Clear();
|
||||
var currentTime = Time.frameCount;
|
||||
|
||||
const int frameCountBeforeProxyRemoval = 5;
|
||||
foreach(var kvp in m_Proxies)
|
||||
{
|
||||
if(currentTime - kvp.Value.LastFrameCount > frameCountBeforeProxyRemoval)
|
||||
m_ProxiesToDestroy.Add(kvp.Key);
|
||||
}
|
||||
|
||||
foreach(var keyToDestroy in m_ProxiesToDestroy)
|
||||
{
|
||||
if (m_Proxies.TryGetValue(keyToDestroy, out SplineUIProxy proxy))
|
||||
{
|
||||
DestroyImmediate(proxy);
|
||||
m_Proxies.Remove(keyToDestroy);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
internal static Rect ReserveSpace(float height, ref Rect total)
|
||||
{
|
||||
Rect current = total;
|
||||
current.height = height;
|
||||
total.y += height;
|
||||
return current;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,11 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 1bc116f398eb3ec44b86d58c5e21da58
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
Loading…
Add table
Add a link
Reference in a new issue