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

# Conflicts:
#	Source/Editor/Viewport/MainEditorGizmoViewport.cs
This commit is contained in:
2026-02-03 17:39:43 +02:00
173 changed files with 8848 additions and 3046 deletions

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

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

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

Binary file not shown.

Binary file not shown.

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

Binary file not shown.

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

Binary file not shown.

Binary file not shown.

Binary file not shown.

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

Binary file not shown.

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

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

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

Binary file not shown.

Binary file not shown.

Binary file not shown.

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

Binary file not shown.

BIN
Content/Editor/Wires Debug Material.flax (Stored with Git LFS)

Binary file not shown.

Binary file not shown.

BIN
Content/Engine/DefaultMaterial.flax (Stored with Git LFS)

Binary file not shown.

BIN
Content/Engine/DefaultRadialMenu.flax (Stored with Git LFS)

Binary file not shown.

BIN
Content/Engine/DefaultTerrainMaterial.flax (Stored with Git LFS)

Binary file not shown.

BIN
Content/Engine/SingleColorMaterial.flax (Stored with Git LFS)

Binary file not shown.

BIN
Content/Engine/SkyboxMaterial.flax (Stored with Git LFS)

Binary file not shown.

BIN
Content/Shaders/GI/DDGI.flax (Stored with Git LFS)

Binary file not shown.

View File

@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:0f34bf867df5f4296ca66ac691c2bca4efa168fb9e21ca4e613e8086669575cf
size 13296
oid sha256:615dff65b01507be6c4de722e126324aba20fc197f8e12dafaa94a05e46cba6e
size 13222

View File

@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:064f54786958f109222c49cbc0358ff4f345b30010fcd5e8cc1fab7bdc68c4fe
size 13349
oid sha256:1f07ebb16820897e8598ae7a0627cb75b3d28e9dceea3ad4bd9ff543d5cdd01c
size 13979

View File

@@ -4,7 +4,7 @@
"Major": 1,
"Minor": 11,
"Revision": 0,
"Build": 6805
"Build": 6806
},
"Company": "Flax",
"Copyright": "Copyright (c) 2012-2025 Wojciech Figat. All rights reserved.",

View File

@@ -0,0 +1,38 @@
// Copyright (c) Wojciech Figat. All rights reserved.
using FlaxEngine;
namespace FlaxEditor.CustomEditors.Dedicated
{
/// <summary>
/// Custom editor for <see cref="NavMeshBoundsVolume"/>.
/// </summary>
/// <seealso cref="ActorEditor" />
[CustomEditor(typeof(NavMeshBoundsVolume)), DefaultEditor]
internal class NavMeshBoundsVolumeEditor : ActorEditor
{
/// <inheritdoc />
public override void Initialize(LayoutElementsContainer layout)
{
base.Initialize(layout);
if (Values.HasDifferentTypes == false)
{
var button = layout.Button("Build");
button.Button.Clicked += OnBuildClicked;
}
}
private void OnBuildClicked()
{
foreach (var value in Values)
{
if (value is NavMeshBoundsVolume volume)
{
Navigation.BuildNavMesh(volume.Box, volume.Scene);
Editor.Instance.Scene.MarkSceneEdited(volume.Scene);
}
}
}
}
}

View File

@@ -909,7 +909,8 @@ namespace FlaxEditor.CustomEditors.Dedicated
settingsButton.Tag = script;
settingsButton.Clicked += OnSettingsButtonClicked;
group.Panel.HeaderTextMargin = new Margin(scriptDrag.Right - 12, 15, 2, 2);
// Adjust margin to not overlap with other ui elements in the header
group.Panel.HeaderTextMargin = group.Panel.HeaderTextMargin with { Left = scriptDrag.Right - 12, Right = settingsButton.Width + Utilities.Constants.UIMargin };
group.Object(values, editor);
// Remove drop down arrows and containment lines if no objects in the group
if (group.Children.Count == 0)

View File

@@ -450,6 +450,7 @@ namespace FlaxEditor.CustomEditors.Editors
protected bool NotNullItems;
private IntValueBox _sizeBox;
private Label _label;
private Color _background;
private int _elementsCount, _minCount, _maxCount;
private bool _readOnly;
@@ -566,7 +567,7 @@ namespace FlaxEditor.CustomEditors.Editors
Parent = dropPanel,
};
var label = new Label
_label = new Label
{
Text = "Size",
AnchorPreset = AnchorPresets.TopRight,
@@ -672,8 +673,10 @@ namespace FlaxEditor.CustomEditors.Editors
Resize(Count + 1);
};
}
}
Layout.ContainerControl.SizeChanged += OnLayoutSizeChanged;
}
private void OnSetupContextMenu(ContextMenu menu, DropPanel panel)
{
if (menu.Items.Any(x => x is ContextMenuButton b && b.Text.Equals("Open All", StringComparison.Ordinal)))
@@ -696,10 +699,24 @@ namespace FlaxEditor.CustomEditors.Editors
});
}
private void OnLayoutSizeChanged(Control control)
{
if (Layout.ContainerControl is DropPanel dropPanel)
{
// Hide "Size" text when array editor title overlaps
var headerTextSize = dropPanel.HeaderTextFont.GetFont().MeasureText(dropPanel.HeaderText);
if (headerTextSize.X + DropPanel.DropDownIconSize >= _label.Left)
_label.TextColor = _label.TextColorHighlighted = Color.Transparent;
else
_label.TextColor = _label.TextColorHighlighted = FlaxEngine.GUI.Style.Current.Foreground;
}
}
/// <inheritdoc />
protected override void Deinitialize()
{
_sizeBox = null;
Layout.ContainerControl.SizeChanged -= OnLayoutSizeChanged;
base.Deinitialize();
}

View File

@@ -44,7 +44,8 @@ namespace FlaxEditor.CustomEditors.Elements
{
var style = Style.Current;
var settingsButtonSize = Panel.HeaderHeight;
return new Image
Panel.HeaderTextMargin = Panel.HeaderTextMargin with { Right = settingsButtonSize + Utilities.Constants.UIMargin };
; return new Image
{
TooltipText = "Settings",
AutoFocus = true,

View File

@@ -23,6 +23,7 @@ using FlaxEngine.Assertions;
using FlaxEngine.GUI;
using FlaxEngine.Interop;
using FlaxEngine.Json;
using FlaxEngine.Utilities;
#pragma warning disable CS1591
@@ -1370,7 +1371,7 @@ namespace FlaxEditor
public void BuildCSG()
{
var scenes = Level.Scenes;
scenes.ToList().ForEach(x => x.BuildCSG(0));
scenes.ForEach(x => x.BuildCSG(0));
Scene.MarkSceneEdited(scenes);
}
@@ -1380,7 +1381,7 @@ namespace FlaxEditor
public void BuildNavMesh()
{
var scenes = Level.Scenes;
scenes.ToList().ForEach(x => Navigation.BuildNavMesh(x, 0));
Navigation.BuildNavMesh();
Scene.MarkSceneEdited(scenes);
}

View File

@@ -502,6 +502,7 @@ namespace FlaxEditor.GUI.ContextMenu
if (base.OnKeyDown(key))
return true;
// Keyboard navigation around the menu
switch (key)
{
case KeyboardKeys.ArrowDown:
@@ -526,6 +527,20 @@ namespace FlaxEditor.GUI.ContextMenu
}
}
break;
case KeyboardKeys.ArrowRight:
for (int i = 0; i < _panel.Children.Count; i++)
{
if (_panel.Children[i] is ContextMenuChildMenu item && item.Visible && item.IsFocused && !item.ContextMenu.IsOpened)
{
item.ShowChild(this);
item.ContextMenu._panel.Children.FirstOrDefault(x => x is ContextMenuButton && x.Visible)?.Focus();
break;
}
}
break;
case KeyboardKeys.ArrowLeft:
ParentCM?.RootWindow.Focus();
break;
}
return false;

View File

@@ -75,6 +75,11 @@ namespace FlaxEditor.GUI.ContextMenu
/// </summary>
public bool HasChildCMOpened => _childCM != null;
/// <summary>
/// Gets the parent context menu (if exists).
/// </summary>
public ContextMenuBase ParentCM => _parentCM;
/// <summary>
/// Gets the topmost context menu.
/// </summary>
@@ -84,9 +89,7 @@ namespace FlaxEditor.GUI.ContextMenu
{
var cm = this;
while (cm._parentCM != null && cm._isSubMenu)
{
cm = cm._parentCM;
}
return cm;
}
}
@@ -111,6 +114,11 @@ namespace FlaxEditor.GUI.ContextMenu
/// </summary>
public bool UseInput = true;
/// <summary>
/// Optional flag that can disable UI navigation (tab/enter).
/// </summary>
public bool UseNavigation = true;
/// <summary>
/// Initializes a new instance of the <see cref="ContextMenuBase"/> class.
/// </summary>
@@ -622,6 +630,21 @@ namespace FlaxEditor.GUI.ContextMenu
case KeyboardKeys.Escape:
Hide();
return true;
case KeyboardKeys.Return:
if (UseNavigation && Root?.FocusedControl != null)
{
Root.SubmitFocused();
return true;
}
break;
case KeyboardKeys.Tab:
if (UseNavigation && Root != null)
{
bool shiftDown = Root.GetKey(KeyboardKeys.Shift);
Root.Navigate(shiftDown ? NavDirection.Previous : NavDirection.Next);
return true;
}
break;
}
return false;
}

View File

