Merge remote-tracking branch 'origin/master' into 1.7

# Conflicts:
#	Source/Engine/Physics/PhysX/SimulationEventCallbackPhysX.cpp
This commit is contained in:
Wojtek Figat
2023-09-13 10:29:28 +02:00
22 changed files with 1328 additions and 136 deletions

BIN
Content/Editor/IconsAtlas.flax (Stored with Git LFS)

Binary file not shown.

View File

@@ -1,9 +1,12 @@
// Copyright (c) 2012-2023 Wojciech Figat. All rights reserved.
using FlaxEditor.Actions;
using FlaxEditor.SceneGraph.Actors;
using System;
using System.Collections.Generic;
using FlaxEngine;
using FlaxEngine.GUI;
using FlaxEditor.Actions;
using FlaxEditor.SceneGraph;
using FlaxEditor.SceneGraph.Actors;
using FlaxEditor.GUI.Tabs;
namespace FlaxEditor.CustomEditors.Dedicated
{
@@ -14,54 +17,853 @@ namespace FlaxEditor.CustomEditors.Dedicated
[CustomEditor(typeof(Spline)), DefaultEditor]
public class SplineEditor : ActorEditor
{
/// <summary>
/// Storage undo spline data
/// </summary>
private struct UndoData
{
public Spline Spline;
public BezierCurve<Transform>.Keyframe[] BeforeKeyframes;
}
/// <summary>
/// Basis for creating tangent manipulation types for bezier curves.
/// </summary>
private class EditTangentOptionBase
{
/// <summary>
/// Called when user set selected tangent mode.
/// </summary>
/// <param name="spline">Current spline selected on editor viewport.</param>
/// <param name="index">Index of current keyframe selected on spline.</param>
public virtual void OnSetMode(Spline spline, int index)
{
}
/// <summary>
/// Called when user select a keyframe (spline point) of current selected spline on editor viewport.
/// </summary>
/// <param name="spline">Current spline selected on editor viewport.</param>
/// <param name="index">Index of current keyframe selected on spline.</param>
public virtual void OnSelectKeyframe(Spline spline, int index)
{
}
/// <summary>
/// Called when user select a tangent of current keyframe selected from spline.
/// </summary>
/// <param name="spline">Current spline selected on editor viewport.</param>
/// <param name="index">Index of current keyframe selected on spline.</param>
public virtual void OnSelectTangent(Spline spline, int index)
{
}
/// <summary>
/// Called when the tangent in from current keyframe selected from spline is moved on editor viewport.
/// </summary>
/// <param name="spline">Current spline selected on editor viewport.</param>
/// <param name="index">Index of current keyframe selected on spline.</param>
public virtual void OnMoveTangentIn(Spline spline, int index)
{
}
/// <summary>
/// Called when the tangent out from current keyframe selected from spline is moved on editor viewport.
/// </summary>
/// <param name="spline">Current spline selected on editor viewport.</param>
/// <param name="index">Current spline selected on editor viewport.</param>
public virtual void OnMoveTangentOut(Spline spline, int index)
{
}
}
/// <summary>
/// Edit curve options manipulate the curve as free mode
/// </summary>
private sealed class FreeTangentMode : EditTangentOptionBase
{
/// <inheritdoc/>
public override void OnSetMode(Spline spline, int index)
{
if (IsLinearTangentMode(spline, index) || IsSmoothInTangentMode(spline, index) || IsSmoothOutTangentMode(spline, index))
{
SetPointSmooth(spline, index);
}
}
}
/// <summary>
/// Edit curve options to set tangents to linear
/// </summary>
private sealed class LinearTangentMode : EditTangentOptionBase
{
/// <inheritdoc/>
public override void OnSetMode(Spline spline, int index)
{
SetKeyframeLinear(spline, index);
// change the selection to tangent parent (a spline point / keyframe)
SetSelectSplinePointNode(spline, index);
}
}
/// <summary>
/// Edit curve options to align tangents of selected spline
/// </summary>
private sealed class AlignedTangentMode : EditTangentOptionBase
{
/// <inheritdoc/>
public override void OnSetMode(Spline spline, int index)
{
SmoothIfNotAligned(spline, index);
}
/// <inheritdoc/>
public override void OnSelectKeyframe(Spline spline, int index)
{
SmoothIfNotAligned(spline, index);
}
/// <inheritdoc/>
public override void OnMoveTangentIn(Spline spline, int index)
{
SetPointAligned(spline, index, true);
}
/// <inheritdoc/>
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);
}
}
/// <summary>
/// Edit curve options manipulate the curve setting selected point
/// tangent in as smoothed but tangent out as linear
/// </summary>
private sealed class SmoothInTangentMode : EditTangentOptionBase
{
/// <inheritdoc/>
public override void OnSetMode(Spline spline, int index)
{
SetTangentSmoothIn(spline, index);
SetSelectTangentIn(spline, index);
}
}
/// <summary>
/// Edit curve options manipulate the curve setting selected point
/// tangent in as linear but tangent out as smoothed
/// </summary>
private sealed class SmoothOutTangentMode : EditTangentOptionBase
{
/// <inheritdoc/>
public override void OnSetMode(Spline spline, int index)
{
SetTangentSmoothOut(spline, index);
SetSelectTangentOut(spline, index);
}
}
private sealed class IconTab : Tab
{
private sealed class IconTabHeader : Tabs.TabHeader
{
public IconTabHeader(Tabs tabs, Tab tab)
: base(tabs, tab)
{
}
public override bool OnMouseUp(Float2 location, MouseButton button)
{
if (EnabledInHierarchy && Tab.Enabled)
((IconTab)Tab)._action();
return true;
}
public override void Draw()
{
base.Draw();
var tab = (IconTab)Tab;
var enabled = EnabledInHierarchy && tab.EnabledInHierarchy;
var style = FlaxEngine.GUI.Style.Current;
var size = Size;
var textHeight = 16.0f;
var iconSize = size.Y - textHeight;
var iconRect = new Rectangle((Width - iconSize) / 2, 0, iconSize, iconSize);
if (tab._mirrorIcon)
{
iconRect.Location.X += iconRect.Size.X;
iconRect.Size.X *= -1;
}
var color = style.Foreground;
if (!enabled)
color *= 0.6f;
Render2D.DrawSprite(tab._customIcon, iconRect, color);
Render2D.DrawText(style.FontMedium, tab._customText, new Rectangle(0, iconSize, size.X, textHeight), color, TextAlignment.Center, TextAlignment.Center);
}
}
private readonly Action _action;
private readonly string _customText;
private readonly SpriteHandle _customIcon;
private readonly bool _mirrorIcon;
public IconTab(Action action, string text, SpriteHandle icon, bool mirrorIcon = false)
: base(string.Empty, SpriteHandle.Invalid)
{
_action = action;
_customText = text;
_customIcon = icon;
_mirrorIcon = mirrorIcon;
}
public override Tabs.TabHeader CreateHeader()
{
return new IconTabHeader((Tabs)Parent, this);
}
}
private EditTangentOptionBase _currentTangentMode;
private Tabs _selectedPointsTabs, _allPointsTabs;
private Tab _freeTangentTab;
private Tab _linearTangentTab;
private Tab _alignedTangentTab;
private Tab _smoothInTangentTab;
private Tab _smoothOutTangentTab;
private Tab _setLinearAllTangentsTab;
private Tab _setSmoothAllTangentsTab;
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;
/// <inheritdoc />
public override void Initialize(LayoutElementsContainer layout)
{
base.Initialize(layout);
if (Values.HasDifferentTypes == false)
{
_currentTangentMode = new FreeTangentMode();
if (Values.HasDifferentTypes || !(Values[0] is Spline spline))
return;
_selectedSpline = spline;
layout.Space(10);
var grid = layout.CustomContainer<UniformGridPanel>();
grid.CustomControl.SlotsHorizontally = 2;
grid.CustomControl.SlotsVertically = 1;
grid.Button("Set Linear Tangents").Button.Clicked += OnSetTangentsLinear;
grid.Button("Set Smooth Tangents").Button.Clicked += OnSetTangentsSmooth;
var tabSize = 46;
var icons = Editor.Instance.Icons;
layout.Header("Selected spline point");
_selectedPointsTabs = new Tabs
{
Height = tabSize,
TabsSize = new Float2(tabSize),
AutoTabsSize = true,
Parent = layout.ContainerControl,
};
_linearTangentTab = _selectedPointsTabs.AddTab(new IconTab(OnSetSelectedLinear, "Linear", icons.SplineLinear64));
_freeTangentTab = _selectedPointsTabs.AddTab(new IconTab(OnSetSelectedFree, "Free", icons.SplineFree64));
_alignedTangentTab = _selectedPointsTabs.AddTab(new IconTab(OnSetSelectedAligned, "Aligned", icons.SplineAligned64));
_smoothInTangentTab = _selectedPointsTabs.AddTab(new IconTab(OnSetSelectedSmoothIn, "Smooth In", icons.SplineSmoothIn64));
_smoothOutTangentTab = _selectedPointsTabs.AddTab(new IconTab(OnSetSelectedSmoothOut, "Smooth Out", icons.SplineSmoothIn64, true));
_selectedPointsTabs.SelectedTabIndex = -1;
layout.Header("All spline points");
_allPointsTabs = new Tabs
{
Height = tabSize,
TabsSize = new Float2(tabSize),
AutoTabsSize = true,
Parent = layout.ContainerControl,
};
_setLinearAllTangentsTab = _allPointsTabs.AddTab(new IconTab(OnSetTangentsLinear, "Set Linear Tangents", icons.SplineLinear64));
_setSmoothAllTangentsTab = _allPointsTabs.AddTab(new IconTab(OnSetTangentsSmooth, "Set Smooth Tangents", icons.SplineAligned64));
_allPointsTabs.SelectedTabIndex = -1;
if (_selectedSpline)
_selectedSpline.SplineUpdated += OnSplineEdited;
SetSelectedTangentTypeAsCurrent();
UpdateEditTabsSelection();
UpdateButtonsEnabled();
}
/// <inheritdoc/>
protected override void Deinitialize()
{
if (_selectedSpline)
_selectedSpline.SplineUpdated -= OnSplineEdited;
}
private void OnSplineEdited()
{
UpdateEditTabsSelection();
UpdateButtonsEnabled();
}
/// <inheritdoc/>
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 UpdateEditTabsSelection()
{
_selectedPointsTabs.Enabled = CanEditTangent();
if (!_selectedPointsTabs.Enabled)
{
_selectedPointsTabs.SelectedTabIndex = -1;
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;
if (isFree)
_selectedPointsTabs.SelectedTab = _freeTangentTab;
else if (isLinear)
_selectedPointsTabs.SelectedTab = _linearTangentTab;
else if (isAligned)
_selectedPointsTabs.SelectedTab = _alignedTangentTab;
else if (isSmoothIn)
_selectedPointsTabs.SelectedTab = _smoothInTangentTab;
else if (isSmoothOut)
_selectedPointsTabs.SelectedTab = _smoothOutTangentTab;
else
_selectedPointsTabs.SelectedTabIndex = -1;
}
private void UpdateButtonsEnabled()
{
_linearTangentTab.Enabled = CanEditTangent();
_freeTangentTab.Enabled = CanEditTangent();
_alignedTangentTab.Enabled = CanEditTangent();
_smoothInTangentTab.Enabled = CanSetTangentSmoothIn();
_smoothOutTangentTab.Enabled = CanSetTangentSmoothOut();
_setLinearAllTangentsTab.Enabled = CanSetAllTangentsLinear();
_setSmoothAllTangentsTab.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);
}
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)
{
_selectedPoint = selectedPoint;
_lastPointSelected = _selectedPoint;
_currentTangentMode.OnSelectKeyframe(_selectedSpline, _lastPointSelected.Index);
}
else
{
_selectedPoint = null;
}
}
else
{
_selectedPoint = null;
}
SetSelectedTangentTypeAsCurrent();
UpdateEditTabsSelection();
UpdateButtonsEnabled();
}
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 selectedPoint)
{
_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 = selectedPoint;
_selectedTangentOut = null;
_currentTangentMode.OnSelectTangent(_selectedSpline, index);
return;
}
if (currentSelected.Transform == _selectedSpline.GetSplineTangent(index, false))
{
_selectedTangentOut = selectedPoint;
_selectedTangentIn = null;
_currentTangentMode.OnSelectTangent(_selectedSpline, index);
return;
}
_selectedTangentIn = null;
_selectedTangentOut = null;
}
private void StartEditSpline()
{
if (Presenter.Undo != null && Presenter.Undo.Enabled)
{
// Capture 'before' state for undo
var splines = new List<UndoData>();
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<Transform>.Keyframe[]
});
}
}
_selectedSplinesUndoData = splines.ToArray();
}
}
private void EndEditSpline()
{
// Update buttons state
UpdateEditTabsSelection();
if (Presenter.Undo != null && Presenter.Undo.Enabled)
{
// Add undo
foreach (var splineUndoData in _selectedSplinesUndoData)
{
Presenter.Undo.AddAction(new EditSplineAction(_selectedSpline, splineUndoData.BeforeKeyframes));
SplineNode.OnSplineEdited(splineUndoData.Spline);
Editor.Instance.Scene.MarkSceneEdited(splineUndoData.Spline.Scene);
}
}
}
private void OnSetSelectedLinear()
{
StartEditSpline();
SetModeLinear();
EndEditSpline();
}
private void OnSetSelectedFree()
{
StartEditSpline();
SetModeFree();
EndEditSpline();
}
private void OnSetSelectedAligned()
{
StartEditSpline();
SetModeAligned();
EndEditSpline();
}
private void OnSetSelectedSmoothIn()
{
StartEditSpline();
SetModeSmoothIn();
EndEditSpline();
}
private void OnSetSelectedSmoothOut()
{
StartEditSpline();
SetModeSmoothOut();
EndEditSpline();
}
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<Transform>.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);
}
}
StartEditSpline();
_selectedSpline.SetTangentsLinear();
_selectedSpline.UpdateSpline();
EndEditSpline();
}
private void OnSetTangentsSmooth()
{
var enableUndo = Presenter.Undo != null && Presenter.Undo.Enabled;
for (int i = 0; i < Values.Count; i++)
StartEditSpline();
_selectedSpline.SetTangentsSmooth();
_selectedSpline.UpdateSpline();
EndEditSpline();
}
private static bool IsFreeTangentMode(Spline spline, int index)
{
if (Values[i] is Spline spline)
if (IsLinearTangentMode(spline, index) ||
IsAlignedTangentMode(spline, index) ||
IsSmoothInTangentMode(spline, index) ||
IsSmoothOutTangentMode(spline, index))
{
var before = enableUndo ? (BezierCurve<Transform>.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 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)
{
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));
}
}
}

