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)
{