@@ -29,7 +29,7 @@ namespace FlaxEditor.GUI.ContextMenu
CloseMenuOnClick = false;
}
private void ShowChild(ContextMenu parentContextMenu)
internal void ShowChild(ContextMenu parentContextMenu)
{
// Hide parent CM popups and set itself as child
var vAlign = parentContextMenu.ItemsAreaMargin.Top;

View File

@@ -522,6 +522,16 @@ namespace FlaxEditor.GUI
cm.AddButton("Show whole curve", _editor.ShowWholeCurve);
cm.AddButton("Reset view", _editor.ResetView);
}
cm.AddSeparator();
var presetCm = cm.AddChildMenu("Apply preset");
foreach (var value in Enum.GetValues(typeof(CurvePreset)))
{
CurvePreset preset = (CurvePreset)value;
string name = Utilities.Utils.GetPropertyNameUI(preset.ToString());
var b = presetCm.ContextMenu.AddButton(name, () => _editor.ApplyPreset(preset));
b.Enabled = !(_editor is LinearCurveEditor<T> && (preset != CurvePreset.Constant && preset != CurvePreset.Linear));
}
_editor.OnShowContextMenu(cm, selectionCount);
cm.Show(this, location);
}
@@ -619,6 +629,33 @@ namespace FlaxEditor.GUI
}
}
/// <summary>
/// A list of avaliable curve presets for the <see cref="CurveEditor{T}"/>.
/// </summary>
public enum CurvePreset
{
/// <summary>
/// A curve where every point has the same value.
/// </summary>
Constant,
/// <summary>
/// A curve linear curve.
/// </summary>
Linear,
/// <summary>
/// A curve that starts a slowly and then accelerates until the end.
/// </summary>
EaseIn,
/// <summary>
/// A curve that starts a steep and then flattens until the end.
/// </summary>
EaseOut,
/// <summary>
/// A combination of the <see cref="CurvePreset.EaseIn"/> and <see cref="CurvePreset.EaseOut"/> preset.
/// </summary>
Smoothstep
}
/// <inheritdoc />
public override void OnKeyframesDeselect(IKeyframesEditor editor)
{

View File

@@ -19,6 +19,48 @@ namespace FlaxEditor.GUI
/// <seealso cref="CurveEditorBase" />
public abstract partial class CurveEditor<T> : CurveEditorBase where T : new()
{
/// <summary>
/// Represents a single point in a <see cref="CurveEditorPreset"/>.
/// </summary>
protected struct CurvePresetPoint
{
/// <summary>
/// The time.
/// </summary>
public float Time;
/// <summary>
/// The value.
/// </summary>
public float Value;
/// <summary>
/// The in tangent. Will be ignored in <see cref="LinearCurveEditor{T}"/>
/// </summary>
public float TangentIn;
/// <summary>
/// The out tangent. Will be ignored in <see cref="LinearCurveEditor{T}"/>
/// </summary>
public float TangentOut;
}
/// <summary>
/// A curve preset.
/// </summary>
protected struct CurveEditorPreset()
{
/// <summary>
/// If the tangents will be linear or smooth.
/// </summary>
public bool LinearTangents;
/// <summary>
/// The points of the preset.
/// </summary>
public List<CurvePresetPoint> Points;
}
private class Popup : ContextMenuBase
{
private CustomEditorPresenter _presenter;
@@ -26,11 +68,12 @@ namespace FlaxEditor.GUI
private List<int> _keyframeIndices;
private bool _isDirty;
public Popup(CurveEditor<T> editor, object[] selection, List<int> keyframeIndices = null, float height = 140.0f)
: this(editor, height)
public Popup(CurveEditor<T> editor, object[] selection, List<int> keyframeIndices = null, float maxHeight = 140.0f)
: this(editor, maxHeight)
{
_presenter.Select(selection);
_presenter.OpenAllGroups();
Size = new Float2(Size.X, Mathf.Min(_presenter.ContainerControl.Size.Y, maxHeight));
_keyframeIndices = keyframeIndices;
if (keyframeIndices != null && selection.Length != keyframeIndices.Count)
throw new Exception();
@@ -169,7 +212,7 @@ namespace FlaxEditor.GUI
if (IsSelected)
color = Editor.ContainsFocus ? style.SelectionBorder : Color.Lerp(style.ForegroundDisabled, style.SelectionBorder, 0.4f);
if (IsMouseOver)
color *= 1.1f;
color *= 1.5f;
Render2D.FillRectangle(rect, color);
}
@@ -285,7 +328,7 @@ namespace FlaxEditor.GUI
/// <summary>
/// The keyframes size.
/// </summary>
protected static readonly Float2 KeyframesSize = new Float2(7.0f);
protected static readonly Float2 KeyframesSize = new Float2(8.0f);
/// <summary>
/// The colors for the keyframe points.
@@ -326,6 +369,63 @@ namespace FlaxEditor.GUI
private Color _labelsColor;
private Font _labelsFont;
/// <summary>
/// Preset values for <see cref="CurvePreset"/> to be applied to a <see cref="CurveEditor{T}"/>.
/// </summary>
protected Dictionary<CurvePreset, CurveEditorPreset> Presets = new Dictionary<CurvePreset, CurveEditorPreset>
{
{ CurvePreset.Constant, new CurveEditorPreset
{
LinearTangents = true,
Points = new List<CurvePresetPoint>
{
new CurvePresetPoint { Time = 0f, Value = 0.5f, TangentIn = 0f, TangentOut = 0f },
new CurvePresetPoint { Time = 1f, Value = 0.5f, TangentIn = 0f, TangentOut = 0f },
}
}
},
{ CurvePreset.EaseIn, new CurveEditorPreset
{
LinearTangents = false,
Points = new List<CurvePresetPoint>
{
new CurvePresetPoint { Time = 0f, Value = 0f, TangentIn = 0f, TangentOut = 0f },
new CurvePresetPoint { Time = 1f, Value = 1f, TangentIn = -1.4f, TangentOut = 0f },
}
}
},
{ CurvePreset.EaseOut, new CurveEditorPreset
{
LinearTangents = false,
Points = new List<CurvePresetPoint>
{
new CurvePresetPoint { Time = 1f, Value = 1f, TangentIn = 0f, TangentOut = 0f },
new CurvePresetPoint { Time = 0f, Value = 0f, TangentIn = 0f, TangentOut = 1.4f },
}
}
},
{ CurvePreset.Linear, new CurveEditorPreset
{
LinearTangents = true,
Points = new List<CurvePresetPoint>
{
new CurvePresetPoint { Time = 0f, Value = 0f, TangentIn = 0f, TangentOut = 0f },
new CurvePresetPoint { Time = 1f, Value = 1f, TangentIn = 0f, TangentOut = 0f },
}
}
},
{ CurvePreset.Smoothstep, new CurveEditorPreset
{
LinearTangents = false,
Points = new List<CurvePresetPoint>
{
new CurvePresetPoint { Time = 0f, Value = 0f, TangentIn = 0f, TangentOut = 0f },
new CurvePresetPoint { Time = 1f, Value = 1f, TangentIn = 0f, TangentOut = 0f },
}
}
},
};
/// <summary>
/// The keyframe UI points.
/// </summary>
@@ -568,6 +668,28 @@ namespace FlaxEditor.GUI
/// <param name="indicesToRemove">The list of indices of the keyframes to remove.</param>
protected abstract void RemoveKeyframesInternal(HashSet<int> indicesToRemove);
/// <summary>
/// Tries to convert a float to the type of the type wildcard of the curve editor.
/// </summary>
/// <param name="value">The float.</param>
/// <returns>The converted value.</returns>
public static object ConvertCurvePresetValueToCurveEditorType(float value)
{
if (typeof(T) == typeof(Float2))
return new Float2(value);
if (typeof(T) == typeof(Float3))
return new Float3(value);
if (typeof(T) == typeof(Float4))
return new Float4(value);
if (typeof(T) == typeof(Vector2))
return new Vector2(value);
if (typeof(T) == typeof(Vector3))
return new Vector3(value);
if (typeof(T) == typeof(Vector4))
return new Vector4(value);
return value;
}
/// <summary>
/// Called when showing a context menu. Can be used to add custom buttons with actions.
/// </summary>
@@ -752,6 +874,17 @@ namespace FlaxEditor.GUI
ShowCurve(false);
}
/// <summary>
/// Applies a <see cref="CurvePreset"/> to the curve editor.
/// </summary>
/// <param name="preset">The preset.</param>
public virtual void ApplyPreset(CurvePreset preset)
{
// Remove existing keyframes
SelectAll();
RemoveKeyframes();
}
/// <inheritdoc />
public override void Evaluate(out object result, float time, bool loop = false)
{
@@ -1028,6 +1161,31 @@ namespace FlaxEditor.GUI
return true;
}
bool left = key == KeyboardKeys.ArrowLeft;
bool right = key == KeyboardKeys.ArrowRight;
bool up = key == KeyboardKeys.ArrowUp;
bool down = key == KeyboardKeys.ArrowDown;
if (left || right || up || down)
{
bool shift = Root.GetKey(KeyboardKeys.Shift);
bool alt = Root.GetKey(KeyboardKeys.Alt);
float deltaValue = 10f;
if (shift || alt)
deltaValue = shift ? 2.5f : 5f;
Float2 moveDelta = Float2.Zero;
if (left || right)
moveDelta.X = left ? -deltaValue : deltaValue;
if (up || down)
moveDelta.Y = up ? -deltaValue : deltaValue;
_contents.OnMoveStart(Float2.Zero);
_contents.OnMove(moveDelta);
_contents.OnMoveEnd(Float2.Zero);
return true;
}
return false;
}
@@ -1526,6 +1684,22 @@ namespace FlaxEditor.GUI
_tangents[i].Visible = false;
}
/// <inheritdoc />
public override void ApplyPreset(CurvePreset preset)
{
base.ApplyPreset(preset);
CurveEditorPreset data = Presets[preset];
foreach (var point in data.Points)
{
float time = point.Time;
object value = ConvertCurvePresetValueToCurveEditorType((float)point.Value);
AddKeyframe(time, value);
}
ShowWholeCurve();
}
/// <inheritdoc />
protected override void DrawCurve(ref Rectangle viewRect)
{
@@ -2312,6 +2486,30 @@ namespace FlaxEditor.GUI
}
}
/// <inheritdoc />
public override void ApplyPreset(CurvePreset preset)
{
base.ApplyPreset(preset);
CurveEditorPreset data = Presets[preset];
foreach (var point in data.Points)
{
float time = point.Time;
object value = ConvertCurvePresetValueToCurveEditorType((float)point.Value);
object tangentIn = ConvertCurvePresetValueToCurveEditorType((float)point.TangentIn);
object tangentOut = ConvertCurvePresetValueToCurveEditorType((float)point.TangentOut);
AddKeyframe(time, value, tangentIn, tangentOut);
}
SelectAll();
if (data.LinearTangents)
SetTangentsLinear();
ShowWholeCurve();
}
/// <inheritdoc />
protected override void SetScaleInternal(ref Float2 scale)
{

View File

@@ -469,7 +469,7 @@ namespace FlaxEditor.GUI.Docking
var childPanels = _childPanels.ToArray();
if (childPanels.Length != 0)
{
// Move tabs from child panels into this one
// Fallback: move tabs from child panels into this one.
DockWindow selectedTab = null;
foreach (var childPanel in childPanels)
{
@@ -490,7 +490,8 @@ namespace FlaxEditor.GUI.Docking
{
// Unlink splitter
var splitterParent = splitter.Parent;
Assert.IsNotNull(splitterParent);
if (splitterParent == null)
return;
splitter.Parent = null;
// Move controls from second split panel to the split panel parent
@@ -507,17 +508,63 @@ namespace FlaxEditor.GUI.Docking
splitter.Dispose();
}
}
else if (IsMaster && _childPanels.Count != 0)
{
if (TryCollapseSplitter(_tabsProxy?.Parent as Panel))
return;
}
else if (!IsMaster)
{
throw new InvalidOperationException();
}
}
else if (_childPanels.Count != 0)
{
if (TryCollapseSplitter(_tabsProxy?.Parent as Panel))
return;
}
else if (!IsMaster)
{
throw new InvalidOperationException();
}
}
internal bool CollapseEmptyTabsProxy()
{
if (TabsCount == 0 && ChildPanelsCount > 0)
{
return TryCollapseSplitter(_tabsProxy?.Parent as Panel);
}
return false;
}
private bool TryCollapseSplitter(Panel removedPanelParent)
{
if (removedPanelParent == null)
return false;
if (!(removedPanelParent.Parent is SplitPanel tabsSplitter))
return false;
var splitterParent = tabsSplitter.Parent;
if (splitterParent == null)
return false;
tabsSplitter.Parent = null;
var scrPanel = removedPanelParent == tabsSplitter.Panel2 ? tabsSplitter.Panel1 : tabsSplitter.Panel2;
var srcPanelChildrenCount = scrPanel.ChildrenCount;
for (int i = srcPanelChildrenCount - 1; i >= 0 && scrPanel.ChildrenCount > 0; i--)
{
scrPanel.GetChild(i).Parent = splitterParent;
}
Assert.IsTrue(scrPanel.ChildrenCount == 0);
Assert.IsTrue(splitterParent.ChildrenCount == srcPanelChildrenCount);
tabsSplitter.Dispose();
if (_tabsProxy != null && _tabsProxy.Parent == removedPanelParent)
_tabsProxy = null;
return true;
}
internal virtual void DockWindowInternal(DockState state, DockWindow window, bool autoSelect = true, float? splitterValue = null)
{
DockWindow(state, window, autoSelect, splitterValue);

View File

@@ -99,6 +99,11 @@ namespace FlaxEditor.GUI.Input
/// </summary>
public event Action SlidingEnd;
/// <summary>
/// If enabled, pressing the arrow up or down key increments/ decrements the value.
/// </summary>
public bool ArrowKeysIncrement = true;
/// <summary>
/// Gets or sets the slider speed. Use value 0 to disable and hide slider UI.
/// </summary>
@@ -239,6 +244,27 @@ namespace FlaxEditor.GUI.Input
ResetViewOffset();
}
/// <inheritdoc />
public override bool OnKeyDown(KeyboardKeys key)
{
if (ArrowKeysIncrement && (key == KeyboardKeys.ArrowUp || key == KeyboardKeys.ArrowDown))
{
bool altDown = Root.GetKey(KeyboardKeys.Alt);
bool shiftDown = Root.GetKey(KeyboardKeys.Shift);
bool controlDown = Root.GetKey(KeyboardKeys.Control);
float deltaValue = altDown ? 0.1f : (shiftDown ? 10f : (controlDown ? 100f : 1f));
float slideDelta = key == KeyboardKeys.ArrowUp ? deltaValue : -deltaValue;
_startSlideValue = Value;
ApplySliding(slideDelta);
EndSliding();
Focus();
return true;
}
return base.OnKeyDown(key);
}
/// <inheritdoc />
public override bool OnMouseDown(Float2 location, MouseButton button)
{

View File

@@ -1140,8 +1140,11 @@ namespace FlaxEditor.GUI.Tree
ParentTree.DraggedOverNode = this;
// Expand node if mouse goes over arrow
if (ArrowRect.Contains(location) && HasAnyVisibleChild)
if (ArrowRect.Contains(location) && HasAnyVisibleChild && IsCollapsed)
{
Expand(true);
ParentTree?.FlushPendingPerformLayout();
}
result = OnDragEnterHeader(data);
}
@@ -1172,8 +1175,11 @@ namespace FlaxEditor.GUI.Tree
ParentTree.DraggedOverNode = this;
// Expand node if mouse goes over arrow
if (ArrowRect.Contains(location) && HasAnyVisibleChild)
if (ArrowRect.Contains(location) && HasAnyVisibleChild && IsCollapsed)
{
Expand(true);
ParentTree?.FlushPendingPerformLayout();
}
if (!_isDragOverHeader)
result = OnDragEnterHeader(data);

View File

@@ -36,11 +36,12 @@ public sealed class ViewportRubberBandSelector
/// Triggers the start of a rubber band selection.
/// </summary>
/// <returns>True if selection started, otherwise false.</returns>
public bool TryStartingRubberBandSelection()
public bool TryStartingRubberBandSelection(Float2 mousePosition)
{
if (!_isRubberBandSpanning && _owner.Gizmos.Active != null && !_owner.Gizmos.Active.IsControllingMouse && !_owner.IsRightMouseButtonDown)
{
_tryStartRubberBand = true;
_cachedStartingMousePosition = mousePosition;
return true;
}
return false;
@@ -82,12 +83,15 @@ public sealed class ViewportRubberBandSelector
return;
}
if (_tryStartRubberBand && (Mathf.Abs(_owner.MouseDelta.X) > 0.1f || Mathf.Abs(_owner.MouseDelta.Y) > 0.1f) && canStart)
if (_tryStartRubberBand && canStart)
{
_isRubberBandSpanning = true;
_cachedStartingMousePosition = mousePosition;
_rubberBandRect = new Rectangle(_cachedStartingMousePosition, Float2.Zero);
_tryStartRubberBand = false;
var delta = mousePosition - _cachedStartingMousePosition;
if (Mathf.Abs(delta.X) > 0.1f || Mathf.Abs(delta.Y) > 0.1f)
{
_isRubberBandSpanning = true;
_rubberBandRect = new Rectangle(_cachedStartingMousePosition, Float2.Zero);
_tryStartRubberBand = false;
}
}
else if (_isRubberBandSpanning && _owner.Gizmos.Active != null && !_owner.Gizmos.Active.IsControllingMouse && !_owner.IsRightMouseButtonDown)
{

View File

@@ -229,7 +229,7 @@ namespace FlaxEditor.Modules
if (!isPlayMode && options.General.AutoRebuildNavMesh && actor.Scene && node.AffectsNavigationWithChildren)
{
var bounds = actor.BoxWithChildren;
Navigation.BuildNavMesh(actor.Scene, bounds, options.General.AutoRebuildNavMeshTimeoutMs);
Navigation.BuildNavMesh(bounds, options.General.AutoRebuildNavMeshTimeoutMs);
}
}

View File

@@ -158,6 +158,7 @@ namespace FlaxEditor.Modules
private ContextMenuButton _menuToolsProfilerWindow;
private ContextMenuButton _menuToolsSetTheCurrentSceneViewAsDefault;
private ContextMenuButton _menuToolsTakeScreenshot;
private ContextMenuButton _menuToolsOpenLocalFolder;
private ContextMenuChildMenu _menuWindowApplyWindowLayout;
private ToolStripButton _toolStripSaveAll;
@@ -733,6 +734,16 @@ namespace FlaxEditor.Modules
_menuToolsTakeScreenshot = cm.AddButton("Take screenshot", inputOptions.TakeScreenshot, Editor.Windows.TakeScreenshot);
cm.AddSeparator();
cm.AddButton("Plugins", () => Editor.Windows.PluginsWin.Show());
cm.AddSeparator();
var childMenu = cm.AddChildMenu("Open Product Local folder");
childMenu.ContextMenu.AddButton("Editor", () => FileSystem.ShowFileExplorer(Globals.ProductLocalFolder));
_menuToolsOpenLocalFolder = childMenu.ContextMenu.AddButton("Game", () =>
{
string localAppData = Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData);
GameSettings settings = GameSettings.Load<GameSettings>();
string path = Path.Combine(localAppData, settings.CompanyName, settings.ProductName);
FileSystem.ShowFileExplorer(path);
});
// Window
MenuWindow = MainMenu.AddButton("Window");
@@ -1084,6 +1095,10 @@ namespace FlaxEditor.Modules
_menuToolsBuildNavMesh.Enabled = canEdit;
_menuToolsCancelBuilding.Enabled = GameCooker.IsRunning;
_menuToolsSetTheCurrentSceneViewAsDefault.Enabled = Level.ScenesCount > 0;
string localAppData = Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData);
GameSettings settings = GameSettings.Load<GameSettings>();
string path = Path.Combine(localAppData, settings.CompanyName, settings.ProductName);
_menuToolsOpenLocalFolder.Enabled = Directory.Exists(path);
c.PerformLayout();
}

View File

@@ -490,10 +490,15 @@ namespace FlaxEditor.Modules
Editor.LogWarning("Empty panel inside layout.");
p.RemoveIt();
}
else
{
p.CollapseEmptyTabsProxy();
}
}
}
panel.SelectTab(selectedTab);
panel.CollapseEmptyTabsProxy();
}
private static void SaveBounds(XmlWriter writer, Window win)

View File

@@ -571,6 +571,10 @@ namespace FlaxEditor.Options
[EditorDisplay("View Flags"), EditorOrder(3260)]
public InputBinding DebugDraw = new InputBinding(KeyboardKeys.Alpha4, KeyboardKeys.Control, KeyboardKeys.Shift);
[DefaultValue(typeof(InputBinding), "None")]
[EditorDisplay("View Flags"), EditorOrder(3270)]
public InputBinding Particles = new InputBinding(KeyboardKeys.None);
#endregion
#region Interface

View File

@@ -42,6 +42,7 @@ namespace FlaxEditor.SceneGraph.Actors
if (value is BoxCollider collider)
collider.AutoResize(!_keepLocalOrientation);
}
Presenter.OnModified();
}
}

View File

@@ -555,7 +555,7 @@ namespace FlaxEditor.SceneGraph.Actors
var options = Editor.Instance.Options.Options.General;
if (options.AutoRebuildNavMesh)
{
Navigation.BuildNavMesh(collider.Scene, collider.Box, options.AutoRebuildNavMeshTimeoutMs);
Navigation.BuildNavMesh(collider.Box, options.AutoRebuildNavMeshTimeoutMs);
}
}
}

View File