View File

@@ -0,0 +1,122 @@
// Copyright (c) 2012-2023 Wojciech Figat. All rights reserved.
using System.Collections.Generic;
using FlaxEngine;
using FlaxEngine.GUI;
namespace FlaxEditor.CustomEditors.Editors
{
/// <summary>
/// Default implementation of the inspector used to edit input event properties.
/// </summary>
[CustomEditor(typeof(InputEvent)), DefaultEditor]
public class InputEventEditor : CustomEditor
{
private Dropdown _dropdown;
/// <inheritdoc />
public override DisplayStyle Style => DisplayStyle.Inline;
/// <inheritdoc />
public override void Initialize(LayoutElementsContainer layout)
{
var dropdownElement = layout.Custom<Dropdown>();
_dropdown = dropdownElement.CustomControl;
var names = new List<LocalizedString>();
foreach (var mapping in Input.ActionMappings)
{
if (!names.Contains(mapping.Name))
names.Add(mapping.Name);
}
_dropdown.Items = names;
if (Values[0] is InputEvent inputEvent && names.Contains(inputEvent.Name))
_dropdown.SelectedItem = inputEvent.Name;
_dropdown.SelectedIndexChanged += OnSelectedIndexChanged;
}
private void OnSelectedIndexChanged(Dropdown dropdown)
{
SetValue(new InputEvent(dropdown.SelectedItem));
}
/// <inheritdoc />
public override void Refresh()
{
base.Refresh();
if (HasDifferentValues)
{
}
else
{
if (Values[0] is InputEvent inputEvent && _dropdown.Items.Contains(inputEvent.Name))
_dropdown.SelectedItem = inputEvent.Name;
}
}
/// <inheritdoc />
protected override void Deinitialize()
{
if (_dropdown != null)
_dropdown.SelectedIndexChanged -= OnSelectedIndexChanged;
_dropdown = null;
}
}
/// <summary>
/// Default implementation of the inspector used to edit input axis properties.
/// </summary>
[CustomEditor(typeof(InputAxis)), DefaultEditor]
public class InputAxisEditor : CustomEditor
{
private Dropdown _dropdown;
/// <inheritdoc />
public override DisplayStyle Style => DisplayStyle.Inline;
/// <inheritdoc />
public override void Initialize(LayoutElementsContainer layout)
{
var dropdownElement = layout.Custom<Dropdown>();
_dropdown = dropdownElement.CustomControl;
var names = new List<LocalizedString>();
foreach (var mapping in Input.AxisMappings)
{
if (!names.Contains(mapping.Name))
names.Add(mapping.Name);
}
_dropdown.Items = names;
if (Values[0] is InputAxis inputAxis && names.Contains(inputAxis.Name))
_dropdown.SelectedItem = inputAxis.Name;
_dropdown.SelectedIndexChanged += OnSelectedIndexChanged;
}
private void OnSelectedIndexChanged(Dropdown dropdown)
{
SetValue(new InputAxis(dropdown.SelectedItem));
}
/// <inheritdoc />
public override void Refresh()
{
base.Refresh();
if (HasDifferentValues)
{
}
else
{
if (Values[0] is InputAxis inputAxis && _dropdown.Items.Contains(inputAxis.Name))
_dropdown.SelectedItem = inputAxis.Name;
}
}
/// <inheritdoc />
protected override void Deinitialize()
{
if (_dropdown != null)
_dropdown.SelectedIndexChanged -= OnSelectedIndexChanged;
_dropdown = null;
}
}
}

