initial commit

This commit is contained in:
Jo 2025-01-07 02:06:59 +01:00
parent 6715289efe
commit 788c3389af
37645 changed files with 2526849 additions and 80 deletions

View file

@ -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);
}
}
}

View file

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: d25ad062b459643449c2494b5237dc00
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View file

@ -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;
}
}
}

View file

@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: a0357b2fb04a410d8dc62740f4fe4cbb
timeCreated: 1618421082

View file

@ -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;
}
}
}

View file

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 29ae16933bbb94210b13408d3f86a072
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View file

@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: eaa3cd6309cba3244b2a227f3e8e7eb3
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View file

@ -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));
}
}
}

View file

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: e1dec5e4d3c4dd84aab4dfbb3c8e6b48
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View file

@ -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;
}
}
}

View file

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 95245c7aa2899544b8fe0daa4947e0be
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View file

@ -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;
}
}
}

View file

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: b5d8a953f9ef52441bd58f4c1e5853a0
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View file

@ -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));
}
}
}

View file

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 3018d9f76949a7f4c8ad97e48caff165
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View file

@ -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;
}
}
}

View file

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 37aa8727811c9e04785d9c0e4cafc5d3
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View file

@ -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);
}
}
}

View file

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: d01d6b9007f24984692322edf5415826
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View file

@ -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);
}
}
}

View file

@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: c74ddef7e6e74f529a51b5ad71552fbd
timeCreated: 1637605071

View file

@ -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;
}
}
}

View file

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 9103eb7dcd041455886438d75625d19c
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View file

@ -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();
}
}
}

View file

@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 97db6026b4154fbf97b957dd2137810d
timeCreated: 1634919075

View file

@ -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);
}
}
}

View file

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: bea6b12ab5854760b485bc57408b9c3e
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View file

@ -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();
}
}
}

View file

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: db00a1694ad7319449442045b8557a93
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View file

@ -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;
}
}
}

View file

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 5bcefd1f1914dff4dae9fe9c4250b8da
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View file

@ -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--;
}
}
}
}

View file

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 07e0a2238ade23d4e8de8e43b83cf1ad
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View file

@ -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;
}
}
}

View file

@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 8ae5aa24d97048439e76e9392765c364
timeCreated: 1637689699

View file

@ -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);
}
}
}

View file

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 5737da4b5db2be640adcc9bb3360aa91
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View file

@ -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;
}
}

View file

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: c574a2f698921d1458f6dfba4b5e1229
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View file

@ -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));
}
}
}

View file

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 0b9129ed6a1eebe48b297bed06a9694e
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View file

@ -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();
};
}
}
}
}

View file

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 8f68fdecdda04574a80cf7990a3cebdb
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View file

@ -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;
}
}
}

View file

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 1bc116f398eb3ec44b86d58c5e21da58
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant: