diff --git a/Source/Editor/CustomEditors/Dedicated/SplineEditor.cs b/Source/Editor/CustomEditors/Dedicated/SplineEditor.cs index 877342154..2fe601108 100644 --- a/Source/Editor/CustomEditors/Dedicated/SplineEditor.cs +++ b/Source/Editor/CustomEditors/Dedicated/SplineEditor.cs @@ -1,9 +1,12 @@ // Copyright (c) 2012-2023 Wojciech Figat. All rights reserved. -using FlaxEditor.Actions; -using FlaxEditor.SceneGraph.Actors; +using System.Collections.Generic; using FlaxEngine; using FlaxEngine.GUI; +using FlaxEditor.Actions; +using FlaxEditor.SceneGraph; +using FlaxEditor.SceneGraph.Actors; +using FlaxEditor.CustomEditors.Elements; namespace FlaxEditor.CustomEditors.Dedicated { @@ -14,54 +17,812 @@ namespace FlaxEditor.CustomEditors.Dedicated [CustomEditor(typeof(Spline)), DefaultEditor] public class SplineEditor : ActorEditor { + /// + /// Storage undo spline data + /// + private struct UndoData + { + public Spline spline; + public BezierCurve.Keyframe[] beforeKeyframes; + } + + /// + /// Basis for creating tangent manipulation types for bezier curves. + /// + private abstract class EditTangentOptionBase + { + /// + /// Called when user set selected tangent mode. + /// + /// Current spline selected on editor viewport. + /// Index of current keyframe selected on spline. + public abstract void OnSetMode(Spline spline, int index); + + /// + /// Called when user select a keyframe (spline point) of current selected spline on editor viewport. + /// + /// Current spline selected on editor viewport. + /// Index of current keyframe selected on spline. + public abstract void OnSelectKeyframe(Spline spline, int index); + + /// + /// Called when user select a tangent of current keyframe selected from spline. + /// + /// Current spline selected on editor viewport. + /// Index of current keyframe selected on spline. + public abstract void OnSelectTangent(Spline spline, int index); + + /// + /// Called when the tangent in from current keyframe selected from spline is moved on editor viewport. + /// + /// Current spline selected on editor viewport. + /// Index of current keyframe selected on spline. + public abstract void OnMoveTangentIn(Spline spline, int index); + + /// + /// Called when the tangent out from current keyframe selected from spline is moved on editor viewport. + /// + /// Current spline selected on editor viewport. + /// Current spline selected on editor viewport. + public abstract void OnMoveTangentOut(Spline spline, int index); + } + + /// + /// Edit curve options manipulate the curve as free mode + /// + private sealed class FreeTangentMode : EditTangentOptionBase + { + /// + public override void OnSetMode(Spline spline, int index) + { + if (IsLinearTangentMode(spline, index) || IsSmoothInTangentMode(spline, index) || IsSmoothOutTangentMode(spline, index)) + { + SetPointSmooth(spline, index); + } + } + + /// + public override void OnMoveTangentIn(Spline spline, int index) { } + + /// + public override void OnMoveTangentOut(Spline spline, int index) { } + + /// + public override void OnSelectKeyframe(Spline spline, int index) { } + + /// + public override void OnSelectTangent(Spline spline, int index) { } + } + + /// + /// Edit curve options to set tangents to linear + /// + private sealed class LinearTangentMode : EditTangentOptionBase + { + /// + public override void OnSetMode(Spline spline, int index) + { + SetKeyframeLinear(spline, index); + + // change the selection to tangent parent (a spline point / keyframe) + SetSelectSplinePointNode(spline, index); + } + + /// + public override void OnMoveTangentIn(Spline spline, int index) { } + + /// + public override void OnMoveTangentOut(Spline spline, int index) { } + + /// + public override void OnSelectKeyframe(Spline spline, int index) { } + + /// + public override void OnSelectTangent(Spline spline, int index) { } + } + + /// + /// Edit curve options to align tangents of selected spline + /// + private sealed class AlignedTangentMode : EditTangentOptionBase + { + /// + public override void OnSetMode(Spline spline, int index) + { + SmoothIfNotAligned(spline, index); + } + + /// + public override void OnSelectKeyframe(Spline spline, int index) + { + SmoothIfNotAligned(spline, index); + } + + /// + public override void OnSelectTangent(Spline selectedSpline, int index) { } + + /// + public override void OnMoveTangentIn(Spline spline, int index) + { + SetPointAligned(spline, index, true); + } + + /// + public override void OnMoveTangentOut(Spline spline, int index) + { + SetPointAligned(spline, index, false); + } + + private void SmoothIfNotAligned(Spline spline, int index) + { + if (!IsAlignedTangentMode(spline, index)) + { + SetPointSmooth(spline, index); + } + } + + private void SetPointAligned(Spline spline, int index, bool alignWithIn) + { + var keyframe = spline.GetSplineKeyframe(index); + var referenceTangent = alignWithIn ? keyframe.TangentIn : keyframe.TangentOut; + var otherTangent = !alignWithIn ? keyframe.TangentIn : keyframe.TangentOut; + + // inverse of reference tangent + otherTangent.Translation = -referenceTangent.Translation.Normalized * otherTangent.Translation.Length; + + if (alignWithIn) keyframe.TangentOut = otherTangent; + if (!alignWithIn) keyframe.TangentIn = otherTangent; + + spline.SetSplineKeyframe(index, keyframe); + } + } + + /// + /// Edit curve options manipulate the curve setting selected point + /// tangent in as smoothed but tangent out as linear + /// + private sealed class SmoothInTangentMode : EditTangentOptionBase + { + /// + public override void OnSetMode(Spline spline, int index) + { + SetTangentSmoothIn(spline, index); + SetSelectTangentIn(spline, index); + } + + /// + public override void OnMoveTangentIn(Spline spline, int index) { } + + /// + public override void OnMoveTangentOut(Spline spline, int index) { } + + /// + public override void OnSelectKeyframe(Spline spline, int index) { } + + /// + public override void OnSelectTangent(Spline spline, int index) { } + } + + /// + /// Edit curve options manipulate the curve setting selected point + /// tangent in as linear but tangent out as smoothed + /// + private sealed class SmoothOutTangentMode : EditTangentOptionBase + { + /// + public override void OnSetMode(Spline spline, int index) + { + SetTangentSmoothOut(spline, index); + SetSelectTangentOut(spline, index); + } + + /// + public override void OnMoveTangentIn(Spline spline, int index) { } + + /// + public override void OnMoveTangentOut(Spline spline, int index) { } + + /// + public override void OnSelectKeyframe(Spline spline, int index) { } + + /// + public override void OnSelectTangent(Spline spline, int index) { } + } + + private readonly Color HighlightedColor = FlaxEngine.GUI.Style.Current.BackgroundSelected; + private readonly Color SelectedButtonColor = FlaxEngine.GUI.Style.Current.BackgroundSelected; + private readonly Color NormalButtonColor = FlaxEngine.GUI.Style.Current.BackgroundNormal; + + private EditTangentOptionBase _currentTangentMode; + + private ButtonElement _freeTangentButton; + private ButtonElement _linearTangentButton; + private ButtonElement _alignedTangentButton; + private ButtonElement _smoothInTangentButton; + private ButtonElement _smoothOutTangentButton; + private ButtonElement _setLinearAllTangentsButton; + private ButtonElement _setSmoothAllTangentsButton; + + private bool _tanInChanged; + private bool _tanOutChanged; + private Vector3 _lastTanInPos; + private Vector3 _lastTanOutPos; + private Spline _selectedSpline; + private SplineNode.SplinePointNode _selectedPoint; + private SplineNode.SplinePointNode _lastPointSelected; + private SplineNode.SplinePointTangentNode _selectedTangentIn; + private SplineNode.SplinePointTangentNode _selectedTangentOut; + + private UndoData[] selectedSplinesUndoData; + + private bool HasPointSelected => _selectedPoint != null; + private bool HasTangentsSelected => _selectedTangentIn != null || _selectedTangentOut != null; + /// public override void Initialize(LayoutElementsContainer layout) { base.Initialize(layout); + _currentTangentMode = new FreeTangentMode(); + if (Values.HasDifferentTypes == false) { + _selectedSpline = !Values.HasDifferentValues && Values[0] is Spline ? (Spline)Values[0] : null; + layout.Space(10); + + layout.Header("Selected spline point"); + var selectedPointsGrid = layout.CustomContainer(); + selectedPointsGrid.CustomControl.SlotsHorizontally = 3; + selectedPointsGrid.CustomControl.SlotsVertically = 2; + + selectedPointsGrid.Control.Size *= new Float2(1, 2); + + _linearTangentButton = selectedPointsGrid.Button("Linear"); + _freeTangentButton = selectedPointsGrid.Button("Free"); + _alignedTangentButton = selectedPointsGrid.Button("Aligned"); + _smoothInTangentButton = selectedPointsGrid.Button("Smooth In"); + _smoothOutTangentButton = selectedPointsGrid.Button("Smooth Out"); + + _linearTangentButton.Button.BackgroundColorHighlighted = HighlightedColor; + _freeTangentButton.Button.BackgroundColorHighlighted = HighlightedColor; + _alignedTangentButton.Button.BackgroundColorHighlighted = HighlightedColor; + _smoothInTangentButton.Button.BackgroundColorHighlighted = HighlightedColor; + _smoothOutTangentButton.Button.BackgroundColorHighlighted = HighlightedColor; + + _linearTangentButton.Button.Clicked += StartEditSpline; + _freeTangentButton.Button.Clicked += StartEditSpline; + _alignedTangentButton.Button.Clicked += StartEditSpline; + _smoothInTangentButton.Button.Clicked += StartEditSpline; + _smoothOutTangentButton.Button.Clicked += StartEditSpline; + + _linearTangentButton.Button.Clicked += SetModeLinear; + _freeTangentButton.Button.Clicked += SetModeFree; + _alignedTangentButton.Button.Clicked += SetModeAligned; + _smoothInTangentButton.Button.Clicked += SetModeSmoothIn; + _smoothOutTangentButton.Button.Clicked += SetModeSmoothOut; + + _linearTangentButton.Button.Clicked += EndEditSpline; + _freeTangentButton.Button.Clicked += EndEditSpline; + _alignedTangentButton.Button.Clicked += EndEditSpline; + _smoothInTangentButton.Button.Clicked += EndEditSpline; + _smoothOutTangentButton.Button.Clicked += EndEditSpline; + + layout.Header("All spline points"); var grid = layout.CustomContainer(); grid.CustomControl.SlotsHorizontally = 2; grid.CustomControl.SlotsVertically = 1; - grid.Button("Set Linear Tangents").Button.Clicked += OnSetTangentsLinear; - grid.Button("Set Smooth Tangents").Button.Clicked += OnSetTangentsSmooth; + + _setLinearAllTangentsButton = grid.Button("Set Linear Tangents"); + _setSmoothAllTangentsButton = grid.Button("Set Smooth Tangents"); + _setLinearAllTangentsButton.Button.BackgroundColorHighlighted = HighlightedColor; + _setSmoothAllTangentsButton.Button.BackgroundColorHighlighted = HighlightedColor; + + _setLinearAllTangentsButton.Button.Clicked += StartEditSpline; + _setSmoothAllTangentsButton.Button.Clicked += StartEditSpline; + + _setLinearAllTangentsButton.Button.Clicked += OnSetTangentsLinear; + _setSmoothAllTangentsButton.Button.Clicked += OnSetTangentsSmooth; + + _setLinearAllTangentsButton.Button.Clicked += EndEditSpline; + _setSmoothAllTangentsButton.Button.Clicked += EndEditSpline; + + if (_selectedSpline) _selectedSpline.SplineUpdated += OnSplineEdited; + + SetSelectedTangentTypeAsCurrent(); + SetEditButtonsColor(); + SetEditButtonsEnabled(); + } + } + + /// + protected override void Deinitialize() + { + if (_selectedSpline) _selectedSpline.SplineUpdated -= OnSplineEdited; + } + + private void OnSplineEdited() + { + SetEditButtonsColor(); + SetEditButtonsEnabled(); + } + + /// + public override void Refresh() + { + base.Refresh(); + + UpdateSelectedPoint(); + UpdateSelectedTangent(); + + if (!CanEditTangent()) + { + return; + } + + var index = _lastPointSelected.Index; + var currentTangentInPosition = _selectedSpline.GetSplineLocalTangent(index, true).Translation; + var currentTangentOutPosition = _selectedSpline.GetSplineLocalTangent(index, false).Translation; + + if (_selectedTangentIn != null) + { + _tanInChanged = _lastTanInPos != currentTangentInPosition; + _lastTanInPos = currentTangentInPosition; + } + + if (_selectedTangentOut != null) + { + _tanOutChanged = _lastTanOutPos != currentTangentOutPosition; + _lastTanOutPos = currentTangentOutPosition; + } + + if (_tanInChanged) _currentTangentMode.OnMoveTangentIn(_selectedSpline, index); + if (_tanOutChanged) _currentTangentMode.OnMoveTangentOut(_selectedSpline, index); + + currentTangentInPosition = _selectedSpline.GetSplineLocalTangent(index, true).Translation; + currentTangentOutPosition = _selectedSpline.GetSplineLocalTangent(index, false).Translation; + + // update last tangents position after changes + if (_selectedSpline) _lastTanInPos = currentTangentInPosition; + if (_selectedSpline) _lastTanOutPos = currentTangentOutPosition; + _tanInChanged = false; + _tanOutChanged = false; + } + + private void SetSelectedTangentTypeAsCurrent() + { + if (_lastPointSelected == null || _selectedPoint == null) return; + if (IsLinearTangentMode(_selectedSpline, _lastPointSelected.Index)) SetModeLinear(); + else if (IsAlignedTangentMode(_selectedSpline, _lastPointSelected.Index)) SetModeAligned(); + else if (IsSmoothInTangentMode(_selectedSpline, _lastPointSelected.Index)) SetModeSmoothIn(); + else if (IsSmoothOutTangentMode(_selectedSpline, _lastPointSelected.Index)) SetModeSmoothOut(); + else if (IsFreeTangentMode(_selectedSpline, _lastPointSelected.Index)) SetModeFree(); + } + + private void SetEditButtonsColor() + { + if (!CanEditTangent()) + { + _linearTangentButton.Button.BackgroundColor = NormalButtonColor; + _freeTangentButton.Button.BackgroundColor = NormalButtonColor; + _alignedTangentButton.Button.BackgroundColor = NormalButtonColor; + _smoothInTangentButton.Button.BackgroundColor = NormalButtonColor; + _smoothOutTangentButton.Button.BackgroundColor = NormalButtonColor; + return; + } + + var isFree = _currentTangentMode is FreeTangentMode; + var isLinear = _currentTangentMode is LinearTangentMode; + var isAligned = _currentTangentMode is AlignedTangentMode; + var isSmoothIn = _currentTangentMode is SmoothInTangentMode; + var isSmoothOut = _currentTangentMode is SmoothOutTangentMode; + + _linearTangentButton.Button.BackgroundColor = isLinear ? SelectedButtonColor : NormalButtonColor; + _freeTangentButton.Button.BackgroundColor = isFree ? SelectedButtonColor : NormalButtonColor; + _alignedTangentButton.Button.BackgroundColor = isAligned ? SelectedButtonColor : NormalButtonColor; + _smoothInTangentButton.Button.BackgroundColor = isSmoothIn ? SelectedButtonColor : NormalButtonColor; + _smoothOutTangentButton.Button.BackgroundColor = isSmoothOut ? SelectedButtonColor : NormalButtonColor; + } + + private void SetEditButtonsEnabled() + { + _linearTangentButton.Button.Enabled = CanEditTangent(); + _freeTangentButton.Button.Enabled = CanEditTangent(); + _alignedTangentButton.Button.Enabled = CanEditTangent(); + _smoothInTangentButton.Button.Enabled = CanSetTangentSmoothIn(); + _smoothOutTangentButton.Button.Enabled = CanSetTangentSmoothOut(); + _setLinearAllTangentsButton.Button.Enabled = CanSetAllTangentsLinear(); + _setSmoothAllTangentsButton.Button.Enabled = CanSetAllTangentsSmooth(); + } + + private bool CanEditTangent() + { + return !HasDifferentTypes && !HasDifferentValues && (HasPointSelected || HasTangentsSelected); + } + + private bool CanSetTangentSmoothIn() + { + if (!CanEditTangent()) return false; + return _lastPointSelected.Index != 0; + } + + private bool CanSetTangentSmoothOut() + { + if (!CanEditTangent()) return false; + return _lastPointSelected.Index < _selectedSpline.SplinePointsCount - 1; + } + + private bool CanSetAllTangentsSmooth() + { + return _selectedSpline != null; + } + + private bool CanSetAllTangentsLinear() + { + return _selectedSpline != null; + } + + private void SetModeLinear() + { + if (_currentTangentMode is LinearTangentMode) return; + _currentTangentMode = new LinearTangentMode(); + _currentTangentMode.OnSetMode(_selectedSpline, _lastPointSelected.Index); + } + + private void SetModeFree() + { + if (_currentTangentMode is FreeTangentMode) return; + _currentTangentMode = new FreeTangentMode(); + _currentTangentMode.OnSetMode(_selectedSpline, _lastPointSelected.Index); + SetEditButtonsColor(); + } + + private void SetModeAligned() + { + if (_currentTangentMode is AlignedTangentMode) return; + _currentTangentMode = new AlignedTangentMode(); + _currentTangentMode.OnSetMode(_selectedSpline, _lastPointSelected.Index); + } + + private void SetModeSmoothIn() + { + if (_currentTangentMode is SmoothInTangentMode) return; + _currentTangentMode = new SmoothInTangentMode(); + _currentTangentMode.OnSetMode(_selectedSpline, _lastPointSelected.Index); + } + + private void SetModeSmoothOut() + { + if (_currentTangentMode is SmoothOutTangentMode) return; + _currentTangentMode = new SmoothOutTangentMode(); + _currentTangentMode.OnSetMode(_selectedSpline, _lastPointSelected.Index); + } + + private void UpdateSelectedPoint() + { + // works only if select one spline + if (_selectedSpline) + { + var currentSelected = Editor.Instance.SceneEditing.Selection[0]; + + if (currentSelected == _selectedPoint) return; + if (currentSelected is SplineNode.SplinePointNode) + { + _selectedPoint = currentSelected as SplineNode.SplinePointNode; + _lastPointSelected = _selectedPoint; + _currentTangentMode.OnSelectKeyframe(_selectedSpline, _lastPointSelected.Index); + } + else + { + _selectedPoint = null; + } + } + else + { + _selectedPoint = null; + } + + SetSelectedTangentTypeAsCurrent(); + SetEditButtonsColor(); + SetEditButtonsEnabled(); + } + + private void UpdateSelectedTangent() + { + // works only if select one spline + if (_lastPointSelected == null || Editor.Instance.SceneEditing.SelectionCount != 1) + { + _selectedTangentIn = null; + _selectedTangentOut = null; + return; + } + + var currentSelected = Editor.Instance.SceneEditing.Selection[0]; + + if (currentSelected is not SplineNode.SplinePointTangentNode) + { + _selectedTangentIn = null; + _selectedTangentOut = null; + return; + } + + if (currentSelected == _selectedTangentIn) return; + if (currentSelected == _selectedTangentOut) return; + + var index = _lastPointSelected.Index; + + if (currentSelected.Transform == _selectedSpline.GetSplineTangent(index, true)) + { + _selectedTangentIn = currentSelected as SplineNode.SplinePointTangentNode; + _selectedTangentOut = null; + _currentTangentMode.OnSelectTangent(_selectedSpline, index); + + return; + } + + if (currentSelected.Transform == _selectedSpline.GetSplineTangent(index, false)) + { + _selectedTangentOut = currentSelected as SplineNode.SplinePointTangentNode; + _selectedTangentIn = null; + _currentTangentMode.OnSelectTangent(_selectedSpline, index); + return; + } + + _selectedTangentIn = null; + _selectedTangentOut = null; + } + + private void StartEditSpline() + { + var enableUndo = Presenter.Undo != null && Presenter.Undo.Enabled; + + if (!enableUndo) + { + return; + } + + var splines = new List(); + + for (int i = 0; i < Values.Count; i++) + { + if (Values[i] is Spline spline) + { + splines.Add(new UndoData { + spline = spline, + beforeKeyframes = spline.SplineKeyframes.Clone() as BezierCurve.Keyframe[] + }); + } + } + + selectedSplinesUndoData = splines.ToArray(); + } + + private void EndEditSpline() + { + var enableUndo = Presenter.Undo != null && Presenter.Undo.Enabled; + + if (!enableUndo) + { + return; + } + + for (int i = 0; i < selectedSplinesUndoData.Length; i++) + { + var splineUndoData = selectedSplinesUndoData[i]; + Presenter.Undo.AddAction(new EditSplineAction(_selectedSpline, splineUndoData.beforeKeyframes)); + SplineNode.OnSplineEdited(splineUndoData.spline); + Editor.Instance.Scene.MarkSceneEdited(splineUndoData.spline.Scene); } } private void OnSetTangentsLinear() { - var enableUndo = Presenter.Undo != null && Presenter.Undo.Enabled; - for (int i = 0; i < Values.Count; i++) - { - if (Values[i] is Spline spline) - { - var before = enableUndo ? (BezierCurve.Keyframe[])spline.SplineKeyframes.Clone() : null; - spline.SetTangentsLinear(); - if (enableUndo) - Presenter.Undo.AddAction(new EditSplineAction(spline, before)); - SplineNode.OnSplineEdited(spline); - Editor.Instance.Scene.MarkSceneEdited(spline.Scene); - } - } + _selectedSpline.SetTangentsLinear(); + _selectedSpline.UpdateSpline(); } private void OnSetTangentsSmooth() { - var enableUndo = Presenter.Undo != null && Presenter.Undo.Enabled; - for (int i = 0; i < Values.Count; i++) + _selectedSpline.SetTangentsSmooth(); + _selectedSpline.UpdateSpline(); + } + + private static bool IsFreeTangentMode(Spline spline, int index) + { + if (IsLinearTangentMode(spline, index) || + IsAlignedTangentMode(spline, index) || + IsSmoothInTangentMode(spline, index) || + IsSmoothOutTangentMode(spline, index)) { - if (Values[i] is Spline spline) + return false; + } + + return true; + } + + private static bool IsLinearTangentMode(Spline spline, int index) + { + var keyframe = spline.GetSplineKeyframe(index); + return keyframe.TangentIn.Translation.Length == 0 && keyframe.TangentOut.Translation.Length == 0; + } + + private static bool IsAlignedTangentMode(Spline spline, int index) + { + var keyframe = spline.GetSplineKeyframe(index); + var tangentIn = keyframe.TangentIn.Translation; + var tangentOut = keyframe.TangentOut.Translation; + + if (tangentIn.Length == 0 || tangentOut.Length == 0) + { + return false; + } + + var angleBetweenTwoTangents = Vector3.Dot(tangentIn.Normalized, tangentOut.Normalized); + + if (angleBetweenTwoTangents < -0.99f) + { + return true; + } + + return false; + } + + private static bool IsSmoothInTangentMode(Spline spline, int index) + { + var keyframe = spline.GetSplineKeyframe(index); + return keyframe.TangentIn.Translation.Length > 0 && keyframe.TangentOut.Translation.Length == 0; + } + + private static bool IsSmoothOutTangentMode(Spline spline, int index) + { + var keyframe = spline.GetSplineKeyframe(index); + return keyframe.TangentOut.Translation.Length > 0 && keyframe.TangentIn.Translation.Length == 0; + } + + private static void SetKeyframeLinear(Spline spline, int index) + { + var keyframe = spline.GetSplineKeyframe(index); + keyframe.TangentIn.Translation = Vector3.Zero; + keyframe.TangentOut.Translation = Vector3.Zero; + + var lastSplineIndex = spline.SplinePointsCount - 1; + if (index == lastSplineIndex && spline.IsLoop) + { + var lastPoint = spline.GetSplineKeyframe(lastSplineIndex); + lastPoint.TangentIn.Translation = Vector3.Zero; + lastPoint.TangentOut.Translation = Vector3.Zero; + spline.SetSplineKeyframe(lastSplineIndex, lastPoint); + } + + spline.SetSplineKeyframe(index, keyframe); + spline.UpdateSpline(); + } + + private static void SetTangentSmoothIn(Spline spline, int index) + { + var keyframe = spline.GetSplineKeyframe(index); + + // auto smooth tangent if's linear + if (keyframe.TangentIn.Translation.Length == 0) + { + var smoothRange = SplineNode.NodeSizeByDistance(spline.GetSplineTangent(index, false).Translation, 10f); + var previousKeyframe = spline.GetSplineKeyframe(index - 1); + var tangentDirection = keyframe.Value.WorldToLocalVector(previousKeyframe.Value.Translation - keyframe.Value.Translation); + tangentDirection = tangentDirection.Normalized * smoothRange; + keyframe.TangentIn.Translation = tangentDirection; + } + + keyframe.TangentOut.Translation = Vector3.Zero; + spline.SetSplineKeyframe(index, keyframe); + spline.UpdateSpline(); + } + + private static void SetTangentSmoothOut(Spline spline, int index) + { + var keyframe = spline.GetSplineKeyframe(index); + + // auto smooth tangent if's linear + if (keyframe.TangentOut.Translation.Length == 0) + { + var smoothRange = SplineNode.NodeSizeByDistance(spline.GetSplineTangent(index, false).Translation, 10f); + var nextKeyframe = spline.GetSplineKeyframe(index + 1); + var tangentDirection = keyframe.Value.WorldToLocalVector(nextKeyframe.Value.Translation - keyframe.Value.Translation); + tangentDirection = tangentDirection.Normalized * smoothRange; + keyframe.TangentOut.Translation = tangentDirection; + } + + keyframe.TangentIn.Translation = Vector3.Zero; + + spline.SetSplineKeyframe(index, keyframe); + spline.UpdateSpline(); + } + + private static void SetPointSmooth(Spline spline, int index) + { + var keyframe = spline.GetSplineKeyframe(index); + var tangentInSize = keyframe.TangentIn.Translation.Length; + var tangentOutSize = keyframe.TangentOut.Translation.Length; + + var isLastKeyframe = index >= spline.SplinePointsCount - 1; + var isFirstKeyframe = index <= 0; + var smoothRange = SplineNode.NodeSizeByDistance(spline.GetSplinePoint(index), 10f); + + // force smooth it's linear point + if (tangentInSize == 0f) tangentInSize = smoothRange; + if (tangentOutSize == 0f) tangentOutSize = smoothRange; + + // try get next / last keyframe + var nextKeyframe = !isLastKeyframe ? spline.GetSplineKeyframe(index + 1) : keyframe; + var previousKeyframe = !isFirstKeyframe ? spline.GetSplineKeyframe(index - 1) : keyframe; + + // calc form from Spline.cpp -> SetTangentsSmooth + // get tangent direction + var tangentDirection = (keyframe.Value.Translation - previousKeyframe.Value.Translation + nextKeyframe.Value.Translation - keyframe.Value.Translation).Normalized; + + keyframe.TangentIn.Translation = -tangentDirection; + keyframe.TangentOut.Translation = tangentDirection; + + keyframe.TangentIn.Translation *= tangentInSize; + keyframe.TangentOut.Translation *= tangentOutSize; + + spline.SetSplineKeyframe(index, keyframe); + spline.UpdateSpline(); + } + + private static SplineNode.SplinePointNode GetSplinePointNode(Spline spline, int index) + { + return (SplineNode.SplinePointNode)SceneGraphFactory.FindNode(spline.ID).ChildNodes[index]; + } + + private static SplineNode.SplinePointTangentNode GetSplineTangentInNode(Spline spline, int index) + { + var point = GetSplinePointNode(spline, index); + var tangentIn = spline.GetSplineTangent(index, true); + var tangentNodes = point.ChildNodes; + + // find tangent in node comparing all child nodes position + for (int i = 0; i < tangentNodes.Count; i++) + { + if (tangentNodes[i].Transform.Translation == tangentIn.Translation) { - var before = enableUndo ? (BezierCurve.Keyframe[])spline.SplineKeyframes.Clone() : null; - spline.SetTangentsSmooth(); - if (enableUndo) - Presenter.Undo.AddAction(new EditSplineAction(spline, before)); - SplineNode.OnSplineEdited(spline); - Editor.Instance.Scene.MarkSceneEdited(spline.Scene); + return (SplineNode.SplinePointTangentNode)tangentNodes[i]; } } + + return null; + } + + private static SplineNode.SplinePointTangentNode GetSplineTangentOutNode(Spline spline, int index) + { + var point = GetSplinePointNode(spline, index); + var tangentOut = spline.GetSplineTangent(index, false); + var tangentNodes = point.ChildNodes; + + // find tangent out node comparing all child nodes position + for (int i = 0; i < tangentNodes.Count; i++) + { + if (tangentNodes[i].Transform.Translation == tangentOut.Translation) + { + return (SplineNode.SplinePointTangentNode)tangentNodes[i]; + } + } + + return null; + } + + private static void SetSelectSplinePointNode(Spline spline, int index) + { + Editor.Instance.SceneEditing.Select(GetSplinePointNode(spline, index)); + } + + private static void SetSelectTangentIn(Spline spline, int index) + { + Editor.Instance.SceneEditing.Select(GetSplineTangentInNode(spline, index)); + } + + private static void SetSelectTangentOut(Spline spline, int index) + { + Editor.Instance.SceneEditing.Select(GetSplineTangentOutNode(spline, index)); } } } diff --git a/Source/Editor/SceneGraph/Actors/SplineNode.cs b/Source/Editor/SceneGraph/Actors/SplineNode.cs index eebee0cd9..9af736fac 100644 --- a/Source/Editor/SceneGraph/Actors/SplineNode.cs +++ b/Source/Editor/SceneGraph/Actors/SplineNode.cs @@ -12,6 +12,7 @@ using FlaxEditor.Modules; using FlaxEngine; using FlaxEngine.Json; using Object = FlaxEngine.Object; +using FlaxEditor.Viewport.Cameras; namespace FlaxEditor.SceneGraph.Actors { @@ -21,7 +22,7 @@ namespace FlaxEditor.SceneGraph.Actors [HideInEditor] public sealed class SplineNode : ActorNode { - private sealed class SplinePointNode : ActorChildNode + public sealed class SplinePointNode : ActorChildNode { public unsafe SplinePointNode(SplineNode node, Guid id, int index) : base(node, id, index) @@ -167,10 +168,11 @@ namespace FlaxEditor.SceneGraph.Actors public override bool RayCastSelf(ref RayCastData ray, out Real distance, out Vector3 normal) { + normal = -ray.Ray.Direction; var actor = (Spline)_node.Actor; var pos = actor.GetSplinePoint(Index); - normal = -ray.Ray.Direction; - return new BoundingSphere(pos, 7.0f).Intersects(ref ray.Ray, out distance); + var nodeSize = NodeSizeByDistance(Transform.Translation, PointNodeSize); + return new BoundingSphere(pos, nodeSize).Intersects(ref ray.Ray, out distance); } public override void OnDebugDraw(ViewportDebugDrawData data) @@ -179,23 +181,26 @@ namespace FlaxEditor.SceneGraph.Actors var pos = actor.GetSplinePoint(Index); var tangentIn = actor.GetSplineTangent(Index, true).Translation; var tangentOut = actor.GetSplineTangent(Index, false).Translation; + var pointSize = NodeSizeByDistance(pos, PointNodeSize); + var tangentInSize = NodeSizeByDistance(tangentIn, TangentNodeSize); + var tangentOutSize = NodeSizeByDistance(tangentOut, TangentNodeSize); // Draw spline path ParentNode.OnDebugDraw(data); // Draw selected point highlight - DebugDraw.DrawSphere(new BoundingSphere(pos, 5.0f), Color.Yellow, 0, false); + DebugDraw.DrawSphere(new BoundingSphere(pos, pointSize), Color.Yellow, 0, false); // Draw tangent points if (tangentIn != pos) { DebugDraw.DrawLine(pos, tangentIn, Color.Blue.AlphaMultiplied(0.6f), 0, false); - DebugDraw.DrawWireSphere(new BoundingSphere(tangentIn, 4.0f), Color.Blue, 0, false); + DebugDraw.DrawWireSphere(new BoundingSphere(tangentIn, tangentInSize), Color.Blue, 0, false); } if (tangentOut != pos) { DebugDraw.DrawLine(pos, tangentOut, Color.Red.AlphaMultiplied(0.6f), 0, false); - DebugDraw.DrawWireSphere(new BoundingSphere(tangentOut, 4.0f), Color.Red, 0, false); + DebugDraw.DrawWireSphere(new BoundingSphere(tangentOut, tangentOutSize), Color.Red, 0, false); } } @@ -219,7 +224,7 @@ namespace FlaxEditor.SceneGraph.Actors } } - private sealed class SplinePointTangentNode : ActorChildNode + public sealed class SplinePointTangentNode : ActorChildNode { private SplineNode _node; private int _index; @@ -249,10 +254,11 @@ namespace FlaxEditor.SceneGraph.Actors public override bool RayCastSelf(ref RayCastData ray, out Real distance, out Vector3 normal) { + normal = -ray.Ray.Direction; var actor = (Spline)_node.Actor; var pos = actor.GetSplineTangent(_index, _isIn).Translation; - normal = -ray.Ray.Direction; - return new BoundingSphere(pos, 7.0f).Intersects(ref ray.Ray, out distance); + var tangentSize = NodeSizeByDistance(Transform.Translation, TangentNodeSize); + return new BoundingSphere(pos, tangentSize).Intersects(ref ray.Ray, out distance); } public override void OnDebugDraw(ViewportDebugDrawData data) @@ -263,7 +269,8 @@ namespace FlaxEditor.SceneGraph.Actors // Draw selected tangent highlight var actor = (Spline)_node.Actor; var pos = actor.GetSplineTangent(_index, _isIn).Translation; - DebugDraw.DrawSphere(new BoundingSphere(pos, 5.0f), Color.YellowGreen, 0, false); + var tangentSize = NodeSizeByDistance(Transform.Translation, TangentNodeSize); + DebugDraw.DrawSphere(new BoundingSphere(pos, tangentSize), Color.YellowGreen, 0, false); } public override void OnContextMenu(ContextMenu contextMenu) @@ -279,6 +286,9 @@ namespace FlaxEditor.SceneGraph.Actors } } + private const Real PointNodeSize = 1.5f; + private const Real TangentNodeSize = 1.0f; + /// public SplineNode(Actor actor) : base(actor) @@ -398,6 +408,43 @@ namespace FlaxEditor.SceneGraph.Actors } } + internal static Real NodeSizeByDistance(Vector3 nodePosition, Real nodeSize) + { + var cameraTransform = Editor.Instance.Windows.EditWin.Viewport.ViewportCamera.Viewport.ViewTransform; + var distance = Vector3.Distance(cameraTransform.Translation, nodePosition) / 100; + return distance * nodeSize; + } + + public override void OnDebugDraw(ViewportDebugDrawData data) + { + DrawSpline((Spline)Actor, Color.White, Actor.Transform, true); + } + + private void DrawSpline(Spline spline, Color color, Transform transform, bool depthTest) + { + var count = spline.SplineKeyframes.Length; + if (count == 0) + return; + var keyframes = spline.SplineKeyframes; + var pointIndex = 0; + var prev = spline.GetSplineKeyframe(0); + var prevPos = transform.LocalToWorld(prev.Value.Translation); + var pointSize = NodeSizeByDistance(spline.GetSplinePoint(0), PointNodeSize); + DebugDraw.DrawWireSphere(new BoundingSphere(prevPos, pointSize), color, 0.0f, depthTest); + for (int i = 0; i < count; i++) + { + var next = keyframes[pointIndex]; + var nextPos = transform.LocalToWorld(next.Value.Translation); + var d = (next.Time - prev.Time) / 3.0f; + pointSize = NodeSizeByDistance(spline.GetSplinePoint(i), PointNodeSize); + DebugDraw.DrawWireSphere(new BoundingSphere(nextPos, pointSize), color, 0.0f, depthTest); + DebugDraw.DrawBezier(prevPos, prevPos + prev.TangentOut.Translation * d, nextPos + next.TangentIn.Translation * d, nextPos, color, 0.0f, depthTest); + prev = next; + prevPos = nextPos; + pointIndex++; + } + } + /// public override bool RayCastSelf(ref RayCastData ray, out Real distance, out Vector3 normal) {