Beispiel #1
0
        /// <summary>
        /// Attempts to find a curve under the provided coordinates.
        /// </summary>
        /// <param name="pixelCoords">Coordinates relative to this GUI element in pixels.</param>
        /// <returns>Index of the curve, or -1 if none found.</returns>
        public int FindCurve(Vector2I pixelCoords)
        {
            PixelToCurveSpace(pixelCoords, out var curveCoords, true);

            float time = curveCoords.x;

            float nearestDistance = float.MaxValue;
            int   curveIdx        = -1;

            for (int i = 0; i < curveInfos.Length; i++)
            {
                EdAnimationCurve curve = curveInfos[i].curve;

                float    value       = curve.Evaluate(time, false);
                Vector2I curPixelPos = CurveToPixelSpace(new Vector2(time, value));

                float distanceToKey = Vector2I.Distance(pixelCoords, curPixelPos);
                if (distanceToKey < nearestDistance)
                {
                    nearestDistance = distanceToKey;
                    curveIdx        = i;
                }
            }

            // We're not near any curve
            if (nearestDistance > 5.0f)
            {
                return(-1);
            }

            return(curveIdx);
        }
Beispiel #2
0
        /// <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);
        }
Beispiel #3
0
        /// <summary>
        /// Attempts to find a keyframe under the provided coordinates.
        /// </summary>
        /// <param name="pixelCoords">Coordinates relative to this GUI element in pixels.</param>
        /// <param name="keyframe">Output object containing keyframe index and index of the curve it belongs to. Only valid
        ///                        if method returns true.</param>
        /// <returns>True if there is a keyframe under the coordinates, false otherwise.</returns>
        public bool FindKeyFrame(Vector2I pixelCoords, out KeyframeRef keyframe)
        {
            keyframe = new KeyframeRef();

            float nearestDistance = float.MaxValue;

            for (int i = 0; i < curveInfos.Length; i++)
            {
                EdAnimationCurve curve     = curveInfos[i].curve;
                KeyFrame[]       keyframes = curve.KeyFrames;

                for (int j = 0; j < keyframes.Length; j++)
                {
                    Vector2  keyframeCurveCoords = new Vector2(keyframes[j].time, keyframes[j].value);
                    Vector2I keyframeCoords      = CurveToPixelSpace(keyframeCurveCoords);

                    float distanceToKey = Vector2I.Distance(pixelCoords, keyframeCoords);
                    if (distanceToKey < nearestDistance)
                    {
                        nearestDistance   = distanceToKey;
                        keyframe.keyIdx   = j;
                        keyframe.curveIdx = i;
                    }
                }
            }

            // We're not near any keyframe
            if (nearestDistance > 5.0f)
            {
                return(false);
            }

            return(true);
        }
Beispiel #4
0
        /// <summary>
        /// Removes all currently selected keyframes from the curves.
        /// </summary>
        private void DeleteSelectedKeyframes()
        {
            if (!disableCurveEdit)
            {
                foreach (var selectedEntry in selectedKeyframes)
                {
                    EdAnimationCurve curve = curveInfos[selectedEntry.curveIdx].curve;

                    // Sort keys from highest to lowest so the indices don't change
                    selectedEntry.keyIndices.Sort((x, y) =>
                    {
                        return(y.CompareTo(x));
                    });

                    foreach (var keyframeIdx in selectedEntry.keyIndices)
                    {
                        curve.RemoveKeyframe(keyframeIdx);
                    }

                    curve.Apply();
                }
            }
            else
            {
                ShowReadOnlyMessage();
            }

            // TODO - UNDOREDO

            ClearSelection();

            OnCurveModified?.Invoke();
            guiCurveDrawing.Rebuild();
            UpdateEventsGUI();
        }
