Просмотр исходного кода

Animation curve editor zoom, scroll, drag and resize functionality (WIP)

BearishSun 9 лет назад
Родитель
Сommit
c2218dc008

+ 45 - 58
Source/MBansheeEditor/Windows/Animation/GUICurveDrawing.cs

@@ -28,6 +28,7 @@ namespace BansheeEditor
         private int height;
         private float xRange = 60.0f;
         private float yRange = 20.0f;
+        private Vector2 offset;
         private int fps = 1;
         private int markedFrameIdx = 0;
 
@@ -94,6 +95,15 @@ namespace BansheeEditor
             this.yRange = yRange;
         }
 
+        /// <summary>
+        /// Returns the offset at which the displayed timeline values start at.
+        /// </summary>
+        /// <param name="offset">Value to start the timeline values at.</param>
+        public void SetOffset(Vector2 offset)
+        {
+            this.offset = offset;
+        }
+
         /// <summary>
         /// Number of frames per second, used for frame selection and marking.
         /// </summary>
@@ -286,8 +296,8 @@ namespace BansheeEditor
 
             float yOffset = yRange / 2.0f;
 
-            float t = relativeCoords.x * lengthPerPixel;
-            float value = yOffset - relativeCoords.y * heightPerPixel;
+            float t = offset.x + relativeCoords.x * lengthPerPixel;
+            float value = offset.y + yOffset - relativeCoords.y * heightPerPixel;
 
             curveCoords = new Vector2();
             curveCoords.x = t;
@@ -305,9 +315,11 @@ namespace BansheeEditor
         {
             int heightOffset = height / 2; // So that y = 0 is at center of canvas
 
+            Vector2 relativeCurveCoords = curveCoords - offset;
+
             Vector2I pixelCoords = new Vector2I();
-            pixelCoords.x = (int)((curveCoords.x / GetRange()) * drawableWidth) + GUIGraphTime.PADDING;
-            pixelCoords.y = heightOffset - (int)((curveCoords.y / yRange) * height);
+            pixelCoords.x = (int)((relativeCurveCoords.x / GetRange()) * drawableWidth) + GUIGraphTime.PADDING;
+            pixelCoords.y = heightOffset - (int)((relativeCurveCoords.y / yRange) * height);
 
             return pixelCoords;
         }