View File

@@ -97,6 +97,10 @@ namespace FlaxEditor
public SpriteHandle Build64;
public SpriteHandle Add64;
public SpriteHandle ShipIt64;
public SpriteHandle SplineFree64;
public SpriteHandle SplineLinear64;
public SpriteHandle SplineAligned64;
public SpriteHandle SplineSmoothIn64;
// 96px
public SpriteHandle Toolbox96;

View File

@@ -77,5 +77,14 @@ namespace FlaxEditor.GUI.Tabs
{
Deselected?.Invoke(this);
}
/// <summary>
/// Factory method for tabs header control (UI for the tabs strip to represent this tab).
/// </summary>
/// <returns>The tab header control.</returns>
public virtual Tabs.TabHeader CreateHeader()
{
return new Tabs.TabHeader((Tabs)Parent, this);
}
}
}

View File

@@ -24,11 +24,6 @@ namespace FlaxEditor.GUI.Tabs
/// </summary>
protected Tabs Tabs;
/// <summary>
/// The index of the tab.
/// </summary>
protected int Index;
/// <summary>
/// The tab.
/// </summary>
@@ -38,21 +33,22 @@ namespace FlaxEditor.GUI.Tabs
/// Initializes a new instance of the <see cref="TabHeader"/> class.
/// </summary>
/// <param name="tabs">The tabs.</param>
/// <param name="index">The tab index.</param>
/// <param name="tab">The tab.</param>
public TabHeader(Tabs tabs, int index, Tab tab)
public TabHeader(Tabs tabs, Tab tab)
: base(Float2.Zero, tabs._tabsSize)
{
Tabs = tabs;
Index = index;
Tab = tab;
}
/// <inheritdoc />
public override bool OnMouseUp(Float2 location, MouseButton button)
{
Tabs.SelectedTabIndex = Index;
if (EnabledInHierarchy && Tab.Enabled)
{
Tabs.SelectedTab = Tab;
Tabs.Focus();
}
return true;
}
@@ -61,19 +57,20 @@ namespace FlaxEditor.GUI.Tabs
{
base.Draw();
// Cache data
var style = Style.Current;
var enabled = EnabledInHierarchy && Tab.EnabledInHierarchy;
var tabRect = new Rectangle(Float2.Zero, Size);
bool isTabSelected = Tabs._selectedIndex == Index;
bool isMouseOverTab = IsMouseOver;
var textOffset = Tabs._orientation == Orientation.Horizontal ? 0 : 8;
// Draw bar
if (isTabSelected)
if (Tabs.SelectedTab == Tab)
{
var color = style.BackgroundSelected;
if (!enabled)
color *= 0.6f;
if (Tabs._orientation == Orientation.Horizontal)
{
Render2D.FillRectangle(tabRect, style.BackgroundSelected);
Render2D.FillRectangle(tabRect, color);
}
else
{
@@ -84,10 +81,10 @@ namespace FlaxEditor.GUI.Tabs
fillRect.Size.X -= lefEdgeWidth;
fillRect.Location.X += lefEdgeWidth;
Render2D.FillRectangle(fillRect, style.Background);
Render2D.FillRectangle(leftEdgeRect, style.BackgroundSelected);
Render2D.FillRectangle(leftEdgeRect, color);
}
}
else if (isMouseOverTab)
else if (IsMouseOver && enabled)
{
Render2D.FillRectangle(tabRect, style.BackgroundHighlighted);
}
@@ -131,20 +128,15 @@ namespace FlaxEditor.GUI.Tabs
{
base.PerformLayoutBeforeChildren();
// Cache data
var tabsSize = Tabs._tabsSize;
var clientSize = GetClientArea();
tabsSize = Float2.Min(tabsSize, clientSize.Size);
var tabRect = new Rectangle(Float2.Zero, tabsSize);
var tabStripOffset = Tabs._orientation == Orientation.Horizontal ? new Float2(tabsSize.X, 0) : new Float2(0, tabsSize.Y);
// Arrange tab header controls
var pos = Float2.Zero;
var sizeMask = Tabs._orientation == Orientation.Horizontal ? Float2.UnitX : Float2.UnitY;
for (int i = 0; i < Children.Count; i++)
{
if (Children[i] is TabHeader tabHeader)
{
tabHeader.Bounds = tabRect;
tabRect.Offset(tabStripOffset);
tabHeader.Location = pos;
pos += tabHeader.Size * sizeMask;
}
}
}
@@ -160,6 +152,11 @@ namespace FlaxEditor.GUI.Tabs
/// </summary>
protected Float2 _tabsSize;
/// <summary>
/// Automatic tab size based on the fill axis.
/// </summary>
protected bool _autoTabsSizeAuto;
/// <summary>
/// The orientation.
/// </summary>
@@ -174,11 +171,27 @@ namespace FlaxEditor.GUI.Tabs
set
{
_tabsSize = value;
if (!_autoTabsSizeAuto)
{
for (int i = 0; i < TabsPanel.ChildrenCount; i++)
{
if (TabsPanel.Children[i] is TabHeader tabHeader)
tabHeader.Size = value;
}
}
PerformLayout();
}
}
/// <summary>
/// Enables automatic tabs size to fill the space.
/// </summary>
public bool AutoTabsSize
{
get => _autoTabsSizeAuto;
set
{
_autoTabsSizeAuto = value;
PerformLayout();
}
}
@@ -339,14 +352,10 @@ namespace FlaxEditor.GUI.Tabs
// Update tabs headers
TabsPanel.DisposeChildren();
int index = 0;
for (int i = 0; i < Children.Count; i++)
{
if (Children[i] is Tab tab)
{
var tabHeader = new TabHeader(this, index++, tab);
TabsPanel.AddChild(tabHeader);
}
TabsPanel.AddChild(tab.CreateHeader());
}
TabsPanel.IsLayoutLocked = wasLocked;
@@ -371,23 +380,46 @@ namespace FlaxEditor.GUI.Tabs
/// <inheritdoc />
protected override void PerformLayoutBeforeChildren()
{
var tabsSize = _tabsSize;
if (_autoTabsSizeAuto)
{
// Horizontal is default for Toolbox so tabs go to the right
int tabsCount = 0;
for (int i = 0; i < TabsPanel.ChildrenCount; i++)
{
if (TabsPanel.Children[i] is TabHeader)
tabsCount++;
}
if (tabsCount == 0)
tabsCount = 1;
if (_orientation == Orientation.Horizontal)
tabsSize.X = Width / tabsCount;
else
tabsSize.Y = Height / tabsCount;
for (int i = 0; i < TabsPanel.ChildrenCount; i++)
{
if (TabsPanel.Children[i] is TabHeader tabHeader)
tabHeader.Size = tabsSize;
}
}
// Fit the tabs panel
TabsPanel.Size = _orientation == Orientation.Horizontal ? new Float2(Width, _tabsSize.Y) : new Float2(_tabsSize.X, Height);
TabsPanel.Size = _orientation == Orientation.Horizontal
? new Float2(Width, tabsSize.Y)
: new Float2(tabsSize.X, Height);
// Hide all pages except selected one
var clientArea = _orientation == Orientation.Horizontal
? new Rectangle(0, _tabsSize.Y, Width, Height - _tabsSize.Y)
: new Rectangle(_tabsSize.X, 0, Width - _tabsSize.X, Height);
for (int i = 0; i < Children.Count; i++)
{
if (Children[i] is Tab tab)
{
// Check if is selected or not
if (i - 1 == _selectedIndex)
{
// Show and fit size
tab.Visible = true;
tab.Bounds = clientArea;
tab.Bounds = _orientation == Orientation.Horizontal
? new Rectangle(0, tabsSize.Y, Width, Height - tabsSize.Y)
: new Rectangle(tabsSize.X, 0, Width - tabsSize.X, Height);
}
else
{

View File

@@ -21,7 +21,7 @@ namespace FlaxEditor.SceneGraph.Actors
[HideInEditor]
public sealed class SplineNode : ActorNode
{
private sealed class SplinePointNode : ActorChildNode<SplineNode>
internal sealed class SplinePointNode : ActorChildNode<SplineNode>
{
public unsafe SplinePointNode(SplineNode node, Guid id, int index)
: base(node, id, index)
@@ -167,10 +167,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 +180,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 +223,7 @@ namespace FlaxEditor.SceneGraph.Actors
}
}
private sealed class SplinePointTangentNode : ActorChildNode
internal sealed class SplinePointTangentNode : ActorChildNode
{
private SplineNode _node;
private int _index;
@@ -249,10 +253,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 +268,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 +285,9 @@ namespace FlaxEditor.SceneGraph.Actors
}
}
private const Real PointNodeSize = 1.5f;
private const Real TangentNodeSize = 1.0f;
/// <inheritdoc />
public SplineNode(Actor actor)
: base(actor)
@@ -398,6 +407,13 @@ 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;
}
/// <inheritdoc />
public override bool RayCastSelf(ref RayCastData ray, out Real distance, out Vector3 normal)
{

View File

@@ -79,6 +79,7 @@ namespace FlaxEditor.Windows
Editor.ContentDatabase.Engine.Visible = value;
Editor.ContentDatabase.Engine.Folder.Visible = value;
RefreshView();
_tree.PerformLayout();
}
}
}
@@ -99,6 +100,7 @@ namespace FlaxEditor.Windows
project.Visible = value;
project.Folder.Visible = value;
RefreshView();
_tree.PerformLayout();
}
}
}

