parent
39249efe51
commit
94e91e2936
17 changed files with 952 additions and 0 deletions
@ -0,0 +1,9 @@ |
||||
fileFormatVersion: 2 |
||||
guid: b52c69ccefdae7545bfb4d0bf9b7df71 |
||||
folderAsset: yes |
||||
timeCreated: 1467189428 |
||||
licenseType: Pro |
||||
DefaultImporter: |
||||
userData: |
||||
assetBundleName: |
||||
assetBundleVariant: |
||||
@ -0,0 +1,847 @@ |
||||
using System; |
||||
using System.Collections.Generic; |
||||
using UnityEngine; |
||||
|
||||
namespace UnityEditor.PostProcessing |
||||
{ |
||||
public sealed class CurveEditor |
||||
{ |
||||
#region Enums |
||||
|
||||
enum EditMode |
||||
{ |
||||
None, |
||||
Moving, |
||||
TangentEdit |
||||
} |
||||
|
||||
enum Tangent |
||||
{ |
||||
In, |
||||
Out |
||||
} |
||||
#endregion |
||||
|
||||
#region Structs |
||||
public struct Settings |
||||
{ |
||||
public Rect bounds; |
||||
public RectOffset padding; |
||||
public Color selectionColor; |
||||
public float curvePickingDistance; |
||||
public float keyTimeClampingDistance; |
||||
|
||||
public static Settings defaultSettings |
||||
{ |
||||
get |
||||
{ |
||||
return new Settings |
||||
{ |
||||
bounds = new Rect(0f, 0f, 1f, 1f), |
||||
padding = new RectOffset(10, 10, 10, 10), |
||||
selectionColor = Color.yellow, |
||||
curvePickingDistance = 6f, |
||||
keyTimeClampingDistance = 1e-4f |
||||
}; |
||||
} |
||||
} |
||||
} |
||||
|
||||
public struct CurveState |
||||
{ |
||||
public bool visible; |
||||
public bool editable; |
||||
public uint minPointCount; |
||||
public float zeroKeyConstantValue; |
||||
public Color color; |
||||
public float width; |
||||
public float handleWidth; |
||||
public bool showNonEditableHandles; |
||||
public bool onlyShowHandlesOnSelection; |
||||
public bool loopInBounds; |
||||
|
||||
public static CurveState defaultState |
||||
{ |
||||
get |
||||
{ |
||||
return new CurveState |
||||
{ |
||||
visible = true, |
||||
editable = true, |
||||
minPointCount = 2, |
||||
zeroKeyConstantValue = 0f, |
||||
color = Color.white, |
||||
width = 2f, |
||||
handleWidth = 2f, |
||||
showNonEditableHandles = true, |
||||
onlyShowHandlesOnSelection = false, |
||||
loopInBounds = false |
||||
}; |
||||
} |
||||
} |
||||
} |
||||
|
||||
public struct Selection |
||||
{ |
||||
public SerializedProperty curve; |
||||
public int keyframeIndex; |
||||
public Keyframe? keyframe; |
||||
|
||||
public Selection(SerializedProperty curve, int keyframeIndex, Keyframe? keyframe) |
||||
{ |
||||
this.curve = curve; |
||||
this.keyframeIndex = keyframeIndex; |
||||
this.keyframe = keyframe; |
||||
} |
||||
} |
||||
|
||||
internal struct MenuAction |
||||
{ |
||||
internal SerializedProperty curve; |
||||
internal int index; |
||||
internal Vector3 position; |
||||
|
||||
internal MenuAction(SerializedProperty curve) |
||||
{ |
||||
this.curve = curve; |
||||
this.index = -1; |
||||
this.position = Vector3.zero; |
||||
} |
||||
|
||||
internal MenuAction(SerializedProperty curve, int index) |
||||
{ |
||||
this.curve = curve; |
||||
this.index = index; |
||||
this.position = Vector3.zero; |
||||
} |
||||
|
||||
internal MenuAction(SerializedProperty curve, Vector3 position) |
||||
{ |
||||
this.curve = curve; |
||||
this.index = -1; |
||||
this.position = position; |
||||
} |
||||
} |
||||
#endregion |
||||
|
||||
#region Fields & properties |
||||
public Settings settings { get; private set; } |
||||
|
||||
Dictionary<SerializedProperty, CurveState> m_Curves; |
||||
Rect m_CurveArea; |
||||
|
||||
SerializedProperty m_SelectedCurve; |
||||
int m_SelectedKeyframeIndex = -1; |
||||
|
||||
EditMode m_EditMode = EditMode.None; |
||||
Tangent m_TangentEditMode; |
||||
|
||||
bool m_Dirty; |
||||
#endregion |
||||
|
||||
#region Constructors & destructors |
||||
public CurveEditor() |
||||
: this(Settings.defaultSettings) |
||||
{} |
||||
|
||||
public CurveEditor(Settings settings) |
||||
{ |
||||
this.settings = settings; |
||||
m_Curves = new Dictionary<SerializedProperty, CurveState>(); |
||||
} |
||||
|
||||
#endregion |
||||
|
||||
#region Public API |
||||
public void Add(params SerializedProperty[] curves) |
||||
{ |
||||
foreach (var curve in curves) |
||||
Add(curve, CurveState.defaultState); |
||||
} |
||||
|
||||
public void Add(SerializedProperty curve) |
||||
{ |
||||
Add(curve, CurveState.defaultState); |
||||
} |
||||
|
||||
public void Add(SerializedProperty curve, CurveState state) |
||||
{ |
||||
// Make sure the property is in fact an AnimationCurve |
||||
var animCurve = curve.animationCurveValue; |
||||
if (animCurve == null) |
||||
throw new ArgumentException("curve"); |
||||
|
||||
if (m_Curves.ContainsKey(curve)) |
||||
Debug.LogWarning("Curve has already been added to the editor"); |
||||
|
||||
m_Curves.Add(curve, state); |
||||
} |
||||
|
||||
public void Remove(SerializedProperty curve) |
||||
{ |
||||
m_Curves.Remove(curve); |
||||
} |
||||
|
||||
public void RemoveAll() |
||||
{ |
||||
m_Curves.Clear(); |
||||
} |
||||
|
||||
public CurveState GetCurveState(SerializedProperty curve) |
||||
{ |
||||
CurveState state; |
||||
if (!m_Curves.TryGetValue(curve, out state)) |
||||
throw new KeyNotFoundException("curve"); |
||||
|
||||
return state; |
||||
} |
||||
|
||||
public void SetCurveState(SerializedProperty curve, CurveState state) |
||||
{ |
||||
if (!m_Curves.ContainsKey(curve)) |
||||
throw new KeyNotFoundException("curve"); |
||||
|
||||
m_Curves[curve] = state; |
||||
} |
||||
|
||||
public Selection GetSelection() |
||||
{ |
||||
Keyframe? key = null; |
||||
if (m_SelectedKeyframeIndex > -1) |
||||
{ |
||||
var curve = m_SelectedCurve.animationCurveValue; |
||||
|
||||
if (m_SelectedKeyframeIndex >= curve.length) |
||||
m_SelectedKeyframeIndex = -1; |
||||
else |
||||
key = curve[m_SelectedKeyframeIndex]; |
||||
} |
||||
|
||||
return new Selection(m_SelectedCurve, m_SelectedKeyframeIndex, key); |
||||
} |
||||
|
||||
public void SetKeyframe(SerializedProperty curve, int keyframeIndex, Keyframe keyframe) |
||||
{ |
||||
var animCurve = curve.animationCurveValue; |
||||
SetKeyframe(animCurve, keyframeIndex, keyframe); |
||||
SaveCurve(curve, animCurve); |
||||
} |
||||
|
||||
public bool OnGUI(Rect rect) |
||||
{ |
||||
if (Event.current.type == EventType.Repaint) |
||||
m_Dirty = false; |
||||
|
||||
GUI.BeginClip(rect); |
||||
{ |
||||
var area = new Rect(Vector2.zero, rect.size); |
||||
m_CurveArea = settings.padding.Remove(area); |
||||
|
||||
foreach (var curve in m_Curves) |
||||
OnCurveGUI(area, curve.Key, curve.Value); |
||||
|
||||
OnGeneralUI(area); |
||||
} |
||||
GUI.EndClip(); |
||||
|
||||
return m_Dirty; |
||||
} |
||||
|
||||
#endregion |
||||
|
||||
#region UI & events |
||||
|
||||
void OnCurveGUI(Rect rect, SerializedProperty curve, CurveState state) |
||||
{ |
||||
// Discard invisible curves |
||||
if (!state.visible) |
||||
return; |
||||
|
||||
var animCurve = curve.animationCurveValue; |
||||
var keys = animCurve.keys; |
||||
var length = keys.Length; |
||||
|
||||
// Curve drawing |
||||
// Slightly dim non-editable curves |
||||
var color = state.color; |
||||
if (!state.editable) |
||||
color.a *= 0.5f; |
||||
|
||||
Handles.color = color; |
||||
var bounds = settings.bounds; |
||||
|
||||
if (length == 0) |
||||
{ |
||||
var p1 = CurveToCanvas(new Vector3(bounds.xMin, state.zeroKeyConstantValue)); |
||||
var p2 = CurveToCanvas(new Vector3(bounds.xMax, state.zeroKeyConstantValue)); |
||||
Handles.DrawAAPolyLine(state.width, p1, p2); |
||||
} |
||||
else if (length == 1) |
||||
{ |
||||
var p1 = CurveToCanvas(new Vector3(bounds.xMin, keys[0].value)); |
||||
var p2 = CurveToCanvas(new Vector3(bounds.xMax, keys[0].value)); |
||||
Handles.DrawAAPolyLine(state.width, p1, p2); |
||||
} |
||||
else |
||||
{ |
||||
var prevKey = keys[0]; |
||||
for (int k = 1; k < length; k++) |
||||
{ |
||||
var key = keys[k]; |
||||
var pts = BezierSegment(prevKey, key); |
||||
|
||||
if (float.IsInfinity(prevKey.outTangent) || float.IsInfinity(key.inTangent)) |
||||
{ |
||||
var s = HardSegment(prevKey, key); |
||||
Handles.DrawAAPolyLine(state.width, s[0], s[1], s[2]); |
||||
} |
||||
else Handles.DrawBezier(pts[0], pts[3], pts[1], pts[2], color, null, state.width); |
||||
|
||||
prevKey = key; |
||||
} |
||||
|
||||
// Curve extents & loops |
||||
if (keys[0].time > bounds.xMin) |
||||
{ |
||||
if (state.loopInBounds) |
||||
{ |
||||
var p1 = keys[length - 1]; |
||||
p1.time -= settings.bounds.width; |
||||
var p2 = keys[0]; |
||||
var pts = BezierSegment(p1, p2); |
||||
|
||||
if (float.IsInfinity(p1.outTangent) || float.IsInfinity(p2.inTangent)) |
||||
{ |
||||
var s = HardSegment(p1, p2); |
||||
Handles.DrawAAPolyLine(state.width, s[0], s[1], s[2]); |
||||
} |
||||
else Handles.DrawBezier(pts[0], pts[3], pts[1], pts[2], color, null, state.width); |
||||
} |
||||
else |
||||
{ |
||||
var p1 = CurveToCanvas(new Vector3(bounds.xMin, keys[0].value)); |
||||
var p2 = CurveToCanvas(keys[0]); |
||||
Handles.DrawAAPolyLine(state.width, p1, p2); |
||||
} |
||||
} |
||||
|
||||
if (keys[length - 1].time < bounds.xMax) |
||||
{ |
||||
if (state.loopInBounds) |
||||
{ |
||||
var p1 = keys[length - 1]; |
||||
var p2 = keys[0]; |
||||
p2.time += settings.bounds.width; |
||||
var pts = BezierSegment(p1, p2); |
||||
|
||||
if (float.IsInfinity(p1.outTangent) || float.IsInfinity(p2.inTangent)) |
||||
{ |
||||
var s = HardSegment(p1, p2); |
||||
Handles.DrawAAPolyLine(state.width, s[0], s[1], s[2]); |
||||
} |
||||
else Handles.DrawBezier(pts[0], pts[3], pts[1], pts[2], color, null, state.width); |
||||
} |
||||
else |
||||
{ |
||||
var p1 = CurveToCanvas(keys[length - 1]); |
||||
var p2 = CurveToCanvas(new Vector3(bounds.xMax, keys[length - 1].value)); |
||||
Handles.DrawAAPolyLine(state.width, p1, p2); |
||||
} |
||||
} |
||||
} |
||||
|
||||
// Make sure selection is correct (undo can break it) |
||||
bool isCurrentlySelectedCurve = curve == m_SelectedCurve; |
||||
|
||||
if (isCurrentlySelectedCurve && m_SelectedKeyframeIndex >= length) |
||||
m_SelectedKeyframeIndex = -1; |
||||
|
||||
// Handles & keys |
||||
for (int k = 0; k < length; k++) |
||||
{ |
||||
bool isCurrentlySelectedKeyframe = k == m_SelectedKeyframeIndex; |
||||
var e = Event.current; |
||||
|
||||
var pos = CurveToCanvas(keys[k]); |
||||
var hitRect = new Rect(pos.x - 8f, pos.y - 8f, 16f, 16f); |
||||
var offset = isCurrentlySelectedCurve |
||||
? new RectOffset(5, 5, 5, 5) |
||||
: new RectOffset(6, 6, 6, 6); |
||||
|
||||
var outTangent = pos + CurveTangentToCanvas(keys[k].outTangent).normalized * 40f; |
||||
var inTangent = pos - CurveTangentToCanvas(keys[k].inTangent).normalized * 40f; |
||||
var inTangentHitRect = new Rect(inTangent.x - 7f, inTangent.y - 7f, 14f, 14f); |
||||
var outTangentHitrect = new Rect(outTangent.x - 7f, outTangent.y - 7f, 14f, 14f); |
||||
|
||||
// Draw |
||||
if (state.showNonEditableHandles) |
||||
{ |
||||
if (e.type == EventType.repaint) |
||||
{ |
||||
var selectedColor = (isCurrentlySelectedCurve && isCurrentlySelectedKeyframe) |
||||
? settings.selectionColor |
||||
: state.color; |
||||
|
||||
// Keyframe |
||||
EditorGUI.DrawRect(offset.Remove(hitRect), selectedColor); |
||||
|
||||
// Tangents |
||||
if (isCurrentlySelectedCurve && (!state.onlyShowHandlesOnSelection || (state.onlyShowHandlesOnSelection && isCurrentlySelectedKeyframe))) |
||||
{ |
||||
Handles.color = selectedColor; |
||||
|
||||
if (k > 0 || state.loopInBounds) |
||||
{ |
||||
Handles.DrawAAPolyLine(state.handleWidth, pos, inTangent); |
||||
EditorGUI.DrawRect(offset.Remove(inTangentHitRect), selectedColor); |
||||
} |
||||
|
||||
if (k < length - 1 || state.loopInBounds) |
||||
{ |
||||
Handles.DrawAAPolyLine(state.handleWidth, pos, outTangent); |
||||
EditorGUI.DrawRect(offset.Remove(outTangentHitrect), selectedColor); |
||||
} |
||||
} |
||||
} |
||||
} |
||||
|
||||
// Events |
||||
if (state.editable) |
||||
{ |
||||
// Keyframe move |
||||
if (m_EditMode == EditMode.Moving && e.type == EventType.MouseDrag && isCurrentlySelectedCurve && isCurrentlySelectedKeyframe) |
||||
{ |
||||
EditMoveKeyframe(animCurve, keys, k); |
||||
} |
||||
|
||||
// Tangent editing |
||||
if (m_EditMode == EditMode.TangentEdit && e.type == EventType.MouseDrag && isCurrentlySelectedCurve && isCurrentlySelectedKeyframe) |
||||
{ |
||||
bool alreadyBroken = !(Mathf.Approximately(keys[k].inTangent, keys[k].outTangent) || (float.IsInfinity(keys[k].inTangent) && float.IsInfinity(keys[k].outTangent))); |
||||
EditMoveTangent(animCurve, keys, k, m_TangentEditMode, e.shift || !(alreadyBroken || e.control)); |
||||
} |
||||
|
||||
// Keyframe selection & context menu |
||||
if (e.type == EventType.mouseDown && rect.Contains(e.mousePosition)) |
||||
{ |
||||
if (hitRect.Contains(e.mousePosition)) |
||||
{ |
||||
if (e.button == 0) |
||||
{ |
||||
SelectKeyframe(curve, k); |
||||
m_EditMode = EditMode.Moving; |
||||
e.Use(); |
||||
} |
||||
else if (e.button == 1) |
||||
{ |
||||
// Keyframe context menu |
||||
var menu = new GenericMenu(); |
||||
menu.AddItem(new GUIContent("Delete Key"), false, (x) => |
||||
{ |
||||
var action = (MenuAction)x; |
||||
var curveValue = action.curve.animationCurveValue; |
||||
action.curve.serializedObject.Update(); |
||||
RemoveKeyframe(curveValue, action.index); |
||||
m_SelectedKeyframeIndex = -1; |
||||
SaveCurve(action.curve, curveValue); |
||||
action.curve.serializedObject.ApplyModifiedProperties(); |
||||
}, new MenuAction(curve, k)); |
||||
menu.ShowAsContext(); |
||||
e.Use(); |
||||
} |
||||
} |
||||
} |
||||
|
||||
// Tangent selection & edit mode |
||||
if (e.type == EventType.mouseDown && rect.Contains(e.mousePosition)) |
||||
{ |
||||
if (inTangentHitRect.Contains(e.mousePosition) && (k > 0 || state.loopInBounds)) |
||||
{ |
||||
SelectKeyframe(curve, k); |
||||
m_EditMode = EditMode.TangentEdit; |
||||
m_TangentEditMode = Tangent.In; |
||||
e.Use(); |
||||
} |
||||
else if (outTangentHitrect.Contains(e.mousePosition) && (k < length - 1 || state.loopInBounds)) |
||||
{ |
||||
SelectKeyframe(curve, k); |
||||
m_EditMode = EditMode.TangentEdit; |
||||
m_TangentEditMode = Tangent.Out; |
||||
e.Use(); |
||||
} |
||||
} |
||||
|
||||
// Mouse up - clean up states |
||||
if (e.rawType == EventType.MouseUp && m_EditMode != EditMode.None) |
||||
{ |
||||
m_EditMode = EditMode.None; |
||||
} |
||||
|
||||
// Set cursors |
||||
{ |
||||
EditorGUIUtility.AddCursorRect(hitRect, MouseCursor.MoveArrow); |
||||
|
||||
if (k > 0 || state.loopInBounds) |
||||
EditorGUIUtility.AddCursorRect(inTangentHitRect, MouseCursor.RotateArrow); |
||||
|
||||
if (k < length - 1 || state.loopInBounds) |
||||
EditorGUIUtility.AddCursorRect(outTangentHitrect, MouseCursor.RotateArrow); |
||||
} |
||||
} |
||||
} |
||||
|
||||
Handles.color = Color.white; |
||||
SaveCurve(curve, animCurve); |
||||
} |
||||
|
||||
void OnGeneralUI(Rect rect) |
||||
{ |
||||
var e = Event.current; |
||||
|
||||
// Selection |
||||
if (e.type == EventType.mouseDown) |
||||
{ |
||||
GUI.FocusControl(null); |
||||
m_SelectedCurve = null; |
||||
m_SelectedKeyframeIndex = -1; |
||||
bool used = false; |
||||
|
||||
var hit = CanvasToCurve(e.mousePosition); |
||||
float curvePickValue = CurveToCanvas(hit).y; |
||||
|
||||
// Try and select a curve |
||||
foreach (var curve in m_Curves) |
||||
{ |
||||
if (!curve.Value.editable || !curve.Value.visible) |
||||
continue; |
||||
|
||||
var prop = curve.Key; |
||||
var state = curve.Value; |
||||
var animCurve = prop.animationCurveValue; |
||||
float hitY = animCurve.length == 0 |
||||
? state.zeroKeyConstantValue |
||||
: animCurve.Evaluate(hit.x); |
||||
|
||||
var curvePos = CurveToCanvas(new Vector3(hit.x, hitY)); |
||||
|
||||
if (Mathf.Abs(curvePos.y - curvePickValue) < settings.curvePickingDistance) |
||||
{ |
||||
m_SelectedCurve = prop; |
||||
|
||||
if (e.clickCount == 2 && e.button == 0) |
||||
{ |
||||
// Create a keyframe on double-click on this curve |
||||
EditCreateKeyframe(animCurve, hit, true, state.zeroKeyConstantValue); |
||||
SaveCurve(prop, animCurve); |
||||
} |
||||
else if (e.button == 1) |
||||
{ |
||||
// Curve context menu |
||||
var menu = new GenericMenu(); |
||||
menu.AddItem(new GUIContent("Add Key"), false, (x) => |
||||
{ |
||||
var action = (MenuAction)x; |
||||
var curveValue = action.curve.animationCurveValue; |
||||
action.curve.serializedObject.Update(); |
||||
EditCreateKeyframe(curveValue, hit, true, 0f); |
||||
SaveCurve(action.curve, curveValue); |
||||
action.curve.serializedObject.ApplyModifiedProperties(); |
||||
}, new MenuAction(prop, hit)); |
||||
menu.ShowAsContext(); |
||||
e.Use(); |
||||
used = true; |
||||
} |
||||
} |
||||
} |
||||
|
||||
if (e.clickCount == 2 && e.button == 0 && m_SelectedCurve == null) |
||||
{ |
||||
// Create a keyframe on every curve on double-click |
||||
foreach (var curve in m_Curves) |
||||
{ |
||||
if (!curve.Value.editable || !curve.Value.visible) |
||||
continue; |
||||
|
||||
var prop = curve.Key; |
||||
var state = curve.Value; |
||||
var animCurve = prop.animationCurveValue; |
||||
EditCreateKeyframe(animCurve, hit, e.alt, state.zeroKeyConstantValue); |
||||
SaveCurve(prop, animCurve); |
||||
} |
||||
} |
||||
else if (!used && e.button == 1) |
||||
{ |
||||
// Global context menu |
||||
var menu = new GenericMenu(); |
||||
menu.AddItem(new GUIContent("Add Key At Position"), false, () => ContextMenuAddKey(hit, false)); |
||||
menu.AddItem(new GUIContent("Add Key On Curves"), false, () => ContextMenuAddKey(hit, true)); |
||||
menu.ShowAsContext(); |
||||
} |
||||
|
||||
e.Use(); |
||||
} |
||||
|
||||
// Delete selected key(s) |
||||
if (e.type == EventType.keyDown && (e.keyCode == KeyCode.Delete || e.keyCode == KeyCode.Backspace)) |
||||
{ |
||||
if (m_SelectedKeyframeIndex != -1 && m_SelectedCurve != null) |
||||
{ |
||||
var animCurve = m_SelectedCurve.animationCurveValue; |
||||
var length = animCurve.length; |
||||
|
||||
if (m_Curves[m_SelectedCurve].minPointCount < length && length >= 0) |
||||
{ |
||||
EditDeleteKeyframe(animCurve, m_SelectedKeyframeIndex); |
||||
m_SelectedKeyframeIndex = -1; |
||||
SaveCurve(m_SelectedCurve, animCurve); |
||||
} |
||||
|
||||
e.Use(); |
||||
} |
||||
} |
||||
} |
||||
|
||||
void SaveCurve(SerializedProperty prop, AnimationCurve curve) |
||||
{ |
||||
prop.animationCurveValue = curve; |
||||
} |
||||
|
||||
void Invalidate() |
||||
{ |
||||
m_Dirty = true; |
||||
} |
||||
|
||||
#endregion |
||||
|
||||
#region Keyframe manipulations |
||||
|
||||
void SelectKeyframe(SerializedProperty curve, int keyframeIndex) |
||||
{ |
||||
m_SelectedKeyframeIndex = keyframeIndex; |
||||
m_SelectedCurve = curve; |
||||
Invalidate(); |
||||
} |
||||
|
||||
void ContextMenuAddKey(Vector3 hit, bool createOnCurve) |
||||
{ |
||||
SerializedObject serializedObject = null; |
||||
|
||||
foreach (var curve in m_Curves) |
||||
{ |
||||
if (!curve.Value.editable || !curve.Value.visible) |
||||
continue; |
||||
|
||||
var prop = curve.Key; |
||||
var state = curve.Value; |
||||
|
||||
if (serializedObject == null) |
||||
{ |
||||
serializedObject = prop.serializedObject; |
||||
serializedObject.Update(); |
||||
} |
||||
|
||||
var animCurve = prop.animationCurveValue; |
||||
EditCreateKeyframe(animCurve, hit, createOnCurve, state.zeroKeyConstantValue); |
||||
SaveCurve(prop, animCurve); |
||||
} |
||||
|
||||
if (serializedObject != null) |
||||
serializedObject.ApplyModifiedProperties(); |
||||
|
||||
Invalidate(); |
||||
} |
||||
|
||||
void EditCreateKeyframe(AnimationCurve curve, Vector3 position, bool createOnCurve, float zeroKeyConstantValue) |
||||
{ |
||||
float tangent = EvaluateTangent(curve, position.x); |
||||
|
||||
if (createOnCurve) |
||||
{ |
||||
position.y = curve.length == 0 |
||||
? zeroKeyConstantValue |
||||
: curve.Evaluate(position.x); |
||||
} |
||||
|
||||
AddKeyframe(curve, new Keyframe(position.x, position.y, tangent, tangent)); |
||||
} |
||||
|
||||
void EditDeleteKeyframe(AnimationCurve curve, int keyframeIndex) |
||||
{ |
||||
RemoveKeyframe(curve, keyframeIndex); |
||||
} |
||||
|
||||
void AddKeyframe(AnimationCurve curve, Keyframe newValue) |
||||
{ |
||||
curve.AddKey(newValue); |
||||
Invalidate(); |
||||
} |
||||
|
||||
void RemoveKeyframe(AnimationCurve curve, int keyframeIndex) |
||||
{ |
||||
curve.RemoveKey(keyframeIndex); |
||||
Invalidate(); |
||||
} |
||||
|
||||
void SetKeyframe(AnimationCurve curve, int keyframeIndex, Keyframe newValue) |
||||
{ |
||||
var keys = curve.keys; |
||||
|
||||
if (keyframeIndex > 0) |
||||
newValue.time = Mathf.Max(keys[keyframeIndex - 1].time + settings.keyTimeClampingDistance, newValue.time); |
||||
|
||||
if (keyframeIndex < keys.Length - 1) |
||||
newValue.time = Mathf.Min(keys[keyframeIndex + 1].time - settings.keyTimeClampingDistance, newValue.time); |
||||
|
||||
curve.MoveKey(keyframeIndex, newValue); |
||||
Invalidate(); |
||||
} |
||||
|
||||
void EditMoveKeyframe(AnimationCurve curve, Keyframe[] keys, int keyframeIndex) |
||||
{ |
||||
var key = CanvasToCurve(Event.current.mousePosition); |
||||
float inTgt = keys[keyframeIndex].inTangent; |
||||
float outTgt = keys[keyframeIndex].outTangent; |
||||
SetKeyframe(curve, keyframeIndex, new Keyframe(key.x, key.y, inTgt, outTgt)); |
||||
} |
||||
|
||||
void EditMoveTangent(AnimationCurve curve, Keyframe[] keys, int keyframeIndex, Tangent targetTangent, bool linkTangents) |
||||
{ |
||||
var pos = CanvasToCurve(Event.current.mousePosition); |
||||
|
||||
float time = keys[keyframeIndex].time; |
||||
float value = keys[keyframeIndex].value; |
||||
|
||||
pos -= new Vector3(time, value); |
||||
|
||||
if (targetTangent == Tangent.In && pos.x > 0f) |
||||
pos.x = 0f; |
||||
|
||||
if (targetTangent == Tangent.Out && pos.x < 0f) |
||||
pos.x = 0f; |
||||
|
||||
float tangent; |
||||
|
||||
if (Mathf.Approximately(pos.x, 0f)) |
||||
tangent = pos.y < 0f ? float.PositiveInfinity : float.NegativeInfinity; |
||||
else |
||||
tangent = pos.y / pos.x; |
||||
|
||||
float inTangent = keys[keyframeIndex].inTangent; |
||||
float outTangent = keys[keyframeIndex].outTangent; |
||||
|
||||
if (targetTangent == Tangent.In || linkTangents) |
||||
inTangent = tangent; |
||||
if (targetTangent == Tangent.Out || linkTangents) |
||||
outTangent = tangent; |
||||
|
||||
SetKeyframe(curve, keyframeIndex, new Keyframe(time, value, inTangent, outTangent)); |
||||
} |
||||
|
||||
#endregion |
||||
|
||||
#region Maths utilities |
||||
|
||||
Vector3 CurveToCanvas(Keyframe keyframe) |
||||
{ |
||||
return CurveToCanvas(new Vector3(keyframe.time, keyframe.value)); |
||||
} |
||||
|
||||
Vector3 CurveToCanvas(Vector3 position) |
||||
{ |
||||
var bounds = settings.bounds; |
||||
var output = new Vector3((position.x - bounds.x) / (bounds.xMax - bounds.x), (position.y - bounds.y) / (bounds.yMax - bounds.y)); |
||||
output.x = output.x * (m_CurveArea.xMax - m_CurveArea.xMin) + m_CurveArea.xMin; |
||||
output.y = (1f - output.y) * (m_CurveArea.yMax - m_CurveArea.yMin) + m_CurveArea.yMin; |
||||
return output; |
||||
} |
||||
|
||||
Vector3 CanvasToCurve(Vector3 position) |
||||
{ |
||||
var bounds = settings.bounds; |
||||
var output = position; |
||||
output.x = (output.x - m_CurveArea.xMin) / (m_CurveArea.xMax - m_CurveArea.xMin); |
||||
output.y = (output.y - m_CurveArea.yMin) / (m_CurveArea.yMax - m_CurveArea.yMin); |
||||
output.x = Mathf.Lerp(bounds.x, bounds.xMax, output.x); |
||||
output.y = Mathf.Lerp(bounds.yMax, bounds.y, output.y); |
||||
return output; |
||||
} |
||||
|
||||
Vector3 CurveTangentToCanvas(float tangent) |
||||
{ |
||||
if (!float.IsInfinity(tangent)) |
||||
{ |
||||
var bounds = settings.bounds; |
||||
float ratio = (m_CurveArea.width / m_CurveArea.height) / ((bounds.xMax - bounds.x) / (bounds.yMax - bounds.y)); |
||||
return new Vector3(1f, -tangent / ratio).normalized; |
||||
} |
||||
|
||||
return float.IsPositiveInfinity(tangent) ? Vector3.up : Vector3.down; |
||||
} |
||||
|
||||
Vector3[] BezierSegment(Keyframe start, Keyframe end) |
||||
{ |
||||
var segment = new Vector3[4]; |
||||
|
||||
segment[0] = CurveToCanvas(new Vector3(start.time, start.value)); |
||||
segment[3] = CurveToCanvas(new Vector3(end.time, end.value)); |
||||
|
||||
float middle = start.time + ((end.time - start.time) * 0.333333f); |
||||
float middle2 = start.time + ((end.time - start.time) * 0.666666f); |
||||
|
||||
segment[1] = CurveToCanvas(new Vector3(middle, ProjectTangent(start.time, start.value, start.outTangent, middle))); |
||||
segment[2] = CurveToCanvas(new Vector3(middle2, ProjectTangent(end.time, end.value, end.inTangent, middle2))); |
||||
|
||||
return segment; |
||||
} |
||||
|
||||
Vector3[] HardSegment(Keyframe start, Keyframe end) |
||||
{ |
||||
var segment = new Vector3[3]; |
||||
|
||||
segment[0] = CurveToCanvas(start); |
||||
segment[1] = CurveToCanvas(new Vector3(end.time, start.value)); |
||||
segment[2] = CurveToCanvas(end); |
||||
|
||||
return segment; |
||||
} |
||||
|
||||
float ProjectTangent(float inPosition, float inValue, float inTangent, float projPosition) |
||||
{ |
||||
return inValue + ((projPosition - inPosition) * inTangent); |
||||
} |
||||
|
||||
float EvaluateTangent(AnimationCurve curve, float time) |
||||
{ |
||||
int prev = -1, next = 0; |
||||
for (int i = 0; i < curve.keys.Length; i++) |
||||
{ |
||||
if (time > curve.keys[i].time) |
||||
{ |
||||
prev = i; |
||||
next = i + 1; |
||||
} |
||||
else break; |
||||
} |
||||
|
||||
if (next == 0) |
||||
return 0f; |
||||
|
||||
if (prev == curve.keys.Length - 1) |
||||
return 0f; |
||||
|
||||
const float kD = 1e-3f; |
||||
float tp = Mathf.Max(time - kD, curve.keys[prev].time); |
||||
float tn = Mathf.Min(time + kD, curve.keys[next].time); |
||||
|
||||
float vp = curve.Evaluate(tp); |
||||
float vn = curve.Evaluate(tn); |
||||
|
||||
if (Mathf.Approximately(tn, tp)) |
||||
return (vn - vp > 0f) ? float.PositiveInfinity : float.NegativeInfinity; |
||||
|
||||
return (vn - vp) / (tn - tp); |
||||
} |
||||
|
||||
#endregion |
||||
} |
||||
} |
||||
@ -0,0 +1,12 @@ |
||||
fileFormatVersion: 2 |
||||
guid: afb349ef0bffd144db2bdd25630f648e |
||||
timeCreated: 1472650750 |
||||
licenseType: Pro |
||||
MonoImporter: |
||||
serializedVersion: 2 |
||||
defaultReferences: [] |
||||
executionOrder: 0 |
||||
icon: {instanceID: 0} |
||||
userData: |
||||
assetBundleName: |
||||
assetBundleVariant: |
||||
@ -0,0 +1,9 @@ |
||||
fileFormatVersion: 2 |
||||
guid: 4b79d54138d9d1a498085393504c7d02 |
||||
folderAsset: yes |
||||
timeCreated: 1466585248 |
||||
licenseType: Pro |
||||
DefaultImporter: |
||||
userData: |
||||
assetBundleName: |
||||
assetBundleVariant: |
||||
@ -0,0 +1,9 @@ |
||||
fileFormatVersion: 2 |
||||
guid: 68327f748e8ffd94889a47317b7d327b |
||||
folderAsset: yes |
||||
timeCreated: 1460383911 |
||||
licenseType: Pro |
||||
DefaultImporter: |
||||
userData: |
||||
assetBundleName: |
||||
assetBundleVariant: |
||||
@ -0,0 +1,12 @@ |
||||
namespace UnityEngine.PostProcessing |
||||
{ |
||||
public sealed class MinAttribute : PropertyAttribute |
||||
{ |
||||
public readonly float min; |
||||
|
||||
public MinAttribute(float min) |
||||
{ |
||||
this.min = min; |
||||
} |
||||
} |
||||
} |
||||
@ -0,0 +1,12 @@ |
||||
fileFormatVersion: 2 |
||||
guid: 9af2f505033843c46a362e251937acb1 |
||||
timeCreated: 1462281908 |
||||
licenseType: Pro |
||||
MonoImporter: |
||||
serializedVersion: 2 |
||||
defaultReferences: [] |
||||
executionOrder: 0 |
||||
icon: {instanceID: 0} |
||||
userData: |
||||
assetBundleName: |
||||
assetBundleVariant: |
||||
@ -0,0 +1,12 @@ |
||||
namespace UnityEngine.PostProcessing |
||||
{ |
||||
public sealed class TrackballAttribute : PropertyAttribute |
||||
{ |
||||
public readonly string method; |
||||
|
||||
public TrackballAttribute(string method) |
||||
{ |
||||
this.method = method; |
||||
} |
||||
} |
||||
} |
||||
@ -0,0 +1,12 @@ |
||||
fileFormatVersion: 2 |
||||
guid: 65e30143f4e114f45b84a1d9cba8f469 |
||||
timeCreated: 1463400829 |
||||
licenseType: Pro |
||||
MonoImporter: |
||||
serializedVersion: 2 |
||||
defaultReferences: [] |
||||
executionOrder: 0 |
||||
icon: {instanceID: 0} |
||||
userData: |
||||
assetBundleName: |
||||
assetBundleVariant: |
||||
@ -0,0 +1,9 @@ |
||||
fileFormatVersion: 2 |
||||
guid: 18fb6a6b698945843a16c2d0111a7af2 |
||||
folderAsset: yes |
||||
timeCreated: 1459945070 |
||||
licenseType: Pro |
||||
DefaultImporter: |
||||
userData: |
||||
assetBundleName: |
||||
assetBundleVariant: |
||||
@ -0,0 +1,9 @@ |
||||
fileFormatVersion: 2 |
||||
guid: e15c29a7abfa52743a8cb7714389c3c7 |
||||
folderAsset: yes |
||||
timeCreated: 1466585230 |
||||
licenseType: Pro |
||||
DefaultImporter: |
||||
userData: |
||||
assetBundleName: |
||||
assetBundleVariant: |
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Loading…
Reference in new issue