Explorar el Código

Animation editor: - Keyframe dragging functional
- Refactored EdAnimationCurve so it better encapsulates AnimationCurve

BearishSun hace 9 años
padre
commit
7fda8a9121

+ 1 - 1
Source/BansheeCore/Source/BsAnimationCurve.cpp

@@ -159,7 +159,7 @@ namespace BansheeEngine
 			float time = keyframes[0].time;
 			for (UINT32 i = 1; i < (UINT32)keyframes.size(); i++)
 			{
-				assert(keyframes[i].time > time);
+				assert(keyframes[i].time >= time);
 				time = keyframes[i].time;
 			}
 		}

+ 5 - 3
Source/BansheeEngine/Include/BsCamera.h

@@ -74,6 +74,8 @@ namespace BansheeEngine
 	/**
 	 * Camera determines how is world geometry projected onto a 2D surface. You may position and orient it in space, set
 	 * options like aspect ratio and field or view and it outputs view and projection matrices required for rendering.
+	 *
+	 * This class contains funcionality common to both core and non-core versions of the camera.
 	 */
 	class BS_EXPORT CameraBase
     {
@@ -520,7 +522,7 @@ namespace BansheeEngine
 		void initialize() override;
 
 		/** @copydoc CameraBase */
-		virtual Rect2I getViewportRect() const override;
+		Rect2I getViewportRect() const override;
 
 		/** @copydoc CoreObject::syncToCore */
 		void syncToCore(const CoreSyncData& data) override;
@@ -571,7 +573,7 @@ namespace BansheeEngine
 			float left = 0.0f, float top = 0.0f, float width = 1.0f, float height = 1.0f);
 
 		/** @copydoc CameraBase */
-		virtual Rect2I getViewportRect() const override;
+		Rect2I getViewportRect() const override;
 
 		/** @copydoc CoreObject::createCore */
 		SPtr<CoreObjectCore> createCore() const override;
@@ -598,7 +600,7 @@ namespace BansheeEngine
 	public:
 		friend class CameraRTTI;
 		static RTTITypeBase* getRTTIStatic();
-		virtual RTTITypeBase* getRTTI() const override;
+		RTTITypeBase* getRTTI() const override;
      };
 
 	/** @} */

+ 101 - 34
Source/MBansheeEditor/Utility/EdAnimationCurve.cs

@@ -29,22 +29,24 @@ namespace BansheeEditor
     internal class EdAnimationCurve
     {
         private AnimationCurve native;
+
+        private KeyFrame[] keyFrames;
         private TangentMode[] tangentModes;
 
-        public AnimationCurve Native
+        public TangentMode[] TangentModes
         {
-            get { return native; }
+            get { return tangentModes; }
         }
 
-        public TangentMode[] TangentModes
+        public KeyFrame[] KeyFrames
         {
-            get { return tangentModes; }
+            get { return keyFrames; }
         }
 
         internal EdAnimationCurve()
         {
-            KeyFrame[] keyframes = new KeyFrame[0];
-            native = new AnimationCurve(keyframes);
+            keyFrames = new KeyFrame[0];
+            native = new AnimationCurve(keyFrames);
 
             tangentModes = new TangentMode[0];
         }
@@ -54,7 +56,7 @@ namespace BansheeEditor
         {
             this.native = native;
 
-            KeyFrame[] keyFrames = native.KeyFrames;
+            keyFrames = native.KeyFrames;
 
             this.tangentModes = new TangentMode[keyFrames.Length];
             if (tangentModes != null)
@@ -63,7 +65,19 @@ namespace BansheeEditor
                 Array.Copy(tangentModes, this.tangentModes, numTangents);
             }
 
-            UpdateTangents(keyFrames, this.tangentModes);
+            Apply();
+        }
+
+        /// <summary>
+        /// Evaluate the animation curve at the specified time.
+        /// </summary>
+        /// <param name="time">Time to evaluate the curve at. </param>
+        /// <param name="loop">If true the curve will loop when it goes past the end or beggining. Otherwise the curve 
+        ///                    value will be clamped.</param>
+        /// <returns>Interpolated value from the curve at provided time.</returns>
+        internal float Evaluate(float time, bool loop = true)
+        {
+            return native.Evaluate(time, loop);
         }
 
         internal void AddKeyframe(float time, float value)
