initial commit
|
@ -0,0 +1,4 @@
|
|||
using System.Runtime.CompilerServices;
|
||||
|
||||
[assembly: InternalsVisibleTo("Unity.Splines.Editor.Tests")]
|
||||
[assembly: InternalsVisibleTo("Unity.Splines.Editor.Debug")]
|
|
@ -0,0 +1,11 @@
|
|||
fileFormatVersion: 2
|
||||
guid: c854ca5f1d7eed04aa92565d35431e96
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
|
@ -0,0 +1,8 @@
|
|||
fileFormatVersion: 2
|
||||
guid: e88477d7eedfb4ca8b5d8912575efa76
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
|
@ -0,0 +1,230 @@
|
|||
using System;
|
||||
using UnityEngine.Splines;
|
||||
using UnityEditor.UIElements;
|
||||
using UnityEngine;
|
||||
using UnityEngine.UIElements;
|
||||
|
||||
namespace UnityEditor.Splines
|
||||
{
|
||||
[CustomEditor(typeof(SplineAnimate))]
|
||||
[CanEditMultipleObjects]
|
||||
class SplineAnimateEditor : UnityEditor.Editor
|
||||
{
|
||||
VisualElement m_Root;
|
||||
Button m_PlayButton;
|
||||
Slider m_ProgressSlider;
|
||||
FloatField m_ElapsedTimeField;
|
||||
EnumField m_ObjectForwardField;
|
||||
EnumField m_ObjectUpField;
|
||||
|
||||
SerializedProperty m_TargetProperty;
|
||||
SerializedProperty m_MethodProperty;
|
||||
SerializedProperty m_DurationProperty;
|
||||
SerializedProperty m_SpeedProperty;
|
||||
SerializedProperty m_ObjectForwardProperty;
|
||||
SerializedProperty m_ObjectUpProperty;
|
||||
SerializedObject m_TransformSO;
|
||||
|
||||
SplineAnimate m_SplineAnimate;
|
||||
|
||||
const string k_UxmlPath = "Packages/com.unity.splines/Editor/Resources/UI/UXML/splineanimate-inspector.uxml";
|
||||
static VisualTreeAsset s_TreeAsset;
|
||||
static StyleSheet s_ThemeStyleSheet;
|
||||
|
||||
void OnEnable()
|
||||
{
|
||||
m_SplineAnimate = target as SplineAnimate;
|
||||
m_SplineAnimate.onUpdated += OnSplineAnimateUpdated;
|
||||
m_TargetProperty = serializedObject.FindProperty("m_Target");
|
||||
m_MethodProperty = serializedObject.FindProperty("m_Method");
|
||||
m_DurationProperty = serializedObject.FindProperty("m_Duration");
|
||||
m_SpeedProperty = serializedObject.FindProperty("m_MaxSpeed");
|
||||
m_ObjectForwardProperty = serializedObject.FindProperty("m_ObjectForwardAxis");
|
||||
m_ObjectUpProperty = serializedObject.FindProperty("m_ObjectUpAxis");
|
||||
m_TransformSO = new SerializedObject(m_SplineAnimate.transform);
|
||||
|
||||
EditorApplication.update += OnEditorUpdate;
|
||||
}
|
||||
|
||||
void OnDisable()
|
||||
{
|
||||
if (m_SplineAnimate != null && m_SplineAnimate.splineContainer != null)
|
||||
{
|
||||
if (!EditorApplication.isPlaying)
|
||||
m_SplineAnimate.Restart(false);
|
||||
|
||||
m_SplineAnimate.onUpdated -= OnSplineAnimateUpdated;
|
||||
}
|
||||
|
||||
EditorApplication.update -= OnEditorUpdate;
|
||||
}
|
||||
|
||||
void OnEditorUpdate()
|
||||
{
|
||||
if (m_SplineAnimate == null || m_SplineAnimate.splineContainer == null)
|
||||
return;
|
||||
|
||||
if (m_SplineAnimate.isPlaying && !EditorApplication.isPlaying)
|
||||
m_SplineAnimate.Update();
|
||||
|
||||
RefreshProgressFields();
|
||||
}
|
||||
|
||||
public override VisualElement CreateInspectorGUI()
|
||||
{
|
||||
m_Root = new VisualElement();
|
||||
|
||||
if (s_TreeAsset == null)
|
||||
s_TreeAsset = (VisualTreeAsset)AssetDatabase.LoadAssetAtPath(k_UxmlPath, typeof(VisualTreeAsset));
|
||||
s_TreeAsset.CloneTree(m_Root);
|
||||
|
||||
if (s_ThemeStyleSheet == null)
|
||||
s_ThemeStyleSheet = AssetDatabase.LoadAssetAtPath<StyleSheet>($"Packages/com.unity.splines/Editor/Stylesheets/SplineAnimateInspector{(EditorGUIUtility.isProSkin ? "Dark" : "Light")}.uss");
|
||||
|
||||
m_Root.styleSheets.Add(s_ThemeStyleSheet);
|
||||
|
||||
var splineField = m_Root.Q<PropertyField>("spline-container");
|
||||
splineField.RegisterValueChangeCallback((_) => { m_SplineAnimate.splineContainer = m_TargetProperty.objectReferenceValue as SplineContainer; });
|
||||
|
||||
var methodField = m_Root.Q<PropertyField>("method");
|
||||
methodField.RegisterValueChangeCallback((_) => { RefreshMethodParamFields((SplineAnimate.Method)m_MethodProperty.enumValueIndex); });
|
||||
RefreshMethodParamFields((SplineAnimate.Method)m_MethodProperty.enumValueIndex);
|
||||
|
||||
var durationField = m_Root.Q<PropertyField>("duration");
|
||||
durationField.RegisterValueChangeCallback((_) => { m_SplineAnimate.duration = m_DurationProperty.floatValue; });
|
||||
|
||||
var speedField = m_Root.Q<PropertyField>("max-speed");
|
||||
speedField.RegisterValueChangeCallback((_) => { m_SplineAnimate.maxSpeed = m_SpeedProperty.floatValue; });
|
||||
|
||||
m_ObjectForwardField = m_Root.Q<EnumField>("object-forward");
|
||||
m_ObjectForwardField.RegisterValueChangedCallback((evt) => OnObjectAxisFieldChange(evt, m_ObjectForwardProperty, m_ObjectUpProperty));
|
||||
|
||||
m_ObjectUpField = m_Root.Q<EnumField>("object-up");
|
||||
m_ObjectUpField.RegisterValueChangedCallback((evt) => OnObjectAxisFieldChange(evt, m_ObjectUpProperty, m_ObjectForwardProperty));
|
||||
|
||||
var playButton = m_Root.Q<Button>("play");
|
||||
playButton.clicked += OnPlayClicked;
|
||||
|
||||
var pauseButton = m_Root.Q<Button>("pause");
|
||||
pauseButton.clicked += OnPauseClicked;
|
||||
|
||||
var resetButton = m_Root.Q<Button>("reset");
|
||||
resetButton.clicked += OnResetClicked;
|
||||
|
||||
m_ProgressSlider = m_Root.Q<Slider>("normalized-progress");
|
||||
m_ProgressSlider.RegisterValueChangedCallback((evt) => OnProgressSliderChange(evt.newValue));
|
||||
|
||||
m_ElapsedTimeField = m_Root.Q<FloatField>("elapsed-time");
|
||||
m_ElapsedTimeField.RegisterValueChangedCallback((evt) => OnElapsedTimeFieldChange(evt.newValue));
|
||||
|
||||
return m_Root;
|
||||
}
|
||||
|
||||
void RefreshMethodParamFields(SplineAnimate.Method method)
|
||||
{
|
||||
var durationField = m_Root.Q<PropertyField>("duration");
|
||||
var maxSpeedField = m_Root.Q<PropertyField>("max-speed");
|
||||
|
||||
if (method == (int) SplineAnimate.Method.Time)
|
||||
{
|
||||
durationField.style.display = DisplayStyle.Flex;
|
||||
maxSpeedField.style.display = DisplayStyle.None;
|
||||
}
|
||||
else
|
||||
{
|
||||
durationField.style.display = DisplayStyle.None;
|
||||
maxSpeedField.style.display = DisplayStyle.Flex;
|
||||
}
|
||||
}
|
||||
|
||||
void RefreshProgressFields()
|
||||
{
|
||||
if (m_ProgressSlider == null || m_ElapsedTimeField == null)
|
||||
return;
|
||||
|
||||
m_ProgressSlider.SetValueWithoutNotify(m_SplineAnimate.GetLoopInterpolation());
|
||||
m_ElapsedTimeField.SetValueWithoutNotify(m_SplineAnimate.elapsedTime);
|
||||
}
|
||||
|
||||
void OnProgressSliderChange(float progress)
|
||||
{
|
||||
m_SplineAnimate.Pause();
|
||||
m_SplineAnimate.normalizedTime = progress;
|
||||
|
||||
RefreshProgressFields();
|
||||
}
|
||||
|
||||
void OnElapsedTimeFieldChange(float elapsedTime)
|
||||
{
|
||||
m_SplineAnimate.Pause();
|
||||
m_SplineAnimate.elapsedTime = elapsedTime;
|
||||
|
||||
RefreshProgressFields();
|
||||
}
|
||||
|
||||
void OnObjectAxisFieldChange(ChangeEvent<Enum> changeEvent, SerializedProperty axisProp, SerializedProperty otherAxisProp)
|
||||
{
|
||||
if (changeEvent.newValue == null)
|
||||
return;
|
||||
|
||||
var newValue = (SplineAnimate.AlignAxis)changeEvent.newValue;
|
||||
var previousValue = (SplineAnimate.AlignAxis)changeEvent.previousValue;
|
||||
|
||||
// Swap axes if the picked value matches that of the other axis field
|
||||
if (newValue == (SplineAnimate.AlignAxis)otherAxisProp.enumValueIndex)
|
||||
{
|
||||
otherAxisProp.enumValueIndex = (int)previousValue;
|
||||
serializedObject.ApplyModifiedProperties();
|
||||
}
|
||||
// Prevent the user from configuring object's forward and up as opposite axes
|
||||
if (((int) newValue) % 3 == otherAxisProp.enumValueIndex % 3)
|
||||
{
|
||||
axisProp.enumValueIndex = (int)previousValue;
|
||||
serializedObject.ApplyModifiedPropertiesWithoutUndo();
|
||||
}
|
||||
}
|
||||
|
||||
void OnPlayClicked()
|
||||
{
|
||||
if (!m_SplineAnimate.isPlaying)
|
||||
{
|
||||
if (m_SplineAnimate.normalizedTime == 1f)
|
||||
m_SplineAnimate.Restart(true);
|
||||
else
|
||||
m_SplineAnimate.Play();
|
||||
}
|
||||
}
|
||||
|
||||
void OnPauseClicked()
|
||||
{
|
||||
m_SplineAnimate.Pause();
|
||||
}
|
||||
|
||||
void OnResetClicked()
|
||||
{
|
||||
m_SplineAnimate.Restart(false);
|
||||
RefreshProgressFields();
|
||||
}
|
||||
|
||||
void OnSplineAnimateUpdated(Vector3 position, Quaternion rotation)
|
||||
{
|
||||
if (!EditorApplication.isPlaying)
|
||||
{
|
||||
m_TransformSO.Update();
|
||||
|
||||
var localPosition = position;
|
||||
var localRotation = rotation;
|
||||
if (m_SplineAnimate.transform.parent != null)
|
||||
{
|
||||
localPosition = m_SplineAnimate.transform.parent.worldToLocalMatrix.MultiplyPoint3x4(position);
|
||||
localRotation = Quaternion.Inverse(m_SplineAnimate.transform.parent.rotation) * localRotation;
|
||||
}
|
||||
|
||||
m_TransformSO.FindProperty("m_LocalPosition").vector3Value = localPosition;
|
||||
m_TransformSO.FindProperty("m_LocalRotation").quaternionValue = localRotation;
|
||||
|
||||
m_TransformSO.ApplyModifiedProperties();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 7292ebc78c5d44eaa80048f8fd7d5fe6
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
|
@ -0,0 +1,51 @@
|
|||
using UnityEngine;
|
||||
using UnityEngine.Splines;
|
||||
|
||||
namespace UnityEditor.Splines
|
||||
{
|
||||
[CustomEditor(typeof(SplineContainer))]
|
||||
class SplineContainerEditor : UnityEditor.Editor
|
||||
{
|
||||
SerializedProperty m_SplineProperty;
|
||||
|
||||
static GUIStyle s_HelpLabelStyle;
|
||||
static GUIContent s_HelpLabelContent;
|
||||
static GUIContent s_HelpLabelContentIcon;
|
||||
|
||||
const string k_ComponentMessage = "Use the <b>Spline Edit Mode</b> in the <b>Scene Tools Overlay</b> to edit this Spline.";
|
||||
const string k_HelpBoxIconPath = "Packages/com.unity.splines/Editor/Resources/Icons/SplineContext.png";
|
||||
|
||||
public void OnEnable()
|
||||
{
|
||||
m_SplineProperty = serializedObject.FindProperty("m_Spline");
|
||||
|
||||
if(s_HelpLabelContent == null)
|
||||
s_HelpLabelContent = EditorGUIUtility.TrTextContent(k_ComponentMessage);
|
||||
|
||||
if(s_HelpLabelContentIcon == null)
|
||||
s_HelpLabelContentIcon = EditorGUIUtility.TrIconContent(k_HelpBoxIconPath);
|
||||
}
|
||||
|
||||
public override void OnInspectorGUI()
|
||||
{
|
||||
serializedObject.Update();
|
||||
|
||||
if (s_HelpLabelStyle == null)
|
||||
{
|
||||
s_HelpLabelStyle = new GUIStyle(EditorStyles.helpBox);
|
||||
s_HelpLabelStyle.padding = new RectOffset(10, 10, 10, 10);
|
||||
}
|
||||
|
||||
EditorGUILayout.BeginHorizontal(s_HelpLabelStyle);
|
||||
EditorGUILayout.LabelField(s_HelpLabelContentIcon, GUILayout.Width(20), GUILayout.ExpandHeight(true));
|
||||
EditorGUILayout.BeginVertical();
|
||||
EditorGUILayout.LabelField(s_HelpLabelContent, new GUIStyle(EditorStyles.label){richText = true, wordWrap = true});
|
||||
EditorGUILayout.EndVertical();
|
||||
EditorGUILayout.EndHorizontal();
|
||||
|
||||
EditorGUILayout.PropertyField(m_SplineProperty);
|
||||
|
||||
serializedObject.ApplyModifiedProperties();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 9b17d23c06da64c139386c53a0b59281
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
|
@ -0,0 +1,8 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 6042ea3ccf1fb5c45b533a9d67e956b7
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
|
@ -0,0 +1,117 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
|
||||
namespace UnityEditor.Splines
|
||||
{
|
||||
static class PathGUIControls
|
||||
{
|
||||
internal static readonly List<Vector3> pointsBuffer = new List<Vector3>();
|
||||
|
||||
public static void MultiEditVector3Field(GUIContent label, IList<Vector3> points)
|
||||
{
|
||||
if (!EditorGUIUtility.wideMode)
|
||||
GUILayout.Label(label);
|
||||
|
||||
EditorGUILayout.BeginHorizontal();
|
||||
|
||||
if (EditorGUIUtility.wideMode)
|
||||
EditorGUILayout.PrefixLabel(label);
|
||||
|
||||
var prevMixedValue = EditorGUI.showMixedValue;
|
||||
var result = GetMultiEditValue(points, out Vector3 value);
|
||||
|
||||
EditorGUI.showMixedValue = result.xMixed;
|
||||
EditorGUI.BeginChangeCheck();
|
||||
GUILayout.Label("X");
|
||||
float x = EditorGUILayout.FloatField(value.x);
|
||||
if (EditorGUI.EndChangeCheck())
|
||||
{
|
||||
for (int i = 0; i < points.Count; ++i)
|
||||
{
|
||||
var oldValue = points[i];
|
||||
oldValue.x = x;
|
||||
points[i] = oldValue;
|
||||
}
|
||||
|
||||
GUI.changed = true;
|
||||
}
|
||||
|
||||
EditorGUI.showMixedValue = result.yMixed;
|
||||
EditorGUI.BeginChangeCheck();
|
||||
GUILayout.Label("Y");
|
||||
float y = EditorGUILayout.FloatField(value.y);
|
||||
if (EditorGUI.EndChangeCheck())
|
||||
{
|
||||
for (int i = 0; i < points.Count; ++i)
|
||||
{
|
||||
var oldValue = points[i];
|
||||
oldValue.y = y;
|
||||
points[i] = oldValue;
|
||||
}
|
||||
|
||||
GUI.changed = true;
|
||||
}
|
||||
|
||||
EditorGUI.showMixedValue = result.zMixed;
|
||||
EditorGUI.BeginChangeCheck();
|
||||
GUILayout.Label("Z");
|
||||
float z = EditorGUILayout.FloatField(value.z);
|
||||
if (EditorGUI.EndChangeCheck())
|
||||
{
|
||||
for (int i = 0; i < points.Count; ++i)
|
||||
{
|
||||
var oldValue = points[i];
|
||||
oldValue.z = z;
|
||||
points[i] = oldValue;
|
||||
}
|
||||
|
||||
GUI.changed = true;
|
||||
}
|
||||
EditorGUILayout.EndHorizontal();
|
||||
|
||||
EditorGUI.showMixedValue = prevMixedValue;
|
||||
}
|
||||
|
||||
//Returns true if the value has multiple values
|
||||
static (bool xMixed, bool yMixed, bool zMixed) GetMultiEditValue(IList<Vector3> points, out Vector3 value)
|
||||
{
|
||||
value = points[0];
|
||||
bool xMixed = false;
|
||||
bool yMixed = false;
|
||||
bool zMixed = false;
|
||||
for (int i = 1; i < points.Count; ++i)
|
||||
{
|
||||
Vector3 point = points[i];
|
||||
xMixed |= !Mathf.Approximately(value.x, point.x);
|
||||
yMixed |= !Mathf.Approximately(value.y, point.y);
|
||||
zMixed |= !Mathf.Approximately(value.z, point.z);
|
||||
|
||||
if (xMixed && yMixed && zMixed)
|
||||
break;
|
||||
}
|
||||
|
||||
return (xMixed, yMixed, zMixed);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get the mixed value for a field
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The target type</typeparam>
|
||||
/// <param name="values">A list of all the values</param>
|
||||
/// <param name="value">The value that should be used for the field</param>
|
||||
/// <returns>True if has mixed values</returns>
|
||||
public static bool GetMixedValue<T>(IList<T> values, out T value)
|
||||
where T : IEquatable<T>
|
||||
{
|
||||
value = values[0];
|
||||
for (int i = 1; i < values.Count; ++i)
|
||||
{
|
||||
if (!value.Equals(values[i]))
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
fileFormatVersion: 2
|
||||
guid: d5e51246e7862284486a935085c13937
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
|
@ -0,0 +1,260 @@
|
|||
using System;
|
||||
using System.Reflection;
|
||||
using Unity.Mathematics;
|
||||
using UnityEngine;
|
||||
using UnityEngine.Splines;
|
||||
|
||||
namespace UnityEditor.Splines
|
||||
{
|
||||
static class SplineDataHandlesDrawer
|
||||
{
|
||||
/// <summary>
|
||||
/// LabelType used to define if label must be displayed along the handle and how it should be formatted
|
||||
/// </summary>
|
||||
internal enum LabelType
|
||||
{
|
||||
None,
|
||||
Index,
|
||||
Time
|
||||
};
|
||||
|
||||
const float k_HandleSize = 0.15f;
|
||||
|
||||
const int k_PickRes = 2;
|
||||
|
||||
internal static void InitCustomHandles<T>(
|
||||
SplineData<T> splineData,
|
||||
ISplineDataHandle drawerInstance)
|
||||
{
|
||||
var ids = new int[splineData.Count];
|
||||
for(int kfIndex = 0; kfIndex < splineData.Count; kfIndex++)
|
||||
ids[kfIndex] = GUIUtility.GetControlID(FocusType.Passive);
|
||||
|
||||
( (SplineDataHandle<T>)drawerInstance ).m_ControlIDs = ids;
|
||||
}
|
||||
|
||||
internal static void DrawCustomHandles<T>(
|
||||
SplineData<T> splineData,
|
||||
Component component,
|
||||
Spline spline,
|
||||
Matrix4x4 localToWorld,
|
||||
Color color,
|
||||
ISplineDataHandle drawerInstance,
|
||||
MethodInfo splineDataDrawMethodInfo,
|
||||
MethodInfo keyframeDrawMethodInfo)
|
||||
{
|
||||
if(splineData.Count == 0)
|
||||
return;
|
||||
|
||||
Undo.RecordObject(component, "Modifying Spline Data Points");
|
||||
|
||||
//Invoke if existing the custom drawer for the whole spline
|
||||
splineDataDrawMethodInfo?.Invoke(drawerInstance,
|
||||
new object[]
|
||||
{
|
||||
splineData,
|
||||
spline,
|
||||
localToWorld,
|
||||
color
|
||||
});
|
||||
|
||||
if (keyframeDrawMethodInfo == null)
|
||||
return;
|
||||
|
||||
var native = new NativeSpline(spline, localToWorld);
|
||||
var ids = ( (SplineDataHandle<T>)drawerInstance ).controlIDs;
|
||||
|
||||
for(int splineDataIndex = 0; splineDataIndex < splineData.Count; splineDataIndex++)
|
||||
{
|
||||
var keyframe = splineData[splineDataIndex];
|
||||
var normalizedT = SplineUtility.GetNormalizedInterpolation(native, keyframe.Index, splineData.PathIndexUnit);
|
||||
|
||||
native.Evaluate(normalizedT, out float3 dataPosition, out float3 dataDirection, out float3 dataUp);
|
||||
using(new Handles.DrawingScope(color))
|
||||
{
|
||||
//Invoke if existing the custom drawer for each keyframe
|
||||
keyframeDrawMethodInfo?.Invoke(drawerInstance,
|
||||
new object[]
|
||||
{
|
||||
ids[splineDataIndex],
|
||||
(Vector3)dataPosition,
|
||||
(Vector3)dataDirection,
|
||||
(Vector3)dataUp,
|
||||
splineData,
|
||||
splineDataIndex
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
native.Dispose();
|
||||
}
|
||||
|
||||
internal static void DrawSplineDataHandles<T>(
|
||||
SplineData<T> splineData,
|
||||
Component component,
|
||||
Spline spline,
|
||||
Matrix4x4 localToWorld,
|
||||
Color color,
|
||||
LabelType labelType)
|
||||
{
|
||||
Undo.RecordObject(component, "Modifying Spline Data Points");
|
||||
using var native = new NativeSpline(spline, localToWorld);
|
||||
|
||||
using(new Handles.DrawingScope(color))
|
||||
{
|
||||
for(int keyframeIndex = 0; keyframeIndex < splineData.Count; keyframeIndex++)
|
||||
{
|
||||
var keyframe = splineData[keyframeIndex];
|
||||
var inUse = SplineDataHandle(splineData, keyframe, native, labelType, keyframeIndex, out float time);
|
||||
|
||||
if(inUse)
|
||||
{
|
||||
keyframe.Index = time;
|
||||
splineData.SetDataPointNoSort(keyframeIndex, keyframe);
|
||||
|
||||
//OnMouseUp event
|
||||
if(GUIUtility.hotControl == 0)
|
||||
splineData.SortIfNecessary();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
internal static bool SplineDataHandle<T>(
|
||||
SplineData<T> splineData,
|
||||
IDataPoint dataPoint,
|
||||
NativeSpline spline,
|
||||
LabelType labelType,
|
||||
int keyframeIndex,
|
||||
out float newTime)
|
||||
{
|
||||
int id = GUIUtility.GetControlID(FocusType.Passive);
|
||||
Event evt = Event.current;
|
||||
EventType eventType = evt.GetTypeForControl(id);
|
||||
|
||||
var normalizedT = SplineUtility.GetNormalizedInterpolation(spline, dataPoint.Index, splineData.PathIndexUnit);
|
||||
var dataPosition = SplineUtility.EvaluatePosition(spline, normalizedT);
|
||||
|
||||
switch (eventType)
|
||||
{
|
||||
case EventType.Layout:
|
||||
{
|
||||
if(!Tools.viewToolActive)
|
||||
{
|
||||
var dist = HandleUtility.DistanceToCircle(dataPosition, k_HandleSize * HandleUtility.GetHandleSize(dataPosition));
|
||||
HandleUtility.AddControl(id, dist);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case EventType.Repaint:
|
||||
DrawSplineDataHandle(dataPosition, id);
|
||||
DrawSplineDataLabel(dataPosition, labelType, dataPoint, keyframeIndex);
|
||||
break;
|
||||
|
||||
case EventType.MouseDown:
|
||||
if (evt.button == 0
|
||||
&& HandleUtility.nearestControl == id
|
||||
&& GUIUtility.hotControl == 0)
|
||||
{
|
||||
GUIUtility.hotControl = id;
|
||||
|
||||
evt.Use();
|
||||
newTime = GetClosestSplineDataT(spline, splineData);
|
||||
return true;
|
||||
}
|
||||
break;
|
||||
|
||||
case EventType.MouseDrag:
|
||||
if (GUIUtility.hotControl == id)
|
||||
{
|
||||
evt.Use();
|
||||
newTime = GetClosestSplineDataT(spline, splineData);
|
||||
return true;
|
||||
}
|
||||
break;
|
||||
|
||||
case EventType.MouseUp:
|
||||
if (GUIUtility.hotControl == id)
|
||||
{
|
||||
evt.Use();
|
||||
if(evt.button == 0)
|
||||
{
|
||||
GUIUtility.hotControl = 0;
|
||||
newTime = GetClosestSplineDataT(spline, splineData);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
case EventType.MouseMove:
|
||||
HandleUtility.Repaint();
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
newTime = dataPoint.Index;
|
||||
return false;
|
||||
}
|
||||
|
||||
static void DrawSplineDataHandle(Vector3 position, int controlID)
|
||||
{
|
||||
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,
|
||||
k_HandleSize * HandleUtility.GetHandleSize(position),
|
||||
EventType.Repaint
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
static void DrawSplineDataLabel(Vector3 position, LabelType labelType, IDataPoint dataPoint, int keyframeIndex)
|
||||
{
|
||||
if(labelType == LabelType.None)
|
||||
return;
|
||||
|
||||
float labelVal = dataPoint.Index;
|
||||
if(labelType == LabelType.Index && keyframeIndex >= 0)
|
||||
labelVal = keyframeIndex;
|
||||
|
||||
var label = ( Mathf.RoundToInt(labelVal * 100) / 100f ).ToString();
|
||||
label = labelType == LabelType.Index ? "[" + label + "]" : "t: "+label;
|
||||
Handles.Label(position - 0.1f * Vector3.up, label);
|
||||
}
|
||||
|
||||
// Spline must be in world space
|
||||
static float GetClosestSplineDataT<T>(NativeSpline spline, SplineData<T> splineData)
|
||||
{
|
||||
var evt = Event.current;
|
||||
var ray = HandleUtility.GUIPointToWorldRay(evt.mousePosition);
|
||||
|
||||
SplineUtility.GetNearestPoint(spline,
|
||||
ray,
|
||||
out float3 _,
|
||||
out float t,
|
||||
k_PickRes);
|
||||
|
||||
var time = SplineUtility.ConvertIndexUnit(
|
||||
spline,
|
||||
t,
|
||||
PathIndexUnit.Normalized,
|
||||
splineData.PathIndexUnit);
|
||||
|
||||
return time;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
|
@ -0,0 +1,3 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 54bce7290a9e479889adc3e8c822798e
|
||||
timeCreated: 1625512898
|
|
@ -0,0 +1,216 @@
|
|||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
|
||||
namespace UnityEditor.Splines
|
||||
{
|
||||
class SplineElementRectSelector
|
||||
{
|
||||
enum Mode
|
||||
{
|
||||
None,
|
||||
Replace,
|
||||
Add,
|
||||
Subtract
|
||||
}
|
||||
|
||||
static class Styles
|
||||
{
|
||||
public static readonly GUIStyle selectionRect = GUI.skin.FindStyle("selectionRect");
|
||||
}
|
||||
|
||||
Rect m_Rect;
|
||||
Vector2 m_StartPos;
|
||||
Mode m_Mode;
|
||||
Mode m_InitialMode;
|
||||
static readonly HashSet<ISplineElement> s_SplineElementsCompareSet = new HashSet<ISplineElement>();
|
||||
static readonly List<ISplineElement> s_SplineElementsBuffer = new List<ISplineElement>();
|
||||
static readonly HashSet<ISplineElement> s_PreRectSelectionElements = new HashSet<ISplineElement>();
|
||||
|
||||
public void OnGUI(IReadOnlyList<IEditableSpline> paths)
|
||||
{
|
||||
int id = GUIUtility.GetControlID(FocusType.Passive);
|
||||
Event evt = Event.current;
|
||||
|
||||
switch (evt.GetTypeForControl(id))
|
||||
{
|
||||
case EventType.Layout:
|
||||
if (!Tools.viewToolActive)
|
||||
{
|
||||
HandleUtility.AddDefaultControl(id);
|
||||
|
||||
if (m_Mode != Mode.None)
|
||||
{
|
||||
// If we've started rect select in Add or Subtract modes, then if we were in a Replace
|
||||
// mode just before (i.e. the shift or action has been released temporarily),
|
||||
// we need to bring back the pre rect selection elements into current selection.
|
||||
if (m_InitialMode != Mode.Replace && RefreshSelectionMode())
|
||||
{
|
||||
SplineSelection.Clear();
|
||||
s_SplineElementsCompareSet.Clear();
|
||||
|
||||
if (m_Mode != Mode.Replace)
|
||||
{
|
||||
foreach (var element in s_PreRectSelectionElements)
|
||||
SplineSelection.Add(element);
|
||||
}
|
||||
|
||||
m_Rect = GetRectFromPoints(m_StartPos, evt.mousePosition);
|
||||
UpdateSelection(m_Rect, paths);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
break;
|
||||
|
||||
case EventType.Repaint:
|
||||
if (GUIUtility.hotControl == id && m_Rect.size != Vector2.zero)
|
||||
{
|
||||
Handles.BeginGUI();
|
||||
Styles.selectionRect.Draw(m_Rect, GUIContent.none, false, false, false, false);
|
||||
Handles.EndGUI();
|
||||
}
|
||||
break;
|
||||
|
||||
case EventType.MouseDown:
|
||||
if (HandleUtility.nearestControl == id && evt.button == 0)
|
||||
{
|
||||
m_StartPos = evt.mousePosition;
|
||||
m_Rect = new Rect(Vector3.zero, Vector2.zero);
|
||||
|
||||
BeginSelection(paths);
|
||||
GUIUtility.hotControl = id;
|
||||
evt.Use();
|
||||
}
|
||||
break;
|
||||
|
||||
case EventType.MouseDrag:
|
||||
if (GUIUtility.hotControl == id)
|
||||
{
|
||||
m_Rect = GetRectFromPoints(m_StartPos, evt.mousePosition);
|
||||
evt.Use();
|
||||
|
||||
UpdateSelection(m_Rect, paths);
|
||||
}
|
||||
break;
|
||||
|
||||
case EventType.MouseUp:
|
||||
if (GUIUtility.hotControl == id)
|
||||
{
|
||||
GUIUtility.hotControl = 0;
|
||||
evt.Use();
|
||||
|
||||
EndSelection(m_Rect, paths);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
protected virtual void BeginSelection(IReadOnlyList<IEditableSpline> paths)
|
||||
{
|
||||
RefreshSelectionMode();
|
||||
m_InitialMode = m_Mode;
|
||||
|
||||
s_SplineElementsCompareSet.Clear();
|
||||
s_SplineElementsBuffer.Clear();
|
||||
if (m_Mode == Mode.Replace)
|
||||
{
|
||||
SplineSelection.Clear();
|
||||
s_PreRectSelectionElements.Clear();
|
||||
}
|
||||
else
|
||||
SplineSelection.GetSelectedElements(s_PreRectSelectionElements);
|
||||
}
|
||||
|
||||
protected virtual void UpdateSelection(Rect rect, IReadOnlyList<IEditableSpline> paths)
|
||||
{
|
||||
//Get all elements in rect
|
||||
s_SplineElementsBuffer.Clear();
|
||||
for (int i = 0; i < paths.Count; ++i)
|
||||
{
|
||||
IEditableSpline spline = paths[i];
|
||||
for (int j = 0; j < spline.knotCount; ++j)
|
||||
GetElementSelection(rect, spline, j, s_SplineElementsBuffer);
|
||||
}
|
||||
|
||||
foreach (var splineElement in s_SplineElementsBuffer)
|
||||
{
|
||||
//Compare current frame buffer with last frame's to find new additions/removals
|
||||
var wasInRectLastFrame = s_SplineElementsCompareSet.Remove(splineElement);
|
||||
if (m_Mode == Mode.Replace || m_Mode == Mode.Add)
|
||||
{
|
||||
var canAdd = m_Mode == Mode.Replace ? true : !s_PreRectSelectionElements.Contains(splineElement);
|
||||
if (!wasInRectLastFrame && canAdd)
|
||||
SplineSelection.Add(splineElement);
|
||||
}
|
||||
else if (m_Mode == Mode.Subtract && !wasInRectLastFrame)
|
||||
{
|
||||
SplineSelection.Remove(splineElement);
|
||||
}
|
||||
}
|
||||
|
||||
//Remaining spline elements from last frame are removed from selection (or added if mode is subtract)
|
||||
foreach (var splineElement in s_SplineElementsCompareSet)
|
||||
{
|
||||
if (m_Mode == Mode.Replace || m_Mode == Mode.Add)
|
||||
{
|
||||
// If we're in Add mode, don't remove elements that were in select prior to rect selection
|
||||
if (m_Mode == Mode.Add && s_PreRectSelectionElements.Contains(splineElement))
|
||||
continue;
|
||||
SplineSelection.Remove(splineElement);
|
||||
}
|
||||
else if (m_Mode == Mode.Subtract && s_PreRectSelectionElements.Contains(splineElement))
|
||||
SplineSelection.Add(splineElement);
|
||||
}
|
||||
|
||||
//Move current elements buffer to hash set for next frame compare
|
||||
s_SplineElementsCompareSet.Clear();
|
||||
foreach (var splineElement in s_SplineElementsBuffer)
|
||||
s_SplineElementsCompareSet.Add(splineElement);
|
||||
}
|
||||
|
||||
bool RefreshSelectionMode()
|
||||
{
|
||||
var modeBefore = m_Mode;
|
||||
if (Event.current.shift)
|
||||
m_Mode = Mode.Add;
|
||||
else if (EditorGUI.actionKey)
|
||||
m_Mode = Mode.Subtract;
|
||||
else
|
||||
m_Mode = Mode.Replace;
|
||||
|
||||
// Return true if the mode has changed
|
||||
return m_Mode != modeBefore;
|
||||
}
|
||||
|
||||
void GetElementSelection(Rect rect, IEditableSpline spline, int index, List<ISplineElement> results)
|
||||
{
|
||||
var knot = spline.GetKnot(index);
|
||||
Vector3 screenSpace = HandleUtility.WorldToGUIPointWithDepth(knot.position);
|
||||
|
||||
if (screenSpace.z > 0 && rect.Contains(screenSpace))
|
||||
results.Add(knot);
|
||||
|
||||
for(int tangentIndex = 0; tangentIndex < knot.tangentCount; tangentIndex++)
|
||||
{
|
||||
var tangent = knot.GetTangent(tangentIndex);
|
||||
screenSpace = HandleUtility.WorldToGUIPointWithDepth(tangent.position);
|
||||
|
||||
if (SplineSelectionUtility.IsSelectable(spline, index, tangent) && screenSpace.z > 0 && rect.Contains(screenSpace))
|
||||
results.Add(tangent);
|
||||
}
|
||||
}
|
||||
|
||||
protected virtual void EndSelection(Rect rect, IReadOnlyList<IEditableSpline> paths)
|
||||
{
|
||||
m_Mode = m_InitialMode = Mode.None;
|
||||
}
|
||||
|
||||
static Rect GetRectFromPoints(Vector2 a, Vector2 b)
|
||||
{
|
||||
Vector2 min = new Vector2(Mathf.Min(a.x, b.x), Mathf.Min(a.y, b.y));
|
||||
Vector2 max = new Vector2(Mathf.Max(a.x, b.x), Mathf.Max(a.y, b.y));
|
||||
|
||||
return new Rect(min, max - min);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 3d68b08563e248f4a02b78fb710d36bf
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
|
@ -0,0 +1,613 @@
|
|||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using UnityEditor.SettingsManagement;
|
||||
using UnityEngine;
|
||||
using UnityEngine.Rendering;
|
||||
using UnityEngine.Splines;
|
||||
using Unity.Mathematics;
|
||||
|
||||
namespace UnityEditor.Splines
|
||||
{
|
||||
static class SplineHandles
|
||||
{
|
||||
[UserSetting]
|
||||
internal static UserSetting<Color> s_LineNormalFrontColor = new UserSetting<Color>(PathSettings.instance, "Handles.CurveNormalInFrontColor", Color.white, SettingsScope.User);
|
||||
|
||||
[UserSetting]
|
||||
internal static UserSetting<Color> s_LineNormalBehindColor = new UserSetting<Color>(PathSettings.instance, "Handles.CurveNormalBehindColor", new Color(0.98f, 0.62f, 0.62f, 0.4f), SettingsScope.User);
|
||||
|
||||
[UserSetting]
|
||||
internal static UserSetting<Color> s_KnotColor = new UserSetting<Color>(PathSettings.instance, "Handles.KnotDefaultColor", new Color(.4f, 1f, .95f, 1f), SettingsScope.User);
|
||||
|
||||
[UserSetting]
|
||||
internal static UserSetting<Color> s_TangentColor = new UserSetting<Color>(PathSettings.instance, "Handles.TangentDefaultColor", Color.black, SettingsScope.User);
|
||||
|
||||
[UserSettingBlock("Handles")]
|
||||
static void HandleColorPreferences(string searchContext)
|
||||
{
|
||||
s_LineNormalFrontColor.value = SettingsGUILayout.SettingsColorField("Curve Color", s_LineNormalFrontColor, searchContext);
|
||||
s_LineNormalBehindColor.value = SettingsGUILayout.SettingsColorField("Curve Color Behind Surface", s_LineNormalBehindColor, searchContext);
|
||||
s_KnotColor.value = SettingsGUILayout.SettingsColorField("Knot Color", s_KnotColor, searchContext);
|
||||
s_TangentColor.value = SettingsGUILayout.SettingsColorField("Tangent Color", s_TangentColor, searchContext);
|
||||
}
|
||||
|
||||
const float k_SizeFactor = 0.15f;
|
||||
const float k_PickingDistance = 8f;
|
||||
|
||||
const float k_HandleWidthDefault = 2f;
|
||||
const float k_HandleWidthHover = 4f;
|
||||
|
||||
const float k_KnotDiscRadiusFactorDefault = 0.06f;
|
||||
const float k_KnotDiscRadiusFactorHover = 0.07f;
|
||||
const float k_KnotDiscRadiusFactorSelected = 0.085f;
|
||||
|
||||
const float k_KnotRotDiscRadius = 0.18f;
|
||||
const float k_KnotRotDiscWidthDefault = 1.5f;
|
||||
const float k_KnotRotDiscWidthHover = 3f;
|
||||
const float k_KnotRotDiscWidthSelected = 4f;
|
||||
|
||||
const float k_TangentLineWidthDefault = 2f;
|
||||
const float k_TangentLineWidthHover = 3.5f;
|
||||
const float k_TangentLineWidthSelected = 4.5f;
|
||||
const float k_TangentStartOffsetFromKnot = 0.22f;
|
||||
const float k_tangentEndOffsetFromHandle = 0.11f;
|
||||
|
||||
const float k_AliasedLineSizeMultiplier = 0.5f;
|
||||
|
||||
const int k_SegmentCount = 30;
|
||||
const float k_CurveLineWidth = 5f;
|
||||
const float k_PreviewCurveOpacity = 0.5f;
|
||||
const string k_TangentLineAATexPath = "Textures/TangentLineAATex";
|
||||
|
||||
static Texture2D s_ThickTangentLineAATex = Resources.Load<Texture2D>(k_TangentLineAATexPath);
|
||||
|
||||
static readonly Vector3[] s_CurveSegmentsBuffer = new Vector3[k_SegmentCount + 1];
|
||||
static readonly Vector3[] s_SegmentBuffer = new Vector3[2];
|
||||
static readonly Vector3[] s_AAWireDiscBuffer = new Vector3[18];
|
||||
|
||||
static ISplineElement s_LastHoveredTangent;
|
||||
static int s_LastHoveredTangentID;
|
||||
static List<int> s_ElementChildIDs = new List<int>();
|
||||
|
||||
internal static void DrawSplineHandles(IReadOnlyList<IEditableSpline> paths, SplineHandlesOptions options)
|
||||
{
|
||||
for (int i = 0; i < paths.Count; ++i)
|
||||
{
|
||||
DrawSplineHandles(paths[i], options);
|
||||
}
|
||||
}
|
||||
|
||||
internal static bool DrawSplineHandles(IEditableSpline spline, SplineHandlesOptions options, bool activeSpline = true)
|
||||
{
|
||||
int lastIndex = spline.closed ? spline.knotCount - 1 : spline.knotCount - 2; //If the spline isn't closed, skip the last index of the spline
|
||||
var isInsertingKnots = HasOption(options, SplineHandlesOptions.KnotInsert);
|
||||
|
||||
int[] curveIDs = new int[0];
|
||||
if(isInsertingKnots && lastIndex+1>=0)
|
||||
{
|
||||
curveIDs = new int[lastIndex+1];
|
||||
for (int idIndex = 0; idIndex < lastIndex+1; ++idIndex)
|
||||
curveIDs[idIndex] = GUIUtility.GetControlID(FocusType.Passive);
|
||||
}
|
||||
|
||||
activeSpline = curveIDs.Contains(HandleUtility.nearestControl) || activeSpline;
|
||||
for (int knotIndex = 0; knotIndex <= lastIndex; ++knotIndex)
|
||||
{
|
||||
var curve = new CurveData(spline, knotIndex);
|
||||
if (isInsertingKnots)
|
||||
CurveHandleWithKnotInsert(curve, curveIDs[knotIndex], activeSpline);
|
||||
else
|
||||
DrawCurve(curve);
|
||||
}
|
||||
|
||||
var drawHandlesAsActive = curveIDs.Contains(HandleUtility.nearestControl) || activeSpline;
|
||||
|
||||
for (int knotIndex = 0; knotIndex < spline.knotCount; ++knotIndex)
|
||||
{
|
||||
var knot = spline.GetKnot(knotIndex);
|
||||
|
||||
if (HasOption(options, SplineHandlesOptions.ShowTangents))
|
||||
{
|
||||
for (int tangentIndex = 0; tangentIndex < knot.tangentCount; ++tangentIndex)
|
||||
{
|
||||
//Not drawing unused tangents
|
||||
if (!spline.closed && ((knotIndex == 0 && tangentIndex == 0) ||
|
||||
(knotIndex != 0 && knotIndex + 1 == spline.knotCount && tangentIndex + 1 == knot.tangentCount)))
|
||||
continue;
|
||||
|
||||
var tangent = knot.GetTangent(tangentIndex);
|
||||
if (HasOption(options, SplineHandlesOptions.SelectableTangents))
|
||||
{
|
||||
var tangentHandlelID = SelectionHandle(tangent);
|
||||
s_ElementChildIDs.Add(tangentHandlelID);
|
||||
}
|
||||
else
|
||||
DrawTangentHandle(tangent, -1, drawHandlesAsActive);
|
||||
}
|
||||
}
|
||||
|
||||
if (HasOption(options, SplineHandlesOptions.SelectableKnots))
|
||||
SelectionHandle(knot);
|
||||
else
|
||||
DrawKnotHandle(knot, null, drawHandlesAsActive);
|
||||
|
||||
s_ElementChildIDs.Clear();
|
||||
}
|
||||
|
||||
if (s_LastHoveredTangent != null && Event.current.GetTypeForControl(s_LastHoveredTangentID) == EventType.Repaint)
|
||||
s_LastHoveredTangent = null;
|
||||
|
||||
return activeSpline;
|
||||
}
|
||||
|
||||
static bool HasOption(SplineHandlesOptions options, SplineHandlesOptions target)
|
||||
{
|
||||
return (options & target) == target;
|
||||
}
|
||||
|
||||
internal static int SelectionHandle(ISplineElement element)
|
||||
{
|
||||
int id = GUIUtility.GetControlID(FocusType.Passive);
|
||||
Event evt = Event.current;
|
||||
EventType eventType = evt.GetTypeForControl(id);
|
||||
|
||||
switch (eventType)
|
||||
{
|
||||
case EventType.Layout:
|
||||
if (!Tools.viewToolActive)
|
||||
{
|
||||
HandleUtility.AddControl(id, SplineHandleUtility.DistanceToCircle(element.position, k_PickingDistance));
|
||||
|
||||
if (element is EditableTangent)
|
||||
{
|
||||
if (HandleUtility.nearestControl == id)
|
||||
{
|
||||
s_LastHoveredTangent = element;
|
||||
s_LastHoveredTangentID = id;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
break;
|
||||
|
||||
case EventType.MouseDown:
|
||||
if (HandleUtility.nearestControl == id)
|
||||
{
|
||||
//Clicking a knot selects it
|
||||
if (evt.button != 0)
|
||||
break;
|
||||
|
||||
GUIUtility.hotControl = id;
|
||||
evt.Use();
|
||||
|
||||
//Add/Remove from knotSelection
|
||||
if (EditorGUI.actionKey || evt.modifiers == EventModifiers.Shift)
|
||||
{
|
||||
if (SplineSelection.Contains(element))
|
||||
SplineSelection.Remove(element);
|
||||
else
|
||||
SplineSelection.Add(element);
|
||||
}
|
||||
else
|
||||
{
|
||||
SplineSelection.Clear();
|
||||
SplineSelection.Add(element);
|
||||
}
|
||||
}
|
||||
|
||||
break;
|
||||
|
||||
case EventType.MouseUp:
|
||||
if (GUIUtility.hotControl == id)
|
||||
{
|
||||
GUIUtility.hotControl = 0;
|
||||
evt.Use();
|
||||
}
|
||||
|
||||
break;
|
||||
|
||||
case EventType.MouseMove:
|
||||
if (id == HandleUtility.nearestControl)
|
||||
HandleUtility.Repaint();
|
||||
break;
|
||||
|
||||
case EventType.Repaint:
|
||||
switch (element)
|
||||
{
|
||||
case EditableKnot knot:
|
||||
DrawKnotHandle(knot, id, s_ElementChildIDs);
|
||||
break;
|
||||
case EditableTangent tangent:
|
||||
DrawTangentHandle(tangent, id);
|
||||
break;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
return id;
|
||||
}
|
||||
|
||||
internal static bool ButtonHandle(int controlID, EditableKnot knot, bool active)
|
||||
{
|
||||
Event evt = Event.current;
|
||||
EventType eventType = evt.GetTypeForControl(controlID);
|
||||
|
||||
var position = knot.position;
|
||||
|
||||
switch (eventType)
|
||||
{
|
||||
case EventType.Layout:
|
||||
{
|
||||
if(!Tools.viewToolActive)
|
||||
HandleUtility.AddControl(controlID, SplineHandleUtility.DistanceToKnot(position));
|
||||
break;
|
||||
}
|
||||
|
||||
case EventType.Repaint:
|
||||
DrawKnotHandle(knot, controlID, null, active);
|
||||
break;
|
||||
|
||||
case EventType.MouseDown:
|
||||
if (HandleUtility.nearestControl == controlID)
|
||||
{
|
||||
//Clicking a knot selects it
|
||||
if (evt.button != 0)
|
||||
break;
|
||||
|
||||
GUIUtility.hotControl = controlID;
|
||||
evt.Use();
|
||||
}
|
||||
break;
|
||||
|
||||
case EventType.MouseUp:
|
||||
if (GUIUtility.hotControl == controlID)
|
||||
{
|
||||
GUIUtility.hotControl = 0;
|
||||
evt.Use();
|
||||
|
||||
return true;
|
||||
}
|
||||
break;
|
||||
|
||||
case EventType.MouseMove:
|
||||
if (HandleUtility.nearestControl == controlID)
|
||||
HandleUtility.Repaint();
|
||||
break;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public static void CurveHandleWithKnotInsert(CurveData curve, int controlID, bool activeSpline)
|
||||
{
|
||||
Event evt = Event.current;
|
||||
EventType eventType = evt.GetTypeForControl(controlID);
|
||||
|
||||
CurveHandleCap(curve, controlID, eventType, false, activeSpline);
|
||||
switch (eventType)
|
||||
{
|
||||
case EventType.Repaint:
|
||||
if (HandleUtility.nearestControl == controlID)
|
||||
{
|
||||
SplineHandleUtility.GetNearestPointOnCurve(curve, out Vector3 position, out float t);
|
||||
if(t > 0f && t < 1f)
|
||||
{
|
||||
var mouseRect = new Rect(evt.mousePosition - new Vector2(500, 500), new Vector2(1000, 1000));
|
||||
EditorGUIUtility.AddCursorRect(mouseRect, MouseCursor.ArrowPlus);
|
||||
|
||||
var previewKnotRotation = quaternion.identity;
|
||||
if (curve.a.spline is BezierEditableSpline)
|
||||
{
|
||||
var bezierCurve = BezierCurve.FromTangent(curve.a.position, curve.a.GetTangent((int)BezierTangent.Out).direction,
|
||||
curve.b.position, curve.b.GetTangent((int)BezierTangent.In).direction);
|
||||
|
||||
var up = CurveUtility.EvaluateUpVector(bezierCurve, t, math.rotate(curve.a.rotation, math.up()), math.rotate(curve.b.rotation, math.up()));
|
||||
var tangentOut = CurveUtility.EvaluateTangent(bezierCurve, t);
|
||||
previewKnotRotation = quaternion.LookRotationSafe(math.normalize(tangentOut), up);
|
||||
}
|
||||
|
||||
DrawKnotHandle(position, previewKnotRotation, false, controlID, false, activeSpline);
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
case EventType.MouseDown:
|
||||
if (HandleUtility.nearestControl == controlID)
|
||||
{
|
||||
if (evt.button != 0)
|
||||
break;
|
||||
|
||||
SplineHandleUtility.GetNearestPointOnCurve(curve, out Vector3 position, out float t);
|
||||
|
||||
//Do not place a new knot on an existing one to prevent creation of singularity points with bad tangents
|
||||
if(t > 0f && t < 1f)
|
||||
{
|
||||
EditableKnot knot = EditableSplineUtility.InsertKnotOnCurve(curve, position, t);
|
||||
|
||||
if(!(evt.control || evt.shift))
|
||||
SplineSelection.Clear();
|
||||
|
||||
SplineSelection.Add(knot);
|
||||
}
|
||||
|
||||
evt.Use();
|
||||
}
|
||||
break;
|
||||
|
||||
case EventType.MouseMove:
|
||||
if(HandleUtility.nearestControl == controlID)
|
||||
HandleUtility.Repaint();
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
internal static void DrawKnotHandle(EditableKnot knot, List<int> tangentControlIDs = null, bool activeSpline = true)
|
||||
{
|
||||
DrawKnotHandle(knot, -1, tangentControlIDs, activeSpline);
|
||||
}
|
||||
|
||||
internal static void DrawKnotHandle(EditableKnot knot, int controlId = -1, List<int> tangentControlIDs = null, bool activeSpline = true)
|
||||
{
|
||||
var mirroredTangentSelected = false;
|
||||
var mirroredTangentHovered = false;
|
||||
|
||||
if (tangentControlIDs != null && knot is BezierEditableKnot bezierKnot &&
|
||||
(bezierKnot.mode == BezierEditableKnot.Mode.Mirrored || bezierKnot.mode == BezierEditableKnot.Mode.Continuous))
|
||||
{
|
||||
for (int i = 0; i < knot.tangentCount; i++)
|
||||
{
|
||||
var tangent = knot.GetTangent(i);
|
||||
if (SplineSelection.Contains(tangent))
|
||||
{
|
||||
mirroredTangentSelected = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!mirroredTangentSelected)
|
||||
{
|
||||
foreach (var tangentID in tangentControlIDs)
|
||||
{
|
||||
if (HandleUtility.nearestControl == tangentID)
|
||||
mirroredTangentHovered = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
DrawKnotHandle(knot.position, knot.rotation, SplineSelection.Contains(knot), controlId, false, activeSpline, mirroredTangentSelected, mirroredTangentHovered);
|
||||
}
|
||||
|
||||
internal static void DrawKnotHandle(Vector3 knotPosition, Quaternion knotRotation, bool selected, int controlId,
|
||||
bool preview = false, bool activeSpline = true, bool mirroredTangentSelected = false, bool mirroredTangentHovered = false)
|
||||
{
|
||||
if(Event.current.GetTypeForControl(controlId) != EventType.Repaint)
|
||||
return;
|
||||
|
||||
var knotColor = s_KnotColor.value;
|
||||
if(preview)
|
||||
knotColor = Color.Lerp(Color.gray, Color.white, 0.5f);
|
||||
else if(selected)
|
||||
knotColor = Handles.selectedColor;
|
||||
else if(controlId > 0 && GUIUtility.hotControl == 0 && HandleUtility.nearestControl == controlId)
|
||||
knotColor = Handles.preselectionColor;
|
||||
|
||||
if(!activeSpline)
|
||||
knotColor = Handles.secondaryColor;
|
||||
|
||||
var handleSize = HandleUtility.GetHandleSize(knotPosition);
|
||||
var hovered = HandleUtility.nearestControl == controlId;
|
||||
|
||||
using (new Handles.DrawingScope(knotColor, Matrix4x4.TRS(knotPosition, knotRotation, Vector3.one)))
|
||||
{
|
||||
// Knot disc
|
||||
if (selected || hovered)
|
||||
{
|
||||
var radius = selected ? k_KnotDiscRadiusFactorSelected : k_KnotDiscRadiusFactorHover;
|
||||
Handles.DrawSolidDisc(Vector3.zero, Vector3.up, radius * handleSize);
|
||||
}
|
||||
else
|
||||
Handles.DrawWireDisc(Vector3.zero, Vector3.up, k_KnotDiscRadiusFactorDefault * handleSize, k_HandleWidthHover * k_AliasedLineSizeMultiplier);
|
||||
}
|
||||
|
||||
var rotationDiscColor = knotColor;
|
||||
if (!selected && mirroredTangentSelected)
|
||||
rotationDiscColor = Handles.selectedColor;
|
||||
|
||||
using (new Handles.DrawingScope(rotationDiscColor, Matrix4x4.TRS(knotPosition, knotRotation, Vector3.one)))
|
||||
{
|
||||
// Knot rotation indicators
|
||||
var rotationDiscWidth = k_KnotRotDiscWidthDefault;
|
||||
if (selected || mirroredTangentSelected)
|
||||
rotationDiscWidth = k_KnotRotDiscWidthSelected;
|
||||
else if (hovered || mirroredTangentHovered)
|
||||
rotationDiscWidth = k_KnotRotDiscWidthHover;
|
||||
|
||||
DrawAAWireDisc(Vector3.zero, Vector3.up, k_KnotRotDiscRadius * handleSize, rotationDiscWidth);
|
||||
|
||||
s_SegmentBuffer[0] = Vector3.zero;
|
||||
s_SegmentBuffer[1] = Vector3.up * 2f * k_SizeFactor * handleSize;
|
||||
Handles.DrawAAPolyLine(k_HandleWidthDefault, s_SegmentBuffer);
|
||||
}
|
||||
}
|
||||
|
||||
internal static void DrawPreviewKnot(EditableKnot knot)
|
||||
{
|
||||
DrawKnotHandle(knot.position, knot.rotation, false, -1, true);
|
||||
}
|
||||
|
||||
internal static void DrawTangentHandle(EditableTangent tangent, int controlId = -1, bool activeHandle = true)
|
||||
{
|
||||
if(Event.current.type != EventType.Repaint)
|
||||
return;
|
||||
|
||||
var knotPos = tangent.owner.position;
|
||||
var tangentPos = tangent.position;
|
||||
|
||||
var tangentHandleSize = HandleUtility.GetHandleSize(tangentPos);
|
||||
|
||||
var tangentColor = s_KnotColor.value;
|
||||
var selected = SplineSelection.Contains(tangent);
|
||||
var hovered = HandleUtility.nearestControl == controlId;
|
||||
if (selected)
|
||||
tangentColor = Handles.selectedColor;
|
||||
else if (hovered)
|
||||
tangentColor = Handles.preselectionColor;
|
||||
|
||||
if(!activeHandle)
|
||||
tangentColor = Handles.secondaryColor;
|
||||
|
||||
var tangentArmColor = tangentColor == s_KnotColor ? s_TangentColor.value : tangentColor;
|
||||
|
||||
var oppositeSelected = IsOppositeTangentSelected(tangent);
|
||||
if (tangentArmColor == s_TangentColor && oppositeSelected)
|
||||
tangentArmColor = Handles.selectedColor;
|
||||
|
||||
var oppositeHovered = IsOppositeTangentHovered(tangent);
|
||||
var mirrored = (tangent.owner is BezierEditableKnot bezierKnot) &&
|
||||
bezierKnot.mode == BezierEditableKnot.Mode.Mirrored;
|
||||
|
||||
using (new ColorScope(tangentArmColor))
|
||||
{
|
||||
var width = k_TangentLineWidthDefault;
|
||||
if (selected || (mirrored && oppositeSelected))
|
||||
width = k_TangentLineWidthSelected;
|
||||
else if (hovered || (mirrored && oppositeHovered))
|
||||
width = k_TangentLineWidthHover;
|
||||
|
||||
var tex = width > k_TangentLineWidthDefault ? s_ThickTangentLineAATex : null;
|
||||
|
||||
var startPos = knotPos;
|
||||
var toTangent = tangentPos - knotPos;
|
||||
var toTangentNorm = math.normalize(toTangent);
|
||||
var length = math.length(toTangent);
|
||||
|
||||
var knotHandleSize = HandleUtility.GetHandleSize(startPos);
|
||||
var knotHandleOffset = knotHandleSize * k_TangentStartOffsetFromKnot;
|
||||
var tangentHandleOffset = tangentHandleSize * k_tangentEndOffsetFromHandle;
|
||||
// Reduce the length slightly, so that there's some space between tangent line endings and handles.
|
||||
length = Mathf.Max(0f, length - knotHandleOffset - tangentHandleOffset);
|
||||
startPos += toTangentNorm * knotHandleOffset;
|
||||
SplineHandleUtility.DrawLineWithWidth(startPos + toTangentNorm * length, startPos, width, tex);
|
||||
}
|
||||
|
||||
var rotation = TransformOperation.CalculateElementSpaceHandleRotation(math.length(tangent.localPosition) >0 ? tangent : tangent.owner);
|
||||
using (new Handles.DrawingScope(tangentColor, Matrix4x4.TRS(tangent.position, rotation, Vector3.one)))
|
||||
{
|
||||
if (selected || hovered)
|
||||
{
|
||||
var radius = (selected ? k_KnotDiscRadiusFactorSelected : k_KnotDiscRadiusFactorHover) * tangentHandleSize;
|
||||
// As Handles.DrawSolidDisc has no thickness parameter, we're drawing a wire disc here so that the solid disc has thickness when viewed from a shallow angle.
|
||||
Handles.DrawWireDisc(Vector3.zero, Vector3.up, radius * 0.7f, k_HandleWidthHover);
|
||||
Handles.DrawSolidDisc(Vector3.zero, Vector3.up, radius);
|
||||
}
|
||||
else
|
||||
Handles.DrawWireDisc(Vector3.zero, Vector3.up, k_KnotDiscRadiusFactorDefault * tangentHandleSize, k_HandleWidthHover * k_AliasedLineSizeMultiplier);
|
||||
}
|
||||
}
|
||||
|
||||
static void DrawAAWireDisc(Vector3 position, Vector3 normal, float radius, float thickness)
|
||||
{
|
||||
// Right vector calculation here is identical to Handles.DrawWireDisc
|
||||
Vector3 right = Vector3.Cross(normal, Vector3.up);
|
||||
if ((double)right.sqrMagnitude < 1.0 / 1000.0)
|
||||
right = Vector3.Cross(normal, Vector3.right);
|
||||
|
||||
var angleStep = 360f / (s_AAWireDiscBuffer.Length - 1);
|
||||
for (int i = 0; i < s_AAWireDiscBuffer.Length - 1; i++)
|
||||
{
|
||||
s_AAWireDiscBuffer[i] = position + right * radius;
|
||||
right = Quaternion.AngleAxis(angleStep, normal) * right;
|
||||
}
|
||||
|
||||
s_AAWireDiscBuffer[s_AAWireDiscBuffer.Length - 1] = s_AAWireDiscBuffer[0];
|
||||
|
||||
var tex = thickness > 2f ? s_ThickTangentLineAATex : null;
|
||||
Handles.DrawAAPolyLine(tex, thickness, s_AAWireDiscBuffer);
|
||||
}
|
||||
|
||||
static bool IsOppositeTangentSelected(EditableTangent tangent)
|
||||
{
|
||||
return tangent.owner is BezierEditableKnot knot
|
||||
&& knot.mode != BezierEditableKnot.Mode.Broken
|
||||
&& knot.TryGetOppositeTangent(tangent, out EditableTangent oppositeTangent)
|
||||
&& SplineSelection.Contains(oppositeTangent);
|
||||
}
|
||||
|
||||
static bool IsOppositeTangentHovered(EditableTangent tangent)
|
||||
{
|
||||
return tangent.owner is BezierEditableKnot knot
|
||||
&& knot.TryGetOppositeTangent(tangent, out EditableTangent oppositeTangent)
|
||||
&& (s_LastHoveredTangent == oppositeTangent);
|
||||
}
|
||||
|
||||
internal static void DrawCurve(CurveData curve)
|
||||
{
|
||||
Event evt = Event.current;
|
||||
if (evt.type == EventType.Repaint)
|
||||
CurveHandleCap(curve, -1, EventType.Repaint);
|
||||
}
|
||||
|
||||
internal static void CurveHandleCap(CurveData curve, int controlID, EventType eventType, bool previewCurve = false, bool activeSpline = true)
|
||||
{
|
||||
switch (eventType)
|
||||
{
|
||||
case EventType.Layout:
|
||||
{
|
||||
SplineHandleUtility.GetCurveSegments(curve, s_CurveSegmentsBuffer);
|
||||
|
||||
float dist = float.MaxValue;
|
||||
for (var i = 0; i < s_CurveSegmentsBuffer.Length - 1; ++i)
|
||||
{
|
||||
var a = s_CurveSegmentsBuffer[i];
|
||||
var b = s_CurveSegmentsBuffer[i + 1];
|
||||
dist = Mathf.Min(HandleUtility.DistanceToLine(a, b), dist);
|
||||
}
|
||||
|
||||
if (!Tools.viewToolActive)
|
||||
HandleUtility.AddControl(controlID, Mathf.Max(0, dist - k_PickingDistance));
|
||||
break;
|
||||
}
|
||||
|
||||
case EventType.Repaint:
|
||||
{
|
||||
SplineHandleUtility.GetCurveSegments(curve, s_CurveSegmentsBuffer);
|
||||
//We attenuate the spline display if a spline can be controlled (id != -1) and
|
||||
//if it's not the current active spline
|
||||
var attenuate = controlID != -1 && !activeSpline;
|
||||
|
||||
var prevColor = Handles.color;
|
||||
|
||||
var color = s_LineNormalFrontColor.value;
|
||||
if (attenuate)
|
||||
color = Handles.secondaryColor;
|
||||
if (previewCurve)
|
||||
color.a *= k_PreviewCurveOpacity;
|
||||
|
||||
Handles.color = color;
|
||||
|
||||
using (new ZTestScope(CompareFunction.Less))
|
||||
{
|
||||
Handles.DrawAAPolyLine(k_CurveLineWidth, s_CurveSegmentsBuffer);
|
||||
}
|
||||
|
||||
color = s_LineNormalBehindColor.value;
|
||||
if (attenuate)
|
||||
color = Handles.secondaryColor;
|
||||
if (previewCurve)
|
||||
color.a *= k_PreviewCurveOpacity;
|
||||
|
||||
Handles.color = color;
|
||||
|
||||
using (new ZTestScope(CompareFunction.Greater))
|
||||
{
|
||||
Handles.DrawAAPolyLine(k_CurveLineWidth, s_CurveSegmentsBuffer);
|
||||
}
|
||||
|
||||
Handles.color = prevColor;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
fileFormatVersion: 2
|
||||
guid: fa11edcb768491b4b8b90ccf074e663f
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
|
@ -0,0 +1,8 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 231d226261711dc47be16b8c01e46909
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
|
@ -0,0 +1,72 @@
|
|||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
using UnityEngine.Splines;
|
||||
using Unity.Mathematics;
|
||||
|
||||
namespace UnityEditor.Splines
|
||||
{
|
||||
struct BezierBuilder
|
||||
{
|
||||
readonly List<BezierKnot> m_ResultKnots;
|
||||
readonly bool m_Closed;
|
||||
|
||||
int knotCount => m_ResultKnots.Count;
|
||||
public int segmentCount => m_Closed ? m_ResultKnots.Count : m_ResultKnots.Count - 1;
|
||||
|
||||
public BezierBuilder(List<BezierKnot> result, bool closed, int targetKnotCount)
|
||||
{
|
||||
m_ResultKnots = result;
|
||||
for (int i = 0; i < targetKnotCount; ++i)
|
||||
{
|
||||
var knot = new BezierKnot();
|
||||
knot.Rotation = quaternion.identity;
|
||||
|
||||
m_ResultKnots.Add(knot);
|
||||
}
|
||||
|
||||
m_Closed = closed;
|
||||
}
|
||||
|
||||
public void SetKnot(int index, float3 position, float3 tangentIn, float3 tangentOut, quaternion rotation)
|
||||
{
|
||||
var current = m_ResultKnots[index];
|
||||
|
||||
current.Position = position;
|
||||
current.TangentIn = tangentIn;
|
||||
current.TangentOut = tangentOut;
|
||||
current.Rotation = rotation;
|
||||
|
||||
m_ResultKnots[index] = current;
|
||||
}
|
||||
|
||||
void GetSegmentEndIndex(int index, out int endIndex)
|
||||
{
|
||||
endIndex = m_Closed ? (index + 1) % knotCount : index + 1;
|
||||
}
|
||||
|
||||
public void SetSegment(int index, float3 posA, float3 tangentOutA, quaternion rotationA, float3 posB, float3 tangentInB, quaternion rotationB)
|
||||
{
|
||||
GetSegmentEndIndex(index, out int nextIndex);
|
||||
var current = m_ResultKnots[index];
|
||||
current.Position = posA;
|
||||
current.Rotation = rotationA;
|
||||
current.TangentOut = tangentOutA;
|
||||
|
||||
var next = m_ResultKnots[nextIndex];
|
||||
next.Position = posB;
|
||||
next.Rotation = rotationB;
|
||||
next.TangentIn = tangentInB;
|
||||
|
||||
if (!m_Closed)
|
||||
{
|
||||
if (index == 0)
|
||||
current.TangentIn = -current.TangentOut;
|
||||
else if (nextIndex == knotCount - 1)
|
||||
next.TangentOut = -next.TangentIn;
|
||||
}
|
||||
|
||||
m_ResultKnots[index] = current;
|
||||
m_ResultKnots[nextIndex] = next;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 6c652dfb0c532cd4991ca0f23b93b51f
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
|
@ -0,0 +1,8 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 411641953fc0a2148a0507f5ee6df182
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
|
@ -0,0 +1,43 @@
|
|||
using System;
|
||||
|
||||
namespace UnityEditor.Splines
|
||||
{
|
||||
[Serializable]
|
||||
struct CurveData
|
||||
{
|
||||
public static readonly CurveData empty = new CurveData
|
||||
{
|
||||
a = null,
|
||||
b = null
|
||||
};
|
||||
|
||||
public EditableKnot a { get; private set; }
|
||||
public EditableKnot b { get; private set; }
|
||||
|
||||
public CurveData(EditableKnot firstKnot)
|
||||
{
|
||||
a = firstKnot;
|
||||
|
||||
//If first knot is last knot of the spline, use index 0 for the closing curve
|
||||
var path = firstKnot.spline;
|
||||
int nextIndex = firstKnot.index + 1;
|
||||
if (nextIndex >= path.knotCount)
|
||||
nextIndex = 0;
|
||||
|
||||
b = path.GetKnot(nextIndex);
|
||||
}
|
||||
|
||||
public CurveData(EditableKnot firstKnot, EditableKnot lastKnot)
|
||||
{
|
||||
a = firstKnot;
|
||||
b = lastKnot;
|
||||
}
|
||||
|
||||
public CurveData(IEditableSpline spline, int firstIndex) : this(spline.GetKnot(firstIndex)){}
|
||||
|
||||
public bool IsValid()
|
||||
{
|
||||
return a != null && b != null;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 92217b25452f8a94998d750fee5381d1
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
|
@ -0,0 +1,120 @@
|
|||
using System;
|
||||
using Unity.Mathematics;
|
||||
using UnityEngine;
|
||||
|
||||
namespace UnityEditor.Splines
|
||||
{
|
||||
[Serializable]
|
||||
class EditableKnot : ISplineElement
|
||||
{
|
||||
internal static event Action<EditableKnot> knotModified;
|
||||
|
||||
[SerializeField]
|
||||
float3 m_LocalPosition;
|
||||
|
||||
[SerializeField]
|
||||
quaternion m_LocalRotation = quaternion.identity;
|
||||
|
||||
public IEditableSpline spline { get; internal set; }
|
||||
public int index { get; internal set; }
|
||||
|
||||
public bool IsValid()
|
||||
{
|
||||
return index >= 0;
|
||||
}
|
||||
|
||||
/// <summary> Matrix that transforms a point from local (knot) into world space. </summary>
|
||||
public Matrix4x4 localToWorldMatrix => spline.localToWorldMatrix * Matrix4x4.TRS(localPosition, localRotation, Vector3.one);
|
||||
/// <summary> Matrix that transforms a point from world space into local (knot) space. </summary>
|
||||
public Matrix4x4 worldToLocalMatrix => localToWorldMatrix.inverse;
|
||||
|
||||
public EditableKnot GetPrevious()
|
||||
{
|
||||
return spline.GetPreviousKnot(index, out EditableKnot previous) ? previous : null;
|
||||
}
|
||||
|
||||
public EditableKnot GetNext()
|
||||
{
|
||||
return spline.GetNextKnot(index, out EditableKnot next) ? next : null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// World space position of the knot.
|
||||
/// </summary>
|
||||
public float3 position
|
||||
{
|
||||
get => spline.localToWorldMatrix.MultiplyPoint3x4(localPosition);
|
||||
set => localPosition = spline.worldToLocalMatrix.MultiplyPoint3x4(value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Local (spline space) position of the knot.
|
||||
/// </summary>
|
||||
public float3 localPosition
|
||||
{
|
||||
get => m_LocalPosition;
|
||||
set
|
||||
{
|
||||
if (m_LocalPosition.Equals(value))
|
||||
return;
|
||||
|
||||
m_LocalPosition = value;
|
||||
SetDirty();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// World space rotation of the knot.
|
||||
/// </summary>
|
||||
public quaternion rotation
|
||||
{
|
||||
get => spline.localToWorldMatrix.rotation * localRotation;
|
||||
set => localRotation = math.mul(spline.worldToLocalMatrix.rotation, value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Local (spline space) rotation of the knot.
|
||||
/// </summary>
|
||||
public quaternion localRotation
|
||||
{
|
||||
get => m_LocalRotation;
|
||||
set
|
||||
{
|
||||
if (m_LocalRotation.Equals(value))
|
||||
return;
|
||||
|
||||
m_LocalRotation = math.normalize(value);
|
||||
SetDirty();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// How many editable tangents a knot contains. Cubic bezier splines contain 2 tangents, except at the ends of
|
||||
/// a Spline that is not closed, in which case the knot contains a single tangent. Other spline type representations
|
||||
/// may contain more or fewer tangents (ex, a Catmull-Rom spline does not expose any editable tangents).
|
||||
/// </summary>
|
||||
public int tangentCount => spline.tangentsPerKnot;
|
||||
|
||||
public virtual void Copy(EditableKnot other)
|
||||
{
|
||||
spline = other.spline;
|
||||
index = other.index;
|
||||
m_LocalPosition = other.localPosition;
|
||||
m_LocalRotation = other.localRotation;
|
||||
for (int i = 0, count = math.min(tangentCount, other.tangentCount); i < count; ++i)
|
||||
GetTangent(i).Copy(other.GetTangent(i));
|
||||
}
|
||||
|
||||
public void SetDirty()
|
||||
{
|
||||
knotModified?.Invoke(this);
|
||||
spline?.SetDirty();
|
||||
}
|
||||
|
||||
internal virtual EditableTangent GetTangent(int index) { return null; }
|
||||
|
||||
public virtual void ValidateData() {}
|
||||
public virtual void OnPathUpdatedFromTarget() {}
|
||||
public virtual void OnKnotInsertedOnCurve(EditableKnot previous, EditableKnot next, float t) {}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
fileFormatVersion: 2
|
||||
guid: c9eab45bfb720d641b442af1b8ad2543
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
|
@ -0,0 +1,296 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using Unity.Mathematics;
|
||||
using UnityEngine;
|
||||
using UnityEngine.Splines;
|
||||
using UObject = UnityEngine.Object;
|
||||
|
||||
namespace UnityEditor.Splines
|
||||
{
|
||||
interface IEditableSpline
|
||||
{
|
||||
bool canBeClosed { get; set; }
|
||||
bool closed { get; set; }
|
||||
int knotCount { get; }
|
||||
/// <summary>
|
||||
/// How many editable tangents a knot contains. Cubic bezier splines contain 2 tangents, except at the ends of
|
||||
/// a Spline that is not closed, in which case the knot contains a single tangent. Other spline type representations
|
||||
/// may contain more or fewer tangents (ex, a Catmull-Rom spline does not expose any editable tangents).
|
||||
/// </summary>
|
||||
int tangentsPerKnot { get; }
|
||||
/// <summary> Matrix that transforms a point from local (spline) into world space. </summary>
|
||||
Matrix4x4 localToWorldMatrix { get; }
|
||||
/// <summary> Matrix that transforms a point from world space into local (spline) space. </summary>
|
||||
Matrix4x4 worldToLocalMatrix { get; }
|
||||
|
||||
EditableKnot GetKnot(int index);
|
||||
bool GetPreviousKnot(int index, out EditableKnot knot);
|
||||
bool GetNextKnot(int index, out EditableKnot knot);
|
||||
void Resize(int targetKnotCount);
|
||||
EditableKnot AddKnot();
|
||||
void RemoveKnotAt(int index);
|
||||
EditableKnot InsertKnot(int index);
|
||||
CurveData GetPreviewCurveForEndKnot(float3 point, float3 normal, float3 tangentOut);
|
||||
void OnKnotAddedAtEnd(EditableKnot knot, float3 normal, float3 tangentOut);
|
||||
float3 GetPointOnCurve(CurveData curve, float t);
|
||||
/// <summary>
|
||||
/// Given an editable knot, returns its in and out tangents in local (spline) space.
|
||||
/// </summary>
|
||||
/// <param name="knot">An editable knot.</param>
|
||||
/// <param name="localTangentIn">Knot's in tangent in local (spline) space.</param>
|
||||
/// <param name="localTangentOut">Knot's out tangent in local (spline) space.</param>
|
||||
void GetLocalTangents(EditableKnot knot, out float3 localTangentIn, out float3 localTangentOut);
|
||||
void SetDirty();
|
||||
void ToBezier(List<BezierKnot> results);
|
||||
void FromBezier(IReadOnlyList<BezierKnot> knots);
|
||||
|
||||
bool isDirty { get; set; }
|
||||
UObject conversionTarget { get; set; }
|
||||
int conversionIndex { get; set; }
|
||||
|
||||
void ValidateData();
|
||||
void CopyConversionDataFrom(IEditableSpline spline);
|
||||
}
|
||||
|
||||
[Serializable]
|
||||
abstract class EditableSpline<T> : IEditableSpline
|
||||
where T : EditableKnot, new()
|
||||
{
|
||||
const int k_MinimumKnotCountToBeClosed = 3;
|
||||
|
||||
//Serialized fields will be used for tools inspector
|
||||
[SerializeField]
|
||||
List<T> m_Knots = new List<T>();
|
||||
|
||||
[SerializeField]
|
||||
bool m_Closed = false;
|
||||
|
||||
UObject m_ConversionTarget;
|
||||
int m_ConversionIndex;
|
||||
|
||||
bool m_IsDirty = false;
|
||||
|
||||
bool m_CanBeClosed = true;
|
||||
|
||||
protected EditableKnot m_PreviewKnotA;
|
||||
protected EditableKnot m_PreviewKnotB;
|
||||
|
||||
public Matrix4x4 localToWorldMatrix =>
|
||||
m_ConversionTarget != null && m_ConversionTarget is Component component
|
||||
? component.transform.localToWorldMatrix
|
||||
: Matrix4x4.identity;
|
||||
|
||||
public Matrix4x4 worldToLocalMatrix => localToWorldMatrix.inverse;
|
||||
|
||||
UObject IEditableSpline.conversionTarget
|
||||
{
|
||||
get => m_ConversionTarget;
|
||||
set => m_ConversionTarget = value;
|
||||
}
|
||||
|
||||
//the index in the target array created at conversion
|
||||
int IEditableSpline.conversionIndex
|
||||
{
|
||||
get => m_ConversionIndex;
|
||||
set => m_ConversionIndex = value;
|
||||
}
|
||||
|
||||
void IEditableSpline.CopyConversionDataFrom(IEditableSpline spline)
|
||||
{
|
||||
m_ConversionTarget = spline.conversionTarget;
|
||||
m_ConversionIndex = spline.conversionIndex;
|
||||
}
|
||||
|
||||
void IEditableSpline.ValidateData()
|
||||
{
|
||||
UpdateKnotIndices();
|
||||
foreach (var knot in m_Knots)
|
||||
{
|
||||
knot.spline = this;
|
||||
knot.ValidateData();
|
||||
}
|
||||
}
|
||||
|
||||
public bool canBeClosed
|
||||
{
|
||||
get => m_CanBeClosed;
|
||||
set
|
||||
{
|
||||
m_CanBeClosed = value;
|
||||
if (!m_CanBeClosed)
|
||||
{
|
||||
m_Closed = false;
|
||||
SetDirty();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public bool closed
|
||||
{
|
||||
get => knotCount >= k_MinimumKnotCountToBeClosed && m_Closed;
|
||||
set
|
||||
{
|
||||
if (m_Closed == value || !m_CanBeClosed)
|
||||
return;
|
||||
|
||||
m_Closed = value;
|
||||
SetDirty();
|
||||
}
|
||||
}
|
||||
|
||||
bool IEditableSpline.isDirty
|
||||
{
|
||||
get => m_IsDirty;
|
||||
set => m_IsDirty = value;
|
||||
}
|
||||
|
||||
public int knotCount => m_Knots.Count;
|
||||
|
||||
public virtual int tangentsPerKnot => 0;
|
||||
|
||||
EditableKnot IEditableSpline.GetKnot(int index)
|
||||
{
|
||||
return GetKnot(index);
|
||||
}
|
||||
|
||||
public T GetKnot(int index)
|
||||
{
|
||||
return m_Knots[index];
|
||||
}
|
||||
|
||||
public bool GetPreviousKnot(int index, out EditableKnot knot)
|
||||
{
|
||||
bool result = GetPreviousKnot(index, out T rawKnot);
|
||||
knot = rawKnot;
|
||||
return result;
|
||||
}
|
||||
|
||||
public bool GetPreviousKnot(int index, out T knot)
|
||||
{
|
||||
if (knotCount > 0)
|
||||
{
|
||||
int next = index - 1;
|
||||
if (next >= 0)
|
||||
{
|
||||
knot = m_Knots[next];
|
||||
return true;
|
||||
}
|
||||
|
||||
if (closed)
|
||||
{
|
||||
knot = m_Knots[m_Knots.Count - 1];
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
knot = null;
|
||||
return false;
|
||||
}
|
||||
|
||||
public bool GetNextKnot(int index, out EditableKnot knot)
|
||||
{
|
||||
if (knotCount > 0)
|
||||
{
|
||||
int next = index + 1;
|
||||
if (next < m_Knots.Count)
|
||||
{
|
||||
knot = m_Knots[next];
|
||||
return true;
|
||||
}
|
||||
|
||||
if (closed)
|
||||
{
|
||||
knot = m_Knots[0];
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
knot = null;
|
||||
return false;
|
||||
}
|
||||
|
||||
public void Resize(int targetKnotCount)
|
||||
{
|
||||
if (knotCount > targetKnotCount)
|
||||
{
|
||||
m_Knots.RemoveRange(targetKnotCount, knotCount - targetKnotCount);
|
||||
}
|
||||
else if (knotCount < targetKnotCount)
|
||||
{
|
||||
while (knotCount < targetKnotCount)
|
||||
{
|
||||
AddKnot();
|
||||
}
|
||||
}
|
||||
|
||||
SetDirty();
|
||||
}
|
||||
|
||||
public EditableKnot AddKnot()
|
||||
{
|
||||
var knot = CreateKnot();
|
||||
knot.index = m_Knots.Count;
|
||||
m_Knots.Add(knot);
|
||||
SetDirty();
|
||||
return knot;
|
||||
}
|
||||
|
||||
public void RemoveKnotAt(int index)
|
||||
{
|
||||
EditableKnot knot = m_Knots[index];
|
||||
|
||||
SplineSelection.Remove(knot);
|
||||
SplineSelection.OnKnotRemoved(this, index);
|
||||
|
||||
knot.index = -1;
|
||||
m_Knots.RemoveAt(index);
|
||||
UpdateKnotIndices();
|
||||
SetDirty();
|
||||
}
|
||||
|
||||
public EditableKnot InsertKnot(int index)
|
||||
{
|
||||
var knot = CreateKnot();
|
||||
m_Knots.Insert(index, knot);
|
||||
UpdateKnotIndices();
|
||||
SetDirty();
|
||||
|
||||
SplineSelection.MoveAllIndexUpFromIndexToEnd(this, index);
|
||||
return knot;
|
||||
}
|
||||
|
||||
protected void CreatePreviewKnotsIfNeeded()
|
||||
{
|
||||
if (m_PreviewKnotA == null)
|
||||
m_PreviewKnotA = CreateKnot();
|
||||
|
||||
if (m_PreviewKnotB == null)
|
||||
m_PreviewKnotB = CreateKnot();
|
||||
}
|
||||
|
||||
void UpdateKnotIndices()
|
||||
{
|
||||
for (int i = 0; i < m_Knots.Count; ++i)
|
||||
{
|
||||
m_Knots[i].index = i;
|
||||
}
|
||||
}
|
||||
|
||||
T CreateKnot()
|
||||
{
|
||||
return new T { spline = this };
|
||||
}
|
||||
|
||||
public void SetDirty()
|
||||
{
|
||||
m_IsDirty = true;
|
||||
}
|
||||
|
||||
public virtual void OnKnotAddedAtEnd(EditableKnot knot, float3 normal, float3 tangentOut) {}
|
||||
public abstract float3 GetPointOnCurve(CurveData curve, float t);
|
||||
public abstract void GetLocalTangents(EditableKnot knot, out float3 localTangentIn, out float3 localTangentOut);
|
||||
public abstract CurveData GetPreviewCurveForEndKnot(float3 point, float3 normal, float3 tangentOut);
|
||||
public abstract void ToBezier(List<BezierKnot> results);
|
||||
public abstract void FromBezier(IReadOnlyList<BezierKnot> knots);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 7e98edb19c8e6294293225f04c7d4a67
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
|
@ -0,0 +1,185 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel;
|
||||
using UnityEngine.Splines;
|
||||
using UObject = UnityEngine.Object;
|
||||
|
||||
namespace UnityEditor.Splines
|
||||
{
|
||||
[InitializeOnLoad]
|
||||
static class EditableSplineManager
|
||||
{
|
||||
// ONLY FOR TESTS. Used to add a path to the manager without requiring all the loops to get to it.
|
||||
[EditorBrowsable(EditorBrowsableState.Never)]
|
||||
internal sealed class TestManagedSpline : IDisposable
|
||||
{
|
||||
readonly UObject m_Target;
|
||||
|
||||
public TestManagedSpline(UObject target, IEditableSpline spline)
|
||||
{
|
||||
m_Target = target;
|
||||
spline.conversionTarget = m_Target;
|
||||
if (!s_Splines.TryGetValue(target, out TargetData data))
|
||||
{
|
||||
data = new TargetData();
|
||||
s_Splines.Add(target, data);
|
||||
}
|
||||
|
||||
data.RawSplines.Clear();
|
||||
data.EditableSplines = new[] { spline };
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
if (m_Target != null)
|
||||
s_Splines.Remove(m_Target);
|
||||
}
|
||||
}
|
||||
|
||||
internal sealed class TargetData
|
||||
{
|
||||
public readonly List<Spline> RawSplines = new List<Spline>();
|
||||
public IEditableSpline[] EditableSplines = new IEditableSpline[0];
|
||||
}
|
||||
|
||||
static readonly List<UObject> s_OwnersBuffer = new List<UObject>();
|
||||
static readonly Dictionary<UObject, TargetData> s_Splines = new Dictionary<UObject, TargetData>();
|
||||
static readonly Dictionary<UObject, TargetData> s_SplinesBackup = new Dictionary<UObject, TargetData>();
|
||||
|
||||
static EditableSplineManager()
|
||||
{
|
||||
AssemblyReloadEvents.beforeAssemblyReload += OnWillDomainReload;
|
||||
Selection.selectionChanged += OnSelectionChanged;
|
||||
Undo.undoRedoPerformed += OnUndoRedoPerformed;
|
||||
OnSelectionChanged();
|
||||
}
|
||||
|
||||
static void OnWillDomainReload()
|
||||
{
|
||||
FreeEntireCache();
|
||||
}
|
||||
|
||||
static void OnSelectionChanged()
|
||||
{
|
||||
UObject[] selection = Selection.GetFiltered(typeof(ISplineProvider), SelectionMode.Editable);
|
||||
UpdateSelection(selection);
|
||||
}
|
||||
|
||||
static void OnUndoRedoPerformed()
|
||||
{
|
||||
FreeEntireCache();
|
||||
}
|
||||
|
||||
internal static bool TryGetTargetData(UObject target, out TargetData targetData)
|
||||
{
|
||||
return s_Splines.TryGetValue(target, out targetData);
|
||||
}
|
||||
|
||||
public static IReadOnlyList<IEditableSpline> GetEditableSplines(UObject target, bool createIfNotCached = true)
|
||||
{
|
||||
if (target == null)
|
||||
return null;
|
||||
|
||||
if (!s_Splines.TryGetValue(target, out TargetData result))
|
||||
{
|
||||
if (!createIfNotCached)
|
||||
return null;
|
||||
|
||||
var splineProvider = target as ISplineProvider;
|
||||
if (splineProvider == null)
|
||||
return null;
|
||||
|
||||
TargetData data = new TargetData();
|
||||
|
||||
var targetSplines = splineProvider.Splines;
|
||||
if (targetSplines != null)
|
||||
data.RawSplines.AddRange(targetSplines);
|
||||
|
||||
s_Splines.Add(target, data);
|
||||
result = data;
|
||||
|
||||
SplineConversionUtility.UpdateEditableSplinesForTarget(target);
|
||||
}
|
||||
|
||||
return result.EditableSplines;
|
||||
}
|
||||
|
||||
public static void GetTargetsOfType(Type type, List<UObject> results)
|
||||
{
|
||||
ValidatePathOwners();
|
||||
results.Clear();
|
||||
foreach (var target in s_Splines.Keys)
|
||||
{
|
||||
if (target != null && type.IsInstanceOfType(target))
|
||||
{
|
||||
results.Add(target);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static void EnsureCacheForTarget(UObject target)
|
||||
{
|
||||
GetEditableSplines(target);
|
||||
}
|
||||
|
||||
public static void UpdateSelection(IEnumerable<UObject> selected)
|
||||
{
|
||||
if (selected == null)
|
||||
return;
|
||||
|
||||
//Copy to backup
|
||||
s_SplinesBackup.Clear();
|
||||
foreach (var keyValuePair in s_Splines)
|
||||
{
|
||||
s_SplinesBackup.Add(keyValuePair.Key, keyValuePair.Value);
|
||||
}
|
||||
|
||||
//Copy all that are still selected to real dictionary and ensure cache for newly selected
|
||||
s_Splines.Clear();
|
||||
foreach (var target in selected)
|
||||
{
|
||||
if (target != null)
|
||||
{
|
||||
if (s_SplinesBackup.TryGetValue(target, out TargetData data))
|
||||
{
|
||||
s_Splines.Add(target, data);
|
||||
s_SplinesBackup.Remove(target);
|
||||
}
|
||||
else
|
||||
{
|
||||
EnsureCacheForTarget(target);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static void FreeEntireCache()
|
||||
{
|
||||
s_Splines.Clear();
|
||||
}
|
||||
|
||||
public static void FreeCacheForTarget(UObject target)
|
||||
{
|
||||
if (s_Splines.TryGetValue(target, out TargetData data))
|
||||
{
|
||||
s_Splines.Remove(target);
|
||||
}
|
||||
}
|
||||
|
||||
static void ValidatePathOwners()
|
||||
{
|
||||
s_OwnersBuffer.Clear();
|
||||
foreach (var data in s_Splines)
|
||||
{
|
||||
// A dictionary key will never be fully null but the object could be destroyed
|
||||
if (data.Key == null)
|
||||
s_OwnersBuffer.Add(data.Key);
|
||||
}
|
||||
|
||||
foreach (var o in s_OwnersBuffer)
|
||||
{
|
||||
s_Splines.Remove(o);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 09cdc9b3ee4c5964082e9f6bae28aab6
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
|
@ -0,0 +1,72 @@
|
|||
using System;
|
||||
using Unity.Mathematics;
|
||||
using UnityEngine;
|
||||
|
||||
namespace UnityEditor.Splines
|
||||
{
|
||||
[Serializable]
|
||||
sealed class EditableTangent : ISplineElement
|
||||
{
|
||||
internal event Action directionChanged;
|
||||
|
||||
[SerializeField]
|
||||
float3 m_LocalPosition;
|
||||
|
||||
/// <summary> Local (knot space) position of the tangent. </summary>
|
||||
public float3 localPosition
|
||||
{
|
||||
get => m_LocalPosition;
|
||||
set
|
||||
{
|
||||
if (m_LocalPosition.Equals(value))
|
||||
return;
|
||||
|
||||
m_LocalPosition = value;
|
||||
directionChanged?.Invoke();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary> World space direction of the tangent. </summary>
|
||||
public float3 direction
|
||||
{
|
||||
get => owner.localToWorldMatrix.MultiplyVector(localPosition);
|
||||
set => localPosition = owner.worldToLocalMatrix.MultiplyVector(value);
|
||||
}
|
||||
|
||||
/// <summary> World space position of the tangent. </summary>
|
||||
public float3 position
|
||||
{
|
||||
get => owner.localToWorldMatrix.MultiplyPoint3x4(localPosition);
|
||||
set => localPosition = owner.worldToLocalMatrix.MultiplyPoint3x4(value);
|
||||
}
|
||||
|
||||
internal void SetLocalPositionNoNotify(float3 localPosition)
|
||||
{
|
||||
m_LocalPosition = localPosition;
|
||||
}
|
||||
|
||||
public int tangentIndex { get; private set; }
|
||||
|
||||
public EditableKnot owner { get; private set; }
|
||||
|
||||
/// <summary> Matrix that transforms a point from local (tangent) into world space. </summary>
|
||||
public Matrix4x4 localToWorldMatrix => owner.localToWorldMatrix *
|
||||
Matrix4x4.TRS(localPosition, quaternion.identity, Vector3.one);
|
||||
/// <summary> Matrix that transforms a point from world space into local (tangent) space. </summary>
|
||||
public Matrix4x4 worldToLocalMatrix => localToWorldMatrix.inverse;
|
||||
|
||||
public EditableTangent() : this(null, -1) {}
|
||||
|
||||
public EditableTangent(EditableKnot owner, int tangentIndex)
|
||||
{
|
||||
this.owner = owner;
|
||||
this.tangentIndex = tangentIndex;
|
||||
}
|
||||
|
||||
public void Copy(EditableTangent other)
|
||||
{
|
||||
tangentIndex = other.tangentIndex;
|
||||
m_LocalPosition = other.localPosition;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
fileFormatVersion: 2
|
||||
guid: b5cfd6cf4523fe044831ed4f356a5472
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
|
@ -0,0 +1,31 @@
|
|||
using UnityEditor.SettingsManagement;
|
||||
using UnityEngine;
|
||||
|
||||
namespace UnityEditor.Splines
|
||||
{
|
||||
sealed class PathSettings
|
||||
{
|
||||
static Settings s_SettingsInstance;
|
||||
|
||||
public static Settings instance
|
||||
{
|
||||
get
|
||||
{
|
||||
if (s_SettingsInstance == null)
|
||||
s_SettingsInstance = new Settings(new [] { new UserSettingsRepository() });
|
||||
return s_SettingsInstance;
|
||||
}
|
||||
}
|
||||
|
||||
// Register a new SettingsProvider that will scrape the owning assembly for [UserSetting] marked fields.
|
||||
[SettingsProvider]
|
||||
static SettingsProvider CreateSettingsProvider()
|
||||
{
|
||||
var provider = new UserSettingsProvider("Preferences/Splines",
|
||||
instance,
|
||||
new[] { typeof(PathSettings).Assembly });
|
||||
|
||||
return provider;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 0834e0204621424449fe7f88d7127f07
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
|
@ -0,0 +1,120 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
using Object = UnityEngine.Object;
|
||||
|
||||
namespace UnityEditor.Splines
|
||||
{
|
||||
[Serializable]
|
||||
struct SelectableSplineElement : IEquatable<SelectableSplineElement>, IEquatable<EditableKnot>, IEquatable<EditableTangent>
|
||||
{
|
||||
public Object target;
|
||||
public int pathIndex;
|
||||
public int knotIndex;
|
||||
public int tangentIndex; //-1 if knot
|
||||
|
||||
public SelectableSplineElement(ISplineElement element)
|
||||
{
|
||||
target = default;
|
||||
pathIndex = -1;
|
||||
knotIndex = -1;
|
||||
tangentIndex = -1;
|
||||
|
||||
EditableKnot knot = null;
|
||||
if (element is EditableKnot knotElement)
|
||||
knot = knotElement;
|
||||
else if (element is EditableTangent tangent)
|
||||
{
|
||||
knot = tangent.owner;
|
||||
tangentIndex = tangent.tangentIndex;
|
||||
}
|
||||
|
||||
if (knot != null)
|
||||
{
|
||||
target = knot.spline.conversionTarget;
|
||||
pathIndex = knot.spline.conversionIndex;
|
||||
knotIndex = knot.index;
|
||||
}
|
||||
}
|
||||
|
||||
public bool isTangent => tangentIndex >= 0;
|
||||
public bool isKnot => tangentIndex < 0;
|
||||
|
||||
public bool Equals(EditableKnot other)
|
||||
{
|
||||
return IsTargetedKnot(other) && tangentIndex < 0;
|
||||
}
|
||||
|
||||
public bool Equals(EditableTangent other)
|
||||
{
|
||||
return other != null && IsTargetedKnot(other.owner) && tangentIndex == other.tangentIndex;
|
||||
}
|
||||
|
||||
public bool IsFromPath(IEditableSpline spline)
|
||||
{
|
||||
var pathInternal = spline;
|
||||
return pathInternal.conversionTarget == target && pathInternal.conversionIndex == pathIndex;
|
||||
}
|
||||
|
||||
bool IsTargetedKnot(EditableKnot knot)
|
||||
{
|
||||
if (knot == null)
|
||||
return false;
|
||||
|
||||
return knotIndex == knot.index
|
||||
&& pathIndex == knot.spline.conversionIndex
|
||||
&& target == knot.spline.conversionTarget;
|
||||
}
|
||||
|
||||
public bool Equals(SelectableSplineElement other)
|
||||
{
|
||||
return target == other.target && pathIndex == other.pathIndex && knotIndex == other.knotIndex && tangentIndex == other.tangentIndex;
|
||||
}
|
||||
|
||||
public override bool Equals(object obj)
|
||||
{
|
||||
if (ReferenceEquals(null, obj)) return false;
|
||||
return obj is SelectableSplineElement other && Equals(other);
|
||||
}
|
||||
|
||||
public override int GetHashCode()
|
||||
{
|
||||
unchecked
|
||||
{
|
||||
var hashCode = (target != null ? target.GetHashCode() : 0);
|
||||
hashCode = (hashCode * 397) ^ pathIndex;
|
||||
hashCode = (hashCode * 397) ^ knotIndex;
|
||||
hashCode = (hashCode * 397) ^ tangentIndex;
|
||||
return hashCode;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
sealed class SelectionContext : ScriptableObject
|
||||
{
|
||||
static SelectionContext s_Instance;
|
||||
|
||||
public List<SelectableSplineElement> selection = new List<SelectableSplineElement>();
|
||||
public int version;
|
||||
|
||||
public static SelectionContext instance
|
||||
{
|
||||
get
|
||||
{
|
||||
if (s_Instance == null)
|
||||
{
|
||||
s_Instance = CreateInstance<SelectionContext>();
|
||||
s_Instance.hideFlags = HideFlags.HideAndDontSave;
|
||||
}
|
||||
|
||||
return s_Instance;
|
||||
}
|
||||
}
|
||||
|
||||
SelectionContext()
|
||||
{
|
||||
if (s_Instance == null)
|
||||
s_Instance = this;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 33dac614537a60e43ac2b6692f9dc39a
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
|
@ -0,0 +1,476 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using Unity.Mathematics;
|
||||
using UnityEngine;
|
||||
using UnityEngine.Splines;
|
||||
using Object = UnityEngine.Object;
|
||||
|
||||
namespace UnityEditor.Splines
|
||||
{
|
||||
interface ISplineElement
|
||||
{
|
||||
float3 position { get; set; }
|
||||
float3 localPosition { get; set; }
|
||||
}
|
||||
|
||||
static class SplineSelection
|
||||
{
|
||||
public static event Action changed;
|
||||
|
||||
static readonly List<SelectableSplineElement> s_ElementBuffer = new List<SelectableSplineElement>();
|
||||
static HashSet<Object> s_ObjectBuffer = new HashSet<Object>();
|
||||
|
||||
static SelectionContext context => SelectionContext.instance;
|
||||
static List<SelectableSplineElement> selection => context.selection;
|
||||
|
||||
static int s_SelectionVersion;
|
||||
|
||||
static SplineSelection()
|
||||
{
|
||||
context.version = 0;
|
||||
|
||||
Undo.undoRedoPerformed += OnUndoRedoPerformed;
|
||||
}
|
||||
|
||||
static void OnUndoRedoPerformed()
|
||||
{
|
||||
if (context.version != s_SelectionVersion)
|
||||
{
|
||||
s_SelectionVersion = context.version;
|
||||
NotifySelectionChanged();
|
||||
}
|
||||
}
|
||||
|
||||
public static void Clear()
|
||||
{
|
||||
if (selection.Count == 0)
|
||||
return;
|
||||
|
||||
IncrementVersion();
|
||||
ClearNoUndo(true);
|
||||
}
|
||||
|
||||
internal static void ClearNoUndo(bool notify)
|
||||
{
|
||||
selection.Clear();
|
||||
if (notify)
|
||||
NotifySelectionChanged();
|
||||
}
|
||||
|
||||
static bool GetKnotFromElement(SelectableSplineElement element, out EditableKnot knot)
|
||||
{
|
||||
var paths = EditableSplineManager.GetEditableSplines(element.target, false);
|
||||
|
||||
if (paths == null || element.pathIndex >= paths.Count)
|
||||
{
|
||||
knot = null;
|
||||
return false;
|
||||
}
|
||||
|
||||
var path = paths[element.pathIndex];
|
||||
if (element.knotIndex < 0 || element.knotIndex >= path.knotCount)
|
||||
{
|
||||
knot = null;
|
||||
return false;
|
||||
}
|
||||
|
||||
knot = path.GetKnot(element.knotIndex);
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool GetTangentFromElement(SelectableSplineElement element, out EditableTangent tangent)
|
||||
{
|
||||
if (!GetKnotFromElement(element, out EditableKnot knot))
|
||||
{
|
||||
tangent = null;
|
||||
return false;
|
||||
}
|
||||
|
||||
tangent = knot.GetTangent(element.tangentIndex);
|
||||
return tangent != null;
|
||||
}
|
||||
|
||||
public static void GetSelectedKnots(List<EditableKnot> knots)
|
||||
{
|
||||
knots.Clear();
|
||||
foreach (var element in selection)
|
||||
if (element.isKnot && GetKnotFromElement(element, out EditableKnot knot))
|
||||
knots.Add(knot);
|
||||
}
|
||||
|
||||
public static void GetSelectedKnots(IEnumerable<Object> targets, List<EditableKnot> knots)
|
||||
{
|
||||
knots.Clear();
|
||||
GetSelectedElementsInternal(targets, s_ElementBuffer);
|
||||
foreach (var element in s_ElementBuffer)
|
||||
if (element.isKnot && GetKnotFromElement(element, out EditableKnot knot))
|
||||
knots.Add(knot);
|
||||
}
|
||||
|
||||
public static void GetSelectedTangents(List<EditableTangent> tangents)
|
||||
{
|
||||
tangents.Clear();
|
||||
foreach (var element in selection)
|
||||
if (element.isTangent && GetTangentFromElement(element, out EditableTangent tangent))
|
||||
tangents.Add(tangent);
|
||||
}
|
||||
|
||||
public static void GetSelectedTangents(IEnumerable<Object> targets, List<EditableTangent> tangents)
|
||||
{
|
||||
tangents.Clear();
|
||||
GetSelectedElementsInternal(targets, s_ElementBuffer);
|
||||
foreach (var element in s_ElementBuffer)
|
||||
if (element.isTangent && GetTangentFromElement(element, out EditableTangent tangent))
|
||||
tangents.Add(tangent);
|
||||
}
|
||||
|
||||
public static int count => selection.Count;
|
||||
|
||||
static ISplineElement ToSplineElement(SelectableSplineElement rawElement)
|
||||
{
|
||||
if (rawElement.isKnot)
|
||||
{
|
||||
if (GetKnotFromElement(rawElement, out EditableKnot knot))
|
||||
return knot;
|
||||
}
|
||||
else if (rawElement.isTangent)
|
||||
{
|
||||
if (GetTangentFromElement(rawElement, out EditableTangent tangent))
|
||||
return tangent;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public static ISplineElement GetActiveElement()
|
||||
{
|
||||
//Get first valid element
|
||||
foreach (var rawElement in selection)
|
||||
{
|
||||
var element = ToSplineElement(rawElement);
|
||||
if (element != null)
|
||||
return element;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public static void GetSelectedElements(ICollection<ISplineElement> elements)
|
||||
{
|
||||
elements.Clear();
|
||||
foreach (var rawElement in selection)
|
||||
{
|
||||
var element = ToSplineElement(rawElement);
|
||||
if (element != null)
|
||||
elements.Add(element);
|
||||
}
|
||||
}
|
||||
|
||||
public static void GetSelectedElements(IEnumerable<Object> targets, ICollection<ISplineElement> elements)
|
||||
{
|
||||
elements.Clear();
|
||||
GetSelectedElementsInternal(targets, s_ElementBuffer);
|
||||
foreach (var rawElement in s_ElementBuffer)
|
||||
{
|
||||
var element = ToSplineElement(rawElement);
|
||||
if (element != null)
|
||||
elements.Add(element);
|
||||
}
|
||||
}
|
||||
|
||||
static void GetSelectedElementsInternal(IEnumerable<Object> targets, List<SelectableSplineElement> results)
|
||||
{
|
||||
results.Clear();
|
||||
foreach (var element in selection)
|
||||
foreach(var target in targets)
|
||||
{
|
||||
if(target != null && element.target == target)
|
||||
{
|
||||
results.Add(element);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static bool IsActiveElement(ISplineElement element)
|
||||
{
|
||||
switch (element)
|
||||
{
|
||||
case EditableKnot knot: return IsActiveElement(knot);
|
||||
case EditableTangent tangent: return IsActiveElement(tangent);
|
||||
default: return false;
|
||||
}
|
||||
}
|
||||
|
||||
public static bool IsActiveElement(EditableKnot knot)
|
||||
{
|
||||
return IsActiveElement(new SelectableSplineElement(knot));
|
||||
}
|
||||
|
||||
public static bool IsActiveElement(EditableTangent tangent)
|
||||
{
|
||||
return IsActiveElement(new SelectableSplineElement(tangent));
|
||||
}
|
||||
|
||||
static bool IsActiveElement(SelectableSplineElement element)
|
||||
{
|
||||
return selection.Count > 0 && selection[0].Equals(element);
|
||||
}
|
||||
|
||||
public static void SetActive(ISplineElement element)
|
||||
{
|
||||
switch (element)
|
||||
{
|
||||
case EditableKnot knot:
|
||||
SetActive(knot);
|
||||
break;
|
||||
case EditableTangent tangent:
|
||||
SetActive(tangent);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
public static void SetActive(EditableKnot knot)
|
||||
{
|
||||
SetActiveElement(new SelectableSplineElement(knot));
|
||||
}
|
||||
|
||||
public static void SetActive(EditableTangent tangent)
|
||||
{
|
||||
SetActiveElement(new SelectableSplineElement(tangent));
|
||||
}
|
||||
|
||||
static void SetActiveElement(SelectableSplineElement element)
|
||||
{
|
||||
int index = selection.IndexOf(element);
|
||||
if (index == 0)
|
||||
return;
|
||||
|
||||
IncrementVersion();
|
||||
|
||||
if (index > 0)
|
||||
selection.RemoveAt(index);
|
||||
|
||||
selection.Insert(0, element);
|
||||
|
||||
if(element.target is Component component)
|
||||
{
|
||||
//Set the active unity object so the spline is the first target
|
||||
Object[] unitySelection = Selection.objects;
|
||||
var target = component.gameObject;
|
||||
|
||||
index = Array.IndexOf(unitySelection, target);
|
||||
if(index > 0)
|
||||
{
|
||||
Object prevObj = unitySelection[0];
|
||||
unitySelection[0] = unitySelection[index];
|
||||
unitySelection[index] = prevObj;
|
||||
Selection.objects = unitySelection;
|
||||
}
|
||||
}
|
||||
|
||||
NotifySelectionChanged();
|
||||
}
|
||||
|
||||
public static void Add(ISplineElement element)
|
||||
{
|
||||
switch (element)
|
||||
{
|
||||
case EditableKnot knot:
|
||||
Add(knot);
|
||||
break;
|
||||
case EditableTangent tangent:
|
||||
Add(tangent);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
public static void Add(IEnumerable<ISplineElement> elements)
|
||||
{
|
||||
IncrementVersion();
|
||||
|
||||
bool changed = false;
|
||||
foreach (var element in elements)
|
||||
changed |= AddElement(new SelectableSplineElement(element));
|
||||
|
||||
if (changed)
|
||||
NotifySelectionChanged();
|
||||
}
|
||||
|
||||
public static void Add(EditableKnot knot)
|
||||
{
|
||||
IncrementVersion();
|
||||
|
||||
if (AddElement(new SelectableSplineElement(knot)))
|
||||
NotifySelectionChanged();
|
||||
}
|
||||
|
||||
public static void Add(IEnumerable<EditableKnot> knots)
|
||||
{
|
||||
IncrementVersion();
|
||||
|
||||
bool changed = false;
|
||||
foreach (var knot in knots)
|
||||
changed |= AddElement(new SelectableSplineElement(knot));
|
||||
|
||||
if (changed)
|
||||
NotifySelectionChanged();
|
||||
}
|
||||
|
||||
public static void Add(EditableTangent tangent)
|
||||
{
|
||||
IncrementVersion();
|
||||
|
||||
if (AddElement(new SelectableSplineElement(tangent)))
|
||||
NotifySelectionChanged();
|
||||
}
|
||||
|
||||
public static void Add(IEnumerable<EditableTangent> tangents)
|
||||
{
|
||||
IncrementVersion();
|
||||
|
||||
bool changed = false;
|
||||
foreach (var tangent in tangents)
|
||||
changed |= AddElement(new SelectableSplineElement(tangent));
|
||||
|
||||
if (changed)
|
||||
NotifySelectionChanged();
|
||||
}
|
||||
|
||||
static bool AddElement(SelectableSplineElement element)
|
||||
{
|
||||
if (!selection.Contains(element))
|
||||
{
|
||||
selection.Insert(0,element);
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public static bool Remove(ISplineElement element)
|
||||
{
|
||||
switch (element)
|
||||
{
|
||||
case EditableKnot knot: return Remove(knot);
|
||||
case EditableTangent tangent: return Remove(tangent);
|
||||
default: return false;
|
||||
}
|
||||
}
|
||||
|
||||
public static bool Remove(EditableKnot knot)
|
||||
{
|
||||
IncrementVersion();
|
||||
|
||||
return RemoveElement(new SelectableSplineElement(knot));
|
||||
}
|
||||
|
||||
public static bool Remove(EditableTangent tangent)
|
||||
{
|
||||
IncrementVersion();
|
||||
|
||||
return RemoveElement(new SelectableSplineElement(tangent));
|
||||
}
|
||||
|
||||
static bool RemoveElement(SelectableSplineElement element)
|
||||
{
|
||||
if (selection.Remove(element))
|
||||
{
|
||||
NotifySelectionChanged();
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public static bool Contains(ISplineElement element)
|
||||
{
|
||||
switch (element)
|
||||
{
|
||||
case EditableKnot knot: return Contains(knot);
|
||||
case EditableTangent tangent: return Contains(tangent);
|
||||
default: return false;
|
||||
}
|
||||
}
|
||||
|
||||
public static bool Contains(EditableKnot knot)
|
||||
{
|
||||
return ContainsElement(new SelectableSplineElement(knot));
|
||||
}
|
||||
|
||||
public static bool Contains(EditableTangent tangent)
|
||||
{
|
||||
return ContainsElement(new SelectableSplineElement(tangent));
|
||||
}
|
||||
|
||||
static bool ContainsElement(SelectableSplineElement element)
|
||||
{
|
||||
return selection.Contains(element);
|
||||
}
|
||||
|
||||
internal static void UpdateObjectSelection(IEnumerable<Object> targets)
|
||||
{
|
||||
s_ObjectBuffer.Clear();
|
||||
foreach (var target in targets)
|
||||
if (target != null)
|
||||
s_ObjectBuffer.Add(target);
|
||||
|
||||
IncrementVersion();
|
||||
if (selection.RemoveAll(ObjectRemovePredicate) > 0)
|
||||
NotifySelectionChanged();
|
||||
}
|
||||
|
||||
static bool ObjectRemovePredicate(SelectableSplineElement element)
|
||||
{
|
||||
return !s_ObjectBuffer.Contains(element.target);
|
||||
}
|
||||
|
||||
//Used when inserting new elements in spline
|
||||
internal static void MoveAllIndexUpFromIndexToEnd(IEditableSpline spline, int index)
|
||||
{
|
||||
for (var i = 0; i < selection.Count; ++i)
|
||||
{
|
||||
var knot = selection[i];
|
||||
if (knot.IsFromPath(spline))
|
||||
{
|
||||
if (knot.knotIndex >= index)
|
||||
++knot.knotIndex;
|
||||
|
||||
selection[i] = knot;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//Used when deleting an element in spline
|
||||
internal static void OnKnotRemoved(IEditableSpline spline, int index)
|
||||
{
|
||||
for (var i = selection.Count - 1; i >= 0; --i)
|
||||
{
|
||||
var knot = selection[i];
|
||||
if (knot.IsFromPath(spline))
|
||||
{
|
||||
if (knot.knotIndex == index)
|
||||
selection.RemoveAt(i);
|
||||
else if (knot.knotIndex >= index)
|
||||
{
|
||||
--knot.knotIndex;
|
||||
selection[i] = knot;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void IncrementVersion()
|
||||
{
|
||||
Undo.RecordObject(context, "Spline Selection Changed");
|
||||
|
||||
++s_SelectionVersion;
|
||||
++context.version;
|
||||
}
|
||||
|
||||
static void NotifySelectionChanged()
|
||||
{
|
||||
changed?.Invoke();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 7529a2acb193d1946b9ce1ecb41dd4d5
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
|
@ -0,0 +1,3 @@
|
|||
fileFormatVersion: 2
|
||||
guid: f0575e8c5ac74a399a62fcb72fe081c4
|
||||
timeCreated: 1634919075
|
|
@ -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:
|
|
@ -0,0 +1,8 @@
|
|||
fileFormatVersion: 2
|
||||
guid: d96dfe6cb862ab34894ef767abef2e84
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
|
@ -0,0 +1,8 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 80c4e990f69fadf42a06e238da57fbfd
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
After Width: | Height: | Size: 324 B |
|
@ -0,0 +1,181 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 954ac4bd24b73a540a2e84414caf856c
|
||||
TextureImporter:
|
||||
internalIDToNameTable: []
|
||||
externalObjects: {}
|
||||
serializedVersion: 11
|
||||
mipmaps:
|
||||
mipMapMode: 0
|
||||
enableMipMap: 0
|
||||
sRGBTexture: 1
|
||||
linearTexture: 0
|
||||
fadeOut: 0
|
||||
borderMipMap: 0
|
||||
mipMapsPreserveCoverage: 0
|
||||
alphaTestReferenceValue: 0.5
|
||||
mipMapFadeDistanceStart: 1
|
||||
mipMapFadeDistanceEnd: 3
|
||||
bumpmap:
|
||||
convertToNormalMap: 0
|
||||
externalNormalMap: 0
|
||||
heightScale: 0.25
|
||||
normalMapFilter: 0
|
||||
isReadable: 0
|
||||
streamingMipmaps: 0
|
||||
streamingMipmapsPriority: 0
|
||||
vTOnly: 0
|
||||
grayScaleToAlpha: 0
|
||||
generateCubemap: 6
|
||||
cubemapConvolution: 0
|
||||
seamlessCubemap: 0
|
||||
textureFormat: 1
|
||||
maxTextureSize: 2048
|
||||
textureSettings:
|
||||
serializedVersion: 2
|
||||
filterMode: 0
|
||||
aniso: 1
|
||||
mipBias: 0
|
||||
wrapU: 1
|
||||
wrapV: 1
|
||||
wrapW: 0
|
||||
nPOTScale: 0
|
||||
lightmap: 0
|
||||
compressionQuality: 50
|
||||
spriteMode: 0
|
||||
spriteExtrude: 1
|
||||
spriteMeshType: 1
|
||||
alignment: 0
|
||||
spritePivot: {x: 0.5, y: 0.5}
|
||||
spritePixelsToUnits: 100
|
||||
spriteBorder: {x: 0, y: 0, z: 0, w: 0}
|
||||
spriteGenerateFallbackPhysicsShape: 1
|
||||
alphaUsage: 1
|
||||
alphaIsTransparency: 1
|
||||
spriteTessellationDetail: -1
|
||||
textureType: 2
|
||||
textureShape: 1
|
||||
singleChannelComponent: 0
|
||||
flipbookRows: 1
|
||||
flipbookColumns: 1
|
||||
maxTextureSizeSet: 0
|
||||
compressionQualitySet: 0
|
||||
textureFormatSet: 0
|
||||
ignorePngGamma: 0
|
||||
applyGammaDecoding: 1
|
||||
platformSettings:
|
||||
- serializedVersion: 3
|
||||
buildTarget: DefaultTexturePlatform
|
||||
maxTextureSize: 64
|
||||
resizeAlgorithm: 0
|
||||
textureFormat: -1
|
||||
textureCompression: 0
|
||||
compressionQuality: 50
|
||||
crunchedCompression: 0
|
||||
allowsAlphaSplitting: 0
|
||||
overridden: 0
|
||||
androidETC2FallbackOverride: 0
|
||||
forceMaximumCompressionQuality_BC6H_BC7: 0
|
||||
- serializedVersion: 3
|
||||
buildTarget: Standalone
|
||||
maxTextureSize: 64
|
||||
resizeAlgorithm: 0
|
||||
textureFormat: -1
|
||||
textureCompression: 1
|
||||
compressionQuality: 50
|
||||
crunchedCompression: 0
|
||||
allowsAlphaSplitting: 0
|
||||
overridden: 0
|
||||
androidETC2FallbackOverride: 0
|
||||
forceMaximumCompressionQuality_BC6H_BC7: 0
|
||||
- serializedVersion: 3
|
||||
buildTarget: Android
|
||||
maxTextureSize: 64
|
||||
resizeAlgorithm: 0
|
||||
textureFormat: -1
|
||||
textureCompression: 1
|
||||
compressionQuality: 50
|
||||
crunchedCompression: 0
|
||||
allowsAlphaSplitting: 0
|
||||
overridden: 0
|
||||
androidETC2FallbackOverride: 0
|
||||
forceMaximumCompressionQuality_BC6H_BC7: 0
|
||||
- serializedVersion: 3
|
||||
buildTarget: iPhone
|
||||
maxTextureSize: 64
|
||||
resizeAlgorithm: 0
|
||||
textureFormat: -1
|
||||
textureCompression: 1
|
||||
compressionQuality: 50
|
||||
crunchedCompression: 0
|
||||
allowsAlphaSplitting: 0
|
||||
overridden: 0
|
||||
androidETC2FallbackOverride: 0
|
||||
forceMaximumCompressionQuality_BC6H_BC7: 0
|
||||
- serializedVersion: 3
|
||||
buildTarget: Windows Store Apps
|
||||
maxTextureSize: 64
|
||||
resizeAlgorithm: 0
|
||||
textureFormat: -1
|
||||
textureCompression: 1
|
||||
compressionQuality: 50
|
||||
crunchedCompression: 0
|
||||
allowsAlphaSplitting: 0
|
||||
overridden: 0
|
||||
androidETC2FallbackOverride: 0
|
||||
forceMaximumCompressionQuality_BC6H_BC7: 0
|
||||
- serializedVersion: 3
|
||||
buildTarget: tvOS
|
||||
maxTextureSize: 64
|
||||
resizeAlgorithm: 0
|
||||
textureFormat: -1
|
||||
textureCompression: 1
|
||||
compressionQuality: 50
|
||||
crunchedCompression: 0
|
||||
allowsAlphaSplitting: 0
|
||||
overridden: 0
|
||||
androidETC2FallbackOverride: 0
|
||||
forceMaximumCompressionQuality_BC6H_BC7: 0
|
||||
- serializedVersion: 3
|
||||
buildTarget: Lumin
|
||||
maxTextureSize: 64
|
||||
resizeAlgorithm: 0
|
||||
textureFormat: -1
|
||||
textureCompression: 1
|
||||
compressionQuality: 50
|
||||
crunchedCompression: 0
|
||||
allowsAlphaSplitting: 0
|
||||
overridden: 0
|
||||
androidETC2FallbackOverride: 0
|
||||
forceMaximumCompressionQuality_BC6H_BC7: 0
|
||||
- serializedVersion: 3
|
||||
buildTarget: CloudRendering
|
||||
maxTextureSize: 64
|
||||
resizeAlgorithm: 0
|
||||
textureFormat: -1
|
||||
textureCompression: 1
|
||||
compressionQuality: 50
|
||||
crunchedCompression: 0
|
||||
allowsAlphaSplitting: 0
|
||||
overridden: 0
|
||||
androidETC2FallbackOverride: 0
|
||||
forceMaximumCompressionQuality_BC6H_BC7: 0
|
||||
spriteSheet:
|
||||
serializedVersion: 2
|
||||
sprites: []
|
||||
outline: []
|
||||
physicsShape: []
|
||||
bones: []
|
||||
spriteID:
|
||||
internalID: 0
|
||||
vertices: []
|
||||
indices:
|
||||
edges: []
|
||||
weights: []
|
||||
secondaryTextures: []
|
||||
nameFileIdTable: {}
|
||||
spritePackingTag:
|
||||
pSDRemoveMatte: 0
|
||||
pSDShowRemoveMatteOption: 0
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
After Width: | Height: | Size: 540 B |
|
@ -0,0 +1,181 @@
|
|||
fileFormatVersion: 2
|
||||
guid: fdacd376014f8b743a9561d5652ca823
|
||||
TextureImporter:
|
||||
internalIDToNameTable: []
|
||||
externalObjects: {}
|
||||
serializedVersion: 11
|
||||
mipmaps:
|
||||
mipMapMode: 0
|
||||
enableMipMap: 0
|
||||
sRGBTexture: 1
|
||||
linearTexture: 0
|
||||
fadeOut: 0
|
||||
borderMipMap: 0
|
||||
mipMapsPreserveCoverage: 0
|
||||
alphaTestReferenceValue: 0.5
|
||||
mipMapFadeDistanceStart: 1
|
||||
mipMapFadeDistanceEnd: 3
|
||||
bumpmap:
|
||||
convertToNormalMap: 0
|
||||
externalNormalMap: 0
|
||||
heightScale: 0.25
|
||||
normalMapFilter: 0
|
||||
isReadable: 0
|
||||
streamingMipmaps: 0
|
||||
streamingMipmapsPriority: 0
|
||||
vTOnly: 0
|
||||
grayScaleToAlpha: 0
|
||||
generateCubemap: 6
|
||||
cubemapConvolution: 0
|
||||
seamlessCubemap: 0
|
||||
textureFormat: 1
|
||||
maxTextureSize: 2048
|
||||
textureSettings:
|
||||
serializedVersion: 2
|
||||
filterMode: 0
|
||||
aniso: 1
|
||||
mipBias: 0
|
||||
wrapU: 1
|
||||
wrapV: 1
|
||||
wrapW: 0
|
||||
nPOTScale: 0
|
||||
lightmap: 0
|
||||
compressionQuality: 50
|
||||
spriteMode: 0
|
||||
spriteExtrude: 1
|
||||
spriteMeshType: 1
|
||||
alignment: 0
|
||||
spritePivot: {x: 0.5, y: 0.5}
|
||||
spritePixelsToUnits: 100
|
||||
spriteBorder: {x: 0, y: 0, z: 0, w: 0}
|
||||
spriteGenerateFallbackPhysicsShape: 1
|
||||
alphaUsage: 1
|
||||
alphaIsTransparency: 1
|
||||
spriteTessellationDetail: -1
|
||||
textureType: 2
|
||||
textureShape: 1
|
||||
singleChannelComponent: 0
|
||||
flipbookRows: 1
|
||||
flipbookColumns: 1
|
||||
maxTextureSizeSet: 0
|
||||
compressionQualitySet: 0
|
||||
textureFormatSet: 0
|
||||
ignorePngGamma: 0
|
||||
applyGammaDecoding: 1
|
||||
platformSettings:
|
||||
- serializedVersion: 3
|
||||
buildTarget: DefaultTexturePlatform
|
||||
maxTextureSize: 64
|
||||
resizeAlgorithm: 0
|
||||
textureFormat: -1
|
||||
textureCompression: 0
|
||||
compressionQuality: 50
|
||||
crunchedCompression: 0
|
||||
allowsAlphaSplitting: 0
|
||||
overridden: 0
|
||||
androidETC2FallbackOverride: 0
|
||||
forceMaximumCompressionQuality_BC6H_BC7: 0
|
||||
- serializedVersion: 3
|
||||
buildTarget: Standalone
|
||||
maxTextureSize: 2048
|
||||
resizeAlgorithm: 0
|
||||
textureFormat: -1
|
||||
textureCompression: 1
|
||||
compressionQuality: 50
|
||||
crunchedCompression: 0
|
||||
allowsAlphaSplitting: 0
|
||||
overridden: 0
|
||||
androidETC2FallbackOverride: 0
|
||||
forceMaximumCompressionQuality_BC6H_BC7: 0
|
||||
- serializedVersion: 3
|
||||
buildTarget: Android
|
||||
maxTextureSize: 64
|
||||
resizeAlgorithm: 0
|
||||
textureFormat: -1
|
||||
textureCompression: 1
|
||||
compressionQuality: 50
|
||||
crunchedCompression: 0
|
||||
allowsAlphaSplitting: 0
|
||||
overridden: 0
|
||||
androidETC2FallbackOverride: 0
|
||||
forceMaximumCompressionQuality_BC6H_BC7: 0
|
||||
- serializedVersion: 3
|
||||
buildTarget: iPhone
|
||||
maxTextureSize: 64
|
||||
resizeAlgorithm: 0
|
||||
textureFormat: -1
|
||||
textureCompression: 1
|
||||
compressionQuality: 50
|
||||
crunchedCompression: 0
|
||||
allowsAlphaSplitting: 0
|
||||
overridden: 0
|
||||
androidETC2FallbackOverride: 0
|
||||
forceMaximumCompressionQuality_BC6H_BC7: 0
|
||||
- serializedVersion: 3
|
||||
buildTarget: Windows Store Apps
|
||||
maxTextureSize: 64
|
||||
resizeAlgorithm: 0
|
||||
textureFormat: -1
|
||||
textureCompression: 1
|
||||
compressionQuality: 50
|
||||
crunchedCompression: 0
|
||||
allowsAlphaSplitting: 0
|
||||
overridden: 0
|
||||
androidETC2FallbackOverride: 0
|
||||
forceMaximumCompressionQuality_BC6H_BC7: 0
|
||||
- serializedVersion: 3
|
||||
buildTarget: tvOS
|
||||
maxTextureSize: 64
|
||||
resizeAlgorithm: 0
|
||||
textureFormat: -1
|
||||
textureCompression: 1
|
||||
compressionQuality: 50
|
||||
crunchedCompression: 0
|
||||
allowsAlphaSplitting: 0
|
||||
overridden: 0
|
||||
androidETC2FallbackOverride: 0
|
||||
forceMaximumCompressionQuality_BC6H_BC7: 0
|
||||
- serializedVersion: 3
|
||||
buildTarget: Lumin
|
||||
maxTextureSize: 64
|
||||
resizeAlgorithm: 0
|
||||
textureFormat: -1
|
||||
textureCompression: 1
|
||||
compressionQuality: 50
|
||||
crunchedCompression: 0
|
||||
allowsAlphaSplitting: 0
|
||||
overridden: 0
|
||||
androidETC2FallbackOverride: 0
|
||||
forceMaximumCompressionQuality_BC6H_BC7: 0
|
||||
- serializedVersion: 3
|
||||
buildTarget: CloudRendering
|
||||
maxTextureSize: 64
|
||||
resizeAlgorithm: 0
|
||||
textureFormat: -1
|
||||
textureCompression: 1
|
||||
compressionQuality: 50
|
||||
crunchedCompression: 0
|
||||
allowsAlphaSplitting: 0
|
||||
overridden: 0
|
||||
androidETC2FallbackOverride: 0
|
||||
forceMaximumCompressionQuality_BC6H_BC7: 0
|
||||
spriteSheet:
|
||||
serializedVersion: 2
|
||||
sprites: []
|
||||
outline: []
|
||||
physicsShape: []
|
||||
bones: []
|
||||
spriteID:
|
||||
internalID: 0
|
||||
vertices: []
|
||||
indices:
|
||||
edges: []
|
||||
weights: []
|
||||
secondaryTextures: []
|
||||
nameFileIdTable: {}
|
||||
spritePackingTag:
|
||||
pSDRemoveMatte: 0
|
||||
pSDShowRemoveMatteOption: 0
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
After Width: | Height: | Size: 424 B |
|
@ -0,0 +1,147 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 3735bf6bd1171ad43896aaea4a3b417f
|
||||
TextureImporter:
|
||||
internalIDToNameTable: []
|
||||
externalObjects: {}
|
||||
serializedVersion: 11
|
||||
mipmaps:
|
||||
mipMapMode: 0
|
||||
enableMipMap: 1
|
||||
sRGBTexture: 1
|
||||
linearTexture: 0
|
||||
fadeOut: 0
|
||||
borderMipMap: 0
|
||||
mipMapsPreserveCoverage: 0
|
||||
alphaTestReferenceValue: 0.5
|
||||
mipMapFadeDistanceStart: 1
|
||||
mipMapFadeDistanceEnd: 3
|
||||
bumpmap:
|
||||
convertToNormalMap: 0
|
||||
externalNormalMap: 0
|
||||
heightScale: 0.25
|
||||
normalMapFilter: 0
|
||||
flipGreenChannel: 0
|
||||
isReadable: 0
|
||||
streamingMipmaps: 0
|
||||
streamingMipmapsPriority: 0
|
||||
vTOnly: 0
|
||||
ignoreMasterTextureLimit: 0
|
||||
grayScaleToAlpha: 0
|
||||
generateCubemap: 6
|
||||
cubemapConvolution: 0
|
||||
seamlessCubemap: 0
|
||||
textureFormat: 1
|
||||
maxTextureSize: 2048
|
||||
textureSettings:
|
||||
serializedVersion: 2
|
||||
filterMode: 1
|
||||
aniso: 2
|
||||
mipBias: 0
|
||||
wrapU: 0
|
||||
wrapV: 0
|
||||
wrapW: 0
|
||||
nPOTScale: 1
|
||||
lightmap: 0
|
||||
compressionQuality: 50
|
||||
spriteMode: 0
|
||||
spriteExtrude: 1
|
||||
spriteMeshType: 1
|
||||
alignment: 0
|
||||
spritePivot: {x: 0.5, y: 0.5}
|
||||
spritePixelsToUnits: 100
|
||||
spriteBorder: {x: 0, y: 0, z: 0, w: 0}
|
||||
spriteGenerateFallbackPhysicsShape: 1
|
||||
alphaUsage: 1
|
||||
alphaIsTransparency: 0
|
||||
spriteTessellationDetail: -1
|
||||
textureType: 0
|
||||
textureShape: 1
|
||||
singleChannelComponent: 0
|
||||
flipbookRows: 1
|
||||
flipbookColumns: 1
|
||||
maxTextureSizeSet: 0
|
||||
compressionQualitySet: 0
|
||||
textureFormatSet: 0
|
||||
ignorePngGamma: 0
|
||||
applyGammaDecoding: 0
|
||||
swizzle: 50462976
|
||||
platformSettings:
|
||||
- serializedVersion: 3
|
||||
buildTarget: DefaultTexturePlatform
|
||||
maxTextureSize: 8192
|
||||
resizeAlgorithm: 0
|
||||
textureFormat: -1
|
||||
textureCompression: 1
|
||||
compressionQuality: 50
|
||||
crunchedCompression: 0
|
||||
allowsAlphaSplitting: 0
|
||||
overridden: 0
|
||||
androidETC2FallbackOverride: 0
|
||||
forceMaximumCompressionQuality_BC6H_BC7: 0
|
||||
- serializedVersion: 3
|
||||
buildTarget: Standalone
|
||||
maxTextureSize: 8192
|
||||
resizeAlgorithm: 0
|
||||
textureFormat: -1
|
||||
textureCompression: 1
|
||||
compressionQuality: 50
|
||||
crunchedCompression: 0
|
||||
allowsAlphaSplitting: 0
|
||||
overridden: 0
|
||||
androidETC2FallbackOverride: 0
|
||||
forceMaximumCompressionQuality_BC6H_BC7: 0
|
||||
- serializedVersion: 3
|
||||
buildTarget: iPhone
|
||||
maxTextureSize: 8192
|
||||
resizeAlgorithm: 0
|
||||
textureFormat: -1
|
||||
textureCompression: 1
|
||||
compressionQuality: 50
|
||||
crunchedCompression: 0
|
||||
allowsAlphaSplitting: 0
|
||||
overridden: 0
|
||||
androidETC2FallbackOverride: 0
|
||||
forceMaximumCompressionQuality_BC6H_BC7: 0
|
||||
- serializedVersion: 3
|
||||
buildTarget: Android
|
||||
maxTextureSize: 8192
|
||||
resizeAlgorithm: 0
|
||||
textureFormat: -1
|
||||
textureCompression: 1
|
||||
compressionQuality: 50
|
||||
crunchedCompression: 0
|
||||
allowsAlphaSplitting: 0
|
||||
overridden: 0
|
||||
androidETC2FallbackOverride: 0
|
||||
forceMaximumCompressionQuality_BC6H_BC7: 0
|
||||
- serializedVersion: 3
|
||||
buildTarget: Windows Store Apps
|
||||
maxTextureSize: 8192
|
||||
resizeAlgorithm: 0
|
||||
textureFormat: -1
|
||||
textureCompression: 1
|
||||
compressionQuality: 50
|
||||
crunchedCompression: 0
|
||||
allowsAlphaSplitting: 0
|
||||
overridden: 0
|
||||
androidETC2FallbackOverride: 0
|
||||
forceMaximumCompressionQuality_BC6H_BC7: 0
|
||||
spriteSheet:
|
||||
serializedVersion: 2
|
||||
sprites: []
|
||||
outline: []
|
||||
physicsShape: []
|
||||
bones: []
|
||||
spriteID:
|
||||
internalID: 0
|
||||
vertices: []
|
||||
indices:
|
||||
edges: []
|
||||
weights: []
|
||||
secondaryTextures: []
|
||||
nameFileIdTable: {}
|
||||
spritePackingTag:
|
||||
pSDRemoveMatte: 0
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
After Width: | Height: | Size: 861 B |
|
@ -0,0 +1,147 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 74e0f4cadf762ff468bb8bb11c2ca3c7
|
||||
TextureImporter:
|
||||
internalIDToNameTable: []
|
||||
externalObjects: {}
|
||||
serializedVersion: 11
|
||||
mipmaps:
|
||||
mipMapMode: 0
|
||||
enableMipMap: 1
|
||||
sRGBTexture: 1
|
||||
linearTexture: 0
|
||||
fadeOut: 0
|
||||
borderMipMap: 0
|
||||
mipMapsPreserveCoverage: 0
|
||||
alphaTestReferenceValue: 0.5
|
||||
mipMapFadeDistanceStart: 1
|
||||
mipMapFadeDistanceEnd: 3
|
||||
bumpmap:
|
||||
convertToNormalMap: 0
|
||||
externalNormalMap: 0
|
||||
heightScale: 0.25
|
||||
normalMapFilter: 0
|
||||
flipGreenChannel: 0
|
||||
isReadable: 0
|
||||
streamingMipmaps: 0
|
||||
streamingMipmapsPriority: 0
|
||||
vTOnly: 0
|
||||
ignoreMasterTextureLimit: 0
|
||||
grayScaleToAlpha: 0
|
||||
generateCubemap: 6
|
||||
cubemapConvolution: 0
|
||||
seamlessCubemap: 0
|
||||
textureFormat: 1
|
||||
maxTextureSize: 2048
|
||||
textureSettings:
|
||||
serializedVersion: 2
|
||||
filterMode: 1
|
||||
aniso: 2
|
||||
mipBias: 0
|
||||
wrapU: 0
|
||||
wrapV: 0
|
||||
wrapW: 0
|
||||
nPOTScale: 1
|
||||
lightmap: 0
|
||||
compressionQuality: 50
|
||||
spriteMode: 0
|
||||
spriteExtrude: 1
|
||||
spriteMeshType: 1
|
||||
alignment: 0
|
||||
spritePivot: {x: 0.5, y: 0.5}
|
||||
spritePixelsToUnits: 100
|
||||
spriteBorder: {x: 0, y: 0, z: 0, w: 0}
|
||||
spriteGenerateFallbackPhysicsShape: 1
|
||||
alphaUsage: 1
|
||||
alphaIsTransparency: 0
|
||||
spriteTessellationDetail: -1
|
||||
textureType: 0
|
||||
textureShape: 1
|
||||
singleChannelComponent: 0
|
||||
flipbookRows: 1
|
||||
flipbookColumns: 1
|
||||
maxTextureSizeSet: 0
|
||||
compressionQualitySet: 0
|
||||
textureFormatSet: 0
|
||||
ignorePngGamma: 0
|
||||
applyGammaDecoding: 0
|
||||
swizzle: 50462976
|
||||
platformSettings:
|
||||
- serializedVersion: 3
|
||||
buildTarget: DefaultTexturePlatform
|
||||
maxTextureSize: 8192
|
||||
resizeAlgorithm: 0
|
||||
textureFormat: -1
|
||||
textureCompression: 1
|
||||
compressionQuality: 50
|
||||
crunchedCompression: 0
|
||||
allowsAlphaSplitting: 0
|
||||
overridden: 0
|
||||
androidETC2FallbackOverride: 0
|
||||
forceMaximumCompressionQuality_BC6H_BC7: 0
|
||||
- serializedVersion: 3
|
||||
buildTarget: Standalone
|
||||
maxTextureSize: 8192
|
||||
resizeAlgorithm: 0
|
||||
textureFormat: -1
|
||||
textureCompression: 1
|
||||
compressionQuality: 50
|
||||
crunchedCompression: 0
|
||||
allowsAlphaSplitting: 0
|
||||
overridden: 0
|
||||
androidETC2FallbackOverride: 0
|
||||
forceMaximumCompressionQuality_BC6H_BC7: 0
|
||||
- serializedVersion: 3
|
||||
buildTarget: iPhone
|
||||
maxTextureSize: 8192
|
||||
resizeAlgorithm: 0
|
||||
textureFormat: -1
|
||||
textureCompression: 1
|
||||
compressionQuality: 50
|
||||
crunchedCompression: 0
|
||||
allowsAlphaSplitting: 0
|
||||
overridden: 0
|
||||
androidETC2FallbackOverride: 0
|
||||
forceMaximumCompressionQuality_BC6H_BC7: 0
|
||||
- serializedVersion: 3
|
||||
buildTarget: Android
|
||||
maxTextureSize: 8192
|
||||
resizeAlgorithm: 0
|
||||
textureFormat: -1
|
||||
textureCompression: 1
|
||||
compressionQuality: 50
|
||||
crunchedCompression: 0
|
||||
allowsAlphaSplitting: 0
|
||||
overridden: 0
|
||||
androidETC2FallbackOverride: 0
|
||||
forceMaximumCompressionQuality_BC6H_BC7: 0
|
||||
- serializedVersion: 3
|
||||
buildTarget: Windows Store Apps
|
||||
maxTextureSize: 8192
|
||||
resizeAlgorithm: 0
|
||||
textureFormat: -1
|
||||
textureCompression: 1
|
||||
compressionQuality: 50
|
||||
crunchedCompression: 0
|
||||
allowsAlphaSplitting: 0
|
||||
overridden: 0
|
||||
androidETC2FallbackOverride: 0
|
||||
forceMaximumCompressionQuality_BC6H_BC7: 0
|
||||
spriteSheet:
|
||||
serializedVersion: 2
|
||||
sprites: []
|
||||
outline: []
|
||||
physicsShape: []
|
||||
bones: []
|
||||
spriteID:
|
||||
internalID: 0
|
||||
vertices: []
|
||||
indices:
|
||||
edges: []
|
||||
weights: []
|
||||
secondaryTextures: []
|
||||
nameFileIdTable: {}
|
||||
spritePackingTag:
|
||||
pSDRemoveMatte: 0
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
After Width: | Height: | Size: 404 B |
|
@ -0,0 +1,181 @@
|
|||
fileFormatVersion: 2
|
||||
guid: fbb7a4425fc8e4e5bb4f932ae7f52073
|
||||
TextureImporter:
|
||||
internalIDToNameTable: []
|
||||
externalObjects: {}
|
||||
serializedVersion: 11
|
||||
mipmaps:
|
||||
mipMapMode: 0
|
||||
enableMipMap: 0
|
||||
sRGBTexture: 1
|
||||
linearTexture: 0
|
||||
fadeOut: 0
|
||||
borderMipMap: 0
|
||||
mipMapsPreserveCoverage: 0
|
||||
alphaTestReferenceValue: 0.5
|
||||
mipMapFadeDistanceStart: 1
|
||||
mipMapFadeDistanceEnd: 3
|
||||
bumpmap:
|
||||
convertToNormalMap: 0
|
||||
externalNormalMap: 0
|
||||
heightScale: 0.25
|
||||
normalMapFilter: 0
|
||||
isReadable: 0
|
||||
streamingMipmaps: 0
|
||||
streamingMipmapsPriority: 0
|
||||
vTOnly: 0
|
||||
grayScaleToAlpha: 0
|
||||
generateCubemap: 6
|
||||
cubemapConvolution: 0
|
||||
seamlessCubemap: 0
|
||||
textureFormat: 1
|
||||
maxTextureSize: 2048
|
||||
textureSettings:
|
||||
serializedVersion: 2
|
||||
filterMode: 0
|
||||
aniso: 1
|
||||
mipBias: 0
|
||||
wrapU: 1
|
||||
wrapV: 1
|
||||
wrapW: 0
|
||||
nPOTScale: 0
|
||||
lightmap: 0
|
||||
compressionQuality: 50
|
||||
spriteMode: 0
|
||||
spriteExtrude: 1
|
||||
spriteMeshType: 1
|
||||
alignment: 0
|
||||
spritePivot: {x: 0.5, y: 0.5}
|
||||
spritePixelsToUnits: 100
|
||||
spriteBorder: {x: 0, y: 0, z: 0, w: 0}
|
||||
spriteGenerateFallbackPhysicsShape: 1
|
||||
alphaUsage: 1
|
||||
alphaIsTransparency: 1
|
||||
spriteTessellationDetail: -1
|
||||
textureType: 2
|
||||
textureShape: 1
|
||||
singleChannelComponent: 0
|
||||
flipbookRows: 1
|
||||
flipbookColumns: 1
|
||||
maxTextureSizeSet: 0
|
||||
compressionQualitySet: 0
|
||||
textureFormatSet: 0
|
||||
ignorePngGamma: 0
|
||||
applyGammaDecoding: 0
|
||||
platformSettings:
|
||||
- serializedVersion: 3
|
||||
buildTarget: DefaultTexturePlatform
|
||||
maxTextureSize: 64
|
||||
resizeAlgorithm: 0
|
||||
textureFormat: -1
|
||||
textureCompression: 0
|
||||
compressionQuality: 50
|
||||
crunchedCompression: 0
|
||||
allowsAlphaSplitting: 0
|
||||
overridden: 0
|
||||
androidETC2FallbackOverride: 0
|
||||
forceMaximumCompressionQuality_BC6H_BC7: 0
|
||||
- serializedVersion: 3
|
||||
buildTarget: Standalone
|
||||
maxTextureSize: 2048
|
||||
resizeAlgorithm: 0
|
||||
textureFormat: -1
|
||||
textureCompression: 1
|
||||
compressionQuality: 50
|
||||
crunchedCompression: 0
|
||||
allowsAlphaSplitting: 0
|
||||
overridden: 0
|
||||
androidETC2FallbackOverride: 0
|
||||
forceMaximumCompressionQuality_BC6H_BC7: 0
|
||||
- serializedVersion: 3
|
||||
buildTarget: Android
|
||||
maxTextureSize: 2048
|
||||
resizeAlgorithm: 0
|
||||
textureFormat: -1
|
||||
textureCompression: 1
|
||||
compressionQuality: 50
|
||||
crunchedCompression: 0
|
||||
allowsAlphaSplitting: 0
|
||||
overridden: 0
|
||||
androidETC2FallbackOverride: 0
|
||||
forceMaximumCompressionQuality_BC6H_BC7: 0
|
||||
- serializedVersion: 3
|
||||
buildTarget: iPhone
|
||||
maxTextureSize: 2048
|
||||
resizeAlgorithm: 0
|
||||
textureFormat: -1
|
||||
textureCompression: 1
|
||||
compressionQuality: 50
|
||||
crunchedCompression: 0
|
||||
allowsAlphaSplitting: 0
|
||||
overridden: 0
|
||||
androidETC2FallbackOverride: 0
|
||||
forceMaximumCompressionQuality_BC6H_BC7: 0
|
||||
- serializedVersion: 3
|
||||
buildTarget: Windows Store Apps
|
||||
maxTextureSize: 2048
|
||||
resizeAlgorithm: 0
|
||||
textureFormat: -1
|
||||
textureCompression: 1
|
||||
compressionQuality: 50
|
||||
crunchedCompression: 0
|
||||
allowsAlphaSplitting: 0
|
||||
overridden: 0
|
||||
androidETC2FallbackOverride: 0
|
||||
forceMaximumCompressionQuality_BC6H_BC7: 0
|
||||
- serializedVersion: 3
|
||||
buildTarget: tvOS
|
||||
maxTextureSize: 2048
|
||||
resizeAlgorithm: 0
|
||||
textureFormat: -1
|
||||
textureCompression: 1
|
||||
compressionQuality: 50
|
||||
crunchedCompression: 0
|
||||
allowsAlphaSplitting: 0
|
||||
overridden: 0
|
||||
androidETC2FallbackOverride: 0
|
||||
forceMaximumCompressionQuality_BC6H_BC7: 0
|
||||
- serializedVersion: 3
|
||||
buildTarget: Lumin
|
||||
maxTextureSize: 2048
|
||||
resizeAlgorithm: 0
|
||||
textureFormat: -1
|
||||
textureCompression: 1
|
||||
compressionQuality: 50
|
||||
crunchedCompression: 0
|
||||
allowsAlphaSplitting: 0
|
||||
overridden: 0
|
||||
androidETC2FallbackOverride: 0
|
||||
forceMaximumCompressionQuality_BC6H_BC7: 0
|
||||
- serializedVersion: 3
|
||||
buildTarget: CloudRendering
|
||||
maxTextureSize: 2048
|
||||
resizeAlgorithm: 0
|
||||
textureFormat: -1
|
||||
textureCompression: 1
|
||||
compressionQuality: 50
|
||||
crunchedCompression: 0
|
||||
allowsAlphaSplitting: 0
|
||||
overridden: 0
|
||||
androidETC2FallbackOverride: 0
|
||||
forceMaximumCompressionQuality_BC6H_BC7: 0
|
||||
spriteSheet:
|
||||
serializedVersion: 2
|
||||
sprites: []
|
||||
outline: []
|
||||
physicsShape: []
|
||||
bones: []
|
||||
spriteID:
|
||||
internalID: 0
|
||||
vertices: []
|
||||
indices:
|
||||
edges: []
|
||||
weights: []
|
||||
secondaryTextures: []
|
||||
nameFileIdTable: {}
|
||||
spritePackingTag:
|
||||
pSDRemoveMatte: 0
|
||||
pSDShowRemoveMatteOption: 0
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
After Width: | Height: | Size: 738 B |
|
@ -0,0 +1,181 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 2ad42e04f6841ea64a7f21707d1aec22
|
||||
TextureImporter:
|
||||
internalIDToNameTable: []
|
||||
externalObjects: {}
|
||||
serializedVersion: 11
|
||||
mipmaps:
|
||||
mipMapMode: 0
|
||||
enableMipMap: 0
|
||||
sRGBTexture: 1
|
||||
linearTexture: 0
|
||||
fadeOut: 0
|
||||
borderMipMap: 0
|
||||
mipMapsPreserveCoverage: 0
|
||||
alphaTestReferenceValue: 0.5
|
||||
mipMapFadeDistanceStart: 1
|
||||
mipMapFadeDistanceEnd: 3
|
||||
bumpmap:
|
||||
convertToNormalMap: 0
|
||||
externalNormalMap: 0
|
||||
heightScale: 0.25
|
||||
normalMapFilter: 0
|
||||
isReadable: 0
|
||||
streamingMipmaps: 0
|
||||
streamingMipmapsPriority: 0
|
||||
vTOnly: 0
|
||||
grayScaleToAlpha: 0
|
||||
generateCubemap: 6
|
||||
cubemapConvolution: 0
|
||||
seamlessCubemap: 0
|
||||
textureFormat: 1
|
||||
maxTextureSize: 2048
|
||||
textureSettings:
|
||||
serializedVersion: 2
|
||||
filterMode: 0
|
||||
aniso: 1
|
||||
mipBias: 0
|
||||
wrapU: 1
|
||||
wrapV: 1
|
||||
wrapW: 0
|
||||
nPOTScale: 0
|
||||
lightmap: 0
|
||||
compressionQuality: 50
|
||||
spriteMode: 0
|
||||
spriteExtrude: 1
|
||||
spriteMeshType: 1
|
||||
alignment: 0
|
||||
spritePivot: {x: 0.5, y: 0.5}
|
||||
spritePixelsToUnits: 100
|
||||
spriteBorder: {x: 0, y: 0, z: 0, w: 0}
|
||||
spriteGenerateFallbackPhysicsShape: 1
|
||||
alphaUsage: 1
|
||||
alphaIsTransparency: 1
|
||||
spriteTessellationDetail: -1
|
||||
textureType: 2
|
||||
textureShape: 1
|
||||
singleChannelComponent: 0
|
||||
flipbookRows: 1
|
||||
flipbookColumns: 1
|
||||
maxTextureSizeSet: 0
|
||||
compressionQualitySet: 0
|
||||
textureFormatSet: 0
|
||||
ignorePngGamma: 0
|
||||
applyGammaDecoding: 0
|
||||
platformSettings:
|
||||
- serializedVersion: 3
|
||||
buildTarget: DefaultTexturePlatform
|
||||
maxTextureSize: 64
|
||||
resizeAlgorithm: 0
|
||||
textureFormat: -1
|
||||
textureCompression: 0
|
||||
compressionQuality: 50
|
||||
crunchedCompression: 0
|
||||
allowsAlphaSplitting: 0
|
||||
overridden: 0
|
||||
androidETC2FallbackOverride: 0
|
||||
forceMaximumCompressionQuality_BC6H_BC7: 0
|
||||
- serializedVersion: 3
|
||||
buildTarget: Standalone
|
||||
maxTextureSize: 2048
|
||||
resizeAlgorithm: 0
|
||||
textureFormat: -1
|
||||
textureCompression: 1
|
||||
compressionQuality: 50
|
||||
crunchedCompression: 0
|
||||
allowsAlphaSplitting: 0
|
||||
overridden: 0
|
||||
androidETC2FallbackOverride: 0
|
||||
forceMaximumCompressionQuality_BC6H_BC7: 0
|
||||
- serializedVersion: 3
|
||||
buildTarget: Android
|
||||
maxTextureSize: 2048
|
||||
resizeAlgorithm: 0
|
||||
textureFormat: -1
|
||||
textureCompression: 1
|
||||
compressionQuality: 50
|
||||
crunchedCompression: 0
|
||||
allowsAlphaSplitting: 0
|
||||
overridden: 0
|
||||
androidETC2FallbackOverride: 0
|
||||
forceMaximumCompressionQuality_BC6H_BC7: 0
|
||||
- serializedVersion: 3
|
||||
buildTarget: iPhone
|
||||
maxTextureSize: 2048
|
||||
resizeAlgorithm: 0
|
||||
textureFormat: -1
|
||||
textureCompression: 1
|
||||
compressionQuality: 50
|
||||
crunchedCompression: 0
|
||||
allowsAlphaSplitting: 0
|
||||
overridden: 0
|
||||
androidETC2FallbackOverride: 0
|
||||
forceMaximumCompressionQuality_BC6H_BC7: 0
|
||||
- serializedVersion: 3
|
||||
buildTarget: Windows Store Apps
|
||||
maxTextureSize: 2048
|
||||
resizeAlgorithm: 0
|
||||
textureFormat: -1
|
||||
textureCompression: 1
|
||||
compressionQuality: 50
|
||||
crunchedCompression: 0
|
||||
allowsAlphaSplitting: 0
|
||||
overridden: 0
|
||||
androidETC2FallbackOverride: 0
|
||||
forceMaximumCompressionQuality_BC6H_BC7: 0
|
||||
- serializedVersion: 3
|
||||
buildTarget: tvOS
|
||||
maxTextureSize: 2048
|
||||
resizeAlgorithm: 0
|
||||
textureFormat: -1
|
||||
textureCompression: 1
|
||||
compressionQuality: 50
|
||||
crunchedCompression: 0
|
||||
allowsAlphaSplitting: 0
|
||||
overridden: 0
|
||||
androidETC2FallbackOverride: 0
|
||||
forceMaximumCompressionQuality_BC6H_BC7: 0
|
||||
- serializedVersion: 3
|
||||
buildTarget: Lumin
|
||||
maxTextureSize: 2048
|
||||
resizeAlgorithm: 0
|
||||
textureFormat: -1
|
||||
textureCompression: 1
|
||||
compressionQuality: 50
|
||||
crunchedCompression: 0
|
||||
allowsAlphaSplitting: 0
|
||||
overridden: 0
|
||||
androidETC2FallbackOverride: 0
|
||||
forceMaximumCompressionQuality_BC6H_BC7: 0
|
||||
- serializedVersion: 3
|
||||
buildTarget: CloudRendering
|
||||
maxTextureSize: 2048
|
||||
resizeAlgorithm: 0
|
||||
textureFormat: -1
|
||||
textureCompression: 1
|
||||
compressionQuality: 50
|
||||
crunchedCompression: 0
|
||||
allowsAlphaSplitting: 0
|
||||
overridden: 0
|
||||
androidETC2FallbackOverride: 0
|
||||
forceMaximumCompressionQuality_BC6H_BC7: 0
|
||||
spriteSheet:
|
||||
serializedVersion: 2
|
||||
sprites: []
|
||||
outline: []
|
||||
physicsShape: []
|
||||
bones: []
|
||||
spriteID:
|
||||
internalID: 0
|
||||
vertices: []
|
||||
indices:
|
||||
edges: []
|
||||
weights: []
|
||||
secondaryTextures: []
|
||||
nameFileIdTable: {}
|
||||
spritePackingTag:
|
||||
pSDRemoveMatte: 0
|
||||
pSDShowRemoveMatteOption: 0
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
After Width: | Height: | Size: 418 B |
|
@ -0,0 +1,109 @@
|
|||
fileFormatVersion: 2
|
||||
guid: ce960f170ac1c3a4488973661e687f09
|
||||
TextureImporter:
|
||||
internalIDToNameTable: []
|
||||
externalObjects: {}
|
||||
serializedVersion: 11
|
||||
mipmaps:
|
||||
mipMapMode: 0
|
||||
enableMipMap: 0
|
||||
sRGBTexture: 1
|
||||
linearTexture: 0
|
||||
fadeOut: 0
|
||||
borderMipMap: 0
|
||||
mipMapsPreserveCoverage: 0
|
||||
alphaTestReferenceValue: 0.5
|
||||
mipMapFadeDistanceStart: 1
|
||||
mipMapFadeDistanceEnd: 3
|
||||
bumpmap:
|
||||
convertToNormalMap: 0
|
||||
externalNormalMap: 0
|
||||
heightScale: 0.25
|
||||
normalMapFilter: 0
|
||||
isReadable: 0
|
||||
streamingMipmaps: 0
|
||||
streamingMipmapsPriority: 0
|
||||
vTOnly: 0
|
||||
grayScaleToAlpha: 0
|
||||
generateCubemap: 6
|
||||
cubemapConvolution: 0
|
||||
seamlessCubemap: 0
|
||||
textureFormat: 1
|
||||
maxTextureSize: 2048
|
||||
textureSettings:
|
||||
serializedVersion: 2
|
||||
filterMode: 1
|
||||
aniso: 1
|
||||
mipBias: 0
|
||||
wrapU: 1
|
||||
wrapV: 1
|
||||
wrapW: 0
|
||||
nPOTScale: 0
|
||||
lightmap: 0
|
||||
compressionQuality: 50
|
||||
spriteMode: 0
|
||||
spriteExtrude: 1
|
||||
spriteMeshType: 1
|
||||
alignment: 0
|
||||
spritePivot: {x: 0.5, y: 0.5}
|
||||
spritePixelsToUnits: 100
|
||||
spriteBorder: {x: 0, y: 0, z: 0, w: 0}
|
||||
spriteGenerateFallbackPhysicsShape: 1
|
||||
alphaUsage: 1
|
||||
alphaIsTransparency: 1
|
||||
spriteTessellationDetail: -1
|
||||
textureType: 2
|
||||
textureShape: 1
|
||||
singleChannelComponent: 0
|
||||
flipbookRows: 1
|
||||
flipbookColumns: 1
|
||||
maxTextureSizeSet: 0
|
||||
compressionQualitySet: 0
|
||||
textureFormatSet: 0
|
||||
ignorePngGamma: 0
|
||||
applyGammaDecoding: 0
|
||||
platformSettings:
|
||||
- serializedVersion: 3
|
||||
buildTarget: DefaultTexturePlatform
|
||||
maxTextureSize: 2048
|
||||
resizeAlgorithm: 0
|
||||
textureFormat: -1
|
||||
textureCompression: 1
|
||||
compressionQuality: 50
|
||||
crunchedCompression: 0
|
||||
allowsAlphaSplitting: 0
|
||||
overridden: 0
|
||||
androidETC2FallbackOverride: 0
|
||||
forceMaximumCompressionQuality_BC6H_BC7: 0
|
||||
- serializedVersion: 3
|
||||
buildTarget: Standalone
|
||||
maxTextureSize: 2048
|
||||
resizeAlgorithm: 0
|
||||
textureFormat: -1
|
||||
textureCompression: 1
|
||||
compressionQuality: 50
|
||||
crunchedCompression: 0
|
||||
allowsAlphaSplitting: 0
|
||||
overridden: 0
|
||||
androidETC2FallbackOverride: 0
|
||||
forceMaximumCompressionQuality_BC6H_BC7: 0
|
||||
spriteSheet:
|
||||
serializedVersion: 2
|
||||
sprites: []
|
||||
outline: []
|
||||
physicsShape: []
|
||||
bones: []
|
||||
spriteID:
|
||||
internalID: 0
|
||||
vertices: []
|
||||
indices:
|
||||
edges: []
|
||||
weights: []
|
||||
secondaryTextures: []
|
||||
nameFileIdTable: {}
|
||||
spritePackingTag:
|
||||
pSDRemoveMatte: 0
|
||||
pSDShowRemoveMatteOption: 0
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
After Width: | Height: | Size: 768 B |
|
@ -0,0 +1,109 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 4099a528d97ba9f4ca8107e0144b3c91
|
||||
TextureImporter:
|
||||
internalIDToNameTable: []
|
||||
externalObjects: {}
|
||||
serializedVersion: 11
|
||||
mipmaps:
|
||||
mipMapMode: 0
|
||||
enableMipMap: 0
|
||||
sRGBTexture: 1
|
||||
linearTexture: 0
|
||||
fadeOut: 0
|
||||
borderMipMap: 0
|
||||
mipMapsPreserveCoverage: 0
|
||||
alphaTestReferenceValue: 0.5
|
||||
mipMapFadeDistanceStart: 1
|
||||
mipMapFadeDistanceEnd: 3
|
||||
bumpmap:
|
||||
convertToNormalMap: 0
|
||||
externalNormalMap: 0
|
||||
heightScale: 0.25
|
||||
normalMapFilter: 0
|
||||
isReadable: 0
|
||||
streamingMipmaps: 0
|
||||
streamingMipmapsPriority: 0
|
||||
vTOnly: 0
|
||||
grayScaleToAlpha: 0
|
||||
generateCubemap: 6
|
||||
cubemapConvolution: 0
|
||||
seamlessCubemap: 0
|
||||
textureFormat: 1
|
||||
maxTextureSize: 2048
|
||||
textureSettings:
|
||||
serializedVersion: 2
|
||||
filterMode: 1
|
||||
aniso: 1
|
||||
mipBias: 0
|
||||
wrapU: 1
|
||||
wrapV: 1
|
||||
wrapW: 0
|
||||
nPOTScale: 0
|
||||
lightmap: 0
|
||||
compressionQuality: 50
|
||||
spriteMode: 0
|
||||
spriteExtrude: 1
|
||||
spriteMeshType: 1
|
||||
alignment: 0
|
||||
spritePivot: {x: 0.5, y: 0.5}
|
||||
spritePixelsToUnits: 100
|
||||
spriteBorder: {x: 0, y: 0, z: 0, w: 0}
|
||||
spriteGenerateFallbackPhysicsShape: 1
|
||||
alphaUsage: 1
|
||||
alphaIsTransparency: 1
|
||||
spriteTessellationDetail: -1
|
||||
textureType: 2
|
||||
textureShape: 1
|
||||
singleChannelComponent: 0
|
||||
flipbookRows: 1
|
||||
flipbookColumns: 1
|
||||
maxTextureSizeSet: 0
|
||||
compressionQualitySet: 0
|
||||
textureFormatSet: 0
|
||||
ignorePngGamma: 0
|
||||
applyGammaDecoding: 0
|
||||
platformSettings:
|
||||
- serializedVersion: 3
|
||||
buildTarget: DefaultTexturePlatform
|
||||
maxTextureSize: 2048
|
||||
resizeAlgorithm: 0
|
||||
textureFormat: -1
|
||||
textureCompression: 1
|
||||
compressionQuality: 50
|
||||
crunchedCompression: 0
|
||||
allowsAlphaSplitting: 0
|
||||
overridden: 0
|
||||
androidETC2FallbackOverride: 0
|
||||
forceMaximumCompressionQuality_BC6H_BC7: 0
|
||||
- serializedVersion: 3
|
||||
buildTarget: Standalone
|
||||
maxTextureSize: 2048
|
||||
resizeAlgorithm: 0
|
||||
textureFormat: -1
|
||||
textureCompression: 1
|
||||
compressionQuality: 50
|
||||
crunchedCompression: 0
|
||||
allowsAlphaSplitting: 0
|
||||
overridden: 0
|
||||
androidETC2FallbackOverride: 0
|
||||
forceMaximumCompressionQuality_BC6H_BC7: 0
|
||||
spriteSheet:
|
||||
serializedVersion: 2
|
||||
sprites: []
|
||||
outline: []
|
||||
physicsShape: []
|
||||
bones: []
|
||||
spriteID:
|
||||
internalID: 0
|
||||
vertices: []
|
||||
indices:
|
||||
edges: []
|
||||
weights: []
|
||||
secondaryTextures: []
|
||||
nameFileIdTable: {}
|
||||
spritePackingTag:
|
||||
pSDRemoveMatte: 0
|
||||
pSDShowRemoveMatteOption: 0
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|