View File

@@ -456,6 +456,19 @@ void Actor::SetLayerName(const StringView& value)
LOG(Warning, "Unknown layer name '{0}'", value);
}
void Actor::SetLayerNameRecursive(const StringView& value)
{
for (int32 i = 0; i < 32; i++)
{
if (Level::Layers[i] == value)
{
SetLayerRecursive(i);
return;
}
}
LOG(Warning, "Unknown layer name '{0}'", value);
}
bool Actor::HasTag() const
{
return Tags.Count() != 0;
@@ -476,6 +489,13 @@ void Actor::AddTag(const Tag& tag)
Tags.AddUnique(tag);
}
void Actor::AddTagRecursive(const Tag& tag)
{
for (const auto& child : Children)
child->AddTagRecursive(tag);
Tags.AddUnique(tag);
}
void Actor::RemoveTag(const Tag& tag)
{
Tags.Remove(tag);
@@ -505,6 +525,17 @@ void Actor::SetLayer(int32 layerIndex)
OnLayerChanged();
}
void Actor::SetLayerRecursive(int32 layerIndex)
{
layerIndex = Math::Clamp(layerIndex, 0, 31);
for (const auto& child : Children)
child->SetLayerRecursive(layerIndex);
if (layerIndex == _layer)
return;
_layer = layerIndex;
OnLayerChanged();
}
void Actor::SetName(const StringView& value)
{
if (_name == value)

View File

@@ -102,6 +102,12 @@ public:
/// <param name="layerIndex">The index of the layer.</param>
API_PROPERTY() void SetLayer(int32 layerIndex);
/// <summary>
/// Sets the layer recursively for all underlying children.
/// </summary>
/// <param name="layerIndex">The index of the layer.</param>
API_FUNCTION() void SetLayerRecursive(int32 layerIndex);
/// <summary>
/// Gets the name of the layer.
/// </summary>
@@ -113,6 +119,11 @@ public:
/// </summary>
API_PROPERTY() void SetLayerName(const StringView& value);
/// <summary>
/// Sets the name of the layer recursively for actor and for all underlying child actors.
/// </summary>
API_FUNCTION() void SetLayerNameRecursive(const StringView& value);
/// <summary>
/// Determines whether this actor has any tag assigned.
/// </summary>
@@ -136,6 +147,12 @@ public:
/// <param name="tag">The tag to add.</param>
API_FUNCTION() void AddTag(const Tag& tag);
/// <summary>
/// Adds a tag to the actor and for all underlying child actors.
/// </summary>
/// <param name="tag">The tag to add.</param>
API_FUNCTION() void AddTagRecursive(const Tag& tag);
/// <summary>
/// Removes a tag to the actor
/// </summary>

View File

@@ -484,8 +484,7 @@ namespace
void Spline::OnDebugDraw()
{
const Color color = GetSplineColor();
DrawSpline(this, color.AlphaMultiplied(0.7f), _transform, true);
DrawSpline(this, GetSplineColor().AlphaMultiplied(0.7f), _transform, true);
// Base
Actor::OnDebugDraw();
@@ -493,8 +492,7 @@ void Spline::OnDebugDraw()
void Spline::OnDebugDrawSelected()
{
const Color color = GetSplineColor();
DrawSpline(this, color.AlphaMultiplied(0.3f), _transform, false);
DrawSpline(this, Color::White, _transform, false);
// Base
Actor::OnDebugDrawSelected();

View File

@@ -354,6 +354,7 @@ public:
protected:
#if USE_EDITOR
// Spline color getter for debug drawing, can be overriden by custom spline types.
virtual Color GetSplineColor()
{
return Color::White;

View File

@@ -223,9 +223,10 @@ void WheeledVehicle::Setup()
return;
// Release previous
void* scene = GetPhysicsScene()->GetPhysicsScene();
if (_vehicle)
{
PhysicsBackend::RemoveVehicle(GetPhysicsScene()->GetPhysicsScene(), this);
PhysicsBackend::RemoveVehicle(scene, this);
PhysicsBackend::DestroyVehicle(_vehicle, (int32)_driveTypeCurrent);
_vehicle = nullptr;
}
@@ -236,7 +237,7 @@ void WheeledVehicle::Setup()
if (!_vehicle)
return;
_driveTypeCurrent = _driveType;
PhysicsBackend::AddVehicle(GetPhysicsScene()->GetPhysicsScene(), this);
PhysicsBackend::AddVehicle(scene, this);
PhysicsBackend::SetRigidDynamicActorSolverIterationCounts(_actor, 12, 4);
#else
LOG(Fatal, "Vehicles are not supported.");
@@ -358,9 +359,24 @@ void WheeledVehicle::OnColliderChanged(Collider* c)
{
RigidBody::OnColliderChanged(c);
if (_useWheelsUpdates)
{
// Rebuild vehicle when someone adds/removed wheels
Setup();
}
}
void WheeledVehicle::OnActiveInTreeChanged()
{
// Skip rebuilds from per-wheel OnColliderChanged when whole vehicle is toggled
_useWheelsUpdates = false;
RigidBody::OnActiveInTreeChanged();
_useWheelsUpdates = true;
// Perform whole rebuild when it gets activated
if (IsActiveInHierarchy())
Setup();
}
void WheeledVehicle::OnPhysicsSceneChanged(PhysicsScene* previous)
{

View File

@@ -329,6 +329,7 @@ private:
DifferentialSettings _differential;
GearboxSettings _gearbox;
bool _fixInvalidForwardDir = false; // [Deprecated on 13.06.2023, expires on 13.06.2025]
bool _useWheelsUpdates = true;
public:
/// <summary>
@@ -484,6 +485,7 @@ public:
void Serialize(SerializeStream& stream, const void* otherObj) override;
void Deserialize(DeserializeStream& stream, ISerializeModifier* modifier) override;
void OnColliderChanged(Collider* c) override;
void OnActiveInTreeChanged() override;
protected:
void OnPhysicsSceneChanged(PhysicsScene* previous) override;

View File

@@ -1231,18 +1231,42 @@ void* PhysicsBackend::CreateScene(const PhysicsSettings& settings)
PxSceneDesc sceneDesc(ToleranceScale);
sceneDesc.gravity = C2P(settings.DefaultGravity);
sceneDesc.flags |= PxSceneFlag::eENABLE_ACTIVE_ACTORS;
sceneDesc.flags |= PxSceneFlag::eENABLE_PCM;
if (!settings.DisableCCD)
sceneDesc.flags |= PxSceneFlag::eENABLE_CCD;
sceneDesc.simulationEventCallback = &scenePhysX->EventsCallback;
sceneDesc.filterShader = FilterShader;
sceneDesc.bounceThresholdVelocity = settings.BounceThresholdVelocity;
switch (settings.SolverType)
{
case PhysicsSolverType::ProjectedGaussSeidelIterativeSolver:
sceneDesc.solverType = PxSolverType::ePGS;
break;
case PhysicsSolverType::TemporalGaussSeidelSolver:
sceneDesc.solverType = PxSolverType::eTGS;
break;
}
if (sceneDesc.cpuDispatcher == nullptr)
{
scenePhysX->CpuDispatcher = PxDefaultCpuDispatcherCreate(Math::Clamp<uint32>(Platform::GetCPUInfo().ProcessorCoreCount - 1, 1, 4));
CHECK_INIT(scenePhysX->CpuDispatcher, "PxDefaultCpuDispatcherCreate failed!");
sceneDesc.cpuDispatcher = scenePhysX->CpuDispatcher;
}
switch (settings.BroadPhaseType)
{
case PhysicsBroadPhaseType::SweepAndPrune:
sceneDesc.broadPhaseType = PxBroadPhaseType::eSAP;
break;
case PhysicsBroadPhaseType::MultiBoxPruning:
sceneDesc.broadPhaseType = PxBroadPhaseType::eMBP;
break;
case PhysicsBroadPhaseType::AutomaticBoxPruning:
sceneDesc.broadPhaseType = PxBroadPhaseType::eABP;
break;
case PhysicsBroadPhaseType::ParallelAutomaticBoxPruning:
sceneDesc.broadPhaseType = PxBroadPhaseType::ePABP;
break;
}
// Create scene
scenePhysX->Scene = PhysX->createScene(sceneDesc);

View File

@@ -11,8 +11,10 @@
namespace
{
void ClearColliderFromCollection(PhysicsColliderActor* collider, Array<SimulationEventCallback::CollidersPair>& collection)
void ClearColliderFromCollection(const PhysicsColliderActor* collider, Array<SimulationEventCallback::CollidersPair>& collection)
{
if (collection.IsEmpty())
return;
for (int32 i = 0; i < collection.Count(); i++)
{
if (collection[i].First == collider || collection[i].Second == collider)

View File

@@ -57,6 +57,8 @@ void PhysicsSettings::Deserialize(DeserializeStream& stream, ISerializeModifier*
DESERIALIZE(FrictionCombineMode);
DESERIALIZE(RestitutionCombineMode);
DESERIALIZE(DisableCCD);
DESERIALIZE(BroadPhaseType);
DESERIALIZE(SolverType);
DESERIALIZE(MaxDeltaTime);
DESERIALIZE(EnableSubstepping);
DESERIALIZE(SubstepDeltaTime);

View File

@@ -6,6 +6,50 @@
#include "Engine/Core/Math/Vector3.h"
#include "Types.h"
/// <summary>
/// Broad phase algorithm used in the simulation.
/// <see href="https://nvidia-omniverse.github.io/PhysX/physx/5.1.0/_build/physx/latest/struct_px_broad_phase_type.html"/>
/// </summary>
API_ENUM() enum class PhysicsBroadPhaseType
{
/// <summary>
/// 3-axes sweep-and-prune. Good generic choice with great performance when many objects are sleeping.
/// </summary>
SweepAndPrune = 0,
/// <summary>
/// Alternative broad phase algorithm that does not suffer from the same performance issues as SAP when all objects are moving or when inserting large numbers of objects.
/// </summary>
MultiBoxPruning = 1,
/// <summary>
/// Revisited implementation of MBP, which automatically manages broad-phase regions.
/// </summary>
AutomaticBoxPruning = 2,
/// <summary>
/// Parallel implementation of ABP. It can often be the fastest (CPU) broadphase, but it can use more memory than ABP.
/// </summary>
ParallelAutomaticBoxPruning = 3,
};
/// <summary>
/// The type of solver used in the simulation.
/// <see href="https://nvidia-omniverse.github.io/PhysX/physx/5.1.0/_build/physx/latest/struct_px_solver_type.html"/>
/// </summary>
API_ENUM() enum class PhysicsSolverType
{
/// <summary>
/// The iterative sequential impulse solver.
/// </summary>
ProjectedGaussSeidelIterativeSolver = 0,
/// <summary>
/// Non linear iterative solver. This kind of solver can lead to improved convergence and handle large mass ratios, long chains and jointed systems better. It is slightly more expensive than the default solver and can introduce more energy to correct joint and contact errors.
/// </summary>
TemporalGaussSeidelSolver = 1,
};
/// <summary>
/// Physics simulation settings container.
/// </summary>
@@ -43,6 +87,18 @@ public:
API_FIELD(Attributes="EditorOrder(70), EditorDisplay(\"Simulation\")")
bool DisableCCD = false;
/// <summary>
/// Broad phase algorithm to use in the simulation.
/// </summary>
API_FIELD(Attributes="EditorOrder(71), EditorDisplay(\"Simulation\")")
PhysicsBroadPhaseType BroadPhaseType = PhysicsBroadPhaseType::ParallelAutomaticBoxPruning;
/// <summary>
/// The solver type to use in the simulation.
/// </summary>
API_FIELD(Attributes="EditorOrder(72), EditorDisplay(\"Simulation\")")
PhysicsSolverType SolverType = PhysicsSolverType::ProjectedGaussSeidelIterativeSolver;
/// <summary>
/// The maximum allowed delta time (in seconds) for the physics simulation step.
/// </summary>

View File

@@ -383,6 +383,8 @@ void ModelTool::Options::Serialize(SerializeStream& stream, const void* otherObj
SERIALIZE(BaseLOD);
SERIALIZE(LODCount);
SERIALIZE(TriangleReduction);
SERIALIZE(SloppyOptimization);
SERIALIZE(LODTargetError);
SERIALIZE(ImportMaterials);
SERIALIZE(ImportTextures);
SERIALIZE(RestoreMaterialsOnReimport);
@@ -424,6 +426,8 @@ void ModelTool::Options::Deserialize(DeserializeStream& stream, ISerializeModifi
DESERIALIZE(BaseLOD);
DESERIALIZE(LODCount);
DESERIALIZE(TriangleReduction);
DESERIALIZE(SloppyOptimization);
DESERIALIZE(LODTargetError);
DESERIALIZE(ImportMaterials);
DESERIALIZE(ImportTextures);
DESERIALIZE(RestoreMaterialsOnReimport);
@@ -1528,7 +1532,11 @@ bool ModelTool::ImportModel(const String& path, ModelData& meshData, Options& op
int32 dstMeshIndexCountTarget = int32(srcMeshIndexCount * triangleReduction) / 3 * 3;
Array<unsigned int> indices;
indices.Resize(dstMeshIndexCountTarget);
int32 dstMeshIndexCount = (int32)meshopt_simplifySloppy(indices.Get(), srcMesh->Indices.Get(), srcMeshIndexCount, (const float*)srcMesh->Positions.Get(), srcMeshVertexCount, sizeof(Float3), dstMeshIndexCountTarget);
int32 dstMeshIndexCount = {};
if (options.SloppyOptimization)
dstMeshIndexCount = (int32)meshopt_simplifySloppy(indices.Get(), srcMesh->Indices.Get(), srcMeshIndexCount, (const float*)srcMesh->Positions.Get(), srcMeshVertexCount, sizeof(Float3), dstMeshIndexCountTarget);
else
dstMeshIndexCount = (int32)meshopt_simplify(indices.Get(), srcMesh->Indices.Get(), srcMeshIndexCount, (const float*)srcMesh->Positions.Get(), srcMeshVertexCount, sizeof(Float3), dstMeshIndexCountTarget, options.LODTargetError);
indices.Resize(dstMeshIndexCount);
if (dstMeshIndexCount == 0)
continue;

View File

@@ -322,6 +322,12 @@ public:
// The target amount of triangles for the generated LOD (based on the higher LOD). Normalized to range 0-1. For instance 0.4 cuts the triangle count to 40%.
API_FIELD(Attributes="EditorOrder(1130), EditorDisplay(\"Level Of Detail\"), VisibleIf(nameof(ShowGeometry)), Limit(0, 1, 0.001f)")
float TriangleReduction = 0.5f;
// Whether to do a sloppy mesh optimization. This is faster but does not follow the topology of the original mesh.
API_FIELD(Attributes="EditorOrder(1140), EditorDisplay(\"Level Of Detail\"), VisibleIf(nameof(ShowGeometry))")
bool SloppyOptimization = true;
// Only used if Sloppy is false. Target error is an approximate measure of the deviation from the original mesh using distance normalized to [0..1] range (e.g. 1e-2f means that simplifier will try to maintain the error to be below 1% of the mesh extents).
API_FIELD(Attributes="EditorOrder(1150), EditorDisplay(\"Level Of Detail\"), VisibleIf(nameof(SloppyOptimization), true), Limit(0.01f, 1, 0.001f)")
float LODTargetError = 0.1f;
public: // Materials

View File

@@ -199,11 +199,11 @@ namespace FlaxEngine.GUI
// UI navigation
if (_canvas.ReceivesEvents)
{
UpdateNavigation(deltaTime, _canvas.NavigationInputActionUp, NavDirection.Up, ref _navigationHeldTimeUp, ref _navigationRateTimeUp);
UpdateNavigation(deltaTime, _canvas.NavigationInputActionDown, NavDirection.Down, ref _navigationHeldTimeDown, ref _navigationRateTimeDown);
UpdateNavigation(deltaTime, _canvas.NavigationInputActionLeft, NavDirection.Left, ref _navigationHeldTimeLeft, ref _navigationRateTimeLeft);
UpdateNavigation(deltaTime, _canvas.NavigationInputActionRight, NavDirection.Right, ref _navigationHeldTimeRight, ref _navigationRateTimeRight);
UpdateNavigation(deltaTime, _canvas.NavigationInputActionSubmit, ref _navigationHeldTimeSubmit, ref _navigationRateTimeSubmit, SubmitFocused);
UpdateNavigation(deltaTime, _canvas.NavigateUp.Name, NavDirection.Up, ref _navigationHeldTimeUp, ref _navigationRateTimeUp);
UpdateNavigation(deltaTime, _canvas.NavigateDown.Name, NavDirection.Down, ref _navigationHeldTimeDown, ref _navigationRateTimeDown);
UpdateNavigation(deltaTime, _canvas.NavigateLeft.Name, NavDirection.Left, ref _navigationHeldTimeLeft, ref _navigationRateTimeLeft);
UpdateNavigation(deltaTime, _canvas.NavigateRight.Name, NavDirection.Right, ref _navigationHeldTimeRight, ref _navigationRateTimeRight);
UpdateNavigation(deltaTime, _canvas.NavigateSubmit.Name, ref _navigationHeldTimeSubmit, ref _navigationRateTimeSubmit, SubmitFocused);
}
else
{

View File

@@ -263,39 +263,39 @@ namespace FlaxEngine
public float NavigationInputRepeatRate { get; set; } = 0.05f;
/// <summary>
/// The name of the input action for performing UI navigation Up (from Input Settings).
/// The input action for performing UI navigation Up (from Input Settings).
/// </summary>
[EditorOrder(510), EditorDisplay("Navigation", "Navigate Up")]
[Tooltip("The name of the input action for performing UI navigation Up (from Input Settings).")]
public string NavigationInputActionUp { get; set; } = "NavigateUp";
[Tooltip("The input action for performing UI navigation Up (from Input Settings).")]
public InputEvent NavigateUp { get; set; } = new InputEvent("NavigateUp");
/// <summary>
/// The name of the input action for performing UI navigation Down (from Input Settings).
/// The input action for performing UI navigation Down (from Input Settings).
/// </summary>
[EditorOrder(520), EditorDisplay("Navigation", "Navigate Down")]
[Tooltip("The name of the input action for performing UI navigation Down (from Input Settings).")]
public string NavigationInputActionDown { get; set; } = "NavigateDown";
[Tooltip("The input action for performing UI navigation Down (from Input Settings).")]
public InputEvent NavigateDown { get; set; } = new InputEvent("NavigateDown");
/// <summary>
/// The name of the input action for performing UI navigation Left (from Input Settings).
/// The input action for performing UI navigation Left (from Input Settings).
/// </summary>
[EditorOrder(530), EditorDisplay("Navigation", "Navigate Left")]
[Tooltip("The name of the input action for performing UI navigation Left (from Input Settings).")]
public string NavigationInputActionLeft { get; set; } = "NavigateLeft";
[Tooltip("The input action for performing UI navigation Left (from Input Settings).")]
public InputEvent NavigateLeft { get; set; } = new InputEvent("NavigateLeft");
/// <summary>
/// The name of the input action for performing UI navigation Right (from Input Settings).
/// The input action for performing UI navigation Right (from Input Settings).
/// </summary>
[EditorOrder(540), EditorDisplay("Navigation", "Navigate Right")]
[Tooltip("The name of the input action for performing UI navigation Right (from Input Settings).")]
public string NavigationInputActionRight { get; set; } = "NavigateRight";
[Tooltip("The input action for performing UI navigation Right (from Input Settings).")]
public InputEvent NavigateRight { get; set; } = new InputEvent("NavigateRight");
/// <summary>
/// The name of the input action for performing UI navigation Submit (from Input Settings).
/// The input action for performing UI navigation Submit (from Input Settings).
/// </summary>
[EditorOrder(540), EditorDisplay("Navigation", "Navigate Submit")]
[Tooltip("The name of the input action for performing UI navigation Submit (from Input Settings).")]
public string NavigationInputActionSubmit { get; set; } = "NavigateSubmit";
[EditorOrder(550), EditorDisplay("Navigation", "Navigate Submit")]
[Tooltip("The input action for performing UI navigation Submit (from Input Settings).")]
public InputEvent NavigateSubmit { get; set; } = new InputEvent("NavigateSubmit");
#endregion
@@ -620,14 +620,36 @@ namespace FlaxEngine
jsonWriter.WriteValue(NavigationInputRepeatDelay);
jsonWriter.WritePropertyName("NavigationInputRepeatRate");
jsonWriter.WriteValue(NavigationInputRepeatRate);
jsonWriter.WritePropertyName("NavigationInputActionUp");
jsonWriter.WriteValue(NavigationInputActionUp);
jsonWriter.WritePropertyName("NavigationInputActionDown");
jsonWriter.WriteValue(NavigationInputActionDown);
jsonWriter.WritePropertyName("NavigationInputActionLeft");
jsonWriter.WriteValue(NavigationInputActionLeft);
jsonWriter.WritePropertyName("NavigationInputActionRight");
jsonWriter.WriteValue(NavigationInputActionRight);
jsonWriter.WritePropertyName("NavigateUp");
jsonWriter.WriteStartObject();
jsonWriter.WritePropertyName("Name");
jsonWriter.WriteValue(NavigateUp.Name);
jsonWriter.WriteEndObject();
jsonWriter.WritePropertyName("NavigateDown");
jsonWriter.WriteStartObject();
jsonWriter.WritePropertyName("Name");
jsonWriter.WriteValue(NavigateDown.Name);
jsonWriter.WriteEndObject();
jsonWriter.WritePropertyName("NavigateLeft");
jsonWriter.WriteStartObject();
jsonWriter.WritePropertyName("Name");
jsonWriter.WriteValue(NavigateLeft.Name);
jsonWriter.WriteEndObject();
jsonWriter.WritePropertyName("NavigateRight");
jsonWriter.WriteStartObject();
jsonWriter.WritePropertyName("Name");
jsonWriter.WriteValue(NavigateRight.Name);
jsonWriter.WriteEndObject();
jsonWriter.WritePropertyName("NavigateSubmit");
jsonWriter.WriteStartObject();
jsonWriter.WritePropertyName("Name");
jsonWriter.WriteValue(NavigateSubmit.Name);
jsonWriter.WriteEndObject();
jsonWriter.WriteEndObject();
}
@@ -713,25 +735,45 @@ namespace FlaxEngine
jsonWriter.WritePropertyName("NavigationInputRepeatRate");
jsonWriter.WriteValue(NavigationInputRepeatRate);
}
if (NavigationInputActionUp != other.NavigationInputActionUp)
if (NavigateUp.Name != other.NavigateUp.Name)
{
jsonWriter.WritePropertyName("NavigationInputActionUp");
jsonWriter.WriteValue(NavigationInputActionUp);
jsonWriter.WritePropertyName("NavigateUp");
jsonWriter.WriteStartObject();
jsonWriter.WritePropertyName("Name");
jsonWriter.WriteValue(NavigateUp.Name);
jsonWriter.WriteEndObject();
}
if (NavigationInputActionDown != other.NavigationInputActionDown)
if (NavigateDown.Name != other.NavigateDown.Name)
{
jsonWriter.WritePropertyName("NavigationInputActionDown");
jsonWriter.WriteValue(NavigationInputActionDown);
jsonWriter.WritePropertyName("NavigateDown");
jsonWriter.WriteStartObject();
jsonWriter.WritePropertyName("Name");
jsonWriter.WriteValue(NavigateDown.Name);
jsonWriter.WriteEndObject();
}
if (NavigationInputActionLeft != other.NavigationInputActionLeft)
if (NavigateLeft.Name != other.NavigateLeft.Name)
{
jsonWriter.WritePropertyName("NavigationInputActionLeft");
jsonWriter.WriteValue(NavigationInputActionLeft);
jsonWriter.WritePropertyName("NavigateLeft");
jsonWriter.WriteStartObject();
jsonWriter.WritePropertyName("Name");
jsonWriter.WriteValue(NavigateLeft.Name);
jsonWriter.WriteEndObject();
}
if (NavigationInputActionRight != other.NavigationInputActionRight)
if (NavigateRight.Name != other.NavigateRight.Name)
{
jsonWriter.WritePropertyName("NavigationInputActionRight");
jsonWriter.WriteValue(NavigationInputActionRight);
jsonWriter.WritePropertyName("NavigateRight");
jsonWriter.WriteStartObject();
jsonWriter.WritePropertyName("Name");
jsonWriter.WriteValue(NavigateRight.Name);
jsonWriter.WriteEndObject();
}
if (NavigateSubmit.Name != other.NavigateSubmit.Name)
{
jsonWriter.WritePropertyName("NavigateSubmit");
jsonWriter.WriteStartObject();
jsonWriter.WritePropertyName("Name");
jsonWriter.WriteValue(NavigateSubmit.Name);
jsonWriter.WriteEndObject();
}
jsonWriter.WriteEndObject();