Browse Source

GUICurveDrawing - A way to draw animation curves for the animation editor (WIP)
EdAnimationCurve - Editor specific animation curve representation that allows easy editing, and tangent manipulation (WIP)

BearishSun 9 years ago
parent
commit
61c6081a12

+ 2 - 0
Source/MBansheeEditor/MBansheeEditor.csproj

@@ -46,8 +46,10 @@
     <Compile Include="Inspectors\AudioListenerInspector.cs" />
     <Compile Include="Inspectors\AudioSourceInspector.cs" />
     <Compile Include="Inspectors\PostProcessSettingsInspector.cs" />
+    <Compile Include="Utility\EdAnimationCurve.cs" />
     <Compile Include="Windows\AboutBox.cs" />
     <Compile Include="Windows\AnimationWindow.cs" />
+    <Compile Include="Windows\Animation\GUICurveDrawing.cs" />
     <Compile Include="Windows\Animation\GUIFieldSelector.cs" />
     <Compile Include="Windows\Animation\GUITimeline.cs" />
     <Compile Include="Windows\BrowseDialog.cs" />

+ 218 - 0
Source/MBansheeEditor/Utility/EdAnimationCurve.cs

@@ -0,0 +1,218 @@
+//********************************** Banshee Engine (www.banshee3d.com) **************************************************//
+//**************** Copyright (c) 2016 Marko Pintera ([email protected]). All rights reserved. **********************//
+using System;
+using System.Collections.Generic;
+using BansheeEngine;
+
+namespace BansheeEditor
+{
+    internal enum TangentAutoType
+    {
+        Auto, Linear, Step
+    }
+
+    internal enum TangentModeType
+    {
+        Auto, Free, Broken
+    }
+
+    internal struct TangentMode
+    {
+        public TangentAutoType inTangent;
+        public TangentAutoType outTangent;
+        public TangentModeType type;
+
+        public static readonly TangentMode AUTO = new TangentMode();
+    }
+
+    internal class EdAnimationCurve
+    {
+        private AnimationCurve native;
+        private TangentMode[] tangentModes;
+
+        public AnimationCurve Native
+        {
+            get { return native; }
+        }
+
+        public TangentMode[] TangentModes
+        {
+            get { return tangentModes; }
+        }
+
+        // Tangent modes should match number of curve keyframes
+        internal EdAnimationCurve(AnimationCurve native, TangentMode[] tangentModes)
+        {
+            this.native = native;
+
+            KeyFrame[] keyFrames = native.KeyFrames;
+
+            this.tangentModes = new TangentMode[keyFrames.Length];
+            if (tangentModes != null)
+            {
+                int numTangents = Math.Min(keyFrames.Length, tangentModes.Length);
+                Array.Copy(tangentModes, this.tangentModes, numTangents);
+            }
+
+            UpdateTangents(keyFrames, this.tangentModes);
+        }
+
+        internal void AddKeyframe(float time, float value)
+        {
+            AddKeyframe(time, value, TangentMode.AUTO);
+        }
+
+        internal void AddKeyframe(float time, float value, TangentMode tangentMode)
+        {
+            KeyFrame[] existingKeyFrames = native.KeyFrames;
+
+            KeyFrame[] newKeyFrames = new KeyFrame[existingKeyFrames.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++)
+            {
+                if (time < existingKeyFrames[i].time)
+                {
+                    insertIdx = i;
+                    break;
+                }
+            }
+
+            Array.Copy(existingKeyFrames, newKeyFrames, insertIdx);
+            Array.Copy(tangentModes, newTangentModes, insertIdx);
+
+            KeyFrame keyFrame = new KeyFrame();
+            keyFrame.time = time;
+            keyFrame.value = value;
+
+            newKeyFrames[insertIdx] = keyFrame;
+            newTangentModes[insertIdx] = tangentMode;
+
+            if (insertIdx < existingKeyFrames.Length)
+            {
+                int remaining = existingKeyFrames.Length - insertIdx;
+                Array.Copy(existingKeyFrames, insertIdx, newKeyFrames, insertIdx + 1, remaining);
+                Array.Copy(tangentModes, insertIdx, newTangentModes, insertIdx + 1, remaining);
+            }
+
+            UpdateTangents(newKeyFrames, newTangentModes);
+
+            tangentModes = newTangentModes;
+            native.KeyFrames = newKeyFrames;
+        }
+
+        internal void RemoveKeyframe(int index)
+        {
+            KeyFrame[] existingKeyFrames = native.KeyFrames;
+            if (index < 0 || index >= existingKeyFrames.Length)
+                return;
+
+            KeyFrame[] newKeyFrames = new KeyFrame[existingKeyFrames.Length - 1];
+            TangentMode[] newTangentModes = new TangentMode[tangentModes.Length - 1];
+
+            Array.Copy(existingKeyFrames, 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(tangentModes, index + 1, newTangentModes, index, remaining);
+            }
+
+            UpdateTangents(newKeyFrames, newTangentModes);
+
+            tangentModes = newTangentModes;
+            native.KeyFrames = newKeyFrames;
+        }
+
+        internal void SetTangentMode(int index, TangentMode mode)
+        {
+            if (index < 0 || index >= tangentModes.Length)
+                return;
+
+            tangentModes[index] = mode;
+
+            KeyFrame[] keyFrames = native.KeyFrames;
+            UpdateTangents(keyFrames, tangentModes);
+            native.KeyFrames = keyFrames;
+        }
+
+        private void UpdateTangents(KeyFrame[] keyFrames, TangentMode[] tangentModes)
+        {
+            if (keyFrames.Length == 0)
+                return;
+
+            if (keyFrames.Length == 1)
+            {
+                keyFrames[0].inTangent = 0.0f;
+                keyFrames[0].outTangent = 0.0f;
+
+                return;
+            }
+
+            // First keyframe
+            {
+                KeyFrame keyThis = keyFrames[0];
+                KeyFrame keyNext = keyFrames[1];
+
+                keyThis.inTangent = 0.0f;
+
+                if (tangentModes[0].type == TangentModeType.Auto)
+                {
+                    TangentAutoType autoType = tangentModes[0].outTangent;
+
+                    switch (autoType)
+                    {
+                        case TangentAutoType.Auto:
+                        case TangentAutoType.Linear:
+                            float diff = keyNext.time - keyThis.time;
+                            keyThis.outTangent = (keyNext.value - keyThis.value)/diff;
+
+                            break;
+                        case TangentAutoType.Step:
+                            keyThis.outTangent = float.PositiveInfinity;
+                            break;
+                    }
+                }
+
+                keyFrames[0] = keyThis;
+            }
+
+            // TODO - Middle keyframes
+            
+            // TODO - How to handle transition from broken to free? 
+
+            // Last keyframe
+            {
+                KeyFrame keyThis = keyFrames[keyFrames.Length - 1];
+                KeyFrame keyPrev = keyFrames[keyFrames.Length - 2];
+
+                keyThis.outTangent = 0.0f;
+
+                if (tangentModes[0].type == TangentModeType.Auto)
+                {
+                    TangentAutoType autoType = tangentModes[0].outTangent;
+
+                    switch (autoType)
+                    {
+                        case TangentAutoType.Auto:
+                        case TangentAutoType.Linear:
+                            float diff = keyThis.time - keyPrev.time;
+                            keyThis.inTangent = (keyThis.value - keyPrev.value) / diff;
+
+                            break;
+                        case TangentAutoType.Step:
+                            keyThis.inTangent = float.PositiveInfinity;
+                            break;
+                    }
+                }
+
+                keyFrames[keyFrames.Length - 1] = keyThis;
+            }
+        }
+    }
+}

