浏览代码

Added tangent display to the GUI curve drawing element

BearishSun 9 年之前
父节点
当前提交
2c8e189d93

+ 63 - 16
Source/MBansheeEditor/Utility/EdAnimationCurve.cs

@@ -5,7 +5,7 @@ using BansheeEngine;
 
 namespace BansheeEditor
 {
-    internal enum TangentTypes
+    internal enum TangentType
     {
         In = 1 << 0,
         Out = 1 << 1
@@ -15,14 +15,14 @@ namespace BansheeEditor
     internal enum TangentMode
     {
         Auto        = 0,
-        InAuto      = TangentTypes.In | 1 << 2,
-        InFree      = TangentTypes.In | 1 << 3,
-        InLinear    = TangentTypes.In | 1 << 4,
-        InStep      = TangentTypes.In | 1 << 5,
-        OutAuto     = TangentTypes.Out | 1 << 6,
-        OutFree     = TangentTypes.Out | 1 << 7,
-        OutLinear   = TangentTypes.Out | 1 << 8,
-        OutStep     = TangentTypes.Out | 1 << 9,
+        InAuto      = TangentType.In | 1 << 2,
+        InFree      = TangentType.In | 1 << 3,
+        InLinear    = TangentType.In | 1 << 4,
+        InStep      = TangentType.In | 1 << 5,
+        OutAuto     = TangentType.Out | 1 << 6,
+        OutFree     = TangentType.Out | 1 << 7,
+        OutLinear   = TangentType.Out | 1 << 8,
+        OutStep     = TangentType.Out | 1 << 9,
         Free        = 1 << 10,
     }
 
@@ -150,6 +150,24 @@ namespace BansheeEditor
             native.KeyFrames = keyFrames;
         }
 
+        internal static Vector2 TangentToNormal(float tangent)
+        {
+            if(tangent == float.PositiveInfinity)
+                return new Vector2(0, 1);
+
+            Vector2 normal = new Vector2(1, tangent);
+            return Vector2.Normalize(normal);
+        }
+
+        internal static float NormalToTangent(Vector2 normal)
+        {
+            // We know the X value must be one, use that to deduce pre-normalized length
+            float length = 1/normal.x;
+
+            // Use length to deduce the tangent (y coordinate)
+            return MathEx.Sqrt(length*length - 1);
+        }
+
         private void UpdateTangents(KeyFrame[] keyFrames, TangentMode[] tangentModes)
         {
             if (keyFrames.Length == 0)
@@ -174,7 +192,11 @@ namespace BansheeEditor
                 if (tangentMode == TangentMode.Auto || tangentMode.HasFlag(TangentMode.OutAuto) || tangentMode.HasFlag(TangentMode.OutLinear))
                 {
                     float diff = keyNext.time - keyThis.time;
-                    keyThis.outTangent = (keyNext.value - keyThis.value) / diff;
+
+                    if(!MathEx.ApproxEquals(diff, 0.0f))
+                        keyThis.outTangent = (keyNext.value - keyThis.value) / diff;
+                    else
+                        keyThis.outTangent = float.PositiveInfinity;
                 }
                 else if (tangentMode.HasFlag(TangentMode.OutStep))
                 {
@@ -197,7 +219,12 @@ namespace BansheeEditor
                 if (tangentMode == TangentMode.Auto) // Both automatic
                 {
                     float diff = keyNext.time - keyPrev.time;
-                    keyThis.outTangent = (keyNext.value - keyPrev.value) / diff;
+
+                    if (!MathEx.ApproxEquals(diff, 0.0f))
+                        keyThis.outTangent = (keyNext.value - keyPrev.value) / diff;
+                    else
+                        keyThis.outTangent = float.PositiveInfinity;
+
                     keyThis.inTangent = keyThis.outTangent;
                 }
                 else if (tangentMode == TangentMode.Free) // Both free
@@ -210,12 +237,20 @@ namespace BansheeEditor
                     if (tangentMode.HasFlag(TangentMode.InAuto))
                     {
                         float diff = keyNext.time - keyPrev.time;
-                        keyThis.inTangent = (keyNext.value - keyPrev.value) / diff;
+
+                        if (!MathEx.ApproxEquals(diff, 0.0f))
+                            keyThis.inTangent = (keyNext.value - keyPrev.value)/diff;
+                        else
+                            keyThis.inTangent = float.PositiveInfinity;
                     }
                     else if (tangentMode.HasFlag(TangentMode.InLinear))
                     {
                         float diff = keyThis.time - keyPrev.time;
-                        keyThis.inTangent = (keyThis.value - keyPrev.value) / diff;
+
+                        if (!MathEx.ApproxEquals(diff, 0.0f))
+                            keyThis.inTangent = (keyThis.value - keyPrev.value) / diff;
+                        else
+                            keyThis.inTangent = float.PositiveInfinity;
                     }
                     else if (tangentMode.HasFlag(TangentMode.InStep))
                     {
@@ -226,12 +261,20 @@ namespace BansheeEditor
                     if (tangentMode.HasFlag(TangentMode.OutAuto))
                     {
                         float diff = keyNext.time - keyPrev.time;
-                        keyThis.outTangent = (keyNext.value - keyPrev.value) / diff;
+
+                        if (!MathEx.ApproxEquals(diff, 0.0f))
+                            keyThis.outTangent = (keyNext.value - keyPrev.value) / diff;
+                        else
+                            keyThis.outTangent = float.PositiveInfinity;
                     }
                     else if (tangentMode.HasFlag(TangentMode.OutLinear))
                     {
                         float diff = keyNext.time - keyThis.time;
-                        keyThis.outTangent = (keyNext.value - keyThis.value) / diff;
+
+                        if (!MathEx.ApproxEquals(diff, 0.0f))
+                            keyThis.outTangent = (keyNext.value - keyThis.value) / diff;
+                        else
+                            keyThis.outTangent = float.PositiveInfinity;
                     }
                     else if (tangentMode.HasFlag(TangentMode.OutStep))
                     {
@@ -253,7 +296,11 @@ namespace BansheeEditor
                 if (tangentMode == TangentMode.Auto || tangentMode.HasFlag(TangentMode.InAuto) || tangentMode.HasFlag(TangentMode.InLinear))
                 {
                     float diff = keyThis.time - keyPrev.time;
-                    keyThis.inTangent = (keyThis.value - keyPrev.value) / diff;
+
+                    if (!MathEx.ApproxEquals(diff, 0.0f))
+                        keyThis.inTangent = (keyThis.value - keyPrev.value)/diff;
+                    else
+                        keyThis.inTangent = float.PositiveInfinity;
                 }
                 else if (tangentMode.HasFlag(TangentMode.InStep))
                 {

+ 121 - 15
Source/MBansheeEditor/Windows/Animation/GUICurveDrawing.cs

@@ -17,6 +17,7 @@ namespace BansheeEditor
     internal class GUICurveDrawing
     {
         private const int LINE_SPLIT_WIDTH = 2;
+        private const int TANGENT_LINE_DISTANCE = 30;
         private static readonly Color COLOR_MID_GRAY = new Color(90.0f / 255.0f, 90.0f / 255.0f, 90.0f / 255.0f, 1.0f);
         private static readonly Color COLOR_DARK_GRAY = new Color(40.0f / 255.0f, 40.0f / 255.0f, 40.0f / 255.0f, 1.0f);
 
@@ -265,6 +266,22 @@ namespace BansheeEditor
             return true;
         }
 
+        /// <summary>
+        /// Converts coordinate in curve space (time, value) into pixel coordinates relative to this element's origin.
+        /// </summary>
+        /// <param name="curveCoords">Time and value of the location to convert.</param>
+        /// <returns>Coordinates relative to this element's origin, in pixels.</returns>
+        private Vector2I CurveToPixelSpace(Vector2 curveCoords)
+        {
+            int heightOffset = height / 2; // So that y = 0 is at center of canvas
+
+            Vector2I pixelCoords = new Vector2I();
+            pixelCoords.x = (int)((curveCoords.x / GetRange()) * drawableWidth) + GUIGraphTime.PADDING;
+            pixelCoords.y = heightOffset - (int)((curveCoords.y / yRange) * height);
+
+            return pixelCoords;
+        }
+
         /// <summary>
         /// Draws a vertical frame marker on the curve area.
         /// </summary>
@@ -293,6 +310,28 @@ namespace BansheeEditor
             canvas.DrawLine(start, end, COLOR_DARK_GRAY);
         }
 
+        /// <summary>
+        /// Draws a diamond shape of the specified size at the coordinates.
+        /// </summary>
+        /// <param name="center">Position at which to place the diamond's center, in pixel coordinates.</param>
+        /// <param name="size">Determines number of pixels to extend the diamond in each direction.</param>
+        /// <param name="innerColor">Color of the diamond's background.</param>
+        /// <param name="outerColor">Color of the diamond's outline.</param>
+        private void DrawDiamond(Vector2I center, int size, Color innerColor, Color outerColor)
+        {
+            Vector2I a = new Vector2I(center.x - size, center.y);
+            Vector2I b = new Vector2I(center.x, center.y - size);
+            Vector2I c = new Vector2I(center.x + size, center.y);
+            Vector2I d = new Vector2I(center.x, center.y + size);
+
+            // Draw diamond shape
+            Vector2I[] linePoints = new Vector2I[] { a, b, c, d, a };
+            Vector2I[] trianglePoints = new Vector2I[] { b, c, a, d };
+
+            canvas.DrawTriangleStrip(trianglePoints, innerColor, 101);
+            canvas.DrawPolyLine(linePoints, outerColor, 100);
+    }
+
         /// <summary>
         /// Draws a keyframe a the specified time and value.
         /// </summary>
@@ -302,26 +341,86 @@ namespace BansheeEditor
         ///                        </param>
         private void DrawKeyframe(float t, float y, bool selected)
         {
-            int heightOffset = height / 2; // So that y = 0 is at center of canvas
+            Vector2I pixelCoords = CurveToPixelSpace(new Vector2(t, y));
 
-            int xPos = (int)((t / GetRange()) * drawableWidth) + GUIGraphTime.PADDING;
-            int yPos = heightOffset - (int)((y/yRange)*height);
+            if (selected)
+                DrawDiamond(pixelCoords, 3, Color.White, Color.BansheeOrange);
+            else
+                DrawDiamond(pixelCoords, 3, Color.White, Color.Black);
+        }
 
-            Vector2I a = new Vector2I(xPos - 3, yPos);
-            Vector2I b = new Vector2I(xPos, yPos - 3);
-            Vector2I c = new Vector2I(xPos + 3, yPos);
-            Vector2I d = new Vector2I(xPos, yPos + 3);
+        /// <summary>
+        /// Draws zero, one or two tangents for the specified keyframe. Whether tangents are drawn depends on the provided
+        /// mode.
+        /// </summary>
+        /// <param name="keyFrame">Keyframe to draw the tangents for.</param>
+        /// <param name="tangentMode">Type of tangents in the keyframe.</param>
+        private void DrawTangents(KeyFrame keyFrame, TangentMode tangentMode)
+        {
+            Vector2I keyframeCoords = CurveToPixelSpace(new Vector2(keyFrame.time, keyFrame.value));
 
-            // Draw diamond shape
-            Vector2I[] linePoints = new Vector2I[] { a, b, c, d, a };
-            Vector2I[] trianglePoints = new Vector2I[] { b, c, a, d };
+            if (IsTangentDisplayed(tangentMode, TangentType.In))
+            {
+                Vector2I tangentCoords = GetTangentPosition(keyFrame, TangentType.In);
 
-            canvas.DrawTriangleStrip(trianglePoints, Color.White, 101);
+                canvas.DrawLine(keyframeCoords, tangentCoords, Color.LightGray);
+                DrawDiamond(tangentCoords, 2, Color.Green, Color.Black);
+            }
 
-            if (selected)
-                canvas.DrawPolyLine(linePoints, Color.BansheeOrange, 100);
+            if (IsTangentDisplayed(tangentMode, TangentType.Out))
+            {
+                Vector2I tangentCoords = GetTangentPosition(keyFrame, TangentType.Out);
+
+                canvas.DrawLine(keyframeCoords, tangentCoords, Color.LightGray);
+                DrawDiamond(tangentCoords, 2, Color.Green, Color.Black);
+            }
+        }
+
+        /// <summary>
+        /// Returns the position of the tangent, in element's pixel space.
+        /// </summary>
+        /// <param name="keyFrame">Keyframe that the tangent belongs to.</param>
+        /// <param name="type">Which tangent to retrieve the position for.</param>
+        /// <returns>Position of the tangent, relative to the this GUI element's origin, in pixels.</returns>
+        private Vector2I GetTangentPosition(KeyFrame keyFrame, TangentType type)
+        {
+            Vector2I position = CurveToPixelSpace(new Vector2(keyFrame.time, keyFrame.value));
+
+            Vector2 normal;
+            if (type == TangentType.In)
+                normal = -EdAnimationCurve.TangentToNormal(keyFrame.inTangent);
+            else
+                normal = EdAnimationCurve.TangentToNormal(keyFrame.outTangent);
+
+            // X/Y ranges aren't scaled 1:1, adjust normal accordingly
+            normal.x /= GetRange();
+            normal.y /= yRange;
+            normal = Vector2.Normalize(normal);
+
+            // Convert normal (in percentage) to pixel values
+            Vector2I offset = new Vector2I((int)(normal.x * TANGENT_LINE_DISTANCE),
+                    (int)(-normal.y * TANGENT_LINE_DISTANCE));
+
+            return position + offset;
+        }
+
+        /// <summary>
+        /// Checks if the tangent should be displayed, depending on the active tangent mode.
+        /// </summary>
+        /// <param name="mode">Tangent mode for the keyframe.</param>
+        /// <param name="type">Which tangent to check for.</param>
+        /// <returns>True if the tangent should be displayed.</returns>
+        private bool IsTangentDisplayed(TangentMode mode, TangentType type)
+        {
+            if (mode == TangentMode.Auto)
+                return false;
+            else if (mode == TangentMode.Free)
+                return true;
+
+            if (type == TangentType.In)
+                return !mode.HasFlag(TangentMode.InAuto);
             else
-                canvas.DrawPolyLine(linePoints, Color.Black, 100);
+                return !mode.HasFlag(TangentMode.OutAuto);
         }
 
         /// <summary>
@@ -385,7 +484,14 @@ namespace BansheeEditor
                 KeyFrame[] keyframes = curve.Native.KeyFrames;
 
                 for (int i = 0; i < keyframes.Length; i++)
-                    DrawKeyframe(keyframes[i].time, keyframes[i].value, IsSelected(curveIdx, i));
+                {
+                    bool isSelected = IsSelected(curveIdx, i);
+
+                    DrawKeyframe(keyframes[i].time, keyframes[i].value, isSelected);
+
+                    if (isSelected)
+                        DrawTangents(keyframes[i], curve.TangentModes[i]);
+                }
 
                 curveIdx++;
             }

+ 5 - 1
Source/MBansheeEditor/Windows/Animation/GUICurveEditor.cs

@@ -312,7 +312,7 @@ namespace BansheeEditor
                 {
                     TangentMode newMode = curve.TangentModes[keyframe.keyIdx];
 
-                    if (mode.HasFlag(TangentTypes.In))
+                    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 |
@@ -334,6 +334,10 @@ namespace BansheeEditor
                     curve.SetTangentMode(keyframe.keyIdx, newMode);
                 }
             }
+
+            // TODO - UNDOREDO
+
+            guiCurveDrawing.Rebuild();
         }
 
         private void AddKeyframeAtPosition()