Răsfoiți Sursa

Refactor: Curve editor is now usable outside of the animation window
- Also added the ability to draw curve ranges
- Events and frame marker display is now optional

BearishSun 7 ani în urmă
părinte
comite
edc408bc79

+ 257 - 28
Source/Scripting/MBansheeEditor/Windows/Animation/GUICurveDrawing.cs

@@ -28,6 +28,8 @@ namespace BansheeEditor
         private float yOffset;
 
         private GUIGraphTicks tickHandler;
+        private bool drawMarkers = true;
+        private bool drawRange = false;
 
         /// <summary>
         /// Creates a new curve drawing GUI element.
@@ -36,11 +38,15 @@ namespace BansheeEditor
         /// <param name="width">Width of the element in pixels.</param>
         /// <param name="height">Height of the element in pixels.</param>
         /// <param name="curveInfos">Initial set of curves to display. </param>
-        public GUICurveDrawing(GUILayout layout, int width, int height, CurveDrawInfo[] curveInfos)
+        /// <param name="drawMarkers">If enabled draw frame markers, or if disabled draw just the curve.</param>
+        public GUICurveDrawing(GUILayout layout, int width, int height, CurveDrawInfo[] curveInfos, bool drawMarkers = true)
             :base(layout, width, height)
         {
-            tickHandler = new GUIGraphTicks(GUITickStepType.Time);
+            if(drawMarkers)
+                tickHandler = new GUIGraphTicks(GUITickStepType.Time);
+
             this.curveInfos = curveInfos;
+            this.drawMarkers = drawMarkers;
             
             ClearSelectedKeyframes(); // Makes sure the array is initialized
         }
@@ -75,6 +81,17 @@ namespace BansheeEditor
             SetOffset(offset.x);
             yOffset = offset.y;
         }
+
+        /// <summary>
+        /// Changes curve rendering mode. Normally the curves are drawn individually, but when range rendering is enabled
+        /// the area between the first two curves is drawn instead. This setting is ignored if less than two curves are
+        /// present. More than two curves are also ignored.
+        /// </summary>
+        /// <param name="drawRange">True to enable range rendering mode, false to enable individual curve rendering.</param>
+        public void SetDrawRange(bool drawRange)
+        {
+            this.drawRange = drawRange;
+        }
         
         /// <summary>
         /// Marks the specified key-frame as selected, changing the way it is displayed.
@@ -301,7 +318,7 @@ namespace BansheeEditor
             if (onTop)
                 depth = 110;
             else
-                depth = 128;
+                depth = 130;
 
             canvas.DrawLine(start, end, color, depth);
         }
@@ -316,7 +333,7 @@ namespace BansheeEditor
             Vector2I start = new Vector2I(0, center.y);
             Vector2I end = new Vector2I(width, center.y);
 
-            canvas.DrawLine(start, end, COLOR_DARK_GRAY);
+            canvas.DrawLine(start, end, COLOR_DARK_GRAY, 130);
         }
 
         /// <summary>
@@ -442,51 +459,61 @@ namespace BansheeEditor
             if (curveInfos == null)
                 return;
 
-            tickHandler.SetRange(rangeOffset, rangeOffset + GetRange(true), drawableWidth + PADDING);
-
-            // Draw vertical frame markers
-            int numTickLevels = tickHandler.NumLevels;
-            for (int i = numTickLevels - 1; i >= 0; i--)
+            if (drawMarkers)
             {
-                float[] ticks = tickHandler.GetTicks(i);
-                float strength = tickHandler.GetLevelStrength(i);
+                tickHandler.SetRange(rangeOffset, rangeOffset + GetRange(true), drawableWidth + PADDING);
 
-                for (int j = 0; j < ticks.Length; j++)
+                // Draw vertical frame markers
+                int numTickLevels = tickHandler.NumLevels;
+                for (int i = numTickLevels - 1; i >= 0; i--)
                 {
-                    Color color = COLOR_DARK_GRAY;
-                    color.a *= strength;
+                    float[] ticks = tickHandler.GetTicks(i);
+                    float strength = tickHandler.GetLevelStrength(i);
+
+                    for (int j = 0; j < ticks.Length; j++)
+                    {
+                        Color color = COLOR_DARK_GRAY;
+                        color.a *= strength;
 
-                    DrawFrameMarker(ticks[j], color, false);
+                        DrawFrameMarker(ticks[j], color, false);
+                    }
                 }
+
+                // Draw center line
+                DrawCenterLine();
             }
 