+ 148 - 0
Source/MBansheeEditor/Windows/Animation/GUICurveDrawing.cs

@@ -0,0 +1,148 @@
+//********************************** Banshee Engine (www.banshee3d.com) **************************************************//
+//**************** Copyright (c) 2016 Marko Pintera ([email protected]). All rights reserved. **********************//
+using System;
+using System.Collections.Generic;
+using BansheeEngine;
+
+namespace BansheeEditor
+{
+    /** @addtogroup AnimationEditor
+     *  @{
+     */
+
+    // TODO DOC
+    internal class GUICurveDrawing
+    {
+        private const int PADDING = 30; // TODO - Shared with GUITimeline
+        private const int LINE_SPLIT_WIDTH = 5;
+
+        private EdAnimationCurve[] curves;
+        private int width;
+        private int height;
+        private float length = 60.0f;
+        private float minY = -1.0f;
+        private float maxY = 1.0f;
+
+        private int drawableWidth;
+        private GUICanvas canvas;
+
+        public GUICurveDrawing(GUILayout layout, int width, int height, EdAnimationCurve[] curves)
+        {
+            canvas = new GUICanvas();
+            layout.AddElement(canvas);
+
+            this.curves = curves;
+
+            SetSize(width, height);
+        }
+
+        public void SetCurves(EdAnimationCurve[] curves)
+        {
+            this.curves = curves;
+
+            Rebuild();
+        }
+
+        public void SetSize(int width, int height)
+        {
+            this.width = width;
+            this.height = height;
+
+            drawableWidth = Math.Max(0, width - PADDING * 2);
+
+            Rebuild();
+        }
+
+        public void SetRange(float length, float minY, float maxY)
+        {
+            this.length = length;
+            this.minY = minY;
+            this.maxY = maxY;
+
+            Rebuild();
+        }
+
+        private void Rebuild()
+        {
+            canvas.Clear();
+
+            if (curves == null)
+                return;
+
+            // TODO - Draw vertical frame markers
+            // TODO - Draw horizontal value markers with text
+
+            foreach (var curve in curves)
+            {
+                // TODO - Pick unique color for each curve
+                DrawCurve(curve, Color.Red);
+            }
+        }
+
+        private void DrawCurve(EdAnimationCurve curve, Color color)
+        {
+            float lengthPerPixel = length/drawableWidth;
+            float heightPerPixel = (maxY - minY)/height;
+
+            int heightOffset = height/2; // So that y = 0 is at center of canvas
+
+            KeyFrame[] keyframes = curve.Native.KeyFrames;
+            if (keyframes.Length == 0)
+                return;
+
+            float start = MathEx.Clamp(keyframes[0].time, 0.0f, length);
+            float end = MathEx.Clamp(keyframes[keyframes.Length - 1].time, 0.0f, length);
+
+            int startPixel = (int)(start * lengthPerPixel);
+            int endPixel = (int)(end * lengthPerPixel);
+
+            // Draw start line
+            {
+                int xPosStart = -PADDING;
+                int xPosEnd = startPixel;
+
+                int yPos = (int)(curve.Native.Evaluate(0.0f, false) * heightPerPixel);
+                yPos = heightOffset - yPos; // Offset and flip height (canvas Y goes down)
+
+                Vector2I a = new Vector2I(xPosStart, yPos);
+                Vector2I b = new Vector2I(xPosEnd, yPos);
+
+                canvas.DrawLine(a, b, Color.LightGray);
+            }
+
+            // Draw in between
+            List<Vector2I> linePoints = new List<Vector2I>();
+
+            int xPos = startPixel;
+            do
+            {
+                float t = xPos * lengthPerPixel;
+
+                int yPos = (int)(curve.Native.Evaluate(t, false) * heightPerPixel);
+                yPos = heightOffset - yPos; // Offset and flip height (canvas Y goes down)
+
+                linePoints.Add(new Vector2I(xPos, yPos));
+
+                xPos += LINE_SPLIT_WIDTH;
+            } while (xPos <= endPixel);
+
+            canvas.DrawPolyLine(linePoints.ToArray(), color);
+
+            // Draw end line
+            {
+                int xPosStart = endPixel;
+                int xPosEnd = width;
+
+                int yPos = (int)(curve.Native.Evaluate(length, false) * heightPerPixel);
+                yPos = heightOffset - yPos; // Offset and flip height (canvas Y goes down)
+
+                Vector2I a = new Vector2I(xPosStart, yPos);
+                Vector2I b = new Vector2I(xPosEnd, yPos);
+
+                canvas.DrawLine(a, b, Color.LightGray);
+            }
+        }
+    }
+
+    /** }@ */
+}