Beispiel #5
0
        /// <summary>
        /// Attempts to find a a tangent handle under the provided coordinates.
        /// </summary>
        /// <param name="pixelCoords">Coordinates relative to this GUI element in pixels.</param>
        /// <param name="tangent">Output object containing keyframe information and tangent type. Only valid if method
        ///                       returns true.</param>
        /// <returns>True if there is a tangent handle under the coordinates, false otherwise.</returns>
        public bool FindTangent(Vector2I pixelCoords, out TangentRef tangent)
        {
            tangent = new TangentRef();

            float nearestDistance = float.MaxValue;

            for (int i = 0; i < curveInfos.Length; i++)
            {
                EdAnimationCurve curve     = curveInfos[i].curve;
                KeyFrame[]       keyframes = curve.KeyFrames;

                for (int j = 0; j < keyframes.Length; j++)
                {
                    if (!IsSelected(i, j))
                    {
                        continue;
                    }

                    TangentMode tangentMode = curve.TangentModes[j];

                    if (IsTangentDisplayed(tangentMode, TangentType.In))
                    {
                        Vector2I tangentCoords = GetTangentPosition(keyframes[j], TangentType.In);

                        float distanceToHandle = Vector2I.Distance(pixelCoords, tangentCoords);
                        if (distanceToHandle < nearestDistance)
                        {
                            nearestDistance              = distanceToHandle;
                            tangent.keyframeRef.keyIdx   = j;
                            tangent.keyframeRef.curveIdx = i;
                            tangent.type = TangentType.In;
                        }
                        ;
                    }

                    if (IsTangentDisplayed(tangentMode, TangentType.Out))
                    {
                        Vector2I tangentCoords = GetTangentPosition(keyframes[j], TangentType.Out);

                        float distanceToHandle = Vector2I.Distance(pixelCoords, tangentCoords);
                        if (distanceToHandle < nearestDistance)
                        {
                            nearestDistance              = distanceToHandle;
                            tangent.keyframeRef.keyIdx   = j;
                            tangent.keyframeRef.curveIdx = i;
                            tangent.type = TangentType.Out;
                        }
                    }
                }
            }

            // We're not near any keyframe
            if (nearestDistance > 5.0f)
            {
                return(false);
            }

            return(true);
        }
        private CurveEditorWindow(AnimationCurve curve, Action <bool, AnimationCurve> closedCallback = null)
            : base(false)
        {
            Title  = new LocString("Curve editor");
            Width  = 600;
            Height = 460;

            curveA = new EdAnimationCurve(curve ?? new AnimationCurve(new KeyFrame[] {}), null);
            this.closedCallback = closedCallback;
        }
Beispiel #7
0
        /// <summary>
        /// Changes the tangent mode for all currently selected keyframes.
        /// </summary>
        /// <param name="mode">Tangent mode to set. If only in or out tangent mode is provided, the mode for the opposite
        ///                    tangent will be kept as is.</param>
        private void ChangeSelectionTangentMode(TangentMode mode)
        {
            if (disableCurveEdit)
            {
                ShowReadOnlyMessage();
                return;
            }

            foreach (var selectedEntry in selectedKeyframes)
            {
                EdAnimationCurve curve = curveInfos[selectedEntry.curveIdx].curve;

                foreach (var keyframeIdx in selectedEntry.keyIndices)
                {
                    if (mode == TangentMode.Auto || mode == TangentMode.Free)
                    {
                        curve.SetTangentMode(keyframeIdx, mode);
                    }
                    else
                    {
                        TangentMode newMode = curve.TangentModes[keyframeIdx];

                        if (mode.HasFlag((TangentMode)TangentType.In))
                        {
                            // Replace only the in tangent mode, keeping the out tangent as is
                            TangentMode inFlags = (TangentMode.InAuto | TangentMode.InFree | TangentMode.InLinear |
                                                   TangentMode.InStep);

                            newMode &= ~inFlags;
                            newMode |= (mode & inFlags);
                        }
                        else
                        {
                            // Replace only the out tangent mode, keeping the in tangent as is
                            TangentMode outFlags = (TangentMode.OutAuto | TangentMode.OutFree | TangentMode.OutLinear |
                                                    TangentMode.OutStep);

                            newMode &= ~outFlags;
                            newMode |= (mode & outFlags);
                        }

                        curve.SetTangentMode(keyframeIdx, newMode);
                    }
                }

                curve.Apply();
            }

            // TODO - UNDOREDO

            OnCurveModified?.Invoke();
            guiCurveDrawing.Rebuild();
        }