-            // Draw center line
-            DrawCenterLine();
+            // Draw range
+            int curvesToDraw = curveInfos.Length;
+            if (drawRange && curveInfos.Length >= 2)
+            {
+                EdAnimationCurve[] curves = {curveInfos[0].curve, curveInfos[1].curve};
+
+                DrawCurveRange(curves, new Color(1.0f, 0.0f, 0.0f, 0.7f));
+                curvesToDraw = 2;
+            }
 
             // Draw curves
-            int curveIdx = 0;
-            foreach (var curveInfo in curveInfos)
+            for (int i = 0; i < curvesToDraw; i++)
             {
-                DrawCurve(curveInfo.curve, curveInfo.color);
+                DrawCurve(curveInfos[i].curve, curveInfos[i].color);
 
                 // Draw keyframes
-                KeyFrame[] keyframes = curveInfo.curve.KeyFrames;
+                KeyFrame[] keyframes = curveInfos[i].curve.KeyFrames;
 
-                for (int i = 0; i < keyframes.Length; i++)
+                for (int j = 0; j < keyframes.Length; j++)
                 {
-                    bool isSelected = IsSelected(curveIdx, i);
+                    bool isSelected = IsSelected(i, j);
 
-                    DrawKeyframe(keyframes[i].time, keyframes[i].value, isSelected);
+                    DrawKeyframe(keyframes[j].time, keyframes[j].value, isSelected);
 
                     if (isSelected)
-                        DrawTangents(keyframes[i], curveInfo.curve.TangentModes[i]);
+                        DrawTangents(keyframes[j], curveInfos[i].curve.TangentModes[j]);
                 }
-
-                curveIdx++;
             }
 
             // Draw selected frame marker
-            if (markedFrameIdx != -1)
+            if (drawMarkers && markedFrameIdx != -1)
                 DrawFrameMarker(GetTimeForFrame(markedFrameIdx), Color.BansheeOrange, true);
         }
 
@@ -612,6 +639,208 @@ namespace BansheeEditor
                 canvas.DrawLine(start, end, COLOR_MID_GRAY);
             }
         }