@@ -320,7 +332,7 @@ namespace BansheeEditor
         /// <param name="onTop">Determines should the marker be drawn above or below the curve.</param>
         private void DrawFrameMarker(float t, Color color, bool onTop)
         {
-            int xPos = (int)((t / GetRange()) * drawableWidth) + GUIGraphTime.PADDING;
+            int xPos = (int)(((t - offset.x) / GetRange()) * drawableWidth) + GUIGraphTime.PADDING;
 
             Vector2I start = new Vector2I(xPos, 0);
             Vector2I end = new Vector2I(xPos, height);
@@ -339,10 +351,10 @@ namespace BansheeEditor
         /// </summary>
         private void DrawCenterLine()
         {
-            int heightOffset = height / 2; // So that y = 0 is at center of canvas
+            Vector2I center = CurveToPixelSpace(new Vector2(0.0f, 0.0f));
 
-            Vector2I start = new Vector2I(0, heightOffset);
-            Vector2I end = new Vector2I(width, heightOffset);
+            Vector2I start = new Vector2I(0, center.y);
+            Vector2I end = new Vector2I(width, center.y);
 
             canvas.DrawLine(start, end, COLOR_DARK_GRAY);
         }
@@ -489,7 +501,7 @@ namespace BansheeEditor
             if (curves == null)
                 return;
 
-            tickHandler.SetRange(0.0f, GetRange(true), drawableWidth + GUIGraphTime.PADDING);
+            tickHandler.SetRange(offset.x, GetRange(true), drawableWidth + GUIGraphTime.PADDING);
 
             // Draw vertical frame markers
             int numTickLevels = tickHandler.NumLevels;
@@ -580,11 +592,7 @@ namespace BansheeEditor
         private void DrawCurve(EdAnimationCurve curve, Color color)
         {
             float range = GetRange();
-
             float lengthPerPixel = range / drawableWidth;
-            float pixelsPerHeight = height/yRange;
-
-            int heightOffset = height/2; // So that y = 0 is at center of canvas
 
             KeyFrame[] keyframes = curve.KeyFrames;
             if (keyframes.Length <= 0)
@@ -592,19 +600,15 @@ namespace BansheeEditor
 
             // Draw start line
             {
-                float start = MathEx.Clamp(keyframes[0].time, 0.0f, range);
-                int startPixel = (int)(start / lengthPerPixel);
+                float curveStart = MathEx.Clamp(keyframes[0].time, 0.0f, range);
+                float curveValue = curve.Evaluate(0.0f, false);
 
-                int xPosStart = 0;
-                int xPosEnd = GUIGraphTime.PADDING + startPixel;
+                Vector2I start = CurveToPixelSpace(new Vector2(0.0f, curveValue));
+                start.x -= GUIGraphTime.PADDING;
 
-                int yPos = (int)(curve.Evaluate(0.0f, false) * pixelsPerHeight);
-                yPos = heightOffset - yPos; // Offset and flip height (canvas Y goes down)
+                Vector2I end = CurveToPixelSpace(new Vector2(curveStart, curveValue));
 
-                Vector2I a = new Vector2I(xPosStart, yPos);
-                Vector2I b = new Vector2I(xPosEnd, yPos);
-
-                canvas.DrawLine(a, b, COLOR_MID_GRAY);
+                canvas.DrawLine(start, end, COLOR_MID_GRAY);
             }
 
             List<Vector2I> linePoints = new List<Vector2I>();
@@ -614,9 +618,6 @@ namespace BansheeEditor
             {
                 float start = MathEx.Clamp(keyframes[i].time, 0.0f, range);
                 float end = MathEx.Clamp(keyframes[i + 1].time, 0.0f, range);
-                
-                int startPixel = (int)(start / lengthPerPixel);
-                int endPixel = (int)(end / lengthPerPixel);
 
                 bool isStep = keyframes[i].outTangent == float.PositiveInfinity ||
                               keyframes[i + 1].inTangent == float.PositiveInfinity;
@@ -624,29 +625,24 @@ namespace BansheeEditor
                 // If step tangent, draw the required lines without sampling, as the sampling will miss the step
                 if (isStep)
                 {
-                    // Line from left to right frame
-                    int xPos = startPixel;
-                    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));
-
-                    xPos = endPixel;
-                    linePoints.Add(new Vector2I(GUIGraphTime.PADDING + xPos, yPosStart));
+                    float startValue = curve.Evaluate(start, false);
+                    float endValue = curve.Evaluate(end, false);
 
-                    // Line representing the step
-                    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));
+                    linePoints.Add(CurveToPixelSpace(new Vector2(start, startValue)));
+                    linePoints.Add(CurveToPixelSpace(new Vector2(end, startValue)));
+                    linePoints.Add(CurveToPixelSpace(new Vector2(end, endValue)));
                 }
                 else // Draw normally
                 {
+                    float timeIncrement = LINE_SPLIT_WIDTH*lengthPerPixel;
+
+                    int startPixel = (int)(start / lengthPerPixel);
+                    int endPixel = (int)(end / lengthPerPixel);
+
                     int numSplits;
-                    float timeIncrement;
                     if (startPixel != endPixel)
                     {
-                        float fNumSplits = (endPixel - startPixel)/(float) LINE_SPLIT_WIDTH;
+                        float fNumSplits = (end - start) / timeIncrement;
 
                         numSplits = MathEx.FloorToInt(fNumSplits);
                         float remainder = fNumSplits - numSplits;
@@ -664,13 +660,10 @@ namespace BansheeEditor
 
                     for (int j = 0; j < numSplits; j++)
                     {
-                        int xPos = Math.Min(startPixel + j * LINE_SPLIT_WIDTH, endPixel);
                         float t = Math.Min(start + j * timeIncrement, end);
+                        float value = curve.Evaluate(t, false);
 
-                        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));
+                        linePoints.Add(CurveToPixelSpace(new Vector2(t, value)));
                     }
                 }
             }