Beispiel #8
0
        /// <summary>
        /// Opens the edit window for the currently selected keyframe.
        /// </summary>
        private void EditSelectedKeyframe()
        {
            if (disableCurveEdit)
            {
                ShowReadOnlyMessage();
                return;
            }

            if (selectedKeyframes.Count == 0)
            {
                return;
            }

            EdAnimationCurve curve = curveInfos[selectedKeyframes[0].curveIdx].curve;

            KeyFrame[] keyFrames = curve.KeyFrames;

            int      keyIndex = selectedKeyframes[0].keyIndices[0];
            KeyFrame keyFrame = keyFrames[keyIndex];
            Vector2I position = guiCurveDrawing.CurveToPixelSpace(new Vector2(keyFrame.time, keyFrame.value));

            Rect2I drawingBounds = GUIUtility.CalculateBounds(drawingPanel, window.GUI);

            position.x = MathEx.Clamp(position.x, 0, drawingBounds.width);
            position.y = MathEx.Clamp(position.y, 0, drawingBounds.height);

            Vector2I windowPos = position + new Vector2I(drawingBounds.x, drawingBounds.y);

            KeyframeEditWindow editWindow = DropDownWindow.Open <KeyframeEditWindow>(window, windowPos);

            editWindow.Initialize(keyFrame, x =>
            {
                curve.UpdateKeyframe(keyIndex, x.time, x.value);
                curve.Apply();
                // TODO UNDOREDO

                guiCurveDrawing.Rebuild();
                OnCurveModified?.Invoke();
            });
        }
Beispiel #9
0
 public CurveDrawInfo(EdAnimationCurve curve, Color color)
 {
     this.curve = curve;
     this.color = color;
 }
Beispiel #10
0
        /// <summary>
        /// Draws the curve using the provided color.
        /// </summary>
        /// <param name="curve">Curve to draw within the currently set range. </param>
        /// <param name="color">Color to draw the curve with.</param>
        private void DrawCurve(EdAnimationCurve curve, Color color)
        {
            float range          = GetRange(true);
            float lengthPerPixel = range / drawableWidth;

            KeyFrame[] keyframes = curve.KeyFrames;
            if (keyframes.Length <= 0)
            {
                return;
            }

            // Draw start line
            {
                float curveStart = keyframes[0].time;
                float curveValue = curve.Evaluate(curveStart, false);

                Vector2I end   = CurveToPixelSpace(new Vector2(curveStart, curveValue));
                Vector2I start = new Vector2I(-GUIGraphTime.PADDING, end.y);

                if (start.x < end.x)
                {
                    canvas.DrawLine(start, end, COLOR_MID_GRAY);
                }
            }

            List <Vector2I> linePoints = new List <Vector2I>();

            // Draw in between keyframes
            float startVisibleTime = rangeOffset;
            float endVisibleTime   = startVisibleTime + range;

            for (int i = 0; i < keyframes.Length - 1; i++)
            {
                float start = keyframes[i].time;
                float end   = keyframes[i + 1].time;

                if (end < startVisibleTime || start > endVisibleTime)
                {
                    continue;
                }

                bool isStep = keyframes[i].outTangent == float.PositiveInfinity ||
                              keyframes[i + 1].inTangent == float.PositiveInfinity;

                // If step tangent, draw the required lines without sampling, as the sampling will miss the step
                if (isStep)
                {
                    float startValue = curve.Evaluate(start, false);
                    float endValue   = curve.Evaluate(end, false);

                    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 splitIncrement = LINE_SPLIT_WIDTH * lengthPerPixel;

                    float startValue = keyframes[i].value;
                    float endValue   = keyframes[i + 1].value;

                    Vector2I startPixel = new Vector2I();
                    startPixel.x = (int)(start / lengthPerPixel);
                    startPixel.y = (int)(startValue / lengthPerPixel);

                    Vector2I endPixel = new Vector2I();
                    endPixel.x = (int)(end / lengthPerPixel);
                    endPixel.y = (int)(endValue / lengthPerPixel);

                    int distance = Vector2I.Distance(startPixel, endPixel);

                    int numSplits;
                    if (distance > 0)
                    {
                        float fNumSplits = distance / splitIncrement;

                        numSplits      = MathEx.CeilToInt(fNumSplits);
                        splitIncrement = distance / (float)numSplits;
                    }
                    else
                    {
                        numSplits      = 1;
                        splitIncrement = 0.0f;
                    }

                    for (int j = 0; j < numSplits; j++)
                    {
                        float t     = Math.Min(start + j * splitIncrement, end);
                        float value = curve.Evaluate(t, false);

                        linePoints.Add(CurveToPixelSpace(new Vector2(t, value)));
                    }
                }
            }

            canvas.DrawPolyLine(linePoints.ToArray(), color);

            // Draw end line
            {
                float curveEnd   = keyframes[keyframes.Length - 1].time;
                float curveValue = curve.Evaluate(curveEnd, false);

                Vector2I start = CurveToPixelSpace(new Vector2(curveEnd, curveValue));
                Vector2I end   = new Vector2I(width, start.y);

                if (start.x < end.x)
                {
                    canvas.DrawLine(start, end, COLOR_MID_GRAY);
                }
            }
        }
        /// <summary>
        /// Draws the curve using the provided color.
        /// </summary>
        /// <param name="curve">Curve to draw within the currently set range. </param>
        /// <param name="color">Color to draw the curve with.</param>
        private void DrawCurve(EdAnimationCurve curve, Color color)
        {
            float range          = GetRange();
            float lengthPerPixel = range / drawableWidth;

            KeyFrame[] keyframes = curve.KeyFrames;
            if (keyframes.Length <= 0)
            {
                return;
            }

            // Draw start line
            {
                float curveStart = MathEx.Clamp(keyframes[0].time, 0.0f, range);
                float curveValue = curve.Evaluate(0.0f, false);

                Vector2I start = CurveToPixelSpace(new Vector2(0.0f, curveValue));
                start.x -= GUIGraphTime.PADDING;

                Vector2I end = CurveToPixelSpace(new Vector2(curveStart, curveValue));

                canvas.DrawLine(start, end, COLOR_MID_GRAY);
            }

            List <Vector2I> linePoints = new List <Vector2I>();

            // Draw in between keyframes
            for (int i = 0; i < keyframes.Length - 1; i++)
            {
                float start = MathEx.Clamp(keyframes[i].time, 0.0f, range);
                float end   = MathEx.Clamp(keyframes[i + 1].time, 0.0f, range);

                bool isStep = keyframes[i].outTangent == float.PositiveInfinity ||
                              keyframes[i + 1].inTangent == float.PositiveInfinity;

                // If step tangent, draw the required lines without sampling, as the sampling will miss the step
                if (isStep)
                {
                    float startValue = curve.Evaluate(start, false);
                    float endValue   = curve.Evaluate(end, false);

                    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;
                    if (startPixel != endPixel)
                    {
                        float fNumSplits = (end - start) / timeIncrement;

                        numSplits = MathEx.FloorToInt(fNumSplits);
                        float remainder = fNumSplits - numSplits;

                        float lengthRounded = (end - start) * (numSplits / fNumSplits);
                        timeIncrement = lengthRounded / numSplits;

                        numSplits += MathEx.CeilToInt(remainder) + 1;
                    }
                    else
                    {
                        numSplits     = 1;
                        timeIncrement = 0.0f;
                    }

                    for (int j = 0; j < numSplits; j++)
                    {
                        float t     = Math.Min(start + j * timeIncrement, end);
                        float value = curve.Evaluate(t, false);

                        linePoints.Add(CurveToPixelSpace(new Vector2(t, value)));
                    }
                }
            }

            canvas.DrawPolyLine(linePoints.ToArray(), color);

            // Draw end line
            {
                float curveEnd   = MathEx.Clamp(keyframes[keyframes.Length - 1].time, 0.0f, range);
                float curveValue = curve.Evaluate(range, false);

                Vector2I start = CurveToPixelSpace(new Vector2(curveEnd, curveValue));
                Vector2I end   = new Vector2I(width, start.y);

                canvas.DrawLine(start, end, COLOR_MID_GRAY);
            }
        }