+
+        /// <summary>
+        /// Draws the area between two curves using the provided color.
+        /// </summary>
+        /// <param name="curves">Curves to draw within the currently set range.</param>
+        /// <param name="color">Color to draw the area with.</param>
+        private void DrawCurveRange(EdAnimationCurve[] curves, Color color)
+        {
+            float range = GetRange();
+
+            if(curves.Length != 2 || curves[0] == null || curves[1] == null)
+                return;
+
+            KeyFrame[][] keyframes = { curves[0].KeyFrames, curves[1].KeyFrames };
+            if (keyframes[0].Length <= 0 || keyframes[1].Length <= 0)
+                return;
+
+            int numSamples = (drawableWidth + LINE_SPLIT_WIDTH - 1) / LINE_SPLIT_WIDTH;
+            float timePerSample = range / numSamples;
+
+            float time = rangeOffset;
+            int[] keyframeIndices = {0, 0};
+
+            // Find first valid keyframe indices
+            for (int curveIdx = 0; curveIdx < 2; curveIdx++)
+            {
+                keyframeIndices[curveIdx] = keyframes[curveIdx].Length;
+
+                for (int i = 0; i < keyframes[curveIdx].Length; i++)
+                {
+                    if (keyframes[curveIdx][i].time > time)
+                        keyframeIndices[curveIdx] = i;
+                }
+            }
+
+            List<float> times = new List<float>();
+            List<float>[] points = { new List<float>(), new List<float>() };
+
+            // Determine start points
+            for (int curveIdx = 0; curveIdx < 2; curveIdx++)
+            {
+                float value = curves[curveIdx].Evaluate(time, false);
+                points[curveIdx].Add(value);
+            }
+
+            times.Add(time);
+
+            float rangeEnd = rangeOffset + range;
+            while (time < rangeEnd)
+            {
+                float nextTime = time + timePerSample;
+                bool hasStep = false;
+
+                // Determine time to sample at. Use fixed increments unless there's a step keyframe within our increment in
+                // which case we use its time so we can evaluate it directly
+                for (int curveIdx = 0; curveIdx < 2; curveIdx++)
+                {
+                    int keyframeIdx = keyframeIndices[curveIdx];
+                    if (keyframeIdx < keyframes[curveIdx].Length)
+                    {
+                        KeyFrame keyframe = keyframes[curveIdx][keyframeIdx];
+
+                        bool isStep = keyframe.inTangent == float.PositiveInfinity ||
+                                      keyframe.outTangent == float.PositiveInfinity;
+
+                        if (isStep && keyframe.time <= nextTime)
+                        {
+                            nextTime = Math.Min(nextTime, keyframe.time);
+                            hasStep = true;
+                        }
+                    }
+                }
+
+                // Evaluate
+                if (hasStep)
+                {
+                    for (int curveIdx = 0; curveIdx < 2; curveIdx++)
+                    {
+                        int keyframeIdx = keyframeIndices[curveIdx];
+                        if (keyframeIdx < keyframes[curveIdx].Length)
+                        {
+                            KeyFrame keyframe = keyframes[curveIdx][keyframeIdx];
+
+                            if (MathEx.ApproxEquals(keyframe.time, nextTime))
+                            {
+                                if (keyframeIdx > 0)
+                                {
+                                    KeyFrame prevKeyframe = keyframes[curveIdx][keyframeIdx - 1];
+                                    points[curveIdx].Add(prevKeyframe.value);
+                                }
+                                else
+                                    points[curveIdx].Add(keyframe.value);
+
+                                points[curveIdx].Add(keyframe.value);
+
+                            }
+                            else
+                            {
+                                // The other curve has step but this one doesn't, we just insert the same value twice
+                                float value = curves[curveIdx].Evaluate(nextTime, false);
+                                points[curveIdx].Add(value);
+                                points[curveIdx].Add(value);
+                            }
+
+                            times.Add(nextTime);
+                            times.Add(nextTime);
+                        }
+                    }
+                }
+                else
+                {
+                    for (int curveIdx = 0; curveIdx < 2; curveIdx++)
+                        points[curveIdx].Add(curves[curveIdx].Evaluate(nextTime, false));
+
+                    times.Add(nextTime);
+                }
+
+                // Advance keyframe indices
+                for (int curveIdx = 0; curveIdx < 2; curveIdx++)
+                {
+                    int keyframeIdx = keyframeIndices[curveIdx];
+                    while (keyframeIdx < keyframes[curveIdx].Length)
+                    {
+                        KeyFrame keyframe = keyframes[curveIdx][keyframeIdx];
+                        if (keyframe.time > nextTime)
+                            break;
+
+                        keyframeIdx = ++keyframeIndices[curveIdx];
+                    }
+                }
+
+                time = nextTime;
+            }
+
+            // End points
+            for (int curveIdx = 0; curveIdx < 2; curveIdx++)
+            {
+                float value = curves[curveIdx].Evaluate(rangeEnd, false);
+                points[curveIdx].Add(value);
+            }
+
+            times.Add(rangeEnd);
+
+            int numQuads = times.Count - 1;
+            List<Vector2I> vertices = new List<Vector2I>();
+
+            for (int i = 0; i < numQuads; i++)
+            {
+                int idxLeft = points[0][i] < points[1][i] ? 0 : 1;
+                int idxRight = points[0][i + 1] < points[1][i + 1] ? 0 : 1;
+
+                Vector2[] left =
+                {
+                    new Vector2(times[i], points[0][i]),
+                    new Vector2(times[i], points[1][i])
+                };
+
+                Vector2[] right =
+                {
+                    new Vector2(times[i + 1], points[0][i + 1]),
+                    new Vector2(times[i + 1], points[1][i + 1])
+                };
+
+                if (idxLeft == idxRight)
+                {
+                    int idxA = idxLeft;
+                    int idxB = (idxLeft + 1) % 2;
+
+                    vertices.Add(CurveToPixelSpace(left[idxB]));
+                    vertices.Add(CurveToPixelSpace(right[idxB]));
+                    vertices.Add(CurveToPixelSpace(left[idxA]));
+
+                    vertices.Add(CurveToPixelSpace(right[idxB]));
+                    vertices.Add(CurveToPixelSpace(right[idxA]));
+                    vertices.Add(CurveToPixelSpace(left[idxA]));
+                }
+                // Lines intersects, can't represent them with a single quad
+                else if (idxLeft != idxRight)
+                {
+                    int idxA = idxLeft;
+                    int idxB = (idxLeft + 1) % 2;
+
+                    Line2 lineA = new Line2(left[idxB], right[idxA] - left[idxB]);
+                    Line2 lineB = new Line2(left[idxA], right[idxB] - left[idxA]);
+
+                    if (lineA.Intersects(lineB, out var t))
+                    {
+                        Vector2 intersection = left[idxB] + t * (right[idxA] - left[idxB]);
+
+                        vertices.Add(CurveToPixelSpace(left[idxB]));
+                        vertices.Add(CurveToPixelSpace(intersection));
+                        vertices.Add(CurveToPixelSpace(left[idxA]));
+
+                        vertices.Add(CurveToPixelSpace(intersection));
+                        vertices.Add(CurveToPixelSpace(right[idxB]));
+                        vertices.Add(CurveToPixelSpace(right[idxA]));
+                    }
+                }
+            }
+
+            canvas.DrawTriangleList(vertices.ToArray(), color, 129);
+        }
     }
 
     /// <summary>