@@ -73,24 +87,22 @@ namespace BansheeEditor
 
         internal void AddKeyframe(float time, float value, TangentMode tangentMode)
         {
-            KeyFrame[] existingKeyFrames = native.KeyFrames;
-
-            KeyFrame[] newKeyFrames = new KeyFrame[existingKeyFrames.Length + 1];
+            KeyFrame[] newKeyFrames = new KeyFrame[keyFrames.Length + 1];
             newKeyFrames[newKeyFrames.Length - 1].time = float.PositiveInfinity;
 
             TangentMode[] newTangentModes = new TangentMode[tangentModes.Length + 1];
 
-            int insertIdx = existingKeyFrames.Length;
-            for (int i = 0; i < existingKeyFrames.Length; i++)
+            int insertIdx = keyFrames.Length;
+            for (int i = 0; i < keyFrames.Length; i++)
             {
-                if (time < existingKeyFrames[i].time)
+                if (time < keyFrames[i].time)
                 {
                     insertIdx = i;
                     break;
                 }
             }
 
-            Array.Copy(existingKeyFrames, newKeyFrames, insertIdx);
+            Array.Copy(keyFrames, newKeyFrames, insertIdx);
             Array.Copy(tangentModes, newTangentModes, insertIdx);
 
             KeyFrame keyFrame = new KeyFrame();
@@ -100,42 +112,90 @@ namespace BansheeEditor
             newKeyFrames[insertIdx] = keyFrame;
             newTangentModes[insertIdx] = tangentMode;
 
-            if (insertIdx < existingKeyFrames.Length)
+            if (insertIdx < keyFrames.Length)
             {
-                int remaining = existingKeyFrames.Length - insertIdx;
-                Array.Copy(existingKeyFrames, insertIdx, newKeyFrames, insertIdx + 1, remaining);
+                int remaining = keyFrames.Length - insertIdx;
+                Array.Copy(keyFrames, insertIdx, newKeyFrames, insertIdx + 1, remaining);
                 Array.Copy(tangentModes, insertIdx, newTangentModes, insertIdx + 1, remaining);
             }
 
-            UpdateTangents(newKeyFrames, newTangentModes);
-
             tangentModes = newTangentModes;
-            native.KeyFrames = newKeyFrames;
+            keyFrames = newKeyFrames;
         }
 
         internal void RemoveKeyframe(int index)
         {
-            KeyFrame[] existingKeyFrames = native.KeyFrames;
-            if (index < 0 || index >= existingKeyFrames.Length)
+            if (index < 0 || index >= KeyFrames.Length)
                 return;
 
-            KeyFrame[] newKeyFrames = new KeyFrame[existingKeyFrames.Length - 1];
+            KeyFrame[] newKeyFrames = new KeyFrame[KeyFrames.Length - 1];
             TangentMode[] newTangentModes = new TangentMode[tangentModes.Length - 1];
 
-            Array.Copy(existingKeyFrames, newKeyFrames, index);
+            Array.Copy(KeyFrames, newKeyFrames, index);
             Array.Copy(tangentModes, newTangentModes, index);
 
             if (index < newKeyFrames.Length)
             {
                 int remaining = newKeyFrames.Length - index;
-                Array.Copy(existingKeyFrames, index + 1, newKeyFrames, index, remaining);
+                Array.Copy(KeyFrames, index + 1, newKeyFrames, index, remaining);
                 Array.Copy(tangentModes, index + 1, newTangentModes, index, remaining);
             }
 
-            UpdateTangents(newKeyFrames, newTangentModes);
-
             tangentModes = newTangentModes;
-            native.KeyFrames = newKeyFrames;
+            keyFrames = newKeyFrames;
+        }
+
+        // Updates key-frame value and returns new keyframe index
+        internal int UpdateKeyframe(int index, float time, float value)
+        {
+            if (index < 0 || index >= keyFrames.Length)
+                return -1;
+
+            keyFrames[index].time = time;
+            keyFrames[index].value = value;
+
+            // Check if key moved before or after other keys. Animation curve automatically sorts
+            // keys and if this happens our key indices will change. So we sort it here and modify
+            // indices.
+
+            int currentKeyIndex = index;
+            int prevKeyIdx = currentKeyIndex - 1;
+            while (prevKeyIdx >= 0)
+            {
+                if (time >= keyFrames[prevKeyIdx].time)
+                    break;
+
+                KeyFrame temp = keyFrames[prevKeyIdx];
+                keyFrames[prevKeyIdx] = keyFrames[currentKeyIndex];
+                keyFrames[currentKeyIndex] = temp;
+
+                TangentMode tempMode = tangentModes[prevKeyIdx];
+                tangentModes[prevKeyIdx] = tangentModes[currentKeyIndex];
+                tangentModes[currentKeyIndex] = tempMode;
+
+                currentKeyIndex = prevKeyIdx;
+                prevKeyIdx--;
+            }
+
+            int nextKeyIdx = currentKeyIndex + 1;
+            while (nextKeyIdx < keyFrames.Length)
+            {
+                if (time <= keyFrames[nextKeyIdx].time)
+                    break;
+
+                KeyFrame temp = keyFrames[nextKeyIdx];
+                keyFrames[nextKeyIdx] = keyFrames[currentKeyIndex];
+                keyFrames[currentKeyIndex] = temp;
+
+                TangentMode tempMode = tangentModes[nextKeyIdx];
+                tangentModes[nextKeyIdx] = tangentModes[currentKeyIndex];
+                tangentModes[currentKeyIndex] = tempMode;
+
+                currentKeyIndex = nextKeyIdx;
+                nextKeyIdx++;
+            }
+
+            return currentKeyIndex;
         }
 
         internal void SetTangentMode(int index, TangentMode mode)