Beispiel #12
0
        /// <summary>
        /// Handles input. Should be called by the owning window whenever a pointer is moved.
        /// </summary>
        /// <param name="ev">Object containing pointer move event information.</param>
        internal void OnPointerMoved(PointerEvent ev)
        {
            if (ev.Button != PointerButton.Left)
            {
                return;
            }

            if (isPointerHeld)
            {
                Vector2I windowPos = window.ScreenToWindowPos(ev.ScreenPos);

                Rect2I   elementBounds = GUIUtility.CalculateBounds(gui, window.GUI);
                Vector2I pointerPos    = windowPos - new Vector2I(elementBounds.x, elementBounds.y);

                if (isMousePressedOverKey || isMousePressedOverTangent)
                {
                    Rect2I   drawingBounds = drawingPanel.Bounds;
                    Vector2I drawingPos    = pointerPos - new Vector2I(drawingBounds.x, drawingBounds.y);

                    if (!isDragInProgress)
                    {
                        int distance = Vector2I.Distance(drawingPos, dragStart);
                        if (distance >= DRAG_START_DISTANCE)
                        {
                            if (isMousePressedOverKey && !disableCurveEdit)
                            {
                                draggedKeyframes.Clear();
                                foreach (var selectedEntry in selectedKeyframes)
                                {
                                    EdAnimationCurve curve     = curveInfos[selectedEntry.curveIdx].curve;
                                    KeyFrame[]       keyFrames = curve.KeyFrames;

                                    DraggedKeyframes newEntry = new DraggedKeyframes();
                                    newEntry.curveIdx = selectedEntry.curveIdx;
                                    draggedKeyframes.Add(newEntry);

                                    foreach (var keyframeIdx in selectedEntry.keyIndices)
                                    {
                                        newEntry.keys.Add(new DraggedKeyframe(keyframeIdx, keyFrames[keyframeIdx]));
                                    }
                                }
                            }

                            // TODO - UNDOREDO record keyframe or tangent

                            isDragInProgress = true;
                        }
                    }

                    if (isDragInProgress)
                    {
                        if (isMousePressedOverKey && !disableCurveEdit)
                        {
                            Vector2 diff = Vector2.Zero;

                            Vector2 dragStartCurve;
                            if (guiCurveDrawing.PixelToCurveSpace(dragStart, out dragStartCurve))
                            {
                                Vector2 currentPosCurve;
                                if (guiCurveDrawing.PixelToCurveSpace(drawingPos, out currentPosCurve))
                                {
                                    diff = currentPosCurve - dragStartCurve;
                                }
                            }

                            foreach (var draggedEntry in draggedKeyframes)
                            {
                                EdAnimationCurve curve = curveInfos[draggedEntry.curveIdx].curve;

                                for (int i = 0; i < draggedEntry.keys.Count; i++)
                                {
                                    DraggedKeyframe draggedKey = draggedEntry.keys[i];

                                    float newTime  = draggedKey.original.time + diff.x;
                                    float newValue = draggedKey.original.value + diff.y;

                                    int newIndex = curve.UpdateKeyframe(draggedKey.index, newTime, newValue);

                                    // It's possible key changed position due to time change, but since we're moving all
                                    // keys at once they cannot change position relative to one another, otherwise we would
                                    // need to update indices for other keys as well.
                                    draggedKey.index     = newIndex;
                                    draggedEntry.keys[i] = draggedKey;
                                }

                                curve.Apply();
                            }

                            // Rebuild selected keys from dragged keys (after potential sorting)
                            ClearSelection();
                            foreach (var draggedEntry in draggedKeyframes)
                            {
                                foreach (var keyframe in draggedEntry.keys)
                                {
                                    SelectKeyframe(new KeyframeRef(draggedEntry.curveIdx, keyframe.index));
                                }
                            }

                            OnCurveModified?.Invoke();
                            guiCurveDrawing.Rebuild();
                            UpdateEventsGUI();
                        }
                        else if (isMousePressedOverTangent && !disableCurveEdit)
                        {
                            EdAnimationCurve curve    = curveInfos[draggedTangent.keyframeRef.curveIdx].curve;
                            KeyFrame         keyframe = curve.KeyFrames[draggedTangent.keyframeRef.keyIdx];

                            Vector2 keyframeCurveCoords = new Vector2(keyframe.time, keyframe.value);

                            Vector2 currentPosCurve;
                            if (guiCurveDrawing.PixelToCurveSpace(drawingPos, out currentPosCurve))
                            {
                                Vector2 normal = currentPosCurve - keyframeCurveCoords;
                                normal = normal.Normalized;

                                float tangent = EdAnimationCurve.NormalToTangent(normal);

                                if (draggedTangent.type == TangentType.In)
                                {
                                    if (normal.x > 0.0f)
                                    {
                                        tangent = float.PositiveInfinity;
                                    }

                                    keyframe.inTangent = -tangent;
                                    if (curve.TangentModes[draggedTangent.keyframeRef.keyIdx] == TangentMode.Free)
                                    {
                                        keyframe.outTangent = -tangent;
                                    }
                                }
                                else
                                {
                                    if (normal.x < 0.0f)
                                    {
                                        tangent = float.PositiveInfinity;
                                    }

                                    keyframe.outTangent = tangent;
                                    if (curve.TangentModes[draggedTangent.keyframeRef.keyIdx] == TangentMode.Free)
                                    {
                                        keyframe.inTangent = tangent;
                                    }
                                }

                                curve.KeyFrames[draggedTangent.keyframeRef.keyIdx] = keyframe;
                                curve.Apply();

                                OnCurveModified?.Invoke();
                                guiCurveDrawing.Rebuild();
                            }
                        }
                    }
                }
                else // Move frame marker
                {
                    int frameIdx = guiTimeline.GetFrame(pointerPos);

                    if (frameIdx != -1)
                    {
                        SetMarkedFrame(frameIdx);
                    }

                    OnFrameSelected?.Invoke(frameIdx);
                }
            }
        }