+ 95 - 44
Source/Scripting/MBansheeEditor/Windows/Animation/GUICurveEditor.cs

@@ -64,7 +64,7 @@ namespace BansheeEditor
         private const int SIDEBAR_WIDTH = 30;
         private const int DRAG_START_DISTANCE = 3;
 
-        private AnimationWindow window;
+        private EditorWindow window;
         private GUILayout gui;
         private GUIPanel drawingPanel;
         private GUIPanel eventsPanel;
@@ -85,6 +85,7 @@ namespace BansheeEditor
 
         private CurveDrawInfo[] curveInfos = new CurveDrawInfo[0];
         private bool disableCurveEdit = false;
+        private bool drawCurveRange = false;
 
         private float xRange = 60.0f;
         private float yRange = 10.0f;
@@ -96,7 +97,9 @@ namespace BansheeEditor
         private int markedFrameIdx;
         private List<SelectedKeyframes> selectedKeyframes = new List<SelectedKeyframes>();
 
+        private bool showEvents = true;
         private List<EventInfo> events = new List<EventInfo>();
+        private SceneObject eventsSO;
 
         private bool isPointerHeld;
         private bool isMousePressedOverKey;
@@ -151,10 +154,12 @@ namespace BansheeEditor
                 yRange = value.y;
 
                 guiTimeline.SetRange(xRange);
-                guiEvents.SetRange(xRange);
                 guiCurveDrawing.SetRange(xRange, yRange * 2.0f);
                 guiSidebar.SetRange(offset.y - yRange, offset.y + yRange);
 
+                if(showEvents)
+                    guiEvents.SetRange(xRange);
+
                 Redraw();
             }
         }
@@ -170,10 +175,12 @@ namespace BansheeEditor
                 offset = value;
 
                 guiTimeline.SetOffset(offset.x);
-                guiEvents.SetOffset(offset.x);
                 guiCurveDrawing.SetOffset(offset);
                 guiSidebar.SetRange(offset.y - yRange, offset.y + yRange);
 
+                if(showEvents)
+                    guiEvents.SetOffset(offset.x);
+
                 Redraw();
             }
         }
@@ -242,10 +249,12 @@ namespace BansheeEditor
         /// <param name="gui">GUI layout into which to place the GUI element.</param>
         /// <param name="width">Width in pixels.</param>
         /// <param name="height">Height in pixels.</param>