+ 0 - 1
Source/MBansheeEditor/Windows/Animation/GUITimeline.cs

@@ -137,7 +137,6 @@ namespace BansheeEditor
 
             // TODO - Transition between interval sizes more lightly (dynamically change tick height?)
             // TODO - Calculate min width
-            // TODO - When at optimal width it should display the entire range
             // TODO - Time values change as width changes, keep them constant?
 
             float range = GetRange();

+ 29 - 5
Source/MBansheeEditor/Windows/AnimationWindow.cs

@@ -14,6 +14,7 @@ namespace BansheeEditor
     internal class AnimationWindow : EditorWindow
     {
         private GUITimeline timeline;
+        private GUICurveDrawing curveDrawing;
         private GUIFloatField lengthField;
         private GUIIntField fpsField;
 
@@ -48,11 +49,39 @@ namespace BansheeEditor
             buttonLayout.AddElement(fpsField);
 
             timeline = new GUITimeline(GUI, Width, 20);
+
+            EdAnimationCurve[] curves = CreateDummyCurves();
+            curveDrawing = new GUICurveDrawing(GUI, Width, Height - 20, curves);
+
+            // TODO - Calculate min/max Y and range to set as default
+            //  - Also recalculate whenever curves change and increase as needed
+        }
+
+        private EdAnimationCurve[] CreateDummyCurves()
+        {
+            EdAnimationCurve[] curves = new EdAnimationCurve[1];
+
+            curves[0].AddKeyframe(0.0f, 1.0f);
+
+            KeyFrame[] keyFrames = new KeyFrame[3];
+            keyFrames[0].time = 0.0f;
+            keyFrames[0].value = 1.0f;
+
+            keyFrames[1].time = 10.0f;
+            keyFrames[1].value = 5.0f;
+
+            keyFrames[2].time = 15.0f;
+            keyFrames[2].value = -2.0f;
+
+            // TODO - Set up tangents
+
+            return curves;
         }
 
         protected override void WindowResized(int width, int height)
         {
             timeline.SetSize(width, 20);
+            curveDrawing.SetSize(width, height - 20);
         }
 
         private void OnEditorUpdate()
@@ -60,11 +89,6 @@ namespace BansheeEditor
             //int position = (int)(MathEx.Sin(Time.RealElapsed)*50.0f + 50.0f);
             //canvas.SetPosition(position, 0);
         }
-
-        private void OnDestroy()
-        {
-            // TODO
-        }
     }
 
     /** @} */

+ 6 - 0
Source/SBansheeEngine/Source/BsScriptAnimationCurve.cpp

@@ -66,6 +66,12 @@ namespace BansheeEngine
 		memcpy(nativeKeyframes.data(), (UINT8*)inArray.getRawPtr<TKeyframe<float>>(),
 			numKeyframes * sizeof(TKeyframe<float>));
 
+		std::sort(nativeKeyframes.begin(), nativeKeyframes.end(), 
+			[](const TKeyframe<float>& x, const TKeyframe<float>& y)
+		{
+			return x.time < y.time;
+		});
+
 		thisPtr->mCurve = bs_shared_ptr_new<TAnimationCurve<float>>(nativeKeyframes);
 	}