@@ -144,10 +204,6 @@ namespace BansheeEditor
                 return;
 
             tangentModes[index] = mode;
-
-            KeyFrame[] keyFrames = native.KeyFrames;
-            UpdateTangents(keyFrames, tangentModes);
-            native.KeyFrames = keyFrames;
         }
 
         internal static Vector2 TangentToNormal(float tangent)
@@ -168,7 +224,18 @@ namespace BansheeEditor
             return MathEx.Sqrt(length*length - 1);
         }
 
-        private void UpdateTangents(KeyFrame[] keyFrames, TangentMode[] tangentModes)
+        internal void Apply()
+        {
+            Array.Sort(keyFrames, (x, y) =>
+            {
+                return x.time.CompareTo(y.time);
+            });
+
+            UpdateTangents();
+            native.KeyFrames = keyFrames;
+        }
+
+        private void UpdateTangents()
         {
             if (keyFrames.Length == 0)
                 return;

+ 10 - 10
Source/MBansheeEditor/Windows/Animation/GUICurveDrawing.cs

@@ -140,7 +140,7 @@ namespace BansheeEditor
 
             for (int i = 0; i < curves.Length; i++)
             {
-                KeyFrame[] keyframes = curves[i].Native.KeyFrames;
+                KeyFrame[] keyframes = curves[i].KeyFrames;
                 selectedKeyframes[i] = new bool[keyframes.Length];
             }
         }