-        public GUICurveEditor(AnimationWindow window, GUILayout gui, int width, int height)
+        /// <param name="showEvents">If true show events on the graph and allow their editing.</param>
+        public GUICurveEditor(EditorWindow window, GUILayout gui, int width, int height, bool showEvents)
         {
             this.window = window;
             this.gui = gui;
+            this.showEvents = showEvents;
 
             this.width = width;
             this.height = height;
@@ -283,26 +292,32 @@ namespace BansheeEditor
             timelineBackground.Bounds = new Rect2I(0, 0, width, TIMELINE_HEIGHT + VERT_PADDING);
             timelineBgPanel.AddElement(timelineBackground);
 
-            eventsPanel = gui.AddPanel();
-            eventsPanel.SetPosition(0, TIMELINE_HEIGHT + VERT_PADDING);
-            guiEvents = new GUIAnimEvents(eventsPanel, width, EVENTS_HEIGHT);
+            int eventsHeaderHeight = 0;
+            if (showEvents)
+            {
+                eventsPanel = gui.AddPanel();
+                eventsPanel.SetPosition(0, TIMELINE_HEIGHT + VERT_PADDING);
+                guiEvents = new GUIAnimEvents(eventsPanel, width, EVENTS_HEIGHT);
+
+                GUIPanel eventsBgPanel = eventsPanel.AddPanel(1);
 
-            GUIPanel eventsBgPanel = eventsPanel.AddPanel(1);
+                eventsBackground = new GUITexture(null, EditorStyles.Header);
+                eventsBackground.Bounds = new Rect2I(0, 0, width, EVENTS_HEIGHT + VERT_PADDING);
+                eventsBgPanel.AddElement(eventsBackground);
 
-            eventsBackground = new GUITexture(null, EditorStyles.Header);
-            eventsBackground.Bounds = new Rect2I(0, 0, width, EVENTS_HEIGHT + VERT_PADDING);
-            eventsBgPanel.AddElement(eventsBackground);
+                eventsHeaderHeight = EVENTS_HEIGHT;
+            }
 
             drawingPanel = gui.AddPanel();
-            drawingPanel.SetPosition(0, TIMELINE_HEIGHT + EVENTS_HEIGHT + VERT_PADDING);
+            drawingPanel.SetPosition(0, TIMELINE_HEIGHT + eventsHeaderHeight + VERT_PADDING);
 
-            guiCurveDrawing = new GUICurveDrawing(drawingPanel, width, height - TIMELINE_HEIGHT - EVENTS_HEIGHT - VERT_PADDING * 2, curveInfos);
+            guiCurveDrawing = new GUICurveDrawing(drawingPanel, width, height - TIMELINE_HEIGHT - eventsHeaderHeight - VERT_PADDING * 2, curveInfos);
             guiCurveDrawing.SetRange(60.0f, 20.0f);
 
             GUIPanel sidebarPanel = gui.AddPanel(-10);
-            sidebarPanel.SetPosition(0, TIMELINE_HEIGHT + EVENTS_HEIGHT + VERT_PADDING);
+            sidebarPanel.SetPosition(0, TIMELINE_HEIGHT + eventsHeaderHeight + VERT_PADDING);
 
-            guiSidebar = new GUIGraphValues(sidebarPanel, SIDEBAR_WIDTH, height - TIMELINE_HEIGHT - EVENTS_HEIGHT - VERT_PADDING * 2);
+            guiSidebar = new GUIGraphValues(sidebarPanel, SIDEBAR_WIDTH, height - TIMELINE_HEIGHT - eventsHeaderHeight - VERT_PADDING * 2);
             guiSidebar.SetRange(-10.0f, 10.0f);
         }
 
@@ -405,7 +420,7 @@ namespace BansheeEditor
                     else
                     {
                         int eventIdx;
-                        if (guiEvents.FindEvent(eventPos, out eventIdx))
+                        if (showEvents && guiEvents.FindEvent(eventPos, out eventIdx))
                         {
                             if (!Input.IsButtonHeld(ButtonCode.LeftShift) && !Input.IsButtonHeld(ButtonCode.RightShift))
                                 ClearSelection();
@@ -459,7 +474,7 @@ namespace BansheeEditor
                         keyframeContextMenu.Open(pointerPos, gui);
                     }
                 }
-                else if (guiEvents.GetFrame(eventPos) != -1) // Clicked over events bar
+                else if (showEvents && guiEvents.GetFrame(eventPos) != -1) // Clicked over events bar
                 {
                     contextClickPosition = eventPos;
 
@@ -584,9 +599,8 @@ namespace BansheeEditor
                             }
 
                             isModifiedDuringDrag = true;
-                            window.RecordClipState();
-
                             guiCurveDrawing.Rebuild();
+
                             UpdateEventsGUI();
                         }
                         else if (isMousePressedOverTangent && !disableCurveEdit)
@@ -627,8 +641,6 @@ namespace BansheeEditor
                                 curve.Apply();
 
                                 isModifiedDuringDrag = true;
-                                window.RecordClipState();
-
                                 guiCurveDrawing.Rebuild();
                             }
                         }
@@ -684,6 +696,20 @@ namespace BansheeEditor
             Redraw();
         }
 