@@ -679,19 +672,13 @@ namespace BansheeEditor
 
             // Draw end line
             {
-                float end = MathEx.Clamp(keyframes[keyframes.Length - 1].time, 0.0f, range);
-                int endPixel = (int)(end / lengthPerPixel);
-
-                int xPosStart = GUIGraphTime.PADDING + endPixel;
-                int xPosEnd = width;
-
-                int yPos = (int)(curve.Evaluate(range, false) * pixelsPerHeight);
-                yPos = heightOffset - yPos; // Offset and flip height (canvas Y goes down)
+                float curveEnd = MathEx.Clamp(keyframes[keyframes.Length - 1].time, 0.0f, range);
+                float curveValue = curve.Evaluate(range, false);
 
-                Vector2I a = new Vector2I(xPosStart, yPos);
-                Vector2I b = new Vector2I(xPosEnd, yPos);
+                Vector2I start = CurveToPixelSpace(new Vector2(curveEnd, curveValue));
+                Vector2I end = new Vector2I(width, start.y);
 
-                canvas.DrawLine(a, b, COLOR_MID_GRAY);
+                canvas.DrawLine(start, end, COLOR_MID_GRAY);
             }
         }
     }

+ 28 - 36
Source/MBansheeEditor/Windows/Animation/GUICurveEditor.cs

@@ -55,6 +55,7 @@ namespace BansheeEditor
         private EdAnimationCurve[] curves = new EdAnimationCurve[0];
         private float xRange = 60.0f;
         private float yRange = 10.0f;
+        private Vector2 offset;
 
         private int width;
         private int height;
@@ -76,19 +77,40 @@ namespace BansheeEditor
         public Action<int> OnFrameSelected;
 
         /// <summary>
-        /// Returns the displayed range of the curve on the x axis (time).
+        /// The displayed range of the curve, where:
+        ///   .x - Range of the horizontal area. Displayed area ranges from [0, x].
+        ///   .y - Range of the vertical area. Displayed area ranges from [-y, y].                 
         /// </summary>
-        public float XRange
+        public Vector2 Range
         {
-            get { return xRange; }
+            get { return new Vector2(xRange, yRange); }
+            set
+            {
+                xRange = value.x;
+                yRange = value.y;
+
+                guiTimeline.SetRange(xRange);
+                guiCurveDrawing.SetRange(xRange, yRange * 2.0f);
+                guiSidebar.SetRange(offset.y - yRange, offset.y + yRange);
+
+                Redraw();
+            }
         }
 
         /// <summary>
-        /// Returns the displayed range of the curve on the y axis.
+        /// Returns the offset of the displayed curve values.
         /// </summary>
-        public float YRange
+        public Vector2 Offset
         {
-            get { return yRange; }
+            get { return offset; }
+            set
+            {
+                offset = value;
+
+                guiTimeline.SetOffset(offset.x);
+                guiCurveDrawing.SetOffset(offset);
+                guiSidebar.SetRange(offset.y - yRange, offset.y + yRange);
+            }
         }
 
         /// <summary>
@@ -415,18 +437,6 @@ namespace BansheeEditor
             Redraw();
         }
 
-        /// <summary>
-        /// Sets the position of the GUI element relative to its parent.
-        /// </summary>
-        /// <param name="x">Horizontal position in pixels.</param>
-        /// <param name="y">Vertical position in pixels.</param>
-        public void SetPosition(int x, int y)
-        {
-            guiTimeline.SetPosition(x, y);
-            drawingPanel.SetPosition(x, y + TIMELINE_HEIGHT);
-            sidebarPanel.SetPosition(x, y + TIMELINE_HEIGHT);
-        }
-
         /// <summary>
         /// Change the physical size of the GUI element.
         /// </summary>
@@ -444,24 +454,6 @@ namespace BansheeEditor
             Redraw();
         }
 
-        /// <summary>
-        /// Changes the visible range that the GUI element displays.
-        /// </summary>
-        /// <param name="xRange">Range of the horizontal area. Displayed area will range from [0, xRange].</param>
-        /// <param name="yRange">Range of the vertical area. Displayed area will range from 
-        ///                      [-yRange, yRange]</param>
-        public void SetRange(float xRange, float yRange)
-        {
-            this.xRange = xRange;
-            this.yRange = yRange;
-
-            guiTimeline.SetRange(xRange);
-            guiCurveDrawing.SetRange(xRange, yRange * 2.0f);
-            guiSidebar.SetRange(yRange, yRange);
-
-            Redraw();
-        }
-
         /// <summary>
         /// Number of frames per second, used for frame selection and marking.
         /// </summary>