@@ -47,6 +47,11 @@ namespace FlaxEditor.SceneGraph.Actors
}
}
/// <summary>
/// Gets the model used by this actor.
/// </summary>
public Model Model => ((StaticModel)Actor).Model;
/// <inheritdoc />
public StaticModelNode(Actor actor)
: base(actor)
@@ -120,12 +125,12 @@ namespace FlaxEditor.SceneGraph.Actors
{
base.OnContextMenu(contextMenu, window);
// Check if every selected node is a primitive
// Check if every selected node is a primitive or has collision asset
var selection = GetSelection(window);
bool autoOptionEnabled = true;
foreach (var node in selection)
{
if (node is StaticModelNode staticModelNode && !staticModelNode.IsPrimitive)
if (node is StaticModelNode staticModelNode && (!staticModelNode.IsPrimitive && GetCollisionData(staticModelNode.Model) == null))
{
autoOptionEnabled = false;
break;
@@ -201,6 +206,54 @@ namespace FlaxEditor.SceneGraph.Actors
return Array.Empty<SceneGraphNode>();
}
private static bool TryCollisionData(Model model, BinaryAssetItem assetItem, out CollisionData collisionData)
{
collisionData = FlaxEngine.Content.Load<CollisionData>(assetItem.ID);
if (collisionData)
{
var options = collisionData.Options;
if (options.Model == model.ID || options.Model == Guid.Empty)
return true;
}
return false;
}
private CollisionData GetCollisionData(Model model)
{
if (model == null)
return null;
// Check if there already is collision data for that model to reuse
var modelItem = (AssetItem)Editor.Instance.ContentDatabase.Find(model.ID);
if (modelItem?.ParentFolder != null)
{
foreach (var child in modelItem.ParentFolder.Children)
{
// Check if there is collision that was made with this model
if (child is BinaryAssetItem b && b.IsOfType<CollisionData>())
{
if (TryCollisionData(model, b, out var collisionData))
return collisionData;
}
// Check if there is an auto-imported collision
if (child is ContentFolder childFolder && childFolder.ShortName == modelItem.ShortName)
{
foreach (var childFolderChild in childFolder.Children)
{
if (childFolderChild is BinaryAssetItem c && c.IsOfType<CollisionData>())
{
if (TryCollisionData(model, c, out var collisionData))
return collisionData;
}
}
}
}
}
return null;
}
private void CreateAuto(StaticModel actor, Spawner spawner, bool singleNode)
{
// Special case for in-built Editor models that can use analytical collision
@@ -243,6 +296,15 @@ namespace FlaxEditor.SceneGraph.Actors
collider.LocalPosition = new Vector3(0, 50.0f, 0);
collider.LocalOrientation = Quaternion.Euler(0, 0, 90.0f);
}
else
{
var collider = new MeshCollider
{
Transform = actor.Transform,
CollisionData = GetCollisionData(model),
};
spawner(collider);
}
}
private void CreateBox(StaticModel actor, Spawner spawner, bool singleNode)

View File

@@ -304,25 +304,14 @@ namespace FlaxEditor.Surface.Archetypes
}
}
internal sealed class CustomCodeNode : SurfaceNode
internal sealed class CustomCodeNode : ResizableSurfaceNode
{
private Rectangle _resizeButtonRect;
private Float2 _startResizingSize;
private Float2 _startResizingCornerOffset;
private bool _isResizing;
private CustomCodeTextBox _textBox;
private int SizeValueIndex => Archetype.TypeID == 8 ? 1 : 3; // Index of the Size stored in Values array
private Float2 SizeValue
{
get => (Float2)Values[SizeValueIndex];
set => SetValue(SizeValueIndex, value, false);
}
public CustomCodeNode(uint id, VisjectSurfaceContext context, NodeArchetype nodeArch, GroupArchetype groupArch)
: base(id, context, nodeArch, groupArch)
{
_sizeValueIndex = Archetype.TypeID == 8 ? 1 : 3; // Index of the Size stored in Values array
Float2 pos = new Float2(FlaxEditor.Surface.Constants.NodeMarginX, FlaxEditor.Surface.Constants.NodeMarginY + FlaxEditor.Surface.Constants.NodeHeaderSize), size;
if (nodeArch.TypeID == 8)
{
@@ -345,126 +334,19 @@ namespace FlaxEditor.Surface.Archetypes
_textBox.EditEnd += () => SetValue(0, _textBox.Text);
}
public override bool CanSelect(ref Float2 location)
{
return base.CanSelect(ref location) && !_resizeButtonRect.MakeOffsetted(Location).Contains(ref location);
}
public override void OnSurfaceLoaded(SurfaceNodeActions action)
{
base.OnSurfaceLoaded(action);
_textBox.Text = (string)Values[0];
var size = SizeValue;
if (Surface != null && Surface.GridSnappingEnabled)
size = Surface.SnapToGrid(size, true);
Resize(size.X, size.Y);
}
public override void OnValuesChanged()
{
base.OnValuesChanged();
var size = SizeValue;
Resize(size.X, size.Y);
_textBox.Text = (string)Values[0];
}
protected override void UpdateRectangles()
{
base.UpdateRectangles();
const float buttonMargin = FlaxEditor.Surface.Constants.NodeCloseButtonMargin;
const float buttonSize = FlaxEditor.Surface.Constants.NodeCloseButtonSize;
_resizeButtonRect = new Rectangle(_closeButtonRect.Left, Height - buttonSize - buttonMargin - 4, buttonSize, buttonSize);
}
public override void Draw()
{
base.Draw();
var style = Style.Current;
if (_isResizing)
{
Render2D.FillRectangle(_resizeButtonRect, style.Selection);
Render2D.DrawRectangle(_resizeButtonRect, style.SelectionBorder);
}
Render2D.DrawSprite(style.Scale, _resizeButtonRect, _resizeButtonRect.Contains(_mousePosition) && Surface.CanEdit ? style.Foreground : style.ForegroundGrey);
}
public override void OnLostFocus()
{
if (_isResizing)
EndResizing();
base.OnLostFocus();
}
public override void OnEndMouseCapture()
{
if (_isResizing)
EndResizing();
base.OnEndMouseCapture();
}
public override bool OnMouseDown(Float2 location, MouseButton button)
{
if (base.OnMouseDown(location, button))
return true;
if (button == MouseButton.Left && _resizeButtonRect.Contains(ref location) && Surface.CanEdit)
{
// Start sliding
_isResizing = true;
_startResizingSize = Size;
_startResizingCornerOffset = Size - location;
StartMouseCapture();
Cursor = CursorType.SizeNWSE;
return true;
}
return false;
}
public override void OnMouseMove(Float2 location)
{
if (_isResizing)
{
var emptySize = CalculateNodeSize(0, 0);
var size = Float2.Max(location - emptySize + _startResizingCornerOffset, new Float2(240, 160));
Resize(size.X, size.Y);
}
else
{
base.OnMouseMove(location);
}
}
public override bool OnMouseUp(Float2 location, MouseButton button)
{
if (button == MouseButton.Left && _isResizing)
{
EndResizing();
return true;
}
return base.OnMouseUp(location, button);
}
private void EndResizing()
{
Cursor = CursorType.Default;
EndMouseCapture();
_isResizing = false;
if (_startResizingSize != Size)
{
var emptySize = CalculateNodeSize(0, 0);
SizeValue = Size - emptySize;
Surface.MarkAsEdited(false);
}
}
}
internal enum MaterialTemplateInputsMapping

View File

@@ -23,11 +23,14 @@ namespace FlaxEditor.Surface.Archetypes
TextureGroup = 4,
}
internal class SampleTextureNode : SurfaceNode
internal class TextureSamplerNode : SurfaceNode
{
private ComboBox _textureGroupPicker;
protected int _samplerTypeValueIndex = -1;
protected int _textureGroupValueIndex = -1;
protected int _level = 5;
public SampleTextureNode(uint id, VisjectSurfaceContext context, NodeArchetype nodeArch, GroupArchetype groupArch)
protected TextureSamplerNode(uint id, VisjectSurfaceContext context, NodeArchetype nodeArch, GroupArchetype groupArch)
: base(id, context, nodeArch, groupArch)
{
}
@@ -48,13 +51,13 @@ namespace FlaxEditor.Surface.Archetypes
private void UpdateUI()
{
if ((int)Values[0] == (int)CommonSamplerType.TextureGroup)
if ((int)Values[_samplerTypeValueIndex] == (int)CommonSamplerType.TextureGroup)
{
if (_textureGroupPicker == null)
{
_textureGroupPicker = new ComboBox
{
Location = new Float2(FlaxEditor.Surface.Constants.NodeMarginX + 50, FlaxEditor.Surface.Constants.NodeMarginY + FlaxEditor.Surface.Constants.NodeHeaderSize + FlaxEditor.Surface.Constants.LayoutOffsetY * 5),
Location = new Float2(FlaxEditor.Surface.Constants.NodeMarginX + 50, FlaxEditor.Surface.Constants.NodeMarginY + FlaxEditor.Surface.Constants.NodeHeaderSize + FlaxEditor.Surface.Constants.LayoutOffsetY * _level),
Width = 100,
Parent = this,
};
@@ -71,7 +74,7 @@ namespace FlaxEditor.Surface.Archetypes
_textureGroupPicker.Visible = true;
}
_textureGroupPicker.SelectedIndexChanged -= OnSelectedTextureGroupChanged;
_textureGroupPicker.SelectedIndex = (int)Values[2];
_textureGroupPicker.SelectedIndex = (int)Values[_textureGroupValueIndex];
_textureGroupPicker.SelectedIndexChanged += OnSelectedTextureGroupChanged;
}
else if (_textureGroupPicker != null)
@@ -83,7 +86,39 @@ namespace FlaxEditor.Surface.Archetypes
private void OnSelectedTextureGroupChanged(ComboBox comboBox)
{
SetValue(2, _textureGroupPicker.SelectedIndex);
SetValue(_textureGroupValueIndex, _textureGroupPicker.SelectedIndex);
}
}
internal class SampleTextureNode : TextureSamplerNode
{
public SampleTextureNode(uint id, VisjectSurfaceContext context, NodeArchetype nodeArch, GroupArchetype groupArch)
: base(id, context, nodeArch, groupArch)
{
_samplerTypeValueIndex = 0;
_textureGroupValueIndex = 2;
}
}
internal class TriplanarSampleTextureNode : TextureSamplerNode
{
public TriplanarSampleTextureNode(uint id, VisjectSurfaceContext context, NodeArchetype nodeArch, GroupArchetype groupArch)
: base(id, context, nodeArch, groupArch)
{
_samplerTypeValueIndex = 3;
_textureGroupValueIndex = 5;
_level = 5;
}
}
internal class ProceduralSampleTextureNode : TextureSamplerNode
{
public ProceduralSampleTextureNode(uint id, VisjectSurfaceContext context, NodeArchetype nodeArch, GroupArchetype groupArch)
: base(id, context, nodeArch, groupArch)
{
_samplerTypeValueIndex = 0;
_textureGroupValueIndex = 2;
_level = 4;
}
}
@@ -280,9 +315,9 @@ namespace FlaxEditor.Surface.Archetypes
ConnectionsHints = ConnectionsHint.Vector,
DefaultValues = new object[]
{
0,
-1.0f,
0,
(int)CommonSamplerType.LinearClamp, // Sampler
-1.0f, // Level
0, // Texture Group
},
Elements = new[]
{
@@ -402,6 +437,7 @@ namespace FlaxEditor.Surface.Archetypes
new NodeArchetype
{
TypeID = 16,
Create = (id, context, arch, groupArch) => new TriplanarSampleTextureNode(id, context, arch, groupArch),
Title = "Triplanar Texture",
Description = "Projects a texture using world-space coordinates with triplanar mapping.",
Flags = NodeFlags.MaterialGraph,
@@ -411,8 +447,9 @@ namespace FlaxEditor.Surface.Archetypes
Float3.One, // Scale
1.0f, // Blend
Float2.Zero, // Offset
2, // Sampler
(int)CommonSamplerType.LinearWrap, // Sampler
false, // Local
0, // Texture Group
},
Elements = new[]
{
@@ -430,17 +467,17 @@ namespace FlaxEditor.Surface.Archetypes
new NodeArchetype
{
TypeID = 17,
Create = (id, context, arch, groupArch) => new SampleTextureNode(id, context, arch, groupArch),
Create = (id, context, arch, groupArch) => new ProceduralSampleTextureNode(id, context, arch, groupArch),
Title = "Procedural Sample Texture",
Description = "Samples a texture to create a more natural look with less obvious tiling.",
Flags = NodeFlags.MaterialGraph,
Size = new Float2(240, 110),
Size = new Float2(240, 130),
ConnectionsHints = ConnectionsHint.Vector,
DefaultValues = new object[]
{
2,
-1.0f,
0,
(int)CommonSamplerType.LinearWrap, // Sampler
-1.0f, // Level
0, // Texture Group
},
Elements = new[]
{
@@ -448,8 +485,8 @@ namespace FlaxEditor.Surface.Archetypes
NodeElementArchetype.Factory.Input(1, "UVs", true, null, 1),
NodeElementArchetype.Factory.Input(2, "Offset", true, typeof(Float2), 3),
NodeElementArchetype.Factory.Output(0, "Color", typeof(Float4), 4),
NodeElementArchetype.Factory.Text(0, Surface.Constants.LayoutOffsetY * 4, "Sampler"),
NodeElementArchetype.Factory.ComboBox(50, Surface.Constants.LayoutOffsetY * 4, 100, 0, typeof(CommonSamplerType))
NodeElementArchetype.Factory.Text(0, Surface.Constants.LayoutOffsetY * 3, "Sampler"),
NodeElementArchetype.Factory.ComboBox(50, Surface.Constants.LayoutOffsetY * 3, 100, 0, typeof(CommonSamplerType))
}
},
new NodeArchetype
@@ -469,6 +506,7 @@ namespace FlaxEditor.Surface.Archetypes
{
TypeID = 23,
Title = "Triplanar Normal Map",
Create = (id, context, arch, groupArch) => new TriplanarSampleTextureNode(id, context, arch, groupArch),
Description = "Projects a normal map texture using world-space coordinates with triplanar mapping.",
Flags = NodeFlags.MaterialGraph,
Size = new Float2(280, 100),
@@ -477,8 +515,9 @@ namespace FlaxEditor.Surface.Archetypes
Float3.One, // Scale
1.0f, // Blend
Float2.Zero, // Offset
2, // Sampler
(int)CommonSamplerType.LinearWrap, // Sampler
false, // Local
0, // Texture Group
},
Elements = new[]
{

View File

@@ -453,7 +453,7 @@ namespace FlaxEditor.Surface.Archetypes
}
}
private class CurveNode<T> : SurfaceNode where T : struct
private class CurveNode<T> : ResizableSurfaceNode where T : struct
{
private BezierCurveEditor<T> _curve;
private bool _isSavingCurve;
@@ -467,7 +467,7 @@ namespace FlaxEditor.Surface.Archetypes
Create = (id, context, arch, groupArch) => new CurveNode<T>(id, context, arch, groupArch),
Description = "An animation spline represented by a set of keyframes, each representing an endpoint of a Bezier curve.",
Flags = NodeFlags.AllGraphs,
Size = new Float2(400, 180.0f),
Size = new Float2(400, 180),
DefaultValues = new object[]
{
// Keyframes count
@@ -491,6 +491,8 @@ namespace FlaxEditor.Surface.Archetypes
0.0f, zero, zero, zero,
0.0f, zero, zero, zero,
0.0f, zero, zero, zero,
new Float2(400, 180),
},
Elements = new[]
{
@@ -504,30 +506,52 @@ namespace FlaxEditor.Surface.Archetypes
public CurveNode(uint id, VisjectSurfaceContext context, NodeArchetype nodeArch, GroupArchetype groupArch)
: base(id, context, nodeArch, groupArch)
{
_sizeValueIndex = 29; // Index of the Size stored in Values array
}
/// <inheritdoc />
public override void OnLoaded(SurfaceNodeActions action)
{
base.OnLoaded(action);
// Create curve editor
var upperLeft = GetBox(0).BottomLeft;
var upperRight = GetBox(1).BottomRight;
float curveMargin = 20.0f;
_curve = new BezierCurveEditor<T>
{
MaxKeyframes = 7,
Bounds = new Rectangle(upperLeft + new Float2(curveMargin, 10.0f), upperRight.X - upperLeft.X - curveMargin * 2.0f, 140.0f),
Parent = this
Parent = this,
AnchorMax = Float2.One,
};
_curve.Edited += OnCurveEdited;
_curve.UnlockChildrenRecursive();
_curve.PerformLayout();
// Sync keyframes
UpdateCurveKeyframes();
}
/// <inheritdoc />
public override void OnSurfaceLoaded(SurfaceNodeActions action)
{
base.OnSurfaceLoaded(action);
// Ensure the whole curve is shown
_curve.ShowWholeCurve();
}
public override void OnValuesChanged()
{
base.OnValuesChanged();
if (!_isSavingCurve)
{
UpdateCurveKeyframes();
}
}
private void OnCurveEdited()
{
if (_isSavingCurve)
@@ -553,17 +577,6 @@ namespace FlaxEditor.Surface.Archetypes
_isSavingCurve = false;
}
/// <inheritdoc />
public override void OnValuesChanged()
{
base.OnValuesChanged();
if (!_isSavingCurve)
{
UpdateCurveKeyframes();
}
}
private void UpdateCurveKeyframes()
{
var count = (int)Values[0];
@@ -1575,7 +1588,7 @@ namespace FlaxEditor.Surface.Archetypes
DefaultValues = new object[]
{
Guid.Empty,
string.Empty
string.Empty,
},
Elements = new[]
{

View File

@@ -0,0 +1,182 @@
// Copyright (c) Wojciech Figat. All rights reserved.
using FlaxEngine;
using FlaxEngine.GUI;
namespace FlaxEditor.Surface
{
/// <summary>
/// Visject Surface node control that cna be resized.
/// </summary>
/// <seealso cref="SurfaceNode" />
[HideInEditor]
public class ResizableSurfaceNode : SurfaceNode
{
private Float2 _startResizingSize;
private Float2 _startResizingCornerOffset;
/// <summary>
/// Indicates whether the node is currently being resized.
/// </summary>
protected bool _isResizing;
/// <summary>
/// Index of the Float2 value in the node values list to store node size.
/// </summary>
protected int _sizeValueIndex = -1;
/// <summary>
/// Minimum node size.
/// </summary>
protected Float2 _sizeMin = new Float2(240, 160);
/// <summary>
/// Node resizing rectangle bounds.
/// </summary>
protected Rectangle _resizeButtonRect;
private Float2 SizeValue
{
get => (Float2)Values[_sizeValueIndex];
set => SetValue(_sizeValueIndex, value, false);
}
/// <inheritdoc />
public ResizableSurfaceNode(uint id, VisjectSurfaceContext context, NodeArchetype nodeArch, GroupArchetype groupArch)
: base(id, context, nodeArch, groupArch)
{
}
/// <inheritdoc />
public override bool CanSelect(ref Float2 location)
{
return base.CanSelect(ref location) && !_resizeButtonRect.MakeOffsetted(Location).Contains(ref location);
}
/// <inheritdoc />
public override void OnSurfaceLoaded(SurfaceNodeActions action)
{
// Reapply the curve node size
var size = SizeValue;
if (Surface != null && Surface.GridSnappingEnabled)
size = Surface.SnapToGrid(size, true);
Resize(size.X, size.Y);
base.OnSurfaceLoaded(action);
}
/// <inheritdoc />
public override void OnValuesChanged()
{
base.OnValuesChanged();
var size = SizeValue;
Resize(size.X, size.Y);
}
/// <inheritdoc />
public override void Draw()
{
base.Draw();
if (Surface.CanEdit)
{
var style = Style.Current;
if (_isResizing)
{
Render2D.FillRectangle(_resizeButtonRect, style.Selection);
Render2D.DrawRectangle(_resizeButtonRect, style.SelectionBorder);
}
Render2D.DrawSprite(style.Scale, _resizeButtonRect, _resizeButtonRect.Contains(_mousePosition) ? style.Foreground : style.ForegroundGrey);
}
}
/// <inheritdoc />
public override void OnLostFocus()
{
if (_isResizing)
EndResizing();
base.OnLostFocus();
}
/// <inheritdoc />
public override void OnEndMouseCapture()
{
if (_isResizing)
EndResizing();
base.OnEndMouseCapture();
}
/// <inheritdoc />
public override bool OnMouseDown(Float2 location, MouseButton button)
{
if (base.OnMouseDown(location, button))
return true;
if (button == MouseButton.Left && _resizeButtonRect.Contains(ref location) && Surface.CanEdit)
{
// Start resizing
_isResizing = true;
_startResizingSize = Size;
_startResizingCornerOffset = Size - location;
StartMouseCapture();
Cursor = CursorType.SizeNWSE;
return true;
}
return false;
}
/// <inheritdoc />
public override void OnMouseMove(Float2 location)
{
if (_isResizing)
{
var emptySize = CalculateNodeSize(0, 0);
var size = Float2.Max(location - emptySize + _startResizingCornerOffset, _sizeMin);
Resize(size.X, size.Y);
}
else
{
base.OnMouseMove(location);
}
}
/// <inheritdoc />
public override bool OnMouseUp(Float2 location, MouseButton button)
{
if (button == MouseButton.Left && _isResizing)
{
EndResizing();
return true;
}
return base.OnMouseUp(location, button);
}
/// <inheritdoc />
protected override void UpdateRectangles()
{
base.UpdateRectangles();
const float buttonMargin = Constants.NodeCloseButtonMargin;
const float buttonSize = Constants.NodeCloseButtonSize;
_resizeButtonRect = new Rectangle(_closeButtonRect.Left, Height - buttonSize - buttonMargin - 4, buttonSize, buttonSize);
}
private void EndResizing()
{
Cursor = CursorType.Default;
EndMouseCapture();
_isResizing = false;
if (_startResizingSize != Size)
{
var emptySize = CalculateNodeSize(0, 0);
SizeValue = Size - emptySize;
Surface.MarkAsEdited(false);
}
}
}
}

View File

@@ -14,18 +14,11 @@ namespace FlaxEditor.Surface
/// </summary>
/// <seealso cref="SurfaceNode" />
[HideInEditor]
public class SurfaceComment : SurfaceNode
public class SurfaceComment : ResizableSurfaceNode
{
private Rectangle _colorButtonRect;
private Rectangle _resizeButtonRect;
private Float2 _startResizingSize;
private readonly TextBox _renameTextBox;
/// <summary>
/// True if sizing tool is in use.
/// </summary>
protected bool _isResizing;
/// <summary>
/// True if rename textbox is active in order to rename comment
/// </summary>
@@ -52,12 +45,6 @@ namespace FlaxEditor.Surface
set => SetValue(1, value, false);
}
private Float2 SizeValue
{
get => (Float2)Values[2];
set => SetValue(2, value, false);
}
private int OrderValue
{
get => (int)Values[3];
@@ -68,6 +55,8 @@ namespace FlaxEditor.Surface
public SurfaceComment(uint id, VisjectSurfaceContext context, NodeArchetype nodeArch, GroupArchetype groupArch)
: base(id, context, nodeArch, groupArch)
{
_sizeValueIndex = 2; // Index of the Size stored in Values array
_sizeMin = new Float2(140.0f, Constants.NodeHeaderSize);
_renameTextBox = new TextBox(false, 0, 0, Width)
{
Height = Constants.NodeHeaderSize,
@@ -86,10 +75,6 @@ namespace FlaxEditor.Surface
// Read node data
Title = TitleValue;
Color = ColorValue;
var size = SizeValue;
if (Surface != null && Surface.GridSnappingEnabled)
size = Surface.SnapToGrid(size, true);
Size = size;
// Order
// Backwards compatibility - When opening with an older version send the old comments to the back
@@ -126,27 +111,6 @@ namespace FlaxEditor.Surface
// Read node data
Title = TitleValue;
Color = ColorValue;
Size = SizeValue;
}
private void EndResizing()
{
// Clear state
_isResizing = false;
if (_startResizingSize != Size)
{
SizeValue = Size;
Surface.MarkAsEdited(false);
}
EndMouseCapture();
}
/// <inheritdoc />
public override bool CanSelect(ref Float2 location)
{
return _headerRect.MakeOffsetted(Location).Contains(ref location) && !_resizeButtonRect.MakeOffsetted(Location).Contains(ref location);
}
/// <inheritdoc />
@@ -158,6 +122,8 @@ namespace FlaxEditor.Surface
/// <inheritdoc />
protected override void UpdateRectangles()
{
base.UpdateRectangles();
const float headerSize = Constants.NodeHeaderSize;
const float buttonMargin = Constants.NodeCloseButtonMargin;
const float buttonSize = Constants.NodeCloseButtonSize;
@@ -222,16 +188,13 @@ namespace FlaxEditor.Surface
// Color button
Render2D.DrawSprite(style.Settings, _colorButtonRect, _colorButtonRect.Contains(_mousePosition) && Surface.CanEdit ? style.Foreground : style.ForegroundGrey);
// Check if is resizing
// Resize button
if (_isResizing)
{
// Draw overlay
Render2D.FillRectangle(_resizeButtonRect, style.Selection);
Render2D.DrawRectangle(_resizeButtonRect, style.SelectionBorder);
}
// Resize button
Render2D.DrawSprite(style.Scale, _resizeButtonRect, _resizeButtonRect.Contains(_mousePosition) && Surface.CanEdit ? style.Foreground : style.ForegroundGrey);
Render2D.DrawSprite(style.Scale, _resizeButtonRect, _resizeButtonRect.Contains(_mousePosition) ? style.Foreground : style.ForegroundGrey);
}
// Selection outline
@@ -247,88 +210,28 @@ namespace FlaxEditor.Surface
/// <inheritdoc />
protected override Float2 CalculateNodeSize(float width, float height)
{
return Size;
// No margins or headers
return new Float2(width, height);
}
/// <inheritdoc />
public override void OnLostFocus()
{
// Check if was resizing
if (_isResizing)
{
EndResizing();
}
// Check if was renaming
if (_isRenaming)
{
Rename(_renameTextBox.Text);
StopRenaming();
}
// Base
base.OnLostFocus();
}
/// <inheritdoc />
public override void OnEndMouseCapture()
{
// Check if was resizing
if (_isResizing)
{
EndResizing();
}
else
{
base.OnEndMouseCapture();
}
}
/// <inheritdoc />
public override bool ContainsPoint(ref Float2 location, bool precise)
{
return _headerRect.Contains(ref location) || _resizeButtonRect.Contains(ref location);
}
/// <inheritdoc />
public override bool OnMouseDown(Float2 location, MouseButton button)
{
if (base.OnMouseDown(location, button))
return true;
// Check if can start resizing
if (button == MouseButton.Left && _resizeButtonRect.Contains(ref location) && Surface.CanEdit)
{
// Start sliding
_isResizing = true;
_startResizingSize = Size;
StartMouseCapture();
return true;
}
return false;
}
/// <inheritdoc />
public override void OnMouseMove(Float2 location)
{
// Check if is resizing
if (_isResizing)
{
// Update size
var size = Float2.Max(location, new Float2(140.0f, _headerRect.Bottom));
if (Surface.GridSnappingEnabled)
size = Surface.SnapToGrid(size, true);
Size = size;
}
else
{
// Base
base.OnMouseMove(location);
}
}
/// <inheritdoc />
public override bool OnMouseDoubleClick(Float2 location, MouseButton button)
{
@@ -394,12 +297,6 @@ namespace FlaxEditor.Surface
/// <inheritdoc />
public override bool OnMouseUp(Float2 location, MouseButton button)
{
if (button == MouseButton.Left && _isResizing)
{
EndResizing();
return true;
}
if (base.OnMouseUp(location, button))
return true;

View File

@@ -192,7 +192,7 @@ namespace FlaxEditor.Tools.Terrain
{
if (terrain.Scene && terrain.HasStaticFlag(StaticFlags.Navigation))
{
Navigation.BuildNavMesh(terrain.Scene, patchBounds, editorOptions.General.AutoRebuildNavMeshTimeoutMs);
Navigation.BuildNavMesh(patchBounds, editorOptions.General.AutoRebuildNavMeshTimeoutMs);
}
}
}

View File

@@ -209,7 +209,7 @@ namespace FlaxEditor.Tools.Terrain
{
if (terrain.Scene && terrain.HasStaticFlag(StaticFlags.Navigation))
{
Navigation.BuildNavMesh(terrain.Scene, patchBounds, editorOptions.General.AutoRebuildNavMeshTimeoutMs);
Navigation.BuildNavMesh(patchBounds, editorOptions.General.AutoRebuildNavMeshTimeoutMs);
}
}
}

View File

@@ -172,7 +172,7 @@ namespace FlaxEditor.Tools.Terrain.Undo
if (_navmeshBoundsModifications != null)
{
foreach (var bounds in _navmeshBoundsModifications)
Navigation.BuildNavMesh(scene, bounds, _dirtyNavMeshTimeoutMs);
Navigation.BuildNavMesh(bounds, _dirtyNavMeshTimeoutMs);
}
Editor.Instance.Scene.MarkSceneEdited(scene);
@@ -217,11 +217,10 @@ namespace FlaxEditor.Tools.Terrain.Undo
}
// Update navmesh
var scene = Terrain.Scene;
if (_navmeshBoundsModifications != null)
{
foreach (var bounds in _navmeshBoundsModifications)
Navigation.BuildNavMesh(scene, bounds, _dirtyNavMeshTimeoutMs);
Navigation.BuildNavMesh(bounds, _dirtyNavMeshTimeoutMs);
}
Editor.Instance.Scene.MarkSceneEdited(Terrain.Scene);

View File

@@ -303,7 +303,7 @@ namespace FlaxEditor.Actions
if (_nodeParents[i] is ActorNode node && node.Actor && node.Actor.Scene && node.AffectsNavigationWithChildren)
{
var bounds = node.Actor.BoxWithChildren;
Navigation.BuildNavMesh(node.Actor.Scene, bounds, options.General.AutoRebuildNavMeshTimeoutMs);
Navigation.BuildNavMesh(bounds, options.General.AutoRebuildNavMeshTimeoutMs);
}
}
}

View File

@@ -121,12 +121,12 @@ namespace FlaxEditor
// Handle simple case where objects were moved just a little and use one navmesh build request to improve performance
if (data.BeforeBounds.Intersects(ref data.AfterBounds))
{
Navigation.BuildNavMesh(data.Scene, BoundingBox.Merge(data.BeforeBounds, data.AfterBounds), options.General.AutoRebuildNavMeshTimeoutMs);
Navigation.BuildNavMesh(BoundingBox.Merge(data.BeforeBounds, data.AfterBounds), options.General.AutoRebuildNavMeshTimeoutMs);
}
else
{
Navigation.BuildNavMesh(data.Scene, data.BeforeBounds, options.General.AutoRebuildNavMeshTimeoutMs);
Navigation.BuildNavMesh(data.Scene, data.AfterBounds, options.General.AutoRebuildNavMeshTimeoutMs);
Navigation.BuildNavMesh(data.BeforeBounds, options.General.AutoRebuildNavMeshTimeoutMs);
Navigation.BuildNavMesh(data.AfterBounds, options.General.AutoRebuildNavMeshTimeoutMs);
}
}
}

View File

@@ -444,6 +444,9 @@ namespace FlaxEditor.Utilities
/// <returns>The result value.</returns>
public static double Parse(string text)
{
// Hack to allow parsing numbers while using "_" as a separator (like this: 1_000)
text = text.Replace("_", string.Empty);
var tokens = Tokenize(text);
var rpn = OrderTokens(tokens);
return EvaluateRPN(rpn);

View File

@@ -1069,6 +1069,7 @@ namespace FlaxEditor.Viewport
InputActions.Add(options => options.Fog, () => Task.ViewFlags ^= ViewFlags.Fog);
InputActions.Add(options => options.SpecularLight, () => Task.ViewFlags ^= ViewFlags.SpecularLight);
InputActions.Add(options => options.Decals, () => Task.ViewFlags ^= ViewFlags.Decals);
InputActions.Add(options => options.Particles, () => Task.ViewFlags ^= ViewFlags.Particles);
InputActions.Add(options => options.CustomPostProcess, () => Task.ViewFlags ^= ViewFlags.CustomPostProcess);
InputActions.Add(options => options.Bloom, () => Task.ViewFlags ^= ViewFlags.Bloom);
InputActions.Add(options => options.ToneMapping, () => Task.ViewFlags ^= ViewFlags.ToneMapping);
@@ -2147,6 +2148,7 @@ namespace FlaxEditor.Viewport
new ViewFlagOptions(ViewFlags.Fog, "Fog", Editor.Instance.Options.Options.Input.Fog),
new ViewFlagOptions(ViewFlags.SpecularLight, "Specular Light", Editor.Instance.Options.Options.Input.SpecularLight),
new ViewFlagOptions(ViewFlags.Decals, "Decals", Editor.Instance.Options.Options.Input.Decals),
new ViewFlagOptions(ViewFlags.Particles, "Particles", Editor.Instance.Options.Options.Input.Particles),
new ViewFlagOptions(ViewFlags.CustomPostProcess, "Custom Post Process", Editor.Instance.Options.Options.Input.CustomPostProcess),
new ViewFlagOptions(ViewFlags.Bloom, "Bloom", Editor.Instance.Options.Options.Input.Bloom),
new ViewFlagOptions(ViewFlags.ToneMapping, "Tone Mapping", Editor.Instance.Options.Options.Input.ToneMapping),
@@ -2166,12 +2168,13 @@ namespace FlaxEditor.Viewport
if (cm.Visible == false)
return;
var ccm = (ContextMenu)cm;
var flags = Task.View.Flags;
foreach (var e in ccm.Items)
{
if (e is ContextMenuButton b && b.Tag != null)
{
var v = (ViewFlags)b.Tag;
b.Icon = (Task.View.Flags & v) != 0 ? Style.Current.CheckBoxTick : SpriteHandle.Invalid;
b.Icon = (flags & v) != 0 ? Style.Current.CheckBoxTick : SpriteHandle.Invalid;
}
}
}

View File

@@ -629,7 +629,7 @@ namespace FlaxEditor.Viewport
base.OnLeftMouseButtonDown();
if (!IsAltKeyDown)
_rubberBandSelector.TryStartingRubberBandSelection();
_rubberBandSelector.TryStartingRubberBandSelection(_viewMousePos);
}
/// <inheritdoc />

View File

@@ -45,7 +45,7 @@ namespace FlaxEditor.Windows
{
Parent = this
};
_saveButton = (ToolStripButton)toolstrip.AddButton(editor.Icons.Save64, SaveData).LinkTooltip("Save");
_saveButton = (ToolStripButton)toolstrip.AddButton(editor.Icons.Save64, SaveData).LinkTooltip("Save.");
_saveButton.Enabled = false;
_tabs = new Tabs
@@ -104,6 +104,8 @@ namespace FlaxEditor.Windows
{
_saveButton.Enabled = true;
_isDataDirty = true;
if (!Title.EndsWith('*'))
Title += "*";
}
}
@@ -113,6 +115,8 @@ namespace FlaxEditor.Windows
{
_saveButton.Enabled = false;
_isDataDirty = false;
if (Title.EndsWith('*'))
Title = Title.Remove(Title.Length - 1);
}
}