+        /// <summary>
+        /// Changes curve rendering mode. Normally the curves are drawn individually, but when range rendering is enabled
+        /// the area between the first two curves is drawn instead. This setting is ignored if less than two curves are
+        /// present. More than two curves are also ignored.
+        /// </summary>
+        /// <param name="drawRange">True to enable range rendering mode, false to enable individual curve rendering.</param>
+        public void SetDrawRange(bool drawRange)
+        {
+            drawCurveRange = drawRange;
+            guiCurveDrawing.SetDrawRange(drawRange);
+
+            Redraw();
+        }
+
         /// <summary>
         /// Change the physical size of the GUI element.
         /// </summary>
@@ -694,13 +720,19 @@ namespace BansheeEditor
             this.width = width;
             this.height = height;
 
+            int eventsHeaderHeight = 0;
+            if (showEvents)
+            {
+                eventsHeaderHeight = EVENTS_HEIGHT;
+                guiEvents.SetSize(width, EVENTS_HEIGHT);
+                eventsBackground.Bounds = new Rect2I(0, 0, width, EVENTS_HEIGHT + VERT_PADDING);
+            }
+
             guiTimeline.SetSize(width, TIMELINE_HEIGHT);
-            guiEvents.SetSize(width, EVENTS_HEIGHT);
-            guiCurveDrawing.SetSize(width, height - TIMELINE_HEIGHT - EVENTS_HEIGHT);
-            guiSidebar.SetSize(SIDEBAR_WIDTH, height - TIMELINE_HEIGHT - EVENTS_HEIGHT);
+            guiCurveDrawing.SetSize(width, height - TIMELINE_HEIGHT - eventsHeaderHeight);
+            guiSidebar.SetSize(SIDEBAR_WIDTH, height - TIMELINE_HEIGHT - eventsHeaderHeight);
 
             timelineBackground.Bounds = new Rect2I(0, 0, width, TIMELINE_HEIGHT + VERT_PADDING);
-            eventsBackground.Bounds = new Rect2I(0, 0, width, EVENTS_HEIGHT + VERT_PADDING);
 
             Redraw();
         }
@@ -712,12 +744,23 @@ namespace BansheeEditor
         public void SetFPS(int fps)
         {
             guiTimeline.SetFPS(fps);
-            guiEvents.SetFPS(fps);
             guiCurveDrawing.SetFPS(fps);
 
+            if(showEvents)
+                guiEvents.SetFPS(fps);
+
             Redraw();
         }
 
+        /// <summary>
+        /// Sets a scene object that will be used for enumerating components/methods used for adding events.
+        /// </summary>
+        /// <param name="so">Scene object containing the animation component.</param>
+        public void SetEventSceneObject(SceneObject so)
+        {
+            eventsSO = so;
+        }
+
         /// <summary>
         /// Returns time for a frame with the specified index. Depends on set range and FPS.
         /// </summary>
@@ -737,9 +780,11 @@ namespace BansheeEditor
             markedFrameIdx = frameIdx;
 
             guiTimeline.SetMarkedFrame(frameIdx);
-            guiEvents.SetMarkedFrame(frameIdx);
             guiCurveDrawing.SetMarkedFrame(frameIdx);
 
+            if(showEvents)
+                guiEvents.SetMarkedFrame(frameIdx);
+
             Redraw();
         }
 
@@ -764,8 +809,6 @@ namespace BansheeEditor
             else
                 ShowReadOnlyMessage();
 
-            window.RecordClipState();
-
             OnCurveModified?.Invoke();
             guiCurveDrawing.Rebuild();
             UpdateEventsGUI();
@@ -778,10 +821,13 @@ namespace BansheeEditor
         {
             ClearSelection();
 
+            if (!showEvents)
+                return;
+
             float eventTime = guiEvents.GetTimeForFrame(markedFrameIdx);
             EventInfo eventInfo = new EventInfo();
             eventInfo.animEvent = new AnimationEvent("", eventTime);
-            
+
             events.Add(eventInfo);
             OnEventAdded?.Invoke();
 
@@ -796,6 +842,9 @@ namespace BansheeEditor
         /// </summary>
         private void UpdateEventsGUI()
         {
+            if (!showEvents)
+                return;
+
             AnimationEvent[] animEvents = new AnimationEvent[events.Count];
             bool[] selected = new bool[events.Count];
 
@@ -816,10 +865,12 @@ namespace BansheeEditor
         {
             guiCurveDrawing.Rebuild();
             guiTimeline.Rebuild();
-            guiEvents.Rebuild();
             guiSidebar.Rebuild();
+
+            if(showEvents)
+                guiEvents.Rebuild();
         }
-        
+
         /// <summary>
         /// Changes the tangent mode for all currently selected keyframes.
         /// </summary>
@@ -871,8 +922,6 @@ namespace BansheeEditor
                 curve.Apply();
             }
 
-            window.RecordClipState();
-
             OnCurveModified?.Invoke();
             guiCurveDrawing.Rebuild();
         }