+ 13 - 11
Source/MBansheeEditor/Windows/Animation/GUIGraphTime.cs

@@ -23,6 +23,7 @@ namespace BansheeEditor
         private int tickHeight;
         private int drawableWidth;
         private float rangeLength = 60.0f;
+        private float rangeOffset = 0.0f;
 
         private GUICanvas canvas;
         private GUIGraphTicks tickHandler;
@@ -65,7 +66,7 @@ namespace BansheeEditor
             Vector2I relativeCoords = windowCoords - new Vector2I(bounds.x + PADDING, bounds.y);
 
             float lengthPerPixel = GetRange() / drawableWidth;
-            float time = relativeCoords.x * lengthPerPixel;
+            float time = rangeOffset + relativeCoords.x * lengthPerPixel;
 
             return MathEx.RoundToInt(time * fps);
         }
@@ -95,7 +96,7 @@ namespace BansheeEditor
             tickHeight = (int)(height * TICK_HEIGHT_PCT);
             drawableWidth = Math.Max(0, width - PADDING * 2);
 
-            tickHandler.SetRange(0.0f, GetRange(true), drawableWidth + PADDING);
+            tickHandler.SetRange(rangeOffset, GetRange(true), drawableWidth + PADDING);
         }
 
         /// <summary>
@@ -106,17 +107,18 @@ namespace BansheeEditor
         {
             rangeLength = Math.Max(0.0f, length);
 
-            tickHandler.SetRange(0.0f, GetRange(true), drawableWidth + PADDING);
+            tickHandler.SetRange(rangeOffset, GetRange(true), drawableWidth + PADDING);
         }
 
         /// <summary>
-        /// Sets the position of the GUI element relative to its parent.
+        /// Returns the offset at which the displayed timeline values start at.
         /// </summary>
-        /// <param name="x">Horizontal position in pixels.</param>
-        /// <param name="y">Vertical position in pixels.</param>
-        public void SetPosition(int x, int y)
+        /// <param name="offset">Value to start the timeline values at.</param>
+        public void SetOffset(float offset)
         {
-            canvas.SetPosition(x, y);
+            rangeOffset = offset;
+
+            tickHandler.SetRange(rangeOffset, GetRange(true), drawableWidth + PADDING);
         }
 
         /// <summary>
@@ -127,7 +129,7 @@ namespace BansheeEditor
         {
             this.fps = Math.Max(1, fps);
 
-            tickHandler.SetRange(0.0f, GetRange(true), drawableWidth + PADDING);
+            tickHandler.SetRange(rangeOffset, GetRange(true), drawableWidth + PADDING);
         }
         
         /// <summary>