View File

@@ -41,6 +41,35 @@ bool Material::IsMaterialInstance() const
return false;
}
#if USE_EDITOR
void Material::GetReferences(Array<Guid>& assets, Array<String>& files) const
{
ShaderAssetTypeBase<MaterialBase>::GetReferences(assets, files);
// Collect references from material graph (needs to load it)
if (!WaitForLoaded() && HasChunk(SHADER_FILE_CHUNK_VISJECT_SURFACE))
{
ScopeLock lock(Locker);
if (!LoadChunks(GET_CHUNK_FLAG(SHADER_FILE_CHUNK_VISJECT_SURFACE)))
{
const auto surfaceChunk = GetChunk(SHADER_FILE_CHUNK_VISJECT_SURFACE);
if (surfaceChunk)
{
MemoryReadStream stream(surfaceChunk->Get(), surfaceChunk->Size());
MaterialGraph graph;
if (!graph.Load(&stream, false))
{
graph.GetReferences(assets);
}
}
}
}
}
#endif
const MaterialInfo& Material::GetInfo() const
{
if (_materialShader)

View File

@@ -38,6 +38,9 @@ public:
public:
// [MaterialBase]
bool IsMaterialInstance() const override;
#if USE_EDITOR
void GetReferences(Array<Guid>& assets, Array<String>& files) const override;
#endif
// [IMaterial]
const MaterialInfo& GetInfo() const override;

View File

@@ -478,16 +478,23 @@ CreateAssetResult ImportModel::Import(CreateAssetContext& context)
}
// Check if restore local changes on asset reimport
constexpr bool RestoreModelOptionsOnReimport = true;
constexpr bool RestoreAnimEventsOnReimport = true;
const bool restoreModelOptions = RestoreModelOptionsOnReimport && (options.Type == ModelTool::ModelType::Model || options.Type == ModelTool::ModelType::SkinnedModel);
const bool restoreMaterials = options.RestoreMaterialsOnReimport && data->Materials.HasItems();
const bool restoreAnimEvents = RestoreAnimEventsOnReimport && options.Type == ModelTool::ModelType::Animation && data->Animations.HasItems();
if ((restoreMaterials || restoreAnimEvents) && FileSystem::FileExists(context.TargetAssetPath))
if ((restoreModelOptions || restoreMaterials || restoreAnimEvents) && FileSystem::FileExists(context.TargetAssetPath))
{
AssetReference<Asset> asset = Content::LoadAsync<Asset>(context.TargetAssetPath);
if (asset && !asset->WaitForLoaded())
{
auto* model = ScriptingObject::Cast<ModelBase>(asset);
auto* animation = ScriptingObject::Cast<Animation>(asset);
if (restoreModelOptions && model)
{
// Copy general properties
data->MinScreenSize = model->MinScreenSize;
}
if (restoreMaterials && model)
{
// Copy material settings

View File

@@ -8,6 +8,7 @@
#include "Engine/Threading/Threading.h"
#include "Engine/Threading/Task.h"
#include "Engine/Profiler/ProfilerCPU.h"
#include "Engine/Profiler/ProfilerMemory.h"
#include "Engine/Scripting/BinaryModule.h"
#include "Engine/Scripting/Scripting.h"
#include "Engine/Scripting/ManagedCLR/MAssembly.h"
@@ -219,6 +220,7 @@ namespace
if (module == GetBinaryModuleCorlib())
return;
PROFILE_CPU();
PROFILE_MEM(EngineDebug);
#if USE_CSHARP
if (auto* managedModule = dynamic_cast<ManagedBinaryModule*>(module))
@@ -381,6 +383,7 @@ DebugCommandsService DebugCommandsServiceInstance;
void DebugCommands::Execute(StringView command)
{
PROFILE_MEM(EngineDebug);
// TODO: fix missing string handle on 1st command execution (command gets invalid after InitCommands due to dotnet GC or dotnet interop handles flush)
String commandCopy = command;
command = commandCopy;
@@ -423,6 +426,7 @@ void DebugCommands::Search(StringView searchText, Array<StringView>& matches, bo
{
if (searchText.IsEmpty())
return;
PROFILE_MEM(EngineDebug);
// TODO: fix missing string handle on 1st command execution (command gets invalid after InitCommands due to dotnet GC or dotnet interop handles flush)
String searchTextCopy = searchText;
searchText = searchTextCopy;

View File

@@ -480,6 +480,7 @@ DebugDrawCall WriteLists(int32& vertexCounter, const Array<T>& listA, const Arra
FORCE_INLINE DebugTriangle* AppendTriangles(int32 count, float duration, bool depthTest)
{
PROFILE_MEM(EngineDebug);
Array<DebugTriangle>* list;
if (depthTest)
list = duration > 0 ? &Context->DebugDrawDepthTest.DefaultTriangles : &Context->DebugDrawDepthTest.OneFrameTriangles;
@@ -490,6 +491,19 @@ FORCE_INLINE DebugTriangle* AppendTriangles(int32 count, float duration, bool de
return list->Get() + startIndex;
}
FORCE_INLINE DebugTriangle* AppendWireTriangles(int32 count, float duration, bool depthTest)
{
PROFILE_MEM(EngineDebug);
Array<DebugTriangle>* list;
if (depthTest)
list = duration > 0 ? &Context->DebugDrawDepthTest.DefaultWireTriangles : &Context->DebugDrawDepthTest.OneFrameWireTriangles;
else
list = duration > 0 ? &Context->DebugDrawDefault.DefaultWireTriangles : &Context->DebugDrawDefault.OneFrameWireTriangles;
const int32 startIndex = list->Count();
list->AddUninitialized(count);
return list->Get() + startIndex;
}
inline void DrawText3D(const DebugText3D& t, const RenderContext& renderContext, const Float3& viewUp, const Matrix& f, const Matrix& vp, const Viewport& viewport, GPUContext* context, GPUTextureView* target, GPUTextureView* depthBuffer)
{
Matrix w, fw, m;
@@ -527,7 +541,7 @@ DebugDrawService DebugDrawServiceInstance;
bool DebugDrawService::Init()
{
PROFILE_MEM(Graphics);
PROFILE_MEM(EngineDebug);
Context = &GlobalContext;
// Init wireframe sphere cache
@@ -646,7 +660,7 @@ void DebugDrawService::Update()
}
PROFILE_CPU();
PROFILE_MEM(Graphics);
PROFILE_MEM(EngineDebug);
// Update lists
float deltaTime = Time::Update.DeltaTime.GetTotalSeconds();
@@ -1102,6 +1116,7 @@ void DebugDraw::DrawRay(const Ray& ray, const Color& color, float length, float
void DebugDraw::DrawLine(const Vector3& start, const Vector3& end, const Color& color, float duration, bool depthTest)
{
PROFILE_MEM(EngineDebug);
const Float3 startF = start - Context->Origin, endF = end - Context->Origin;
auto& debugDrawData = depthTest ? Context->DebugDrawDepthTest : Context->DebugDrawDefault;
if (duration > 0)
@@ -1120,6 +1135,7 @@ void DebugDraw::DrawLine(const Vector3& start, const Vector3& end, const Color&
void DebugDraw::DrawLine(const Vector3& start, const Vector3& end, const Color& startColor, const Color& endColor, float duration, bool depthTest)
{
PROFILE_MEM(EngineDebug);
const Float3 startF = start - Context->Origin, endF = end - Context->Origin;
auto& debugDrawData = depthTest ? Context->DebugDrawDepthTest : Context->DebugDrawDefault;
if (duration > 0)
@@ -1149,6 +1165,7 @@ void DebugDraw::DrawLines(const Span<Float3>& lines, const Matrix& transform, co
}
// Draw lines
PROFILE_MEM(EngineDebug);
const Float3* p = lines.Get();
auto& debugDrawData = depthTest ? Context->DebugDrawDepthTest : Context->DebugDrawDefault;
const Matrix transformF = transform * Matrix::Translation(-Context->Origin);
@@ -1188,6 +1205,7 @@ void DebugDraw::DrawLines(GPUBuffer* lines, const Matrix& transform, float durat
}
// Draw lines
PROFILE_MEM(EngineDebug);
auto& debugDrawData = depthTest ? Context->DebugDrawDepthTest : Context->DebugDrawDefault;
auto& geometry = debugDrawData.GeometryBuffers.AddOne();
geometry.Buffer = lines;
@@ -1212,6 +1230,7 @@ void DebugDraw::DrawLines(const Span<Double3>& lines, const Matrix& transform, c
}
// Draw lines
PROFILE_MEM(EngineDebug);
const Double3* p = lines.Get();
auto& debugDrawData = depthTest ? Context->DebugDrawDepthTest : Context->DebugDrawDefault;
const Matrix transformF = transform * Matrix::Translation(-Context->Origin);
@@ -1258,6 +1277,7 @@ void DebugDraw::DrawBezier(const Vector3& p1, const Vector3& p2, const Vector3&
const float segmentCountInv = 1.0f / (float)segmentCount;
// Draw segmented curve from lines
PROFILE_MEM(EngineDebug);
auto& debugDrawData = depthTest ? Context->DebugDrawDepthTest : Context->DebugDrawDefault;
if (duration > 0)
{
@@ -1298,6 +1318,7 @@ void DebugDraw::DrawWireBox(const BoundingBox& box, const Color& color, float du
c -= Context->Origin;
// Draw lines
PROFILE_MEM(EngineDebug);
auto& debugDrawData = depthTest ? Context->DebugDrawDepthTest : Context->DebugDrawDefault;
if (duration > 0)
{
@@ -1332,6 +1353,7 @@ void DebugDraw::DrawWireFrustum(const BoundingFrustum& frustum, const Color& col
c -= Context->Origin;
// Draw lines
PROFILE_MEM(EngineDebug);
auto& debugDrawData = depthTest ? Context->DebugDrawDepthTest : Context->DebugDrawDefault;
if (duration > 0)
{
@@ -1366,6 +1388,7 @@ void DebugDraw::DrawWireBox(const OrientedBoundingBox& box, const Color& color,
c -= Context->Origin;
// Draw lines
PROFILE_MEM(EngineDebug);
auto& debugDrawData = depthTest ? Context->DebugDrawDepthTest : Context->DebugDrawDefault;
if (duration > 0)
{
@@ -1407,6 +1430,7 @@ void DebugDraw::DrawWireSphere(const BoundingSphere& sphere, const Color& color,
auto& cache = SphereCache[index];
// Draw lines of the unit sphere after linear transform
PROFILE_MEM(EngineDebug);
auto& debugDrawData = depthTest ? Context->DebugDrawDepthTest : Context->DebugDrawDefault;
if (duration > 0)
{
@@ -1442,6 +1466,7 @@ void DebugDraw::DrawSphere(const BoundingSphere& sphere, const Color& color, flo
list = duration > 0 ? &Context->DebugDrawDepthTest.DefaultTriangles : &Context->DebugDrawDepthTest.OneFrameTriangles;
else
list = duration > 0 ? &Context->DebugDrawDefault.DefaultTriangles : &Context->DebugDrawDefault.OneFrameTriangles;
PROFILE_MEM(EngineDebug);
list->EnsureCapacity(list->Count() + SphereTriangleCache.Count());
const Float3 centerF = sphere.Center - Context->Origin;
@@ -1473,6 +1498,7 @@ void DebugDraw::DrawCircle(const Vector3& position, const Float3& normal, float
Matrix::Multiply(scale, world, matrix);
// Draw lines of the unit circle after linear transform
PROFILE_MEM(EngineDebug);
Float3 prev = Float3::Transform(CircleCache[0], matrix);
auto& debugDrawData = depthTest ? Context->DebugDrawDepthTest : Context->DebugDrawDefault;
for (int32 i = 1; i < DEBUG_DRAW_CIRCLE_VERTICES;)
@@ -1503,6 +1529,7 @@ void DebugDraw::DrawWireTriangle(const Vector3& v0, const Vector3& v1, const Vec
void DebugDraw::DrawTriangle(const Vector3& v0, const Vector3& v1, const Vector3& v2, const Color& color, float duration, bool depthTest)
{
PROFILE_MEM(EngineDebug);
DebugTriangle t;
t.Color = Color32(color);
t.TimeLeft = duration;
@@ -1558,6 +1585,7 @@ void DebugDraw::DrawTriangles(GPUBuffer* triangles, const Matrix& transform, flo
DebugLog::ThrowException("Cannot draw debug lines with incorrect amount of items in array");
return;
}
PROFILE_MEM(EngineDebug);
auto& debugDrawData = depthTest ? Context->DebugDrawDepthTest : Context->DebugDrawDefault;
auto& geometry = debugDrawData.GeometryBuffers.AddOne();
geometry.Buffer = triangles;
@@ -1714,7 +1742,7 @@ void DebugDraw::DrawWireTriangles(const Span<Float3>& vertices, const Color& col
DebugTriangle t;
t.Color = Color32(color);
t.TimeLeft = duration;
auto dst = AppendTriangles(vertices.Length() / 3, duration, depthTest);
auto dst = AppendWireTriangles(vertices.Length() / 3, duration, depthTest);
const Float3 origin = Context->Origin;
for (int32 i = 0; i < vertices.Length();)
{
@@ -1736,7 +1764,7 @@ void DebugDraw::DrawWireTriangles(const Span<Float3>& vertices, const Span<int32
DebugTriangle t;
t.Color = Color32(color);
t.TimeLeft = duration;
auto dst = AppendTriangles(indices.Length() / 3, duration, depthTest);
auto dst = AppendWireTriangles(indices.Length() / 3, duration, depthTest);
const Float3 origin = Context->Origin;
for (int32 i = 0; i < indices.Length();)
{
@@ -1758,7 +1786,7 @@ void DebugDraw::DrawWireTriangles(const Span<Double3>& vertices, const Color& co
DebugTriangle t;
t.Color = Color32(color);
t.TimeLeft = duration;
auto dst = AppendTriangles(vertices.Length() / 3, duration, depthTest);
auto dst = AppendWireTriangles(vertices.Length() / 3, duration, depthTest);
const Double3 origin = Context->Origin;
for (int32 i = 0; i < vertices.Length();)
{
@@ -1780,7 +1808,7 @@ void DebugDraw::DrawWireTriangles(const Span<Double3>& vertices, const Span<int3
DebugTriangle t;
t.Color = Color32(color);
t.TimeLeft = duration;
auto dst = AppendTriangles(indices.Length() / 3, duration, depthTest);
auto dst = AppendWireTriangles(indices.Length() / 3, duration, depthTest);
const Double3 origin = Context->Origin;
for (int32 i = 0; i < indices.Length();)
{
@@ -1847,6 +1875,7 @@ void DebugDraw::DrawWireCapsule(const Vector3& position, const Quaternion& orien
Matrix::Multiply(rotation, translation, world);
// Write vertices
PROFILE_MEM(EngineDebug);
auto& debugDrawData = depthTest ? Context->DebugDrawDepthTest : Context->DebugDrawDefault;
Color32 color32(color);
if (duration > 0)
@@ -1941,6 +1970,7 @@ namespace
void DrawCylinder(Array<DebugTriangle>* list, const Vector3& position, const Quaternion& orientation, float radius, float height, const Color& color, float duration)
{
// Setup cache
PROFILE_MEM(EngineDebug);
Float3 CylinderCache[DEBUG_DRAW_CYLINDER_VERTICES];
const float angleBetweenFacets = TWO_PI / DEBUG_DRAW_CYLINDER_RESOLUTION;
const float verticalOffset = height * 0.5f;
@@ -2012,6 +2042,7 @@ namespace
void DrawCone(Array<DebugTriangle>* list, const Vector3& position, const Quaternion& orientation, float radius, float angleXY, float angleXZ, const Color& color, float duration)
{
PROFILE_MEM(EngineDebug);
const float tolerance = 0.001f;
const float angle1 = Math::Clamp(angleXY, tolerance, PI - tolerance);
const float angle2 = Math::Clamp(angleXZ, tolerance, PI - tolerance);
@@ -2101,6 +2132,7 @@ void DebugDraw::DrawArc(const Vector3& position, const Quaternion& orientation,
{
if (angle <= 0)
return;
PROFILE_MEM(EngineDebug);
if (angle > TWO_PI)
angle = TWO_PI;
Array<DebugTriangle>* list;
@@ -2133,6 +2165,7 @@ void DebugDraw::DrawWireArc(const Vector3& position, const Quaternion& orientati
{
if (angle <= 0)
return;
PROFILE_MEM(EngineDebug);
if (angle > TWO_PI)
angle = TWO_PI;
const int32 resolution = Math::CeilToInt((float)DEBUG_DRAW_CONE_RESOLUTION / TWO_PI * angle);
@@ -2199,6 +2232,7 @@ void DebugDraw::DrawBox(const BoundingBox& box, const Color& color, float durati
list = duration > 0 ? &Context->DebugDrawDepthTest.DefaultTriangles : &Context->DebugDrawDepthTest.OneFrameTriangles;
else
list = duration > 0 ? &Context->DebugDrawDefault.DefaultTriangles : &Context->DebugDrawDefault.OneFrameTriangles;
PROFILE_MEM(EngineDebug);
list->EnsureCapacity(list->Count() + 36);
for (int i0 = 0; i0 < 36;)
{
@@ -2227,6 +2261,7 @@ void DebugDraw::DrawBox(const OrientedBoundingBox& box, const Color& color, floa
list = duration > 0 ? &Context->DebugDrawDepthTest.DefaultTriangles : &Context->DebugDrawDepthTest.OneFrameTriangles;
else
list = duration > 0 ? &Context->DebugDrawDefault.DefaultTriangles : &Context->DebugDrawDefault.OneFrameTriangles;
PROFILE_MEM(EngineDebug);
list->EnsureCapacity(list->Count() + 36);
for (int i0 = 0; i0 < 36;)
{
@@ -2242,6 +2277,7 @@ void DebugDraw::DrawText(const StringView& text, const Float2& position, const C
{
if (text.Length() == 0 || size < 4)
return;
PROFILE_MEM(EngineDebug);
Array<DebugText2D>* list = duration > 0 ? &Context->DebugDrawDefault.DefaultText2D : &Context->DebugDrawDefault.OneFrameText2D;
auto& t = list->AddOne();
t.Text.Resize(text.Length() + 1);
@@ -2257,6 +2293,7 @@ void DebugDraw::DrawText(const StringView& text, const Vector3& position, const
{
if (text.Length() == 0 || size < 4)
return;
PROFILE_MEM(EngineDebug);
Array<DebugText3D>* list = duration > 0 ? &Context->DebugDrawDefault.DefaultText3D : &Context->DebugDrawDefault.OneFrameText3D;
auto& t = list->AddOne();
t.Text.Resize(text.Length() + 1);
@@ -2274,6 +2311,7 @@ void DebugDraw::DrawText(const StringView& text, const Transform& transform, con
{
if (text.Length() == 0 || size < 4)
return;
PROFILE_MEM(EngineDebug);
Array<DebugText3D>* list = duration > 0 ? &Context->DebugDrawDefault.DefaultText3D : &Context->DebugDrawDefault.OneFrameText3D;
auto& t = list->AddOne();
t.Text.Resize(text.Length() + 1);

View File

@@ -44,20 +44,39 @@ void Foliage::AddToCluster(ChunkedArray<FoliageCluster, FOLIAGE_CLUSTER_CHUNKS_S
ASSERT(instance.Bounds.Radius > ZeroTolerance);
ASSERT(cluster->Bounds.Intersects(instance.Bounds));
// Find target cluster
while (cluster->Children[0])
// Minor clusters don't use bounds intersection but try to find the first free cluster instead
if (cluster->IsMinor)
{
// Insert into the first non-full child cluster or subdivide 1st child
#define CHECK_CHILD(idx) \
if (cluster->Children[idx]->Instances.Count() < FOLIAGE_CLUSTER_CAPACITY) \
{ \
cluster->Children[idx]->Instances.Add(&instance); \
return; \
}
CHECK_CHILD(3);
CHECK_CHILD(2);
CHECK_CHILD(1);
cluster = cluster->Children[0];
#undef CHECK_CHILD
}
else
{
// Find target cluster
while (cluster->Children[0])
{
#define CHECK_CHILD(idx) \
if (cluster->Children[idx]->Bounds.Intersects(instance.Bounds)) \
{ \
cluster = cluster->Children[idx]; \
continue; \
}
CHECK_CHILD(0);
CHECK_CHILD(1);
CHECK_CHILD(2);
CHECK_CHILD(3);
CHECK_CHILD(0);
CHECK_CHILD(1);
CHECK_CHILD(2);
CHECK_CHILD(3);
#undef CHECK_CHILD
}
}
// Check if it's not full
@@ -79,11 +98,20 @@ void Foliage::AddToCluster(ChunkedArray<FoliageCluster, FOLIAGE_CLUSTER_CHUNKS_S
// Setup children
const Vector3 min = cluster->Bounds.Minimum;
const Vector3 max = cluster->Bounds.Maximum;
const Vector3 size = cluster->Bounds.GetSize();
const Vector3 size = max - min;
cluster->Children[0]->Init(BoundingBox(min, min + size * Vector3(0.5f, 1.0f, 0.5f)));
cluster->Children[1]->Init(BoundingBox(min + size * Vector3(0.5f, 0.0f, 0.5f), max));
cluster->Children[2]->Init(BoundingBox(min + size * Vector3(0.5f, 0.0f, 0.0f), min + size * Vector3(1.0f, 1.0f, 0.5f)));
cluster->Children[3]->Init(BoundingBox(min + size * Vector3(0.0f, 0.0f, 0.5f), min + size * Vector3(0.5f, 1.0f, 1.0f)));
if (cluster->IsMinor || size.MinValue() < 1.0f)
{
// Mark children as minor to avoid infinite subdivision
cluster->IsMinor = true;
cluster->Children[0]->IsMinor = true;
cluster->Children[1]->IsMinor = true;
cluster->Children[2]->IsMinor = true;
cluster->Children[3]->IsMinor = true;
}
// Move instances to a proper cells
for (int32 i = 0; i < cluster->Instances.Count(); i++)

View File

@@ -9,6 +9,7 @@ void FoliageCluster::Init(const BoundingBox& bounds)
Bounds = bounds;
TotalBounds = bounds;
MaxCullDistance = 0.0f;
IsMinor = false;
Children[0] = nullptr;
Children[1] = nullptr;

View File

@@ -33,6 +33,11 @@ public:
/// </summary>
float MaxCullDistance;
/// <summary>
/// Flag used by clusters that are not typical quad-tree nodes but have no volume (eg. lots of instances placed on top of each other).
/// </summary>
int32 IsMinor : 1;
/// <summary>
/// The child clusters. If any element is valid then all are created.
/// </summary>

View File

@@ -1075,20 +1075,25 @@ API_ENUM(Attributes="Flags") enum class ViewFlags : uint64
/// </summary>
LightsDebug = 1 << 27,
/// <summary>
/// Shows/hides particle effects.
/// </summary>
Particles = 1 << 28,
/// <summary>
/// Default flags for Game.
/// </summary>
DefaultGame = Reflections | DepthOfField | Fog | Decals | MotionBlur | SSR | AO | GI | DirectionalLights | PointLights | SpotLights | SkyLights | Shadows | SpecularLight | AntiAliasing | CustomPostProcess | Bloom | ToneMapping | EyeAdaptation | CameraArtifacts | LensFlares | ContactShadows | GlobalSDF | Sky,
DefaultGame = Reflections | DepthOfField | Fog | Decals | MotionBlur | SSR | AO | GI | DirectionalLights | PointLights | SpotLights | SkyLights | Shadows | SpecularLight | AntiAliasing | CustomPostProcess | Bloom | ToneMapping | EyeAdaptation | CameraArtifacts | LensFlares | ContactShadows | GlobalSDF | Sky | Particles,
/// <summary>
/// Default flags for Editor.
/// </summary>
DefaultEditor = Reflections | Fog | Decals | DebugDraw | SSR | AO | GI | DirectionalLights | PointLights | SpotLights | SkyLights | Shadows | SpecularLight | AntiAliasing | CustomPostProcess | Bloom | ToneMapping | EyeAdaptation | CameraArtifacts | LensFlares | EditorSprites | ContactShadows | GlobalSDF | Sky,
DefaultEditor = Reflections | Fog | Decals | DebugDraw | SSR | AO | GI | DirectionalLights | PointLights | SpotLights | SkyLights | Shadows | SpecularLight | AntiAliasing | CustomPostProcess | Bloom | ToneMapping | EyeAdaptation | CameraArtifacts | LensFlares | EditorSprites | ContactShadows | GlobalSDF | Sky | Particles,
/// <summary>
/// Default flags for materials/models previews generating.
/// </summary>
DefaultAssetPreview = Reflections | Decals | DirectionalLights | PointLights | SpotLights | SkyLights | SpecularLight | AntiAliasing | Bloom | ToneMapping | EyeAdaptation | CameraArtifacts | LensFlares | ContactShadows | Sky,
DefaultAssetPreview = Reflections | Decals | DirectionalLights | PointLights | SpotLights | SkyLights | SpecularLight | AntiAliasing | Bloom | ToneMapping | EyeAdaptation | CameraArtifacts | LensFlares | ContactShadows | Sky | Particles,
};
DECLARE_ENUM_OPERATORS(ViewFlags);

View File

@@ -10,7 +10,7 @@
/// <summary>
/// Current materials shader version.
/// </summary>
#define MATERIAL_GRAPH_VERSION 178
#define MATERIAL_GRAPH_VERSION 179
class Material;
class GPUShader;

View File

@@ -191,7 +191,7 @@ bool GlobalIlluminationFeature::Bind(MaterialShader::BindParameters& params, Spa
{
// Unbind SRVs to prevent issues
data.DDGI.CascadesCount = 0;
data.DDGI.FallbackIrradiance = Float3::Zero;
data.DDGI.FallbackIrradiance = Float4::Zero;
params.GPUContext->UnBindSR(srv + 0);
params.GPUContext->UnBindSR(srv + 1);
params.GPUContext->UnBindSR(srv + 2);

View File

@@ -17,7 +17,7 @@ public:
/// <summary>
/// Mesh data stream.
/// </summary>
struct Stream
struct FLAXENGINE_API Stream
{
friend MeshAccessor;

View File

@@ -378,7 +378,7 @@ API_STRUCT() struct FLAXENGINE_API GlobalIlluminationSettings : ISerializable
/// The irradiance lighting outside the GI range used as a fallback to prevent pure-black scene outside the Global Illumination range.
/// </summary>
API_FIELD(Attributes="EditorOrder(40), PostProcessSetting((int)GlobalIlluminationSettingsOverride.FallbackIrradiance)")
Color FallbackIrradiance = Color::Black;
Color FallbackIrradiance = Color::Transparent;
public:
/// <summary>

View File

@@ -620,6 +620,40 @@ void RenderTools::ComputeSphereModelDrawMatrix(const RenderView& view, const Flo
resultIsViewInside = Float3::DistanceSquared(view.Position, position) < Math::Square(radius * 1.1f); // Manually tweaked bias
}
Float3 RenderTools::GetColorQuantizationError(PixelFormat format)
{
Float3 mantissaBits;
switch (format)
{
case PixelFormat::R11G11B10_Float:
mantissaBits = Float3(6, 6, 5);
break;
case PixelFormat::R10G10B10A2_UNorm:
mantissaBits = Float3(10, 10, 10);
break;
case PixelFormat::R16G16B16A16_Float:
mantissaBits = Float3(16, 16, 16);
break;
case PixelFormat::R32G32B32A32_Float:
mantissaBits = Float3(23, 23, 23);
break;
case PixelFormat::R9G9B9E5_SharedExp:
mantissaBits = Float3(5, 6, 5);
break;
case PixelFormat::R8G8B8A8_UNorm:
case PixelFormat::B8G8R8A8_UNorm:
mantissaBits = Float3(8, 8, 8);
break;
default:
return Float3::Zero;
}
return {
Math::Pow(0.5f, mantissaBits.X),
Math::Pow(0.5f, mantissaBits.Y),
Math::Pow(0.5f, mantissaBits.Z)
};
}
int32 MipLevelsCount(int32 width)
{
int32 result = 1;

View File

@@ -140,6 +140,9 @@ public:
static void CalculateTangentFrame(Float3& resultNormal, Float4& resultTangent, const Float3& normal, const Float3& tangent);
static void ComputeSphereModelDrawMatrix(const RenderView& view, const Float3& position, float radius, Matrix& resultWorld, bool& resultIsViewInside);
// Calculates error for a given render target format to reduce floating-point precision artifacts via QuantizeColor (from Noise.hlsl).
static Float3 GetColorQuantizationError(PixelFormat format);
};
// Calculates mip levels count for a texture 1D.

View File

@@ -59,7 +59,7 @@ namespace
elements.Get()[j].Slot = (byte)slot;
}
}
GPUVertexLayout* result = anyValid ? GPUVertexLayout::Get(elements) : nullptr;
GPUVertexLayout* result = anyValid ? GPUVertexLayout::Get(elements, true) : nullptr;
VertexBufferCache.Add(key, result);
return result;
}
@@ -97,6 +97,7 @@ GPUVertexLayout::GPUVertexLayout()
void GPUVertexLayout::SetElements(const Elements& elements, bool explicitOffsets)
{
uint32 offsets[GPU_MAX_VB_BINDED + 1] = {};
uint32 maxOffset[GPU_MAX_VB_BINDED + 1] = {};
_elements = elements;
for (int32 i = 0; i < _elements.Count(); i++)
{
@@ -108,9 +109,10 @@ void GPUVertexLayout::SetElements(const Elements& elements, bool explicitOffsets
else
e.Offset = (byte)offset;
offset += PixelFormatExtensions::SizeInBytes(e.Format);
maxOffset[e.Slot] = Math::Max(maxOffset[e.Slot], offset);
}
_stride = 0;
for (uint32 offset : offsets)
for (uint32 offset : maxOffset)
_stride += offset;
}
@@ -139,7 +141,7 @@ VertexElement GPUVertexLayout::FindElement(VertexElement::Types type) const
GPUVertexLayout* GPUVertexLayout::Get(const Elements& elements, bool explicitOffsets)
{
// Hash input layout
uint32 hash = 0;
uint32 hash = explicitOffsets ? 131 : 0;
for (const VertexElement& element : elements)
{
CombineHash(hash, GetHash(element));

View File

@@ -4,6 +4,7 @@
#include "Engine/Graphics/Shaders/GPUShaderProgram.h"
#include "Engine/Core/Types/DataContainer.h"
#include "Engine/Core/Collections/Dictionary.h"
#include "../IncludeDirectXHeaders.h"
#if GRAPHICS_API_DIRECTX11

View File

@@ -3,16 +3,9 @@
#if GRAPHICS_API_DIRECTX12
#include "Engine/Graphics/Config.h"
#include "Engine/Platform/Platform.h"
#include "../IncludeDirectXHeaders.h"
#if USE_PIX && GPU_ALLOW_PROFILE_EVENTS
// Include these header files before pix3
#define WIN32_LEAN_AND_MEAN
#define NOMINMAX
#define NOGDI
#define NODRAWTEXT
//#define NOCTLMGR
#define NOFLATSBAPIS
#include <Windows.h>
#include <d3d12.h>
#include <ThirdParty/WinPixEventRuntime/pix3.h>
#endif
#include "GPUContextDX12.h"

View File

@@ -81,6 +81,8 @@ Delegate<const Float2&, float> Input::MouseWheel;
Delegate<const Float2&> Input::MouseMove;
Delegate<const Float2&> Input::MouseMoveRelative;
Action Input::MouseLeave;
Delegate<InputGamepadIndex, GamepadButton> Input::GamepadButtonDown;
Delegate<InputGamepadIndex, GamepadButton> Input::GamepadButtonUp;
Delegate<const Float2&, int32> Input::TouchDown;
Delegate<const Float2&, int32> Input::TouchMove;
Delegate<const Float2&, int32> Input::TouchUp;
@@ -1045,6 +1047,19 @@ void InputService::Update()
break;
}
}
// TODO: route gamepad button events into global InputEvents queue to improve processing
for (int32 i = 0; i < Input::Gamepads.Count(); i++)
{
auto gamepad = Input::Gamepads[i];
for (int32 buttonIdx = 1; buttonIdx < (int32)GamepadButton::MAX; buttonIdx++)
{
GamepadButton button = (GamepadButton)buttonIdx;
if (gamepad->GetButtonDown(button))
Input::GamepadButtonDown((InputGamepadIndex)i, button);
else if (gamepad->GetButtonUp(button))
Input::GamepadButtonUp((InputGamepadIndex)i, button);
}
}
// Update all actions
for (int32 i = 0; i < Input::ActionMappings.Count(); i++)

View File

@@ -118,6 +118,16 @@ public:
/// </summary>
API_EVENT() static Action MouseLeave;
/// <summary>
/// Event fired when gamepad button goes down.
/// </summary>
API_EVENT() static Delegate<InputGamepadIndex, GamepadButton> GamepadButtonDown;
/// <summary>
/// Event fired when gamepad button goes up.
/// </summary>
API_EVENT() static Delegate<InputGamepadIndex, GamepadButton> GamepadButtonUp;
/// <summary>
/// Event fired when touch action begins.
/// </summary>

View File

@@ -107,6 +107,7 @@ void LightWithShadow::Serialize(SerializeStream& stream, const void* otherObj)
SERIALIZE(ContactShadowsLength);
SERIALIZE(ShadowsUpdateRate);
SERIALIZE(ShadowsUpdateRateAtDistance);
SERIALIZE(ShadowsResolution);
}
void LightWithShadow::Deserialize(DeserializeStream& stream, ISerializeModifier* modifier)
@@ -125,4 +126,5 @@ void LightWithShadow::Deserialize(DeserializeStream& stream, ISerializeModifier*
DESERIALIZE(ContactShadowsLength);
DESERIALIZE(ShadowsUpdateRate);
DESERIALIZE(ShadowsUpdateRateAtDistance);
DESERIALIZE(ShadowsResolution);
}

View File

@@ -25,6 +25,7 @@ NavMesh::NavMesh(const SpawnParams& params)
void NavMesh::SaveNavMesh()
{
#if COMPILE_WITH_ASSETS_IMPORTER
PROFILE_MEM(NavigationMesh);
// Skip if scene is missing
const auto scene = GetScene();
@@ -111,7 +112,7 @@ void NavMesh::OnAssetLoaded(Asset* asset, void* caller)
if (Data.Tiles.HasItems())
return;
ScopeLock lock(DataAsset->Locker);
PROFILE_MEM(Navigation);
PROFILE_MEM(NavigationMesh);
// Remove added tiles
if (_navMeshActive)

View File

@@ -6,7 +6,7 @@
#if USE_EDITOR
#include "Editor/Editor.h"
#include "Editor/Managed/ManagedEditor.h"
#include "NavMeshBuilder.h"
#include "Navigation.h"
#endif
NavMeshBoundsVolume::NavMeshBoundsVolume(const SpawnParams& params)
@@ -55,9 +55,30 @@ void NavMeshBoundsVolume::OnBoundsChanged(const BoundingBox& prevBounds)
// Auto-rebuild modified navmesh area
if (IsDuringPlay() && IsActiveInHierarchy() && !Editor::IsPlayMode && Editor::Managed->CanAutoBuildNavMesh())
{
BoundingBox dirtyBounds;
BoundingBox::Merge(prevBounds, _box, dirtyBounds);
NavMeshBuilder::Build(GetScene(), dirtyBounds, ManagedEditor::ManagedEditorOptions.AutoRebuildNavMeshTimeoutMs);
if (_box.Intersects(prevBounds))
{
// Bounds were moved a bit so merge into a single request (for performance reasons)
BoundingBox dirtyBounds;
BoundingBox::Merge(prevBounds, _box, dirtyBounds);
Navigation::BuildNavMesh(dirtyBounds, ManagedEditor::ManagedEditorOptions.AutoRebuildNavMeshTimeoutMs);
}
else
{
// Dirty each bounds in separate
Navigation::BuildNavMesh(prevBounds, ManagedEditor::ManagedEditorOptions.AutoRebuildNavMeshTimeoutMs);
Navigation::BuildNavMesh(_box, ManagedEditor::ManagedEditorOptions.AutoRebuildNavMeshTimeoutMs);
}
}
}
void NavMeshBoundsVolume::OnActiveInTreeChanged()
{
BoxVolume::OnActiveInTreeChanged();
// Auto-rebuild
if (IsDuringPlay() && !Editor::IsPlayMode && Editor::Managed->CanAutoBuildNavMesh())
{
Navigation::BuildNavMesh(_box, ManagedEditor::ManagedEditorOptions.AutoRebuildNavMeshTimeoutMs);
}
}

View File

@@ -30,6 +30,7 @@ protected:
void OnDisable() override;
#if USE_EDITOR
void OnBoundsChanged(const BoundingBox& prevBounds) override;
void OnActiveInTreeChanged() override;
Color GetWiresColor() override;
#endif
};

View File

@@ -3,6 +3,7 @@
#if COMPILE_WITH_NAV_MESH_BUILDER
#include "NavMeshBuilder.h"
#include "Navigation.h"
#include "NavMesh.h"
#include "NavigationSettings.h"
#include "NavMeshBoundsVolume.h"
@@ -706,6 +707,7 @@ struct BuildRequest
ScriptingObjectReference<Scene> Scene;
DateTime Time;
BoundingBox DirtyBounds;
bool SpecificScene;
};
CriticalSection NavBuildQueueLocker;
@@ -713,6 +715,7 @@ Array<BuildRequest> NavBuildQueue;
CriticalSection NavBuildTasksLocker;
int32 NavBuildTasksMaxCount = 0;
bool NavBuildCheckMissingNavMeshes = false;
Array<class NavMeshTileBuildTask*> NavBuildTasks;
class NavMeshTileBuildTask : public ThreadPoolTask
@@ -733,7 +736,7 @@ public:
bool Run() override
{
PROFILE_CPU_NAMED("BuildNavMeshTile");
PROFILE_MEM(Navigation);
PROFILE_MEM(NavigationBuilding);
const auto navMesh = NavMesh.Get();
if (!navMesh)
return false;
@@ -776,13 +779,13 @@ void CancelNavMeshTileBuildTasks(NavMeshRuntime* runtime)
NavBuildTasksLocker.Unlock();
}
void CancelNavMeshTileBuildTasks(NavMeshRuntime* runtime, int32 x, int32 y)
void CancelNavMeshTileBuildTasks(NavMeshRuntime* runtime, int32 x, int32 y, NavMesh* navMesh)
{
NavBuildTasksLocker.Lock();
for (int32 i = 0; i < NavBuildTasks.Count(); i++)
{
auto task = NavBuildTasks[i];
if (task->Runtime == runtime && task->X == x && task->Y == y)
if (task->Runtime == runtime && task->X == x && task->Y == y && task->NavMesh == navMesh)
{
NavBuildTasksLocker.Unlock();
@@ -838,7 +841,7 @@ void NavMeshBuilder::Init()
Level::SceneUnloading.Bind<OnSceneUnloading>();
}
bool NavMeshBuilder::IsBuildingNavMesh()
bool Navigation::IsBuildingNavMesh()
{
NavBuildTasksLocker.Lock();
const bool hasAnyTask = NavBuildTasks.HasItems();
@@ -847,7 +850,7 @@ bool NavMeshBuilder::IsBuildingNavMesh()
return hasAnyTask;
}
float NavMeshBuilder::GetNavMeshBuildingProgress()
float Navigation::GetNavMeshBuildingProgress()
{
NavBuildTasksLocker.Lock();
float result = 1.0f;
@@ -907,15 +910,13 @@ void BuildDirtyBounds(Scene* scene, NavMesh* navMesh, const BoundingBox& dirtyBo
// Align dirty bounds to tile size
BoundingBox dirtyBoundsNavMesh;
BoundingBox::Transform(dirtyBounds, worldToNavMesh, dirtyBoundsNavMesh);
BoundingBox dirtyBoundsAligned;
dirtyBoundsAligned.Minimum = Float3::Floor(dirtyBoundsNavMesh.Minimum / tileSize) * tileSize;
dirtyBoundsAligned.Maximum = Float3::Ceil(dirtyBoundsNavMesh.Maximum / tileSize) * tileSize;
dirtyBoundsNavMesh.Minimum = Float3::Floor(dirtyBoundsNavMesh.Minimum / tileSize) * tileSize;
dirtyBoundsNavMesh.Maximum = Float3::Ceil(dirtyBoundsNavMesh.Maximum / tileSize) * tileSize;
// Calculate tiles range for the given navigation dirty bounds (aligned to tiles size)
const Int3 tilesMin(dirtyBoundsAligned.Minimum / tileSize);
const Int3 tilesMax(dirtyBoundsAligned.Maximum / tileSize);
const int32 tilesX = tilesMax.X - tilesMin.X;
const int32 tilesY = tilesMax.Z - tilesMin.Z;
const Int3 tilesMin(dirtyBoundsNavMesh.Minimum / tileSize);
const Int3 tilesMax(dirtyBoundsNavMesh.Maximum / tileSize);
const int32 tilesXZ = (tilesMax.X - tilesMin.X) * (tilesMax.Z - tilesMin.Z);
{
PROFILE_CPU_NAMED("Prepare");
@@ -932,18 +933,18 @@ void BuildDirtyBounds(Scene* scene, NavMesh* navMesh, const BoundingBox& dirtyBo
// Remove all tiles from navmesh runtime
runtime->RemoveTiles(navMesh);
runtime->SetTileSize(tileSize);
runtime->EnsureCapacity(tilesX * tilesY);
runtime->EnsureCapacity(tilesXZ);
// Remove all tiles from navmesh data
navMesh->Data.TileSize = tileSize;
navMesh->Data.Tiles.Clear();
navMesh->Data.Tiles.EnsureCapacity(tilesX * tilesX);
navMesh->Data.Tiles.EnsureCapacity(tilesXZ);
navMesh->IsDataDirty = true;
}
else
{
// Ensure to have enough memory for tiles
runtime->EnsureCapacity(tilesX * tilesY);
runtime->EnsureCapacity(tilesXZ);
}
runtime->Locker.Unlock();
@@ -959,11 +960,10 @@ void BuildDirtyBounds(Scene* scene, NavMesh* navMesh, const BoundingBox& dirtyBo
// Cache navmesh volumes
Array<BoundingBox, InlinedAllocation<8>> volumes;
for (int32 i = 0; i < scene->Navigation.Volumes.Count(); i++)
for (const NavMeshBoundsVolume* volume : scene->Navigation.Volumes)
{
const auto volume = scene->Navigation.Volumes.Get()[i];
if (!volume->AgentsMask.IsNavMeshSupported(navMesh->Properties) ||
!volume->GetBox().Intersects(dirtyBoundsAligned))
!volume->GetBox().Intersects(dirtyBoundsNavMesh))
continue;
auto& bounds = volumes.AddOne();
BoundingBox::Transform(volume->GetBox(), worldToNavMesh, bounds);
@@ -1026,7 +1026,7 @@ void BuildDirtyBounds(Scene* scene, NavMesh* navMesh, const BoundingBox& dirtyBo
for (const auto& tile : unusedTiles)
{
// Wait for any async tasks that are producing this tile
CancelNavMeshTileBuildTasks(runtime, tile.X, tile.Y);
CancelNavMeshTileBuildTasks(runtime, tile.X, tile.Y, navMesh);
}
runtime->Locker.Lock();
for (const auto& tile : unusedTiles)
@@ -1095,6 +1095,7 @@ void BuildDirtyBounds(Scene* scene, const BoundingBox& dirtyBounds, bool rebuild
else if (settings->AutoAddMissingNavMeshes)
{
// Spawn missing navmesh
PROFILE_MEM(Navigation);
navMesh = New<NavMesh>();
navMesh->SetStaticFlags(StaticFlags::FullyStatic);
navMesh->SetName(TEXT("NavMesh.") + navMeshProperties.Name);
@@ -1108,39 +1109,6 @@ void BuildDirtyBounds(Scene* scene, const BoundingBox& dirtyBounds, bool rebuild
{
BuildDirtyBounds(scene, navMesh, dirtyBounds, rebuild);
}
// Remove unused navmeshes
if (settings->AutoRemoveMissingNavMeshes)
{
for (NavMesh* navMesh : scene->Navigation.Meshes)
{
// Skip used navmeshes
if (navMesh->Data.Tiles.HasItems())
continue;
// Skip navmeshes during async building
int32 usageCount = 0;
NavBuildTasksLocker.Lock();
for (int32 i = 0; i < NavBuildTasks.Count(); i++)
{
if (NavBuildTasks.Get()[i]->NavMesh == navMesh)
usageCount++;
}
NavBuildTasksLocker.Unlock();
if (usageCount != 0)
continue;
navMesh->DeleteObject();
}
}
}
void BuildWholeScene(Scene* scene)
{
// Compute total navigation area bounds
const BoundingBox worldBounds = scene->Navigation.GetNavigationBounds();
BuildDirtyBounds(scene, worldBounds, true);
}
void ClearNavigation(Scene* scene)
@@ -1154,22 +1122,58 @@ void ClearNavigation(Scene* scene)
}
}
void BuildNavigation(BuildRequest& request)
{
// If scene is not specified then build all loaded scenes
if (!request.Scene)
{
for (Scene* scene : Level::Scenes)
{
request.Scene = scene;
BuildNavigation(request);
}
return;
}
// Early out if scene is not using navigation
if (request.Scene->Navigation.Volumes.IsEmpty())
{
ClearNavigation(request.Scene);
return;
}
// Check if similar request is already in a queue
for (auto& e : NavBuildQueue)
{
if (e.Scene == request.Scene && (e.DirtyBounds == request.DirtyBounds || request.DirtyBounds == BoundingBox::Empty))
{
e = request;
return;
}
}
// Enqueue request
NavBuildQueue.Add(request);
}
void NavMeshBuilder::Update()
{
PROFILE_MEM(Navigation);
PROFILE_MEM(NavigationBuilding);
ScopeLock lock(NavBuildQueueLocker);
// Process nav mesh building requests and kick the tasks
const auto now = DateTime::NowUTC();
bool didRebuild = false;
for (int32 i = 0; NavBuildQueue.HasItems() && i < NavBuildQueue.Count(); i++)
{
auto req = NavBuildQueue.Get()[i];
if (now - req.Time >= 0)
{
NavBuildQueue.RemoveAt(i--);
const auto scene = req.Scene.Get();
Scene* scene = req.Scene.Get();
if (!scene)
continue;
bool rebuild = req.DirtyBounds == BoundingBox::Empty;
// Early out if scene has no bounds volumes to define nav mesh area
if (scene->Navigation.Volumes.IsEmpty())
@@ -1179,80 +1183,69 @@ void NavMeshBuilder::Update()
}
// Check if build a custom dirty bounds or whole scene
if (req.DirtyBounds == BoundingBox::Empty)
{
BuildWholeScene(scene);
}
if (rebuild)
req.DirtyBounds = scene->Navigation.GetNavigationBounds(); // Compute total navigation area bounds
if (didRebuild)
rebuild = false; // When rebuilding navmesh for multiple scenes, rebuild only the first one (other scenes will use additive update)
else
didRebuild = true;
BuildDirtyBounds(scene, req.DirtyBounds, rebuild);
NavBuildCheckMissingNavMeshes = true;
}
}
// Remove unused navmeshes (when all active tasks are done)
// TODO: ignore AutoRemoveMissingNavMeshes in game and make it editor-only?
if (NavBuildCheckMissingNavMeshes && NavBuildTasksMaxCount == 0 && NavigationSettings::Get()->AutoRemoveMissingNavMeshes)
{
NavBuildCheckMissingNavMeshes = false;
NavBuildTasksLocker.Lock();
int32 taskCount = NavBuildTasks.Count();
NavBuildTasksLocker.Unlock();
if (taskCount == 0)
{
for (Scene* scene : Level::Scenes)
{
BuildDirtyBounds(scene, req.DirtyBounds, false);
for (NavMesh* navMesh : scene->Navigation.Meshes)
{
if (!navMesh->Data.Tiles.HasItems())
{
navMesh->DeleteObject();
}
}
}
}
}
}
void NavMeshBuilder::Build(Scene* scene, float timeoutMs)
void Navigation::BuildNavMesh(Scene* scene, float timeoutMs)
{
if (!scene)
{
LOG(Warning, "Could not generate navmesh without scene.");
return;
}
// Early out if scene is not using navigation
if (scene->Navigation.Volumes.IsEmpty())
{
ClearNavigation(scene);
return;
}
PROFILE_CPU_NAMED("NavMeshBuilder");
PROFILE_MEM(Navigation);
PROFILE_CPU();
PROFILE_MEM(NavigationBuilding);
ScopeLock lock(NavBuildQueueLocker);
BuildRequest req;
req.Scene = scene;
req.Time = DateTime::NowUTC() + TimeSpan::FromMilliseconds(timeoutMs);
req.DirtyBounds = BoundingBox::Empty;
for (int32 i = 0; i < NavBuildQueue.Count(); i++)
{
auto& e = NavBuildQueue.Get()[i];
if (e.Scene == scene && e.DirtyBounds == req.DirtyBounds)
{
e = req;
return;
}
}
NavBuildQueue.Add(req);
req.SpecificScene = scene != nullptr;
BuildNavigation(req);
}
void NavMeshBuilder::Build(Scene* scene, const BoundingBox& dirtyBounds, float timeoutMs)
void Navigation::BuildNavMesh(const BoundingBox& dirtyBounds, Scene* scene, float timeoutMs)
{
if (!scene)
{
LOG(Warning, "Could not generate navmesh without scene.");
return;
}
// Early out if scene is not using navigation
if (scene->Navigation.Volumes.IsEmpty())
{
ClearNavigation(scene);
return;
}
PROFILE_CPU_NAMED("NavMeshBuilder");
PROFILE_MEM(Navigation);
if (dirtyBounds.GetVolume() <= ZeroTolerance)
return; // Skip updating empty bounds
PROFILE_CPU();
PROFILE_MEM(NavigationBuilding);
ScopeLock lock(NavBuildQueueLocker);
BuildRequest req;
req.Scene = scene;
req.Time = DateTime::NowUTC() + TimeSpan::FromMilliseconds(timeoutMs);
req.DirtyBounds = dirtyBounds;
NavBuildQueue.Add(req);
req.SpecificScene = scene != nullptr;
BuildNavigation(req);
}
#endif

View File

@@ -15,11 +15,7 @@ class FLAXENGINE_API NavMeshBuilder
{
public:
static void Init();
static bool IsBuildingNavMesh();
static float GetNavMeshBuildingProgress();
static void Update();
static void Build(Scene* scene, float timeoutMs);
static void Build(Scene* scene, const BoundingBox& dirtyBounds, float timeoutMs);
};
#endif

View File

@@ -5,6 +5,9 @@
#include "NavMesh.h"
#include "Engine/Core/Log.h"
#include "Engine/Core/Random.h"
#if COMPILE_WITH_DEBUG_DRAW
#include "Engine/Level/Scene/Scene.h"
#endif
#include "Engine/Profiler/ProfilerCPU.h"
#include "Engine/Profiler/ProfilerMemory.h"
#include "Engine/Threading/Threading.h"
@@ -326,7 +329,7 @@ void NavMeshRuntime::EnsureCapacity(int32 tilesToAddCount)
if (newTilesCount <= capacity)
return;
PROFILE_CPU_NAMED("NavMeshRuntime.EnsureCapacity");
PROFILE_MEM(Navigation);
PROFILE_MEM(NavigationMesh);
// Navmesh tiles capacity growing rule
int32 newCapacity = capacity ? capacity : 32;
@@ -387,7 +390,7 @@ void NavMeshRuntime::AddTiles(NavMesh* navMesh)
return;
auto& data = navMesh->Data;
PROFILE_CPU_NAMED("NavMeshRuntime.AddTiles");
PROFILE_MEM(Navigation);
PROFILE_MEM(NavigationMesh);
ScopeLock lock(Locker);
// Validate data (must match navmesh) or init navmesh to match the tiles options
@@ -419,7 +422,7 @@ void NavMeshRuntime::AddTile(NavMesh* navMesh, NavMeshTileData& tileData)
ASSERT(navMesh);
auto& data = navMesh->Data;
PROFILE_CPU_NAMED("NavMeshRuntime.AddTile");
PROFILE_MEM(Navigation);
PROFILE_MEM(NavigationMesh);
ScopeLock lock(Locker);
// Validate data (must match navmesh) or init navmesh to match the tiles options
@@ -603,7 +606,21 @@ void NavMeshRuntime::DebugDraw()
if (!tile->header)
continue;
//DebugDraw::DrawWireBox(*(BoundingBox*)&tile->header->bmin[0], Color::CadetBlue);
#if 0
// Debug draw tile bounds and owner scene name
BoundingBox tileBounds = *(BoundingBox*)&tile->header->bmin[0];
DebugDraw::DrawWireBox(tileBounds, Color::CadetBlue);
// TODO: build map from tile coords to tile data to avoid this loop
for (const auto& e : _tiles)
{
if (e.X == tile->header->x && e.Y == tile->header->y && e.Layer == tile->header->layer)
{
if (e.NavMesh && e.NavMesh->GetScene())
DebugDraw::DrawText(e.NavMesh->GetScene()->GetName(), tileBounds.Minimum + tileBounds.GetSize() * Float3(0.5f, 0.8f, 0.5f), Color::CadetBlue);
break;
}
}
#endif
for (int i = 0; i < tile->header->polyCount; i++)
{

View File

@@ -111,7 +111,7 @@ public:
/// <param name="startPosition">The start position.</param>
/// <param name="hitInfo">The result hit information. Valid only when query succeed.</param>
/// <param name="maxDistance">The maximum distance to search for wall (search radius).</param>
/// <returns>True if ray hits an matching object, otherwise false.</returns>
/// <returns>True if ray hits a matching object, otherwise false.</returns>
API_FUNCTION() bool FindDistanceToWall(const Vector3& startPosition, NavMeshHit& hitInfo, float maxDistance = MAX_float) const;
/// <summary>
@@ -187,7 +187,7 @@ public:
/// <param name="startPosition">The start position.</param>
/// <param name="endPosition">The end position.</param>
/// <param name="hitInfo">The result hit information. Valid only when query succeed.</param>
/// <returns>True if ray hits an matching object, otherwise false.</returns>
/// <returns>True if ray hits a matching object, otherwise false.</returns>
API_FUNCTION() bool RayCast(const Vector3& startPosition, const Vector3& endPosition, API_PARAM(Out) NavMeshHit& hitInfo) const;
public:

View File

@@ -2,7 +2,7 @@
#include "NavModifierVolume.h"
#include "NavigationSettings.h"
#include "NavMeshBuilder.h"
#include "Navigation.h"
#include "Engine/Level/Scene/Scene.h"
#include "Engine/Serialization/Serialization.h"
#if USE_EDITOR
@@ -83,7 +83,7 @@ void NavModifierVolume::OnBoundsChanged(const BoundingBox& prevBounds)
#else
const float timeoutMs = 0.0f;
#endif
NavMeshBuilder::Build(GetScene(), dirtyBounds, timeoutMs);
Navigation::BuildNavMesh(dirtyBounds, GetScene(), timeoutMs);
}
#endif
}

View File

@@ -180,7 +180,7 @@ NavigationService NavigationServiceInstance;
void* dtAllocDefault(size_t size, dtAllocHint)
{
PROFILE_MEM(Navigation);
PROFILE_MEM(NavigationMesh);
return Allocator::Allocate(size);
}
@@ -382,30 +382,6 @@ bool Navigation::RayCast(const Vector3& startPosition, const Vector3& endPositio
return NavMeshes.First()->RayCast(startPosition, endPosition, hitInfo);
}
#if COMPILE_WITH_NAV_MESH_BUILDER
bool Navigation::IsBuildingNavMesh()
{
return NavMeshBuilder::IsBuildingNavMesh();
}
float Navigation::GetNavMeshBuildingProgress()
{
return NavMeshBuilder::GetNavMeshBuildingProgress();
}
void Navigation::BuildNavMesh(Scene* scene, float timeoutMs)
{
NavMeshBuilder::Build(scene, timeoutMs);
}
void Navigation::BuildNavMesh(Scene* scene, const BoundingBox& dirtyBounds, float timeoutMs)
{
NavMeshBuilder::Build(scene, dirtyBounds, timeoutMs);
}
#endif
#if COMPILE_WITH_DEBUG_DRAW
void Navigation::DrawNavMesh()

View File

@@ -19,7 +19,7 @@ public:
/// <param name="startPosition">The start position.</param>
/// <param name="hitInfo">The result hit information. Valid only when query succeed.</param>
/// <param name="maxDistance">The maximum distance to search for wall (search radius).</param>
/// <returns>True if ray hits an matching object, otherwise false.</returns>
/// <returns>True if ray hits a matching object, otherwise false.</returns>
API_FUNCTION() static bool FindDistanceToWall(const Vector3& startPosition, API_PARAM(Out) NavMeshHit& hitInfo, float maxDistance = MAX_float);
/// <summary>
@@ -81,12 +81,10 @@ public:
/// <param name="startPosition">The start position.</param>
/// <param name="endPosition">The end position.</param>
/// <param name="hitInfo">The result hit information. Valid only when query succeed.</param>
/// <returns>True if ray hits an matching object, otherwise false.</returns>
/// <returns>True if ray hits a matching object, otherwise false.</returns>
API_FUNCTION() static bool RayCast(const Vector3& startPosition, const Vector3& endPosition, API_PARAM(Out) NavMeshHit& hitInfo);
public:
#if COMPILE_WITH_NAV_MESH_BUILDER
/// <summary>
/// Returns true if navigation system is during navmesh building (any request is valid or async task active).
/// </summary>
@@ -100,32 +98,49 @@ public:
/// <summary>
/// Builds the Nav Mesh for the given scene (discards all its tiles).
/// </summary>
/// <remarks>
/// Requests are enqueued till the next game scripts update. Actual navmesh building in done via Thread Pool tasks in a background to prevent game thread stalls.
/// </remarks>
/// <param name="scene">The scene.</param>
/// <remarks>Requests are enqueued till the next game scripts update. Actual navmesh building in done via Thread Pool tasks in a background to prevent game thread stalls.</remarks>
/// <param name="scene">The scene. Pass null to build navmesh for all loaded scenes.</param>
/// <param name="timeoutMs">The timeout to wait before building Nav Mesh (in milliseconds).</param>
API_FUNCTION() static void BuildNavMesh(Scene* scene, float timeoutMs = 50);
API_FUNCTION() static void BuildNavMesh(Scene* scene = nullptr, float timeoutMs = 50);
/// <summary>
/// Builds the Nav Mesh for the given scene (builds only the tiles overlapping the given bounding box).
/// </summary>
/// <remarks>
/// Requests are enqueued till the next game scripts update. Actual navmesh building in done via Thread Pool tasks in a background to prevent game thread stalls.
/// </remarks>
/// <remarks>Requests are enqueued till the next game scripts update. Actual navmesh building in done via Thread Pool tasks in a background to prevent game thread stalls.</remarks>
/// <param name="dirtyBounds">The bounds in world-space to build overlapping tiles.</param>
/// <param name="scene">The scene. Pass null to build navmesh for all loaded scenes that intersect with a given bounds.</param>
/// <param name="timeoutMs">The timeout to wait before building Nav Mesh (in milliseconds).</param>
API_FUNCTION() static void BuildNavMesh(const BoundingBox& dirtyBounds, Scene* scene = nullptr, float timeoutMs = 50);
/// <summary>
/// Builds the Nav Mesh for all the loaded scenes (builds only the tiles overlapping the given bounding box).
/// </summary>
/// <remarks>Requests are enqueued till the next game scripts update. Actual navmesh building in done via Thread Pool tasks in a background to prevent game thread stalls.</remarks>
/// <param name="dirtyBounds">The bounds in world-space to build overlapping tiles.</param>
/// <param name="timeoutMs">The timeout to wait before building Nav Mesh (in milliseconds).</param>
API_FUNCTION() static void BuildNavMesh(const BoundingBox& dirtyBounds, float timeoutMs = 50)
{
BuildNavMesh(dirtyBounds, nullptr, timeoutMs);
}
/// <summary>
/// Builds the Nav Mesh for the given scene (builds only the tiles overlapping the given bounding box).
/// [Deprecated in v1.12]
/// </summary>
/// <remarks>Requests are enqueued till the next game scripts update. Actual navmesh building in done via Thread Pool tasks in a background to prevent game thread stalls.</remarks>
/// <param name="scene">The scene.</param>
/// <param name="dirtyBounds">The bounds in world-space to build overlapping tiles.</param>
/// <param name="timeoutMs">The timeout to wait before building Nav Mesh (in milliseconds).</param>
API_FUNCTION() static void BuildNavMesh(Scene* scene, const BoundingBox& dirtyBounds, float timeoutMs = 50);
API_FUNCTION() DEPRECATED("Use BuildNavMesh with reordered arguments instead") static void BuildNavMesh(Scene* scene, const BoundingBox& dirtyBounds, float timeoutMs = 50)
{
BuildNavMesh(dirtyBounds, scene, timeoutMs);
}
#endif
#if COMPILE_WITH_DEBUG_DRAW
/// <summary>
/// Draws the navigation for all the scenes (uses DebugDraw interface).
/// </summary>
static void DrawNavMesh();
#endif
};

Some files were not shown because too many files have changed in this diff Show More