@@ -901,8 +950,6 @@ namespace BansheeEditor
                 else
                     ShowReadOnlyMessage();
 
-                window.RecordClipState();
-
                 OnCurveModified?.Invoke();
                 guiCurveDrawing.Rebuild();
                 UpdateEventsGUI();
@@ -914,6 +961,9 @@ namespace BansheeEditor
         /// </summary>
         private void AddEventAtPosition()
         {
+            if (!showEvents)
+                return;
+
             int frame = guiEvents.GetFrame(contextClickPosition);
             if (frame != -1)
             {
@@ -959,7 +1009,6 @@ namespace BansheeEditor
             else
                 ShowReadOnlyMessage();
 
-            window.RecordClipState();
             ClearSelection();
 
             OnCurveModified?.Invoke();
@@ -980,7 +1029,6 @@ namespace BansheeEditor
             }
 
             events = newEvents;
-            window.RecordClipState();
 
             OnEventDeleted?.Invoke();
             ClearSelection();
@@ -1087,12 +1135,11 @@ namespace BansheeEditor
                 curve.Apply();
 
                 guiCurveDrawing.Rebuild();
-                OnCurveModified?.Invoke();
             },
             x =>
             {
                 if (x)
-                    window.RecordClipState();
+                    OnCurveModified?.Invoke();
             });
         }
 