@@ -174,7 +174,7 @@ namespace BansheeEditor
             for (int i = 0; i < curves.Length; i++)
             {
                 EdAnimationCurve curve = curves[i];
-                KeyFrame[] keyframes = curve.Native.KeyFrames;
+                KeyFrame[] keyframes = curve.KeyFrames;
 
                 for (int j = 0; j < keyframes.Length; j++)
                 {
@@ -213,7 +213,7 @@ namespace BansheeEditor
             for (int i = 0; i < curves.Length; i++)
             {
                 EdAnimationCurve curve = curves[i];
-                KeyFrame[] keyframes = curve.Native.KeyFrames;
+                KeyFrame[] keyframes = curve.KeyFrames;
 
                 for (int j = 0; j < keyframes.Length; j++)
                 {
@@ -511,7 +511,7 @@ namespace BansheeEditor
                 DrawCurve(curve, color);
 
                 // Draw keyframes
-                KeyFrame[] keyframes = curve.Native.KeyFrames;
+                KeyFrame[] keyframes = curve.KeyFrames;
 
                 for (int i = 0; i < keyframes.Length; i++)
                 {
@@ -579,7 +579,7 @@ namespace BansheeEditor
 
             int heightOffset = height/2; // So that y = 0 is at center of canvas
 
-            KeyFrame[] keyframes = curve.Native.KeyFrames;
+            KeyFrame[] keyframes = curve.KeyFrames;
             if (keyframes.Length < 0)
                 return;
 
@@ -591,7 +591,7 @@ namespace BansheeEditor
                 int xPosStart = 0;
                 int xPosEnd = GUIGraphTime.PADDING + startPixel;
 
-                int yPos = (int)(curve.Native.Evaluate(0.0f, false) * pixelsPerHeight);
+                int yPos = (int)(curve.Evaluate(0.0f, false) * pixelsPerHeight);
                 yPos = heightOffset - yPos; // Offset and flip height (canvas Y goes down)
 
                 Vector2I a = new Vector2I(xPosStart, yPos);
@@ -619,7 +619,7 @@ namespace BansheeEditor
                 {
                     // Line from left to right frame
                     int xPos = startPixel;
-                    int yPosStart = (int)(curve.Native.Evaluate(start, false) * pixelsPerHeight);
+                    int yPosStart = (int)(curve.Evaluate(start, false) * pixelsPerHeight);
                     yPosStart = heightOffset - yPosStart; // Offset and flip height (canvas Y goes down)
 
                     linePoints.Add(new Vector2I(GUIGraphTime.PADDING + xPos, yPosStart));
@@ -628,7 +628,7 @@ namespace BansheeEditor
                     linePoints.Add(new Vector2I(GUIGraphTime.PADDING + xPos, yPosStart));
 
                     // Line representing the step
-                    int yPosEnd = (int)(curve.Native.Evaluate(end, false) * pixelsPerHeight);
+                    int yPosEnd = (int)(curve.Evaluate(end, false) * pixelsPerHeight);
                     yPosEnd = heightOffset - yPosEnd; // Offset and flip height (canvas Y goes down)
 
                     linePoints.Add(new Vector2I(GUIGraphTime.PADDING + xPos, yPosEnd));
@@ -660,7 +660,7 @@ namespace BansheeEditor
                         int xPos = Math.Min(startPixel + j * LINE_SPLIT_WIDTH, endPixel);
                         float t = Math.Min(start + j * timeIncrement, end);
 
-                        int yPos = (int)(curve.Native.Evaluate(t, false) * pixelsPerHeight);
+                        int yPos = (int)(curve.Evaluate(t, false) * pixelsPerHeight);
                         yPos = heightOffset - yPos; // Offset and flip height (canvas Y goes down)
 
                         linePoints.Add(new Vector2I(GUIGraphTime.PADDING + xPos, yPos));
@@ -678,7 +678,7 @@ namespace BansheeEditor
                 int xPosStart = GUIGraphTime.PADDING + endPixel;
                 int xPosEnd = width;
 
-                int yPos = (int)(curve.Native.Evaluate(range, false) * pixelsPerHeight);
+                int yPos = (int)(curve.Evaluate(range, false) * pixelsPerHeight);
                 yPos = heightOffset - yPos; // Offset and flip height (canvas Y goes down)
 
                 Vector2I a = new Vector2I(xPosStart, yPos);

+ 209 - 55
Source/MBansheeEditor/Windows/Animation/GUICurveEditor.cs

@@ -12,8 +12,33 @@ namespace BansheeEditor
 
     internal class GUICurveEditor
     {
+        class SelectedKeyframes
+        {
+            public int curveIdx;
+            public List<int> keyIndices = new List<int>();
+        }
+
+        struct DraggedKeyframe
+        {
+            public DraggedKeyframe(int index, KeyFrame original)
+            {
+                this.index = index;
+                this.original = original;
+            }
+
+            public int index;
+            public KeyFrame original;
+        }
+
+        class DraggedKeyframes
+        {
+            public int curveIdx;
+            public List<DraggedKeyframe> keys = new List<DraggedKeyframe>();
+        }
+
         private const int TIMELINE_HEIGHT = 20;
         private const int SIDEBAR_WIDTH = 30;
+        private const int DRAG_START_DISTANCE = 3;
 
         private EditorWindow window;
         private GUILayout gui;
@@ -29,12 +54,15 @@ namespace BansheeEditor
         private EdAnimationCurve[] curves = new EdAnimationCurve[0];
 
         private int markedFrameIdx;
-        private List<KeyframeRef> selectedKeyframes = new List<KeyframeRef>();
+        private List<SelectedKeyframes> selectedKeyframes = new List<SelectedKeyframes>();
 
         private bool isPointerHeld;
         private bool isMousePressedOverKey;
-        private KeyFrame[] draggedKeyframes;
-        private Vector2 dragStart;
+        private bool isMousePressedOverTangent;
+        private bool isDragInProgress;
+        private List<DraggedKeyframes> draggedKeyframes = new List<DraggedKeyframes>();
+        private TangentRef draggedTangent;
+        private Vector2I dragStart;
 
         public GUICurveEditor(EditorWindow window, GUILayout gui, int width, int height)
         {
@@ -98,16 +126,29 @@ namespace BansheeEditor
                 {
                     KeyframeRef keyframeRef;
                     if (!guiCurveDrawing.FindKeyFrame(drawingPos, out keyframeRef))
+                    {
                         ClearSelection();
+
+                        TangentRef tangentRef;
+                        if (guiCurveDrawing.FindTangent(drawingPos, out tangentRef))
+                        {
+                            isMousePressedOverTangent = true;
+                            dragStart = drawingPos;
+                            draggedTangent = tangentRef;
+                        }
+                    }
                     else
                     {
-                        if (!Input.IsButtonHeld(ButtonCode.LeftShift) && !Input.IsButtonHeld(ButtonCode.RightShift))
-                            ClearSelection();
+                        if (!IsSelected(keyframeRef))
+                        {
+                            if (!Input.IsButtonHeld(ButtonCode.LeftShift) && !Input.IsButtonHeld(ButtonCode.RightShift))
+                                ClearSelection();
 
-                        SelectKeyframe(keyframeRef);
+                            SelectKeyframe(keyframeRef);
+                        }
 
                         isMousePressedOverKey = true;
-                        dragStart = curveCoord;
+                        dragStart = drawingPos;
                     }
 
                     guiCurveDrawing.Rebuild();
@@ -160,19 +201,100 @@ namespace BansheeEditor
 
             if (isPointerHeld)
             {
-                if (isMousePressedOverKey)
-                {
-                    // TODO - Check if pointer moves some minimal amount
-                    // - If so, start drag. Record all current positions
-                    // - Calculate offset in curve space and apply to all keyframes
-                }
-                else
+                Vector2I windowPos = window.ScreenToWindowPos(ev.ScreenPos);
+
+                Rect2I elementBounds = GUIUtility.CalculateBounds(gui, window.GUI);
+                Vector2I pointerPos = windowPos - new Vector2I(elementBounds.x, elementBounds.y);
+
+                if (isMousePressedOverKey || isMousePressedOverTangent)
                 {
-                    Vector2I windowPos = window.ScreenToWindowPos(ev.ScreenPos);
+                    Rect2I drawingBounds = drawingPanel.Bounds;
+                    Vector2I drawingPos = pointerPos - new Vector2I(drawingBounds.x, drawingBounds.y);
+
+                    if (!isDragInProgress)
+                    {
+                        int distance = Vector2I.Distance(drawingPos, dragStart);
+                        if (distance >= DRAG_START_DISTANCE)
+                        {
+                            if (isMousePressedOverKey)
+                            {
+                                draggedKeyframes.Clear();
+                                foreach (var selectedEntry in selectedKeyframes)
+                                {
+                                    EdAnimationCurve curve = curves[selectedEntry.curveIdx];
+                                    KeyFrame[] keyFrames = curve.KeyFrames;
+
+                                    DraggedKeyframes newEntry = new DraggedKeyframes();
+                                    draggedKeyframes.Add(newEntry);
+
+                                    foreach (var keyframeIdx in selectedEntry.keyIndices)
+                                        newEntry.keys.Add(new DraggedKeyframe(keyframeIdx, keyFrames[keyframeIdx]));
+                                }
+                            }
+
+                            // TODO - UNDOREDO record keyframe or tangent
+
+                            isDragInProgress = true;
+                        }
+                    }
+
+                    if (isDragInProgress)
+                    {
+                        if (isMousePressedOverKey)
+                        {
+                            Vector2 diff = Vector2.Zero;
+
+                            Vector2 dragStartCurve;
+                            if (guiCurveDrawing.PixelToCurveSpace(dragStart, out dragStartCurve))
+                            {
+                                Vector2 currentPosCurve;
+                                if (guiCurveDrawing.PixelToCurveSpace(drawingPos, out currentPosCurve))
+                                    diff = currentPosCurve - dragStartCurve;
+                            }
+
+                            foreach (var draggedEntry in draggedKeyframes)
+                            {
+                                EdAnimationCurve curve = curves[draggedEntry.curveIdx];
+
+                                for (int i = 0; i < draggedEntry.keys.Count; i++)
+                                {
+                                    DraggedKeyframe draggedKey = draggedEntry.keys[i];
+
+                                    float newTime = draggedKey.original.time + diff.x;
+                                    float newValue = draggedKey.original.value + diff.y;
 
-                    Rect2I elementBounds = GUIUtility.CalculateBounds(gui, window.GUI);
-                    Vector2I pointerPos = windowPos - new Vector2I(elementBounds.x, elementBounds.y);
+                                    int newIndex = curve.UpdateKeyframe(draggedKey.index, newTime, newValue);
 
+                                    // It's possible key changed position due to time change, but since we're moving all
+                                    // keys at once they cannot change position relative to one another, otherwise we would
+                                    // need to update indices for other keys as well.
+                                    draggedKey.index = newIndex;
+                                    draggedEntry.keys[i] = draggedKey;
+                                }
+
+                                curve.Apply();
+                            }
+
+                            // Rebuild selected keys from dragged keys (after potential sorting)
+                            ClearSelection();
+                            foreach (var draggedEntry in draggedKeyframes)
+                            {
+                                foreach (var keyframe in draggedEntry.keys)
+                                    SelectKeyframe(new KeyframeRef(draggedEntry.curveIdx, keyframe.index));
+                            }
+
+                            guiCurveDrawing.Rebuild();
+                        }
+                        else if (isMousePressedOverTangent)
+                        {
+                            // TODO - Update tangent
+
+                            guiCurveDrawing.Rebuild();
+                        }
+                    }
+                }
+                else // Move frame marker
+                {
                     int frameIdx = guiTimeline.GetFrame(pointerPos);
 
                     if (frameIdx != -1)
@@ -184,7 +306,9 @@ namespace BansheeEditor
         private void OnPointerReleased(PointerEvent ev)
         {
             isPointerHeld = false;
+            isDragInProgress = false;
             isMousePressedOverKey = false;
+            isMousePressedOverTangent = false;
         }
 
         private void OnButtonUp(ButtonEvent ev)
@@ -269,9 +393,10 @@ namespace BansheeEditor
             foreach (var curve in curves)
             {
                 float t = guiCurveDrawing.GetTimeForFrame(markedFrameIdx);
-                float value = curve.Native.Evaluate(t);
+                float value = curve.Evaluate(t);
 
                 curve.AddKeyframe(t, value);
+                curve.Apply();
             }
 
             // TODO - UNDOREDO
@@ -288,37 +413,42 @@ namespace BansheeEditor
         
         private void ChangeSelectionTangentMode(TangentMode mode)
         {
-            foreach (var keyframe in selectedKeyframes)
+            foreach (var selectedEntry in selectedKeyframes)
             {
-                EdAnimationCurve curve = curves[keyframe.curveIdx];
+                EdAnimationCurve curve = curves[selectedEntry.curveIdx];
 
-                if (mode == TangentMode.Auto || mode == TangentMode.Free)
-                    curve.SetTangentMode(keyframe.keyIdx, mode);
-                else
+                foreach (var keyframeIdx in selectedEntry.keyIndices)
                 {
-                    TangentMode newMode = curve.TangentModes[keyframe.keyIdx];
-
-                    if (mode.HasFlag((TangentMode)TangentType.In))
-                    {
-                        // Replace only the in tangent mode, keeping the out tangent as is
-                        TangentMode inFlags = (TangentMode.InAuto | TangentMode.InFree | TangentMode.InLinear |
-                                               TangentMode.InAuto);
-
-                        newMode &= ~inFlags;
-                        newMode |= (mode & inFlags);
-                    }
+                    if (mode == TangentMode.Auto || mode == TangentMode.Free)
+                        curve.SetTangentMode(keyframeIdx, mode);
                     else
                     {
-                        // Replace only the out tangent mode, keeping the in tangent as is
-                        TangentMode outFlags = (TangentMode.OutAuto | TangentMode.OutFree | TangentMode.OutLinear |
-                                               TangentMode.OutAuto);
+                        TangentMode newMode = curve.TangentModes[keyframeIdx];
 
-                        newMode &= ~outFlags;
-                        newMode |= (mode & outFlags);
-                    }
+                        if (mode.HasFlag((TangentMode) TangentType.In))
+                        {
+                            // Replace only the in tangent mode, keeping the out tangent as is
+                            TangentMode inFlags = (TangentMode.InAuto | TangentMode.InFree | TangentMode.InLinear |
+                                                   TangentMode.InAuto);
 
-                    curve.SetTangentMode(keyframe.keyIdx, newMode);
+                            newMode &= ~inFlags;
+                            newMode |= (mode & inFlags);
+                        }
+                        else
+                        {
+                            // Replace only the out tangent mode, keeping the in tangent as is
+                            TangentMode outFlags = (TangentMode.OutAuto | TangentMode.OutFree | TangentMode.OutLinear |
+                                                    TangentMode.OutAuto);
+
+                            newMode &= ~outFlags;
+                            newMode |= (mode & outFlags);
+                        }
+
+                        curve.SetTangentMode(keyframeIdx, newMode);
+                    }
                 }
+
+                curve.Apply();
             }
 
             // TODO - UNDOREDO
@@ -339,6 +469,7 @@ namespace BansheeEditor
                     float value = curveCoord.y;
 
                     curve.AddKeyframe(t, value);
+                    curve.Apply();
                 }
 
                 // TODO - UNDOREDO
@@ -354,18 +485,21 @@ namespace BansheeEditor
 
         private void DeleteSelectedKeyframes()
         {
-            // Sort keys from highest to lowest so they can be removed without changing the indices of the keys
-            // after them
-            selectedKeyframes.Sort((x, y) =>
+            foreach (var selectedEntry in selectedKeyframes)
             {
-                if (x.curveIdx.Equals(y.curveIdx))
-                    return y.keyIdx.CompareTo(x.keyIdx);
+                EdAnimationCurve curve = curves[selectedEntry.curveIdx];
 
-                return x.curveIdx.CompareTo(y.curveIdx);
-            });
+                // Sort keys from highest to lowest so the indices don't change
+                selectedEntry.keyIndices.Sort((x, y) =>
+                {
+                    return y.CompareTo(x);
+                });
+
+                foreach (var keyframeIdx in selectedEntry.keyIndices)
+                    curve.RemoveKeyframe(keyframeIdx);
 
-            foreach (var keyframe in selectedKeyframes)
-                curves[keyframe.curveIdx].RemoveKeyframe(keyframe.keyIdx);
+                curve.Apply();
+            }
 
             // TODO - UNDOREDO
 
@@ -378,7 +512,6 @@ namespace BansheeEditor
         {
             guiCurveDrawing.ClearSelectedKeyframes();
             selectedKeyframes.Clear();
-            isMousePressedOverKey = false;
         }
 
         private void SelectKeyframe(KeyframeRef keyframeRef)
@@ -386,17 +519,38 @@ namespace BansheeEditor
             guiCurveDrawing.SelectKeyframe(keyframeRef, true);
 
             if (!IsSelected(keyframeRef))
-                selectedKeyframes.Add(keyframeRef);
+            {
+                int curveIdx = selectedKeyframes.FindIndex(x =>
+                {
+                    return x.curveIdx == keyframeRef.curveIdx;
+                });
+
+                if (curveIdx == -1)
+                {
+                    curveIdx = selectedKeyframes.Count;
+                    selectedKeyframes.Add(new SelectedKeyframes());
+                }
+
+                selectedKeyframes[curveIdx].keyIndices.Add(keyframeRef.keyIdx);
+            }
         }
 
         private bool IsSelected(KeyframeRef keyframeRef)
         {
-            int existingIdx = selectedKeyframes.FindIndex(x =>
+            int curveIdx = selectedKeyframes.FindIndex(x =>
+            {
+                return x.curveIdx == keyframeRef.curveIdx;
+            });
+
+            if (curveIdx == -1)
+                return false;
+
+            int keyIdx = selectedKeyframes[curveIdx].keyIndices.FindIndex(x =>
             {
-                return x.curveIdx == keyframeRef.curveIdx && x.keyIdx == keyframeRef.keyIdx;
+                return x == keyframeRef.keyIdx;
             });
 
-            return (existingIdx != -1);
+            return keyIdx != -1;
         }
     }
 

+ 2 - 2
Source/MBansheeEditor/Windows/Animation/GUIGraphTime.cs

@@ -64,10 +64,10 @@ namespace BansheeEditor
 
             Vector2I relativeCoords = windowCoords - new Vector2I(bounds.x + PADDING, bounds.y);
 
-            float lengthPerPixel = rangeLength / drawableWidth;
+            float lengthPerPixel = GetRange() / drawableWidth;
             float time = relativeCoords.x * lengthPerPixel;
 
-            return (int)(time * fps);
+            return MathEx.RoundToInt(time * fps);
         }
 
         /// <summary>

+ 1 - 0
Source/MBansheeEditor/Windows/AnimationWindow.cs

@@ -99,6 +99,7 @@ namespace BansheeEditor
             curves[0].AddKeyframe(10.0f, 5.0f);
             curves[0].AddKeyframe(15.0f, -2.0f);
             curves[0].AddKeyframe(20.0f, 3.0f, TangentMode.InStep);
+            curves[0].Apply();
 
             return curves;
         }