@@ -167,7 +169,7 @@ namespace BansheeEditor
         ///                                Ignored if no text is drawn.</param>
         private void DrawTick(float t, float strength, bool drawText, bool displayAsMinutes)
         {
-            int xPos = (int)((t / GetRange()) * drawableWidth) + PADDING;
+            int xPos = (int)(((t - rangeOffset) / GetRange()) * drawableWidth) + PADDING;
 
             // Draw tick
             Vector2I start = new Vector2I(xPos, height - (int)(tickHeight * strength));
@@ -189,7 +191,7 @@ namespace BansheeEditor
         /// <param name="t">Time at which to draw the marker.</param>
         private void DrawFrameMarker(float t)
         {
-            int xPos = (int)((t / GetRange()) * drawableWidth) + PADDING;
+            int xPos = (int)(((t - rangeOffset) / GetRange()) * drawableWidth) + PADDING;
 
             Vector2I start = new Vector2I(xPos, 0);
             Vector2I end = new Vector2I(xPos, height);

+ 3 - 1
Source/MBansheeEditor/Windows/Animation/GUIGraphValues.cs

@@ -127,6 +127,8 @@ namespace BansheeEditor
             else
                 pixelsPerHeight = 0;
 
+            float yOffset = rangeStart + (rangeEnd - rangeStart)*0.5f;
+
             int numTickLevels = tickHandler.NumLevels;
             for (int i = numTickLevels - 1; i >= 0; i--)
             {
@@ -140,7 +142,7 @@ namespace BansheeEditor
 
                     for (int j = 0; j < ticks.Length; j++)
                     {
-                        int yPos = (int) (ticks[j]*pixelsPerHeight);
+                        int yPos = (int) ((ticks[j] - yOffset) * pixelsPerHeight);
                         yPos = heightOffset - yPos; // Offset and flip height (canvas Y goes down)
 
                         Vector2I start = new Vector2I(0, yPos);

+ 95 - 144
Source/MBansheeEditor/Windows/AnimationWindow.cs

@@ -194,8 +194,6 @@ namespace BansheeEditor
             addKeyframeButton.OnClick += () =>
             {
                 guiCurveEditor.AddKeyFrameAtMarker();
-
-                // TODO - Update local curves?
             };
 
             addEventButton.OnClick += () =>
@@ -295,7 +293,6 @@ namespace BansheeEditor
             SetCurrentFrame(currentFrameIdx);
             UpdateScrollBarSize();
 
-            visibleOffset = new Vector2(0.0f, 0.0f);
             isInitialized = true;
         }
 
@@ -320,8 +317,7 @@ namespace BansheeEditor
         private bool isButtonHeld;
         private bool isDragInProgress;
 
-        private Vector2 minimalRange;
-        private Vector2 visibleOffset;
+        private float zoomAmount;
 
         private void HandleDragAndZoomInput()
         {
@@ -331,46 +327,12 @@ namespace BansheeEditor
                 float dragX = Input.GetAxisValue(InputAxis.MouseX) * DRAG_SCALE;
                 float dragY = Input.GetAxisValue(InputAxis.MouseY) * DRAG_SCALE;
 
-                Vector2 totalRange = new Vector2(guiCurveEditor.XRange, guiCurveEditor.YRange);
-                Vector2 visibleRange = GetVisibleRange();
-                Vector2 offset = visibleOffset;
-
-                if (dragX > 0.0f)
-                {
-                    offset.x += dragX;
-
-                    float visibleRight = offset.x + visibleRange.x;
-                    if (visibleRight > totalRange.x)
-                        totalRange.x = visibleRight;
-                }
-                else
-                {
-                    float actualDragX = offset.x - Math.Max(0.0f, offset.x + dragX);
-
-                    offset.x -= actualDragX;
-
-                    float visibleRight = offset.x + visibleRange.x;
-                    totalRange.x = Math.Max(minimalRange.x, visibleRight);
-                }
-
-                if (dragY > 0.0f)
-                {
-                    offset.y += dragY;
-
-                    float visibleTop = offset.y + visibleRange.y;
-                    if (visibleTop > totalRange.y)
-                        totalRange.y = visibleTop;
-                }
-                else
-                {
-                    offset.y += dragY;
-
-                    float visibleYMax = Math.Abs(offset.y) + visibleRange.y;
-                    totalRange.y = Math.Max(minimalRange.y, visibleYMax);
-                }
-
-                SetTotalRange(totalRange.x, totalRange.y);
-                SetVisibleOffset(offset);
+                Vector2 offset = guiCurveEditor.Offset;
+                offset.x = Math.Max(0.0f, offset.x + dragX);
+                offset.y += dragY;
+                
+                guiCurveEditor.Offset = offset;
+                UpdateScrollBarSize();
                 UpdateScrollBarPosition();
             }
 
@@ -390,131 +352,108 @@ namespace BansheeEditor
 
         private void SetVertScrollbarProperties(float position, float size)
         {
-            Vector2 visibleRange = GetVisibleRange();
-            float scrollableRange = guiCurveEditor.YRange - visibleRange.y;
+            Vector2 visibleRange = guiCurveEditor.Range;
+            Vector2 totalRange = GetTotalRange();
 
-            Vector2 offset = visibleOffset;
-            offset.y = scrollableRange * position;
+            visibleRange.y = totalRange.y*size;
+            guiCurveEditor.Range = visibleRange;
 
-            SetVisibleOffset(offset);
+            float scrollableRange = totalRange.y - visibleRange.y;
 
-            int height = (int)(guiCurveEditor.Height / size);
-            guiCurveEditor.SetSize(guiCurveEditor.Width, height);
+            Vector2 offset = guiCurveEditor.Offset;
+            offset.y = scrollableRange * (position * 2.0f - 1.0f);
+
+            guiCurveEditor.Offset = offset;
         }
 
         private void SetHorzScrollbarProperties(float position, float size)
         {
-            Vector2 visibleRange = GetVisibleRange();
-            float scrollableRange = guiCurveEditor.XRange - visibleRange.x;
+            Vector2 visibleRange = guiCurveEditor.Range;
+            Vector2 totalRange = GetTotalRange();
 
-            Vector2 offset = visibleOffset;
-            offset.x = scrollableRange * position;
+            visibleRange.x = totalRange.x * size;
+            guiCurveEditor.Range = visibleRange;
 
-            SetVisibleOffset(offset);
+            float scrollableRange = totalRange.x - visibleRange.x;
 
-            int width = (int)(guiCurveEditor.Width / size);
-            guiCurveEditor.SetSize(width, guiCurveEditor.Height);
-        }
-
-        private Vector2 GetVisibleRange()
-        {
-            float unitsPerXPixel = guiCurveEditor.XRange / guiCurveEditor.Width;
-            float unitsPerYPixel = guiCurveEditor.YRange / guiCurveEditor.Height;
+            Vector2 offset = guiCurveEditor.Offset;
+            offset.x = scrollableRange * position;
 
-            Vector2I visibleSize = GetCurveEditorSize();
-            return new Vector2(unitsPerXPixel * visibleSize.x, unitsPerYPixel * visibleSize.y);
+            guiCurveEditor.Offset = offset;
         }
 
-        private void SetVisibleOffset(Vector2 offset)
+        private void UpdateScrollBarSize()
         {
-            visibleOffset = offset;
-
-            float pixelsPerXUnit = guiCurveEditor.Width / guiCurveEditor.XRange;
-            float pixelsPerYUnit = guiCurveEditor.Height / (guiCurveEditor.YRange * 2.0f);
+            Vector2 visibleRange = guiCurveEditor.Range;
+            Vector2 totalRange = GetTotalRange();
 
-            int x = (int)(pixelsPerXUnit * visibleOffset.x);
-            int y = (int)(pixelsPerYUnit * visibleOffset.y);
-
-            guiCurveEditor.SetPosition(x, y);
+            horzScrollBar.HandleSize = visibleRange.x / totalRange.x;
+            vertScrollBar.HandleSize = visibleRange.y / totalRange.y;
         }
 
-        // Increases range without zooming in (increasing width/height accordingly)
-        private void SetTotalRange(float x, float y)
+        private void UpdateScrollBarPosition()
         {
-            float pixelsPerXUnit = guiCurveEditor.Width / guiCurveEditor.XRange;
-            float pixelsPerYUnit = guiCurveEditor.Height / (guiCurveEditor.YRange * 2.0f);
-
-            int width = (int)(pixelsPerXUnit * x);
-            int height = (int)(pixelsPerYUnit * y);
+            Vector2 visibleRange = guiCurveEditor.Range;
+            Vector2 totalRange = GetTotalRange();
+            Vector2 scrollableRange = totalRange - visibleRange;
 
-            guiCurveEditor.SetRange(x, y);
-            guiCurveEditor.SetSize(width, height);
+            Vector2 offset = guiCurveEditor.Offset;
+            // Transform Y from [-x, +x] range to [0, x]
+            offset.y += visibleRange.y;
+            offset.y /= 2.0f;
 
-            UpdateScrollBarSize();
+            horzScrollBar.Position = offset.x / scrollableRange.x;
+            vertScrollBar.Position = offset.y / scrollableRange.y;
         }
 
-        private void UpdateScrollBarSize()
+        private Vector2 GetZoomedRange()
         {
-            Vector2 visibleRange = GetVisibleRange();
-            Vector2 totalRange = new Vector2(guiCurveEditor.XRange, guiCurveEditor.YRange);
+            float zoomLevel = MathEx.Pow(2, zoomAmount);
 
-            horzScrollBar.HandleSize = visibleRange.x / totalRange.x;
-            vertScrollBar.HandleSize = visibleRange.y / totalRange.y;
+            Vector2 optimalRange = GetOptimalRange();
+            return optimalRange / zoomLevel;
         }
 
-        private void UpdateScrollBarPosition()
+        private Vector2 GetTotalRange()
         {
-            Vector2 visibleRange = GetVisibleRange();
-            Vector2 totalRange = new Vector2(guiCurveEditor.XRange, guiCurveEditor.YRange);
-            Vector2 scrollableRange = totalRange - visibleRange;
+            Vector2 visibleRange = guiCurveEditor.Range;
+            Vector2 totalRange = guiCurveEditor.Offset;
+            totalRange.x += visibleRange.x;
+            totalRange.y = Math.Abs(totalRange.y) + visibleRange.y;
 
-            horzScrollBar.Position = visibleOffset.x / scrollableRange.x;
-            vertScrollBar.Position = visibleOffset.y / scrollableRange.y;
+            Vector2 optimalRange = GetOptimalRange();
+            return Vector2.Max(totalRange, optimalRange);
         }
 
         private void Zoom(Vector2 curvePos, float amount)
         {
-            Vector2 relativePos = curvePos - visibleOffset;
-            Vector2 visibleRange = GetVisibleRange();
+            Vector2 oldZoomedRange = GetZoomedRange();
+            zoomAmount += amount;
+            Vector2 zoomedRange = GetZoomedRange();
 
-            relativePos.x /= visibleRange.x;
-            relativePos.y /= visibleRange.y;
+            Vector2 zoomedDiff = zoomedRange - oldZoomedRange;
+            zoomedDiff.y *= 0.5f;
 
-            relativePos.x = relativePos.x * 2.0f - 1.0f;
-            relativePos.y = relativePos.y * 2.0f - 1.0f;
-
-            Vector2 offset = visibleOffset;
-            offset.x += relativePos.x * amount;
-            offset.y += relativePos.y * amount;
+            Vector2 currentRange = guiCurveEditor.Range;
+            Vector2 newRange = currentRange + zoomedDiff;
 
-            offset.x = Math.Max(0.0f, offset.x);
-
-            SetVisibleOffset(offset);
-            UpdateScrollBarPosition();
+            Vector2 offset = guiCurveEditor.Offset;
+            Vector2 relativePos = curvePos - offset;
 
-            int width = guiCurveEditor.Width + (int)amount;
-            int height = guiCurveEditor.Height + (int)amount;
+            relativePos.x /= currentRange.x;
+            relativePos.y /= currentRange.y;
 
-            // If we aren't at the minimum size, modify size and offset
-            Vector2I visibleSize = GetCurveEditorSize();
-            if (width > visibleSize.x || height > visibleSize.y)
-            {
-                width = Math.Max(width, visibleSize.x);
-                height = Math.Max(height, visibleSize.y);
+            relativePos.x = relativePos.x * 2.0f - 1.0f;
+            relativePos.y = relativePos.y * 2.0f - 1.0f;
 
-                guiCurveEditor.SetSize(width, height);
-                UpdateScrollBarSize();
-            }
-            else // Otherwise start increasing range for zoom in
-            {
-                float unitsPerXPixel = guiCurveEditor.XRange / guiCurveEditor.Width;
-                float unitsPerYPixel = guiCurveEditor.YRange / guiCurveEditor.Height;
+            offset.x += relativePos.x * zoomedDiff.x;
+            offset.y += relativePos.y * zoomedDiff.y * 2.0f;
 
-                float rangeX = guiCurveEditor.XRange + unitsPerXPixel * amount;
-                float rangeY = guiCurveEditor.YRange + unitsPerYPixel * amount;
+            guiCurveEditor.Offset = offset;
+            guiCurveEditor.Range = newRange;
 
-                SetTotalRange(rangeX, rangeY);
-            }
+            UpdateScrollBarSize();
+            UpdateScrollBarPosition();
         }
         #endregion
 
@@ -612,21 +551,22 @@ namespace BansheeEditor
             guiFieldDisplay.SetDisplayValues(values.ToArray());
         }
 
-        private void UpdateDisplayedCurves()
+        private Vector2 GetOptimalRange()
         {
-            List<EdAnimationCurve> curvesToDisplay = new List<EdAnimationCurve>();
+            List<EdAnimationCurve> displayedCurves = new List<EdAnimationCurve>();
             for (int i = 0; i < selectedFields.Count; i++)
             {
                 EdAnimationCurve curve;
                 if (TryGetCurve(selectedFields[i], out curve))
-                    curvesToDisplay.Add(curve);
+                    displayedCurves.Add(curve);
             }
 
-            guiCurveEditor.SetCurves(curvesToDisplay.ToArray());
-
             float xRange;
             float yRange;
-            CalculateRange(curvesToDisplay, out xRange, out yRange);
+            CalculateRange(displayedCurves, out xRange, out yRange);
+
+            // Add padding to y range
+            yRange *= 1.05f;
 
             // Don't allow zero range
             if (xRange == 0.0f)
@@ -635,17 +575,28 @@ namespace BansheeEditor
             if (yRange == 0.0f)
                 yRange = 10.0f;
 
-            // Add padding to y range
-            yRange *= 1.05f;
+            return new Vector2(xRange, yRange);
+        }
 
-            // Don't reduce visible range
-            minimalRange.x = Math.Max(xRange, minimalRange.x);
-            minimalRange.y = Math.Max(yRange, minimalRange.y);
+        private void UpdateDisplayedCurves()
+        {
+            List<EdAnimationCurve> curvesToDisplay = new List<EdAnimationCurve>();
+            for (int i = 0; i < selectedFields.Count; i++)
+            {
+                EdAnimationCurve curve;
+                if (TryGetCurve(selectedFields[i], out curve))
+                    curvesToDisplay.Add(curve);
+            }
 
-            xRange = Math.Max(xRange, guiCurveEditor.XRange);
-            yRange = Math.Max(yRange, guiCurveEditor.YRange);
+            guiCurveEditor.SetCurves(curvesToDisplay.ToArray());
+
+            Vector2 newRange = GetOptimalRange();
+
+            // Don't reduce visible range
+            newRange.x = Math.Max(newRange.x, guiCurveEditor.Range.x);
+            newRange.y = Math.Max(newRange.y, guiCurveEditor.Range.y);
 
-            guiCurveEditor.SetRange(xRange, yRange);
+            guiCurveEditor.Range = newRange;
             UpdateScrollBarSize();
         }
         #endregion 

+ 22 - 0
Source/MBansheeEngine/Math/Vector2.cs

@@ -216,6 +216,28 @@ namespace BansheeEngine
             return (v.x * v.x + v.y * v.y);
         }
 
+        /// <summary>
+        /// Returns the maximum of all the vector components as a new vector.
+        /// </summary>
+        /// <param name="a">First vector.</param>
+        /// <param name="b">Second vector.</param>
+        /// <returns>Vector consisting of maximum components of the first and second vector.</returns>
+        public static Vector2 Max(Vector2 a, Vector2 b)
+		{
+			return new Vector2(MathEx.Max(a.x, b.x), MathEx.Max(a.y, b.y));
+		}
+
+        /// <summary>
+        /// Returns the minimum of all the vector components as a new vector.
+        /// </summary>
+        /// <param name="a">First vector.</param>
+        /// <param name="b">Second vector.</param>
+        /// <returns>Vector consisting of minimum components of the first and second vector.</returns>
+        public static Vector2 Min(Vector2 a, Vector2 b)
+        {
+            return new Vector2(MathEx.Min(a.x, b.x), MathEx.Min(a.y, b.y));
+        }
+
         /// <summary>
         /// Scales the components of the vector by specified scale factors.
         /// </summary>

+ 22 - 0
Source/MBansheeEngine/Math/Vector3.cs

@@ -308,6 +308,28 @@ namespace BansheeEngine
             z.Normalize();
         }
 
+        /// <summary>
+        /// Returns the maximum of all the vector components as a new vector.
+        /// </summary>
+        /// <param name="a">First vector.</param>
+        /// <param name="b">Second vector.</param>
+        /// <returns>Vector consisting of maximum components of the first and second vector.</returns>
+        public static Vector3 Max(Vector3 a, Vector3 b)
+        {
+            return new Vector3(MathEx.Max(a.x, b.x), MathEx.Max(a.y, b.y), MathEx.Max(a.z, b.z));
+        }
+
+        /// <summary>
+        /// Returns the minimum of all the vector components as a new vector.
+        /// </summary>
+        /// <param name="a">First vector.</param>
+        /// <param name="b">Second vector.</param>
+        /// <returns>Vector consisting of minimum components of the first and second vector.</returns>
+        public static Vector3 Min(Vector3 a, Vector3 b)
+        {
+            return new Vector3(MathEx.Min(a.x, b.x), MathEx.Min(a.y, b.y), MathEx.Min(a.z, b.z));
+        }
+
         /// <inheritdoc/>
         public override int GetHashCode()
         {