@@ -1101,6 +1148,9 @@ namespace BansheeEditor
         /// </summary>
         private void EditSelectedEvent()
         {
+            if (!showEvents)
+                return;
+
             for (int i = 0; i < events.Count; i++)
             {
                 if (events[i].selected)
@@ -1126,8 +1176,10 @@ namespace BansheeEditor
             Rect2I eventBounds = GUIUtility.CalculateBounds(eventsPanel, window.GUI);
             Vector2I windowPos = position + new Vector2I(eventBounds.x, eventBounds.y);
 
-            SceneObject so = window.SelectedSO;
-            Component[] components = so.GetComponents();
+            if (eventsSO == null)
+                return;
+
+            Component[] components = eventsSO.GetComponents();
             string[] componentNames = new string[components.Length];
             for (int i = 0; i < components.Length; i++)
                 componentNames[i] = components[i].GetType().Name;
@@ -1136,12 +1188,11 @@ namespace BansheeEditor
             editWindow.Initialize(animEvent, componentNames, () =>
             {
                 UpdateEventsGUI();
-                OnEventModified?.Invoke();
             },
             x =>
             {
                 if(x)
-                    window.RecordClipState();
+                    OnEventModified?.Invoke();
             });
         }
 

+ 21 - 14
Source/Scripting/MBansheeEditor/Windows/AnimationWindow.cs

@@ -26,14 +26,6 @@ namespace BansheeEditor
 
         private SceneObject selectedSO;
 
-        /// <summary>
-        /// Scene object for which are we currently changing the animation for.
-        /// </summary>
-        internal SceneObject SelectedSO
-        {
-            get { return selectedSO; }
-        }
-
         #region Overrides
 
         /// <summary>
@@ -446,13 +438,28 @@ namespace BansheeEditor
             scrollBarWidth = vertScrollBar.Bounds.width;
 
             Vector2I curveEditorSize = GetCurveEditorSize();
-            guiCurveEditor = new GUICurveEditor(this, editorPanel, curveEditorSize.x, curveEditorSize.y);
+            guiCurveEditor = new GUICurveEditor(this, editorPanel, curveEditorSize.x, curveEditorSize.y, true);
+            guiCurveEditor.SetEventSceneObject(selectedSO);
+
             guiCurveEditor.OnFrameSelected += OnFrameSelected;
-            guiCurveEditor.OnEventAdded += OnEventsChanged;
-            guiCurveEditor.OnEventModified += EditorApplication.SetProjectDirty;
-            guiCurveEditor.OnEventDeleted += OnEventsChanged;
+            guiCurveEditor.OnEventAdded += () =>
+            {
+                OnEventsChanged();
+                RecordClipState();
+            };
+            guiCurveEditor.OnEventModified += () =>
+            {
+                EditorApplication.SetProjectDirty();
+                RecordClipState();
+            };
+            guiCurveEditor.OnEventDeleted += () =>
+            {
+                OnEventsChanged();
+                RecordClipState();
+            };
             guiCurveEditor.OnCurveModified += () =>
             {
+                RecordClipState();
                 SwitchState(State.Normal);
 
                 ApplyClipChanges();
@@ -828,7 +835,7 @@ namespace BansheeEditor
         /// <summary>
         /// Records current clip state for undo/redo purposes.
         /// </summary>
-        internal void RecordClipState()
+        private void RecordClipState()
         {
             AnimationClipState clipState = CreateClipState();
 
@@ -841,7 +848,7 @@ namespace BansheeEditor
         /// <summary>
         /// Records current clip state for undo/redo purposes.
         /// </summary>
-        internal AnimationClipState CreateClipState()
+        private AnimationClipState CreateClipState()
         {
             AnimationClipState clipState = new AnimationClipState();
 

+ 2 - 1
Source/Scripting/MBansheeEngine/MBansheeEngine.csproj

@@ -42,6 +42,7 @@
     <Compile Include="Animation\Animation.cs" />
     <Compile Include="GUI\GUICanvas.cs" />
     <Compile Include="GUI\GUIScrollBar.cs" />
+    <Compile Include="Math\Line2.cs" />
     <Compile Include="Rendering\Material.cs" />
     <Compile Include="Serialization\ShowInInspector.cs" />
     <Compile Include="Serialization\Step.cs" />
@@ -155,4 +156,4 @@
   <Target Name="AfterBuild">
   </Target>
   -->
-</Project>
+</Project>

+ 55 - 0
Source/Scripting/MBansheeEngine/Math/Line2.cs

@@ -0,0 +1,55 @@
+using System;
+
+namespace BansheeEngine
+{
+    /** @addtogroup Math
+     *  @{
+     */
+
+    /// <summary>
+    /// A line in 2D space represented with an origin and direction.
+    /// </summary>
+    public class Line2
+    {
+        public Line2() { }
+
+        public Line2(Vector2 origin, Vector2 direction)
+        {
+            this.origin = origin;
+            this.direction = direction;
+        }
+
+        /// <summary>
+        /// Line/Line intersection, returns boolean result and distance to intersection point.
+        /// </summary>
+        /// <returns>True on intersection, false otherwise.</returns>
+        public bool Intersects(Line2 rhs, out float t)
+        {
+            t = 0.0f;
+
+            Vector2 diff = rhs.origin - origin;
+
+            float rxs = Vector2.Cross(direction, rhs.direction);
+            float qpxr = Vector2.Cross(diff, direction);
+
+            if (rxs == 0.0f && qpxr == 0.0f)
+                return false;
+
+            if (rxs == 0.0f && qpxr != 0.0f)
+                return false;
+
+            t = Vector2.Cross(diff, rhs.direction) / rxs;
+            var u = Vector2.Cross(diff, direction) / rxs;
+
+            if (rxs != 0.0f && (0 <= t && t <= 1) && (0 <= u && u <= 1))
+                return true;
+
+            return false;
+        }
+
+        public Vector2 origin = new Vector2(0.0f, 0.0f);
+        public Vector2 direction = new Vector2(0.0f, 1.0f);
+    }
+
+    /** @} */
+}

+ 11 - 0
Source/Scripting/MBansheeEngine/Math/Vector2.cs

@@ -184,6 +184,17 @@ namespace BansheeEngine
             return lhs.x * rhs.x + lhs.y * rhs.y;
         }
 
+        /// <summary>
+        /// Calculates the cross product of the two vectors.
+        /// </summary>
+        /// <param name="lhs">First two dimensional vector.</param>
+        /// <param name="rhs">Second two dimensional vector.</param>
+        /// <returns>Cross product between the two vectors.</returns>
+        public static float Cross(Vector2 lhs, Vector2 rhs)
+        {
+            return lhs.x * rhs.y - lhs.y * rhs.x;
+        }
+
         /// <summary>
         /// Calculates the distance between two points.
         /// </summary>