Merge branch 'master' into 1.8

This commit is contained in:
Wojtek Figat
2024-01-05 17:13:51 +01:00
49 changed files with 619 additions and 261 deletions

View File

@@ -22,6 +22,22 @@ namespace FlaxEditor.Content
/// </summary>
public abstract string TypeName { get; }
/// <summary>
/// Gets a value indicating whether this instance is virtual Proxy not linked to any asset.
/// </summary>
protected virtual bool IsVirtual { get; }
/// <summary>
/// Determines whether [is virtual proxy].
/// </summary>
/// <returns>
/// <c>true</c> if [is virtual proxy]; otherwise, <c>false</c>.
/// </returns>
public bool IsVirtualProxy()
{
return IsVirtual && CanExport == false;
}
/// <summary>
/// Checks if this proxy supports the given asset type id at the given path.
/// </summary>

View File

@@ -1,9 +1,9 @@
// Copyright (c) 2012-2023 Wojciech Figat. All rights reserved.
using FlaxEditor.CustomEditors;
using FlaxEditor.GUI.Docking;
using FlaxEditor.Windows;
using FlaxEngine.GUI;
using DockState = FlaxEditor.GUI.Docking.DockState;
namespace FlaxEditor
{
@@ -97,9 +97,12 @@ namespace FlaxEditor
/// Shows the window.
/// </summary>
/// <param name="state">Initial window state.</param>
public void Show(DockState state = DockState.Float)
/// <param name="toDock">The panel to dock to, if any.</param>
/// <param name="autoSelect">Only used if <paramref name="toDock"/> is set. If true the window will be selected after docking it.</param>
/// <param name="splitterValue">The splitter value to use if toDock is not null. If not specified, a default value will be used.</param>
public void Show(DockState state = DockState.Float, DockPanel toDock = null, bool autoSelect = true, float? splitterValue = null)
{
_win.Show(state);
_win.Show(state, toDock, autoSelect, splitterValue);
}
}
}

View File

@@ -3,6 +3,7 @@
using System.Linq;
using FlaxEditor.CustomEditors.Elements;
using FlaxEditor.GUI.Dialogs;
using FlaxEditor.GUI.Input;
using FlaxEngine;
using FlaxEngine.GUI;
@@ -17,6 +18,7 @@ namespace FlaxEditor.CustomEditors.Editors
private FloatValueElement _yElement;
private FloatValueElement _zElement;
private FloatValueElement _wElement;
private ColorValueBox _colorBox;
private CustomElement<ColorSelector> _trackball;
/// <inheritdoc />
@@ -53,7 +55,7 @@ namespace FlaxEditor.CustomEditors.Editors
gridControl.SlotPadding = new Margin(4, 2, 2, 2);
gridControl.ClipChildren = false;
gridControl.SlotsHorizontally = 1;
gridControl.SlotsVertically = 4;
gridControl.SlotsVertically = 5;
LimitAttribute limit = null;
var attributes = Values.GetAttributes();
@@ -61,7 +63,8 @@ namespace FlaxEditor.CustomEditors.Editors
{
limit = (LimitAttribute)attributes.FirstOrDefault(x => x is LimitAttribute);
}
_colorBox = grid.Custom<ColorValueBox>().CustomControl;
_colorBox.ValueChanged += OnColorBoxChanged;
_xElement = CreateFloatEditor(grid, limit, Color.Red);
_yElement = CreateFloatEditor(grid, limit, Color.Green);
_zElement = CreateFloatEditor(grid, limit, Color.Blue);
@@ -93,6 +96,13 @@ namespace FlaxEditor.CustomEditors.Editors
SetValue(value, token);
}
private void OnColorBoxChanged()
{
var token = _colorBox.IsSliding ? this : null;
var color = _colorBox.Value;
SetValue(new Float4(color.R, color.G, color.B, color.A), token);
}
private void OnValueChanged()
{
if (IsSetBlocked)
@@ -130,6 +140,7 @@ namespace FlaxEditor.CustomEditors.Editors
_yElement.Value = color.Y;
_zElement.Value = color.Z;
_wElement.Value = scale;
_colorBox.Value = new Color(color.X, color.Y, color.Z, scale);
_trackball.CustomControl.Color = Float3.Abs(color);
}
}

View File

@@ -88,6 +88,8 @@ namespace FlaxEditor.CustomEditors.Editors
_element.Value = asFloat;
else if (value is double asDouble)
_element.Value = (float)asDouble;
else if (value is int asInt)
_element.Value = (float)asInt;
else
throw new Exception(string.Format("Invalid value type {0}.", value?.GetType().ToString() ?? "<null>"));
}

View File

@@ -66,9 +66,9 @@ namespace FlaxEditor.CustomEditors.Editors
public HeaderAttribute Header;
/// <summary>
/// The visible if attribute.
/// The visible if attributes.
/// </summary>
public VisibleIfAttribute VisibleIf;
public VisibleIfAttribute[] VisibleIfs;
/// <summary>
/// The read-only attribute usage flag.
@@ -128,7 +128,7 @@ namespace FlaxEditor.CustomEditors.Editors
CustomEditorAlias = (CustomEditorAliasAttribute)attributes.FirstOrDefault(x => x is CustomEditorAliasAttribute);
Space = (SpaceAttribute)attributes.FirstOrDefault(x => x is SpaceAttribute);
Header = (HeaderAttribute)attributes.FirstOrDefault(x => x is HeaderAttribute);
VisibleIf = (VisibleIfAttribute)attributes.FirstOrDefault(x => x is VisibleIfAttribute);
VisibleIfs = attributes.OfType<VisibleIfAttribute>().ToArray();
IsReadOnly = attributes.FirstOrDefault(x => x is ReadOnlyAttribute) != null;
ExpandGroups = attributes.FirstOrDefault(x => x is ExpandGroupsAttribute) != null;
@@ -210,17 +210,24 @@ namespace FlaxEditor.CustomEditors.Editors
private struct VisibleIfCache
{
public ScriptMemberInfo Target;
public ScriptMemberInfo Source;
public ScriptMemberInfo[] Sources;
public PropertiesListElement PropertiesList;
public GroupElement Group;
public bool Invert;
public bool[] InversionList;
public int LabelIndex;
public bool GetValue(object instance)
{
var value = (bool)Source.GetValue(instance);
if (Invert)
value = !value;
bool value = true;
for (int i = 0; i < Sources.Length; i++)
{
bool currentValue = (bool)Sources[i].GetValue(instance);
if (InversionList[i])
currentValue = !currentValue;
value = value && currentValue;
}
return value;
}
}
@@ -298,40 +305,48 @@ namespace FlaxEditor.CustomEditors.Editors
return items;
}
private static ScriptMemberInfo GetVisibleIfSource(ScriptType type, VisibleIfAttribute visibleIf)
private static ScriptMemberInfo[] GetVisibleIfSources(ScriptType type, VisibleIfAttribute[] visibleIfs)
{
var property = type.GetProperty(visibleIf.MemberName, BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.Static);
if (property != ScriptMemberInfo.Null)
ScriptMemberInfo[] members = Array.Empty<ScriptMemberInfo>();
for (int i = 0; i < visibleIfs.Length; i++)
{
if (!property.HasGet)
var property = type.GetProperty(visibleIfs[i].MemberName, BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.Static);
if (property != ScriptMemberInfo.Null)
{
Debug.LogError("Invalid VisibleIf rule. Property has missing getter " + visibleIf.MemberName);
return ScriptMemberInfo.Null;
if (!property.HasGet)
{
Debug.LogError("Invalid VisibleIf rule. Property has missing getter " + visibleIfs[i].MemberName);
continue;
}
if (property.ValueType.Type != typeof(bool))
{
Debug.LogError("Invalid VisibleIf rule. Property has to return bool type " + visibleIfs[i].MemberName);
continue;
}
members = members.Append(property).ToArray();
continue;
}
if (property.ValueType.Type != typeof(bool))
var field = type.GetField(visibleIfs[i].MemberName, BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.Static);
if (field != ScriptMemberInfo.Null)
{
Debug.LogError("Invalid VisibleIf rule. Property has to return bool type " + visibleIf.MemberName);
return ScriptMemberInfo.Null;
if (field.ValueType.Type != typeof(bool))
{
Debug.LogError("Invalid VisibleIf rule. Field has to be bool type " + visibleIfs[i].MemberName);
continue;
}
members = members.Append(field).ToArray();
continue;
}
return property;
Debug.LogError("Invalid VisibleIf rule. Cannot find member " + visibleIfs[i].MemberName);
}
var field = type.GetField(visibleIf.MemberName, BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.Static);
if (field != ScriptMemberInfo.Null)
{
if (field.ValueType.Type != typeof(bool))
{
Debug.LogError("Invalid VisibleIf rule. Field has to be bool type " + visibleIf.MemberName);
return ScriptMemberInfo.Null;
}
return field;
}
Debug.LogError("Invalid VisibleIf rule. Cannot find member " + visibleIf.MemberName);
return ScriptMemberInfo.Null;
return members;
}
private static void GroupPanelCheckIfCanRevert(LayoutElementsContainer layout, ref bool canRevertReference, ref bool canRevertDefault)
@@ -575,7 +590,7 @@ namespace FlaxEditor.CustomEditors.Editors
protected virtual void SpawnProperty(LayoutElementsContainer itemLayout, ValueContainer itemValues, ItemInfo item)
{
int labelIndex = 0;
if ((item.IsReadOnly || item.VisibleIf != null) &&
if ((item.IsReadOnly || item.VisibleIfs.Length > 0) &&
itemLayout.Children.Count > 0 &&
itemLayout.Children[itemLayout.Children.Count - 1] is PropertiesListElement propertiesListElement)
{
@@ -616,7 +631,7 @@ namespace FlaxEditor.CustomEditors.Editors
}
}
}
if (item.VisibleIf != null && itemLayout.Children.Count > 0)
if (item.VisibleIfs.Length > 0 && itemLayout.Children.Count > 0)
{
PropertiesListElement list = null;
GroupElement group = null;
@@ -628,8 +643,8 @@ namespace FlaxEditor.CustomEditors.Editors
return;
// Get source member used to check rule
var sourceMember = GetVisibleIfSource(item.Info.DeclaringType, item.VisibleIf);
if (sourceMember == ScriptType.Null)
var sourceMembers = GetVisibleIfSources(item.Info.DeclaringType, item.VisibleIfs);
if (sourceMembers.Length == 0)
return;
// Resize cache
@@ -645,11 +660,11 @@ namespace FlaxEditor.CustomEditors.Editors
_visibleIfCaches[count] = new VisibleIfCache
{
Target = item.Info,
Source = sourceMember,
Sources = sourceMembers,
PropertiesList = list,
Group = group,
LabelIndex = labelIndex,
Invert = item.VisibleIf.Invert,
InversionList = item.VisibleIfs.Select((x, i) => x.Invert).ToArray(),
};
}
}

View File

@@ -1,8 +1,12 @@
// Copyright (c) 2012-2023 Wojciech Figat. All rights reserved.
using System;
using System.Linq;
using FlaxEditor.CustomEditors.Elements;
using FlaxEditor.GUI;
using FlaxEditor.Scripting;
using FlaxEngine;
using FlaxEngine.Utilities;
namespace FlaxEditor.CustomEditors.Editors
{
@@ -13,6 +17,9 @@ namespace FlaxEditor.CustomEditors.Editors
public sealed class GuidEditor : CustomEditor
{
private TextBoxElement _element;
private AssetPicker _picker;
private bool _isReference;
private bool _isRefreshing;
/// <inheritdoc />
public override DisplayStyle Style => DisplayStyle.Inline;
@@ -20,8 +27,55 @@ namespace FlaxEditor.CustomEditors.Editors
/// <inheritdoc />
public override void Initialize(LayoutElementsContainer layout)
{
_element = layout.TextBox();
_element.TextBox.EditEnd += OnEditEnd;
var attributes = Values.GetAttributes();
var assetReference = (AssetReferenceAttribute)attributes?.FirstOrDefault(x => x is AssetReferenceAttribute);
if (assetReference != null)
{
_picker = layout.Custom<AssetPicker>().CustomControl;
ScriptType assetType = new ScriptType();
float height = 48;
if (assetReference.UseSmallPicker)
height = 32;
if (string.IsNullOrEmpty(assetReference.TypeName))
{
assetType = ScriptType.Void;
}
else if (assetReference.TypeName.Length > 1 && assetReference.TypeName[0] == '.')
{
// Generic file picker
assetType = ScriptType.Null;
_picker.Validator.FileExtension = assetReference.TypeName;
}
else
{
var customType = TypeUtils.GetType(assetReference.TypeName);
if (customType != ScriptType.Null)
assetType = customType;
else if (!Content.Settings.GameSettings.OptionalPlatformSettings.Contains(assetReference.TypeName))
Debug.LogWarning(string.Format("Unknown asset type '{0}' to use for asset picker filter.", assetReference.TypeName));
else
assetType = ScriptType.Void;
}
_picker.Validator.AssetType = assetType;
_picker.Height = height;
_picker.SelectedItemChanged += OnSelectedItemChanged;
_isReference = true;
}
else
{
_element = layout.TextBox();
_element.TextBox.EditEnd += OnEditEnd;
}
}
private void OnSelectedItemChanged()
{
if (_isRefreshing)
return;
SetValue(_picker.Validator.SelectedID);
}
private void OnEditEnd()
@@ -36,17 +90,32 @@ namespace FlaxEditor.CustomEditors.Editors
public override void Refresh()
{
base.Refresh();
_isRefreshing = true;
if (HasDifferentValues)
{
_element.TextBox.Text = string.Empty;
_element.TextBox.WatermarkText = "Different values";
if (_isReference)
{
// Not supported
}
else
{
_element.TextBox.Text = string.Empty;
_element.TextBox.WatermarkText = "Different values";
}
}
else
{
_element.TextBox.Text = ((Guid)Values[0]).ToString("D");
_element.TextBox.WatermarkText = string.Empty;
if (_isReference)
{
_picker.Validator.SelectedID = (Guid)Values[0];
}
else
{
_element.TextBox.Text = ((Guid)Values[0]).ToString("D");
_element.TextBox.WatermarkText = string.Empty;
}
}
_isRefreshing = false;
}
}
}

View File

@@ -518,9 +518,9 @@ namespace FlaxEditor.GUI.Docking
}
}
internal virtual void DockWindowInternal(DockState state, DockWindow window)
internal virtual void DockWindowInternal(DockState state, DockWindow window, bool autoSelect = true, float? splitterValue = null)
{
DockWindow(state, window);
DockWindow(state, window, autoSelect, splitterValue);
}
/// <summary>
@@ -528,7 +528,9 @@ namespace FlaxEditor.GUI.Docking
/// </summary>
/// <param name="state">The state.</param>
/// <param name="window">The window.</param>
protected virtual void DockWindow(DockState state, DockWindow window)
/// <param name="autoSelect">Whether or not to automatically select the window after docking it.</param>
/// <param name="splitterValue">The splitter value to use when docking to window.</param>
protected virtual void DockWindow(DockState state, DockWindow window, bool autoSelect = true, float? splitterValue = null)
{
CreateTabsProxy();
@@ -536,12 +538,12 @@ namespace FlaxEditor.GUI.Docking
if (state == DockState.DockFill)
{
// Add tab
AddTab(window);
AddTab(window, autoSelect);
}
else
{
// Create child panel
var dockPanel = CreateChildPanel(state, DefaultSplitterValue);
var dockPanel = CreateChildPanel(state, splitterValue ?? DefaultSplitterValue);
// Dock window as a tab in a child panel
dockPanel.DockWindow(DockState.DockFill, window);

View File

@@ -63,12 +63,9 @@ namespace FlaxEditor.GUI.Docking
public bool IsHidden => !Visible || _dockedTo == null;
/// <summary>
/// Gets the default window size.
/// Gets the default window size (in UI units, unscaled by DPI which is handled by windowing system).
/// </summary>
/// <remarks>
/// Scaled by the DPI, because the window should be large enough for its content on every monitor
/// </remarks>
public virtual Float2 DefaultSize => new Float2(900, 580) * DpiScale;
public virtual Float2 DefaultSize => new Float2(900, 580);
/// <summary>
/// Gets the serialization typename.
@@ -217,7 +214,9 @@ namespace FlaxEditor.GUI.Docking
/// </summary>
/// <param name="state">Initial window state.</param>
/// <param name="toDock">Panel to dock to it.</param>
public void Show(DockState state = DockState.Float, DockPanel toDock = null)
/// <param name="autoSelect">Only used if <paramref name="toDock"/> is set. If true the window will be selected after docking it.</param>
/// <param name="splitterValue">Only used if <paramref name="toDock"/> is set. The splitter value to use. If not specified, a default value will be used.</param>
public void Show(DockState state = DockState.Float, DockPanel toDock = null, bool autoSelect = true, float? splitterValue = null)
{
if (state == DockState.Hidden)
{
@@ -235,7 +234,7 @@ namespace FlaxEditor.GUI.Docking
Undock();
// Then dock
(toDock ?? _masterPanel).DockWindowInternal(state, this);
(toDock ?? _masterPanel).DockWindowInternal(state, this, autoSelect, splitterValue);
OnShow();
PerformLayout();
}

View File

@@ -24,6 +24,11 @@ namespace FlaxEditor.GUI
/// </summary>
public object[] Values { get; set; }
/// <summary>
/// Gets or sets the cell background colors. Null if unused, transparent values are ignored.
/// </summary>
public Color[] BackgroundColors { get; set; }
/// <summary>
/// Gets or sets the row depth level.
/// </summary>
@@ -58,6 +63,7 @@ namespace FlaxEditor.GUI
{
float x = 0;
int end = Mathf.Min(Values.Length, _table.Columns.Length);
var backgroundColors = BackgroundColors;
for (int i = 0; i < end; i++)
{
var column = _table.Columns[i];
@@ -98,6 +104,8 @@ namespace FlaxEditor.GUI
rect.Width -= leftDepthMargin;
Render2D.PushClip(rect);
if (backgroundColors != null && backgroundColors[i].A > 0)
Render2D.FillRectangle(rect, backgroundColors[i]);
Render2D.DrawText(style.FontMedium, text, rect, style.Foreground, column.CellAlignment, TextAlignment.Center);
Render2D.PopClip();

View File

@@ -196,6 +196,25 @@ namespace FlaxEditor.Modules
return null;
}
/// <summary>
/// Gets the virtual proxy object from given path.
/// <br></br>use case if the asset u trying to display is not a flax asset but u like to add custom functionality
/// <br></br>to context menu,or display it the asset
/// </summary>
/// <param name="path">The asset path.</param>
/// <returns>Asset proxy or null if cannot find.</returns>
public AssetProxy GetAssetVirtuallProxy(string path)
{
for (int i = 0; i < Proxy.Count; i++)
{
if (Proxy[i] is AssetProxy proxy && proxy.IsVirtualProxy() && path.EndsWith(proxy.FileExtension, StringComparison.OrdinalIgnoreCase))
{
return proxy;
}
}
return null;
}
/// <summary>
/// Refreshes the given item folder. Tries to find new content items and remove not existing ones.
@@ -996,7 +1015,14 @@ namespace FlaxEditor.Modules
item = proxy?.ConstructItem(path, assetInfo.TypeName, ref assetInfo.ID);
}
if (item == null)
item = new FileItem(path);
{
var proxy = GetAssetVirtuallProxy(path);
item = proxy?.ConstructItem(path, assetInfo.TypeName, ref assetInfo.ID);
if (item == null)
{
item = new FileItem(path);
}
}
// Link
item.ParentFolder = parent.Folder;

View File

@@ -36,6 +36,22 @@ namespace FlaxEditor.Modules
{
public string AssemblyName;
public string TypeName;
public DockState DockState;
public DockPanel DockedTo;
public float? SplitterValue = null;
public bool SelectOnShow = false;
public bool Maximize;
public bool Minimize;
public Float2 FloatSize;
public Float2 FloatPosition;
// Constructor, to allow for default values
public WindowRestoreData()
{
}
}
private readonly List<WindowRestoreData> _restoreWindows = new List<WindowRestoreData>();
@@ -676,7 +692,9 @@ namespace FlaxEditor.Modules
if (newLocation == DockState.Float)
{
// Check if there is a floating window that has the same size
var defaultSize = window.DefaultSize;
var dpi = (float)Platform.Dpi / 96.0f;
var dpiScale = Platform.CustomDpiScale;
var defaultSize = window.DefaultSize * dpi;
for (var i = 0; i < Editor.UI.MasterPanel.FloatingPanels.Count; i++)
{
var win = Editor.UI.MasterPanel.FloatingPanels[i];
@@ -688,7 +706,7 @@ namespace FlaxEditor.Modules
}
}
window.ShowFloating(defaultSize);
window.ShowFloating(defaultSize * dpiScale);
}
else
{
@@ -800,10 +818,38 @@ namespace FlaxEditor.Modules
if (constructor == null || type.IsGenericType)
return;
WindowRestoreData winData;
var winData = new WindowRestoreData();
var panel = win.Window.ParentDockPanel;
// Ensure that this window is only selected following recompilation
// if it was the active tab in its dock panel. Otherwise, there is a
// risk of interrupting the user's workflow by potentially selecting
// background tabs.
winData.SelectOnShow = panel.SelectedTab == win.Window;
if (panel is FloatWindowDockPanel)
{
winData.DockState = DockState.Float;
var window = win.Window.RootWindow.Window;
winData.FloatPosition = window.Position;
winData.FloatSize = window.ClientSize;
winData.Maximize = window.IsMaximized;
winData.Minimize = window.IsMinimized;
}
else
{
if (panel.TabsCount > 1)
{
winData.DockState = DockState.DockFill;
winData.DockedTo = panel;
}else
{
winData.DockState = panel.TryGetDockState(out var splitterValue);
winData.DockedTo = panel.ParentDockPanel;
winData.SplitterValue = splitterValue;
}
}
winData.AssemblyName = type.Assembly.GetName().Name;
winData.TypeName = type.FullName;
// TODO: cache and restore docking info
_restoreWindows.Add(winData);
}
@@ -822,7 +868,24 @@ namespace FlaxEditor.Modules
if (type != null)
{
var win = (CustomEditorWindow)Activator.CreateInstance(type);
win.Show();
win.Show(winData.DockState, winData.DockedTo, winData.SelectOnShow, winData.SplitterValue);
if (winData.DockState == DockState.Float)
{
var window = win.Window.RootWindow.Window;
window.Position = winData.FloatPosition;
if (winData.Maximize)
{
window.Maximize();
}
else if (winData.Minimize)
{
window.Minimize();
}
else
{
window.ClientSize = winData.FloatSize;
}
}
}
}
}

View File

@@ -1310,8 +1310,17 @@ namespace FlaxEditor.Utilities
inputActions.Add(options => options.BuildSDF, Editor.Instance.BuildAllMeshesSDF);
inputActions.Add(options => options.TakeScreenshot, Editor.Instance.Windows.TakeScreenshot);
inputActions.Add(options => options.ProfilerWindow, () => Editor.Instance.Windows.ProfilerWin.FocusOrShow());
inputActions.Add(options => options.ProfilerStartStop, () => { Editor.Instance.Windows.ProfilerWin.LiveRecording = !Editor.Instance.Windows.ProfilerWin.LiveRecording; Editor.Instance.UI.AddStatusMessage($"Profiling {(Editor.Instance.Windows.ProfilerWin.LiveRecording ? "started" : "stopped")}."); });
inputActions.Add(options => options.ProfilerClear, () => { Editor.Instance.Windows.ProfilerWin.Clear(); Editor.Instance.UI.AddStatusMessage($"Profiling results cleared."); });
inputActions.Add(options => options.ProfilerStartStop, () =>
{
bool recording = !Editor.Instance.Windows.ProfilerWin.LiveRecording;
Editor.Instance.Windows.ProfilerWin.LiveRecording = recording;
Editor.Instance.UI.AddStatusMessage($"Profiling {(recording ? "started" : "stopped")}.");
});
inputActions.Add(options => options.ProfilerClear, () =>
{
Editor.Instance.Windows.ProfilerWin.Clear();
Editor.Instance.UI.AddStatusMessage($"Profiling results cleared.");
});
inputActions.Add(options => options.SaveScenes, () => Editor.Instance.Scene.SaveScenes());
inputActions.Add(options => options.CloseScenes, () => Editor.Instance.Scene.CloseAllScenes());
inputActions.Add(options => options.OpenScriptsProject, () => Editor.Instance.CodeEditing.OpenSolution());

View File

@@ -1113,7 +1113,7 @@ namespace FlaxEditor.Viewport
private void OnFarPlaneChanged(FloatValueBox control)
{
_farPlane = control.Value;
_editor.ProjectCache.SetCustomData("CameraNearPlaneValue", _farPlane.ToString());
_editor.ProjectCache.SetCustomData("CameraFarPlaneValue", _farPlane.ToString());
}
/// <summary>

View File

@@ -386,6 +386,7 @@ namespace FlaxEditor.Windows.Assets
// Spawn it
Spawn(actor);
Rename();
}
/// <summary>
@@ -415,6 +416,7 @@ namespace FlaxEditor.Windows.Assets
// Create undo action
var action = new CustomDeleteActorsAction(new List<SceneGraphNode>(1) { actorNode }, true);
Undo.AddAction(action);
Select(actorNode);
}
private void OnTreeRightClick(TreeNode node, Float2 location)

View File

@@ -452,7 +452,6 @@ namespace FlaxEditor.Windows.Profiler
var data = _events.Get(_mainChart.SelectedSampleIndex);
if (data == null || data.Length == 0)
return;
float totalTimeMs = _mainChart.SelectedSample;
// Add rows
@@ -501,17 +500,24 @@ namespace FlaxEditor.Windows.Profiler
row = new Row
{
Values = new object[6],
BackgroundColors = new Color[6],
};
for (int k = 0; k < row.BackgroundColors.Length; k++)
row.BackgroundColors[k] = Color.Transparent;
}
{
// Event
row.Values[0] = name;
// Total (%)
row.Values[1] = (int)(time / totalTimeMs * 1000.0f) / 10.0f;
float rowTotalTimePerc = (float)(time / totalTimeMs);
row.Values[1] = (int)(rowTotalTimePerc * 1000.0f) / 10.0f;
row.BackgroundColors[1] = Color.Red.AlphaMultiplied(Mathf.Min(1, rowTotalTimePerc) * 0.5f);
// Self (%)
row.Values[2] = (int)((time - subEventsTimeTotal) / time * 1000.0f) / 10.0f;
float rowSelfTimePerc = (float)((time - subEventsTimeTotal) / totalTimeMs);
row.Values[2] = (int)(rowSelfTimePerc * 1000.0f) / 10.0f;
row.BackgroundColors[2] = Color.Red.AlphaMultiplied(Mathf.Min(1, rowSelfTimePerc) * 0.5f);
// Time ms
row.Values[3] = (float)((time * 10000.0f) / 10000.0f);

View File

@@ -321,8 +321,7 @@ namespace FlaxEditor.Windows.Profiler
var data = _events.Get(_drawTimeCPU.SelectedSampleIndex);
if (data == null || data.Length == 0)
return;
float totalTimeMs = _drawTimeCPU.SelectedSample;
float totalTimeMs = _drawTimeGPU.SelectedSample;
// Add rows
var rowColor2 = Style.Current.Background * 1.4f;
@@ -343,14 +342,19 @@ namespace FlaxEditor.Windows.Profiler
row = new Row
{
Values = new object[6],
BackgroundColors = new Color[6],
};
for (int k = 0; k < row.BackgroundColors.Length; k++)
row.BackgroundColors[k] = Color.Transparent;
}
{
// Event
row.Values[0] = name;
// Total (%)
row.Values[1] = (int)(e.Time / totalTimeMs * 1000.0f) / 10.0f;
float rowTimePerc = (float)(e.Time / totalTimeMs);
row.Values[1] = (int)(rowTimePerc * 1000.0f) / 10.0f;
row.BackgroundColors[1] = Color.Red.AlphaMultiplied(Mathf.Min(1, rowTimePerc) * 0.5f);
// GPU ms
row.Values[2] = (e.Time * 10000.0f) / 10000.0f;

View File

@@ -38,6 +38,7 @@ namespace FlaxEditor.Windows.Profiler
if (value != LiveRecording)
{
_liveRecordingButton.Checked = value;
OnLiveRecordingChanged();
}
}
}

View File

@@ -282,6 +282,20 @@ void AnimGraphExecutor::Update(AnimGraphInstanceData& data, float dt)
}
}
}
#if !BUILD_RELEASE
{
// Perform sanity check on nodes pose to prevent crashes due to NaNs
bool anyInvalid = animResult->RootMotion.IsNanOrInfinity();
for (int32 i = 0; i < animResult->Nodes.Count(); i++)
anyInvalid |= animResult->Nodes.Get()[i].IsNanOrInfinity();
if (anyInvalid)
{
LOG(Error, "Animated Model pose contains NaNs due to animations sampling/blending bug.");
context.Data = nullptr;
return;
}
}
#endif
SkeletonData* animResultSkeleton = &skeleton;
// Retarget animation when using output pose from other skeleton

View File

@@ -197,6 +197,7 @@ struct FLAXENGINE_API AnimGraphSlot
float BlendOutTime = 0.0f;
int32 LoopCount = 0;
bool Pause = false;
bool Reset = false;
};
/// <summary>

View File

@@ -1367,33 +1367,29 @@ void AnimGraphExecutor::ProcessGroupAnimation(Box* boxBase, Node* nodeBase, Valu
}
// Use 1D blend if points are on the same line (degenerated triangle)
// TODO: simplify this code
struct BlendData
{
float AlphaX, AlphaY;
Animation* AnimA, *AnimB;
const Float4* AnimAd, *AnimBd;
};
BlendData blendData;
if (v1.Y >= v0.Y)
{
if (p.Y < v0.Y && v1.Y >= v0.Y)
{
const float alpha = p.Y / v0.Y;
value = SampleAnimationsWithBlend(node, loop, data.Length, startTimePos, bucket.TimePosition, newTimePos, aAnim, bAnim, aData.W, bData.W, alpha);
}
blendData = { p.Y, v0.Y, aAnim, bAnim, &aData, &bData };
else
{
const float alpha = (p.Y - v0.Y) / (v1.Y - v0.Y);
value = SampleAnimationsWithBlend(node, loop, data.Length, startTimePos, bucket.TimePosition, newTimePos, bAnim, cAnim, bData.W, cData.W, alpha);
}
blendData = { p.Y - v0.Y, v1.Y - v0.Y, bAnim, cAnim, &bData, &cData };
}
else
{
if (p.Y < v1.Y)
{
const float alpha = p.Y / v1.Y;
value = SampleAnimationsWithBlend(node, loop, data.Length, startTimePos, bucket.TimePosition, newTimePos, aAnim, cAnim, aData.W, cData.W, alpha);
}
blendData = { p.Y, v1.Y, aAnim, cAnim, &aData, &cData };
else
{
const float alpha = (p.Y - v1.Y) / (v0.Y - v1.Y);
value = SampleAnimationsWithBlend(node, loop, data.Length, startTimePos, bucket.TimePosition, newTimePos, cAnim, bAnim, cData.W, bData.W, alpha);
}
blendData = { p.Y - v1.Y, v0.Y - v1.Y, cAnim, bAnim, &cData, &bData };
}
const float alpha = Math::IsZero(blendData.AlphaY) ? 0.0f : blendData.AlphaX / blendData.AlphaY;
value = SampleAnimationsWithBlend(node, loop, data.Length, startTimePos, bucket.TimePosition, newTimePos, blendData.AnimA, blendData.AnimB, blendData.AnimAd->W, blendData.AnimBd->W, alpha);
}
else
{
@@ -2102,6 +2098,12 @@ void AnimGraphExecutor::ProcessGroupAnimation(Box* boxBase, Node* nodeBase, Valu
auto& slot = slots[bucket.Index];
Animation* anim = slot.Animation;
ASSERT(slot.Animation && slot.Animation->IsLoaded());
if (slot.Reset)
{
// Start from the begining
slot.Reset = false;
bucket.TimePosition = 0.0f;
}
const float deltaTime = slot.Pause ? 0.0f : context.DeltaTime * slot.Speed;
const float length = anim->GetLength();
const bool loop = bucket.LoopsLeft != 0;

View File

@@ -150,9 +150,7 @@ void BinaryAsset::ClearDependencies()
{
auto asset = Cast<BinaryAsset>(Content::GetAsset(e.First));
if (asset)
{
asset->_dependantAssets.Remove(this);
}
}
Dependencies.Clear();
}
@@ -387,6 +385,16 @@ bool BinaryAsset::SaveToAsset(const StringView& path, AssetInitData& data, bool
if (binaryAsset)
binaryAsset->_isSaving = false;
if (binaryAsset)
{
// Inform dependant asset (use cloned version because it might be modified by assets when they got reloaded)
auto dependantAssets = binaryAsset->_dependantAssets;
for (auto& e : dependantAssets)
{
e->OnDependencyModified(binaryAsset);
}
}
return result;
}

View File

@@ -963,6 +963,7 @@ bool FlaxStorage::Create(WriteStream* stream, const AssetInitData* data, int32 d
// Asset Dependencies
stream->WriteInt32(header.Dependencies.Count());
stream->WriteBytes(header.Dependencies.Get(), header.Dependencies.Count() * sizeof(Pair<Guid, DateTime>));
static_assert(sizeof(Pair<Guid, DateTime>) == sizeof(Guid) + sizeof(DateTime), "Invalid data size.");
}
#if ASSETS_LOADING_EXTRA_VERIFICATION

View File

@@ -12,22 +12,22 @@ const int32 CachedDaysToMonth[] = { 0, 31, 59, 90, 120, 151, 181, 212, 243, 273,
DateTime::DateTime(int32 year, int32 month, int32 day, int32 hour, int32 minute, int32 second, int32 millisecond)
{
ASSERT_LOW_LAYER(Validate(year, month, day, hour, minute, second, millisecond));
int32 totalDays = 0;
int32 daysSum = 0;
if (month > 2 && IsLeapYear(year))
totalDays++;
daysSum++;
year--;
month--;
totalDays += year * 365 + year / 4 - year / 100 + year / 400 + CachedDaysToMonth[month] + day - 1;
Ticks = totalDays * Constants::TicksPerDay
+ hour * Constants::TicksPerHour
+ minute * Constants::TicksPerMinute
+ second * Constants::TicksPerSecond
+ millisecond * Constants::TicksPerMillisecond;
daysSum += year * 365 + year / 4 - year / 100 + year / 400 + CachedDaysToMonth[month] + day - 1;
Ticks = daysSum * TimeSpan::TicksPerDay
+ hour * TimeSpan::TicksPerHour
+ minute * TimeSpan::TicksPerMinute
+ second * TimeSpan::TicksPerSecond
+ millisecond * TimeSpan::TicksPerMillisecond;
}
DateTime DateTime::GetDate() const
{
return DateTime(Ticks - Ticks % Constants::TicksPerDay);
return DateTime(Ticks - Ticks % TimeSpan::TicksPerDay);
}
void DateTime::GetDate(int32& year, int32& month, int32& day) const
@@ -35,8 +35,7 @@ void DateTime::GetDate(int32& year, int32& month, int32& day) const
// Based on:
// Fliegel, H. F. and van Flandern, T. C.,
// Communications of the ACM, Vol. 11, No. 10 (October 1968).
int32 l = Math::FloorToInt(static_cast<float>(GetJulianDay() + 0.5)) + 68569;
int32 l = Math::FloorToInt((float)(GetDate().GetJulianDay() + 0.5)) + 68569;
const int32 n = 4 * l / 146097;
l = l - (146097 * n + 3) / 4;
int32 i = 4000 * (l + 1) / 1461001;
@@ -46,7 +45,6 @@ void DateTime::GetDate(int32& year, int32& month, int32& day) const
l = j / 11;
j = j + 2 - 12 * l;
i = 100 * (n - 49) + i + l;
year = i;
month = j;
day = k;
@@ -61,7 +59,7 @@ int32 DateTime::GetDay() const
DayOfWeek DateTime::GetDayOfWeek() const
{
return static_cast<DayOfWeek>((Ticks / Constants::TicksPerDay) % 7);
return static_cast<DayOfWeek>((Ticks / TimeSpan::TicksPerDay) % 7);
}
int32 DateTime::GetDayOfYear() const
@@ -75,7 +73,7 @@ int32 DateTime::GetDayOfYear() const
int32 DateTime::GetHour() const
{
return static_cast<int32>(Ticks / Constants::TicksPerHour % 24);
return static_cast<int32>(Ticks / TimeSpan::TicksPerHour % 24);
}
int32 DateTime::GetHour12() const
@@ -90,7 +88,7 @@ int32 DateTime::GetHour12() const
double DateTime::GetJulianDay() const
{
return 1721425.5 + static_cast<double>(Ticks) / Constants::TicksPerDay;
return 1721425.5 + static_cast<double>(Ticks) / TimeSpan::TicksPerDay;
}
double DateTime::GetModifiedJulianDay() const
@@ -100,12 +98,12 @@ double DateTime::GetModifiedJulianDay() const
int32 DateTime::GetMillisecond() const
{
return static_cast<int32>(Ticks / Constants::TicksPerMillisecond % 1000);
return static_cast<int32>(Ticks / TimeSpan::TicksPerMillisecond % 1000);
}
int32 DateTime::GetMinute() const
{
return static_cast<int32>(Ticks / Constants::TicksPerMinute % 60);
return static_cast<int32>(Ticks / TimeSpan::TicksPerMinute % 60);
}
int32 DateTime::GetMonth() const
@@ -122,12 +120,12 @@ MonthOfYear DateTime::GetMonthOfYear() const
int32 DateTime::GetSecond() const
{
return static_cast<int32>(Ticks / Constants::TicksPerSecond % 60);
return static_cast<int32>(Ticks / TimeSpan::TicksPerSecond % 60);
}
TimeSpan DateTime::GetTimeOfDay() const
{
return TimeSpan(Ticks % Constants::TicksPerDay);
return TimeSpan(Ticks % TimeSpan::TicksPerDay);
}
int32 DateTime::GetYear() const
@@ -137,11 +135,6 @@ int32 DateTime::GetYear() const
return year;
}
int32 DateTime::ToUnixTimestamp() const
{
return static_cast<int32>((Ticks - DateTime(1970, 1, 1).Ticks) / Constants::TicksPerSecond);
}
int32 DateTime::DaysInMonth(int32 year, int32 month)
{
ASSERT_LOW_LAYER((month >= 1) && (month <= 12));
@@ -155,16 +148,6 @@ int32 DateTime::DaysInYear(int32 year)
return IsLeapYear(year) ? 366 : 365;
}
DateTime DateTime::FromJulianDay(double julianDay)
{
return DateTime(static_cast<int64>((julianDay - 1721425.5) * Constants::TicksPerDay));
}
DateTime DateTime::FromUnixTimestamp(int32 unixTime)
{
return DateTime(1970, 1, 1) + TimeSpan(static_cast<int64>(unixTime) * Constants::TicksPerSecond);
}
bool DateTime::IsLeapYear(int32 year)
{
if ((year % 4) == 0)
@@ -176,7 +159,7 @@ bool DateTime::IsLeapYear(int32 year)
DateTime DateTime::MaxValue()
{
return DateTime(3652059 * Constants::TicksPerDay - 1);
return DateTime(3652059 * TimeSpan::TicksPerDay - 1);
}
DateTime DateTime::Now()

View File

@@ -199,11 +199,6 @@ public:
/// </summary>
int32 GetYear() const;
/// <summary>
/// Gets this date as the number of seconds since the Unix Epoch (January 1st of 1970).
/// </summary>
int32 ToUnixTimestamp() const;
public:
/// <summary>
/// Gets the number of days in the year and month.
@@ -220,20 +215,6 @@ public:
/// <returns>The number of days.</returns>
static int32 DaysInYear(int32 year);
/// <summary>
/// Returns the proleptic Gregorian date for the given Julian Day.
/// </summary>
/// <param name="julianDay">The Julian Day.</param>
/// <returns>Gregorian date and time.</returns>
static DateTime FromJulianDay(double julianDay);
/// <summary>
/// Returns the date from Unix time (seconds from midnight 1970-01-01).
/// </summary>
/// <param name="unixTime">The Unix time (seconds from midnight 1970-01-01).</param>
/// <returns>The Gregorian date and time.</returns>
static DateTime FromUnixTimestamp(int32 unixTime);
/// <summary>
/// Determines whether the specified year is a leap year.
/// </summary>

View File

@@ -6,38 +6,53 @@
TimeSpan TimeSpan::FromDays(double days)
{
ASSERT_LOW_LAYER((days >= MinValue().GetTotalDays()) && (days <= MaxValue().GetTotalDays()));
return TimeSpan(static_cast<int64>(days * Constants::TicksPerDay));
return TimeSpan(static_cast<int64>(days * TicksPerDay));
}
TimeSpan TimeSpan::FromHours(double hours)
{
ASSERT_LOW_LAYER((hours >= MinValue().GetTotalHours()) && (hours <= MaxValue().GetTotalHours()));
return TimeSpan(static_cast<int64>(hours * Constants::TicksPerHour));
return TimeSpan(static_cast<int64>(hours * TicksPerHour));
}
TimeSpan TimeSpan::FromMilliseconds(double milliseconds)
{
ASSERT_LOW_LAYER((milliseconds >= MinValue().GetTotalMilliseconds()) && (milliseconds <= MaxValue().GetTotalMilliseconds()));
return TimeSpan(static_cast<int64>(milliseconds * Constants::TicksPerMillisecond));
return TimeSpan(static_cast<int64>(milliseconds * TicksPerMillisecond));
}
TimeSpan TimeSpan::FromMinutes(double minutes)
{
ASSERT_LOW_LAYER((minutes >= MinValue().GetTotalMinutes()) && (minutes <= MaxValue().GetTotalMinutes()));
return TimeSpan(static_cast<int64>(minutes * Constants::TicksPerMinute));
return TimeSpan(static_cast<int64>(minutes * TicksPerMinute));
}
TimeSpan TimeSpan::FromSeconds(double seconds)
{
ASSERT_LOW_LAYER((seconds >= MinValue().GetTotalSeconds()) && (seconds <= MaxValue().GetTotalSeconds()));
return TimeSpan(static_cast<int64>(seconds * Constants::TicksPerSecond));
return TimeSpan(static_cast<int64>(seconds * TicksPerSecond));
}
TimeSpan TimeSpan::MaxValue()
{
return TimeSpan(9223372036854775807);
}
TimeSpan TimeSpan::MinValue()
{
return TimeSpan(-9223372036854775807 - 1);
}
TimeSpan TimeSpan::Zero()
{
return TimeSpan(0);
}
void TimeSpan::Set(int32 days, int32 hours, int32 minutes, int32 seconds, int32 milliseconds)
{
const int64 totalMs = 1000 * (60 * 60 * 24 * (int64)days + 60 * 60 * (int64)hours + 60 * (int64)minutes + (int64)seconds) + (int64)milliseconds;
ASSERT_LOW_LAYER((totalMs >= MinValue().GetTotalMilliseconds()) && (totalMs <= MaxValue().GetTotalMilliseconds()));
Ticks = totalMs * Constants::TicksPerMillisecond;
Ticks = totalMs * TicksPerMillisecond;
}
String TimeSpan::ToString() const

View File

@@ -6,32 +6,30 @@
#include "Engine/Core/Formatting.h"
#include "Engine/Core/Templates.h"
namespace Constants
{
// The number of timespan ticks per day.
const int64 TicksPerDay = 864000000000;
// The number of timespan ticks per hour.
const int64 TicksPerHour = 36000000000;
// The number of timespan ticks per millisecond.
const int64 TicksPerMillisecond = 10000;
// The number of timespan ticks per minute.
const int64 TicksPerMinute = 600000000;
// The number of timespan ticks per second.
const int64 TicksPerSecond = 10000000;
// The number of timespan ticks per week.
const int64 TicksPerWeek = 6048000000000;
}
/// <summary>
/// Represents the difference between two dates and times.
/// </summary>
API_STRUCT(InBuild, Namespace="System") struct FLAXENGINE_API TimeSpan
{
public:
// The number of timespan ticks per day.
static constexpr int64 TicksPerDay = 864000000000;
// The number of timespan ticks per hour.
static constexpr int64 TicksPerHour = 36000000000;
// The number of timespan ticks per millisecond.
static constexpr int64 TicksPerMillisecond = 10000;
// The number of timespan ticks per minute.
static constexpr int64 TicksPerMinute = 600000000;
// The number of timespan ticks per second.
static constexpr int64 TicksPerSecond = 10000000;
// The number of timespan ticks per week.
static constexpr int64 TicksPerWeek = 6048000000000;
public:
/// <summary>
/// Time span in 100 nanoseconds resolution.
@@ -170,7 +168,7 @@ public:
/// </summary>
FORCE_INLINE int32 GetDays() const
{
return (int32)(Ticks / Constants::TicksPerDay);
return (int32)(Ticks / TicksPerDay);
}
/// <summary>
@@ -186,7 +184,7 @@ public:
/// </summary>
FORCE_INLINE int32 GetHours() const
{
return (int32)(Ticks / Constants::TicksPerHour % 24);
return (int32)(Ticks / TicksPerHour % 24);
}
/// <summary>
@@ -194,7 +192,7 @@ public:
/// </summary>
FORCE_INLINE int32 GetMilliseconds() const
{
return (int32)(Ticks / Constants::TicksPerMillisecond % 1000);
return (int32)(Ticks / TicksPerMillisecond % 1000);
}
/// <summary>
@@ -202,7 +200,7 @@ public:
/// </summary>
FORCE_INLINE int32 GetMinutes() const
{
return (int32)(Ticks / Constants::TicksPerMinute % 60);
return (int32)(Ticks / TicksPerMinute % 60);
}
/// <summary>
@@ -210,7 +208,7 @@ public:
/// </summary>
FORCE_INLINE int32 GetSeconds() const
{
return (int32)(Ticks / Constants::TicksPerSecond % 60);
return (int32)(Ticks / TicksPerSecond % 60);
}
/// <summary>
@@ -218,7 +216,7 @@ public:
/// </summary>
FORCE_INLINE double GetTotalDays() const
{
return (double)Ticks / Constants::TicksPerDay;
return (double)Ticks / TicksPerDay;
}
/// <summary>
@@ -226,7 +224,7 @@ public:
/// </summary>
FORCE_INLINE double GetTotalHours() const
{
return (double)Ticks / Constants::TicksPerHour;
return (double)Ticks / TicksPerHour;
}
/// <summary>
@@ -234,7 +232,7 @@ public:
/// </summary>
FORCE_INLINE double GetTotalMilliseconds() const
{
return (double)Ticks / Constants::TicksPerMillisecond;
return (double)Ticks / TicksPerMillisecond;
}
/// <summary>
@@ -242,7 +240,7 @@ public:
/// </summary>
FORCE_INLINE double GetTotalMinutes() const
{
return (double)Ticks / Constants::TicksPerMinute;
return (double)Ticks / TicksPerMinute;
}
/// <summary>
@@ -250,7 +248,7 @@ public:
/// </summary>
FORCE_INLINE float GetTotalSeconds() const
{
return static_cast<float>(Ticks) / Constants::TicksPerSecond;
return static_cast<float>(Ticks) / TicksPerSecond;
}
public:
@@ -293,29 +291,17 @@ public:
/// <summary>
/// Returns the maximum time span value.
/// </summary>
/// <returns>The time span.</returns>
static TimeSpan MaxValue()
{
return TimeSpan(9223372036854775807);
}
static TimeSpan MaxValue();
/// <summary>
/// Returns the minimum time span value.
/// </summary>
/// <returns>The time span.</returns>
static TimeSpan MinValue()
{
return TimeSpan(-9223372036854775807 - 1);
}
static TimeSpan MinValue();
/// <summary>
/// Returns the zero time span value.
/// </summary>
/// <returns>The time span.</returns>
static TimeSpan Zero()
{
return TimeSpan(0);
}
static TimeSpan Zero();
private:
void Set(int32 days, int32 hours, int32 minutes, int32 seconds, int32 milliseconds);

View File

@@ -67,7 +67,7 @@ void Time::TickData::OnBeforeRun(float targetFps, double currentTime)
{
Time = UnscaledTime = TimeSpan::Zero();
DeltaTime = UnscaledDeltaTime = targetFps > ZeroTolerance ? TimeSpan::FromSeconds(1.0f / targetFps) : TimeSpan::Zero();
LastLength = static_cast<double>(DeltaTime.Ticks) / Constants::TicksPerSecond;
LastLength = static_cast<double>(DeltaTime.Ticks) / TimeSpan::TicksPerSecond;
LastBegin = currentTime - LastLength;
LastEnd = currentTime;
NextBegin = targetFps > ZeroTolerance ? LastBegin + (1.0f / targetFps) : 0.0;
@@ -76,7 +76,7 @@ void Time::TickData::OnBeforeRun(float targetFps, double currentTime)
void Time::TickData::OnReset(float targetFps, double currentTime)
{
DeltaTime = UnscaledDeltaTime = targetFps > ZeroTolerance ? TimeSpan::FromSeconds(1.0f / targetFps) : TimeSpan::Zero();
LastLength = static_cast<double>(DeltaTime.Ticks) / Constants::TicksPerSecond;
LastLength = static_cast<double>(DeltaTime.Ticks) / TimeSpan::TicksPerSecond;
LastBegin = currentTime - LastLength;
LastEnd = currentTime;
}

View File

@@ -236,6 +236,27 @@ API_ENUM(Attributes="Flags") enum class ShadowsCastingMode
DECLARE_ENUM_OPERATORS(ShadowsCastingMode);
/// <summary>
/// The partitioning mode for shadow cascades.
/// </summary>
API_ENUM() enum class PartitionMode
{
/// <summary>
/// Internally defined cascade splits.
/// </summary>
Manual = 0,
/// <summary>
/// Logarithmic cascade splits.
/// </summary>
Logarithmic = 1,
/// <summary>
/// PSSM cascade splits.
/// </summary>
PSSM = 2,
};
/// <summary>
/// Identifies expected GPU resource use during rendering. The usage directly reflects whether a resource is accessible by the CPU and/or the GPU.
/// </summary>

View File

@@ -207,13 +207,12 @@ void PostFxMaterialsSettings::BlendWith(PostFxMaterialsSettings& other, float we
if (isHalf)
{
int32 indexSrc = 0;
const auto materialsSrc = other.Materials.Get();
const AssetReference<MaterialBase>* materialsSrc = other.Materials.Get();
while (Materials.Count() != POST_PROCESS_SETTINGS_MAX_MATERIALS && indexSrc < other.Materials.Count())
{
if (materialsSrc[indexSrc])
{
Materials.Add(materialsSrc[indexSrc]);
}
MaterialBase* mat = materialsSrc[indexSrc].Get();
if (mat && !Materials.Contains(mat))
Materials.Add(mat);
indexSrc++;
}
}

View File

@@ -494,6 +494,7 @@ void AnimatedModel::StopSlotAnimation(const StringView& slotName, Animation* ani
if (slot.Animation == anim && slot.Name == slotName)
{
slot.Animation = nullptr;
slot.Reset = true;
break;
}
}

View File

@@ -41,6 +41,12 @@ void DirectionalLight::Draw(RenderContext& renderContext)
data.RenderedVolumetricFog = 0;
data.ShadowsMode = ShadowsMode;
data.CascadeCount = CascadeCount;
data.Cascade1Spacing = Cascade1Spacing;
data.Cascade2Spacing = Cascade2Spacing;
data.Cascade3Spacing = Cascade3Spacing;
data.Cascade4Spacing = Cascade4Spacing;
data.PartitionMode = PartitionMode;
data.ContactShadowsLength = ContactShadowsLength;
data.StaticFlags = GetStaticFlags();
data.ID = GetID();
@@ -56,6 +62,12 @@ void DirectionalLight::Serialize(SerializeStream& stream, const void* otherObj)
SERIALIZE_GET_OTHER_OBJ(DirectionalLight);
SERIALIZE(CascadeCount);
SERIALIZE(Cascade1Spacing);
SERIALIZE(Cascade2Spacing);
SERIALIZE(Cascade3Spacing);
SERIALIZE(Cascade4Spacing);
SERIALIZE(PartitionMode);
}
void DirectionalLight::Deserialize(DeserializeStream& stream, ISerializeModifier* modifier)
@@ -64,6 +76,12 @@ void DirectionalLight::Deserialize(DeserializeStream& stream, ISerializeModifier
LightWithShadow::Deserialize(stream, modifier);
DESERIALIZE(CascadeCount);
DESERIALIZE(Cascade1Spacing);
DESERIALIZE(Cascade2Spacing);
DESERIALIZE(Cascade3Spacing);
DESERIALIZE(Cascade4Spacing);
DESERIALIZE(PartitionMode);
}
bool DirectionalLight::IntersectsItself(const Ray& ray, Real& distance, Vector3& normal)

View File

@@ -12,12 +12,42 @@ class FLAXENGINE_API DirectionalLight : public LightWithShadow
{
DECLARE_SCENE_OBJECT(DirectionalLight);
public:
/// <summary>
/// The partitioning mode for the shadow cascades.
/// </summary>
API_FIELD(Attributes = "EditorOrder(64), DefaultValue(PartitionMode.Manual), EditorDisplay(\"Shadow\")")
PartitionMode PartitionMode = PartitionMode::Manual;
/// <summary>
/// The number of cascades used for slicing the range of depth covered by the light during shadow rendering. Values are 1, 2 or 4 cascades; a typical scene uses 4 cascades.
/// </summary>
API_FIELD(Attributes="EditorOrder(65), DefaultValue(4), Limit(1, 4), EditorDisplay(\"Shadow\")")
int32 CascadeCount = 4;
/// <summary>
/// Percentage of the shadow distance used by the first cascade.
/// </summary>
API_FIELD(Attributes = "EditorOrder(66), DefaultValue(0.05f), VisibleIf(nameof(ShowCascade1)), Limit(0, 1, 0.001f), EditorDisplay(\"Shadow\")")
float Cascade1Spacing = 0.05f;
/// <summary>
/// Percentage of the shadow distance used by the second cascade.
/// </summary>
API_FIELD(Attributes = "EditorOrder(67), DefaultValue(0.15f), VisibleIf(nameof(ShowCascade2)), Limit(0, 1, 0.001f), EditorDisplay(\"Shadow\")")
float Cascade2Spacing = 0.15f;
/// <summary>
/// Percentage of the shadow distance used by the third cascade.
/// </summary>
API_FIELD(Attributes = "EditorOrder(68), DefaultValue(0.50f), VisibleIf(nameof(ShowCascade3)), Limit(0, 1, 0.001f), EditorDisplay(\"Shadow\")")
float Cascade3Spacing = 0.50f;
/// <summary>
/// Percentage of the shadow distance used by the fourth cascade.
/// </summary>
API_FIELD(Attributes = "EditorOrder(69), DefaultValue(1.0f), VisibleIf(nameof(ShowCascade4)), Limit(0, 1, 0.001f), EditorDisplay(\"Shadow\")")
float Cascade4Spacing = 1.0f;
public:
// [LightWithShadow]
void Draw(RenderContext& renderContext) override;

View File

@@ -0,0 +1,10 @@
namespace FlaxEngine
{
public partial class DirectionalLight
{
bool ShowCascade1 => CascadeCount >= 1 && PartitionMode == PartitionMode.Manual;
bool ShowCascade2 => CascadeCount >= 2 && PartitionMode == PartitionMode.Manual;
bool ShowCascade3 => CascadeCount >= 3 && PartitionMode == PartitionMode.Manual;
bool ShowCascade4 => CascadeCount >= 4 && PartitionMode == PartitionMode.Manual;
}
}

View File

@@ -43,7 +43,7 @@ API_STRUCT(Namespace="FlaxEngine.Networking") struct FLAXENGINE_API NetworkConfi
/// </summary>
/// <remarks>Object is managed by the created network peer (will be deleted on peer shutdown).</remarks>
API_FIELD()
ScriptingObject* NetworkDriver;
ScriptingObject* NetworkDriver = nullptr;
/// <summary>
/// The upper limit on how many peers can join when we're listening.

View File

@@ -2151,6 +2151,18 @@ void NetworkInternal::OnNetworkMessageObjectRpc(NetworkEvent& event, NetworkClie
NetworkMessageObjectRpc msgData;
event.Message.ReadStructure(msgData);
ScopeLock lock(ObjectsLock);
// Find RPC info
NetworkRpcName name;
name.First = Scripting::FindScriptingType(msgData.RpcTypeName);
name.Second = msgData.RpcName;
const NetworkRpcInfo* info = NetworkRpcInfo::RPCsTable.TryGet(name);
if (!info)
{
NETWORK_REPLICATOR_LOG(Error, "[NetworkReplicator] Unknown RPC {}::{} for object {}", String(msgData.RpcTypeName), String(msgData.RpcName), msgData.ObjectId);
return;
}
NetworkReplicatedObject* e = ResolveObject(msgData.ObjectId, msgData.ParentId, msgData.ObjectTypeName);
if (e)
{
@@ -2159,17 +2171,7 @@ void NetworkInternal::OnNetworkMessageObjectRpc(NetworkEvent& event, NetworkClie
if (!obj)
return;
// Find RPC info
NetworkRpcName name;
name.First = Scripting::FindScriptingType(msgData.RpcTypeName);
name.Second = msgData.RpcName;
const NetworkRpcInfo* info = NetworkRpcInfo::RPCsTable.TryGet(name);
if (!info)
{
NETWORK_REPLICATOR_LOG(Error, "[NetworkReplicator] Unknown RPC {}::{} for object {}", String(msgData.RpcTypeName), String(msgData.RpcName), msgData.ObjectId);
return;
}
// Validate RPC
if (info->Server && NetworkManager::IsClient())
{
@@ -2192,7 +2194,7 @@ void NetworkInternal::OnNetworkMessageObjectRpc(NetworkEvent& event, NetworkClie
// Execute RPC
info->Execute(obj, stream, info->Tag);
}
else
else if(info->Channel != static_cast<uint8>(NetworkChannelType::Unreliable) && info->Channel != static_cast<uint8>(NetworkChannelType::UnreliableOrdered))
{
NETWORK_REPLICATOR_LOG(Error, "[NetworkReplicator] Unknown object {} RPC {}::{}", msgData.ObjectId, String(msgData.RpcTypeName), String(msgData.RpcName));
}

View File

@@ -2260,7 +2260,13 @@ void PhysicsBackend::SetRigidActorPose(void* actor, const Vector3& position, con
if (kinematic)
{
auto actorPhysX = (PxRigidDynamic*)actor;
actorPhysX->setKinematicTarget(trans);
if (actorPhysX->getActorFlags() & PxActorFlag::eDISABLE_SIMULATION)
{
// Ensures the disabled kinematic actor ends up in the correct pose after enabling simulation
actorPhysX->setGlobalPose(trans, wakeUp);
}
else
actorPhysX->setKinematicTarget(trans);
}
else
{

View File

@@ -244,7 +244,7 @@ bool AndroidFileSystem::FileExists(const StringView& path)
bool AndroidFileSystem::DeleteFile(const StringView& path)
{
const StringAsANSI<> pathANSI(*path, path.Length());
return unlink(pathANSI.Get()) == 0;
return unlink(pathANSI.Get()) != 0;
}
uint64 AndroidFileSystem::GetFileSize(const StringView& path)
@@ -517,7 +517,7 @@ DateTime AndroidFileSystem::GetFileLastEditTime(const StringView& path)
const StringAsANSI<> pathANSI(*path, path.Length());
if (stat(pathANSI.Get(), &fileInfo) == -1)
return DateTime::MinValue();
const TimeSpan timeSinceEpoch(0, 0, fileInfo.st_mtime);
const TimeSpan timeSinceEpoch(0, 0, 0, fileInfo.st_mtime);
const DateTime UnixEpoch(1970, 1, 1);
return UnixEpoch + timeSinceEpoch;
}

View File

@@ -218,7 +218,7 @@ bool AppleFileSystem::FileExists(const StringView& path)
bool AppleFileSystem::DeleteFile(const StringView& path)
{
const StringAsANSI<> pathANSI(*path, path.Length());
return unlink(pathANSI.Get()) == 0;
return unlink(pathANSI.Get()) != 0;
}
uint64 AppleFileSystem::GetFileSize(const StringView& path)
@@ -498,7 +498,7 @@ DateTime AppleFileSystem::GetFileLastEditTime(const StringView& path)
return DateTime::MinValue();
}
const TimeSpan timeSinceEpoch(0, 0, fileInfo.st_mtime);
const TimeSpan timeSinceEpoch(0, 0, 0, fileInfo.st_mtime);
return UnixEpoch + timeSinceEpoch;
}

View File

@@ -364,7 +364,7 @@ bool LinuxFileSystem::FileExists(const StringView& path)
bool LinuxFileSystem::DeleteFile(const StringView& path)
{
const StringAsANSI<> pathANSI(*path, path.Length());
return unlink(pathANSI.Get()) == 0;
return unlink(pathANSI.Get()) != 0;
}
uint64 LinuxFileSystem::GetFileSize(const StringView& path)
@@ -657,7 +657,7 @@ DateTime LinuxFileSystem::GetFileLastEditTime(const StringView& path)
return DateTime::MinValue();
}
const TimeSpan timeSinceEpoch(0, 0, fileInfo.st_mtime);
const TimeSpan timeSinceEpoch(0, 0, 0, fileInfo.st_mtime);
return UnixEpoch + timeSinceEpoch;
}

View File

@@ -137,7 +137,7 @@ DateTime UnixFile::GetLastWriteTime() const
{
return DateTime::MinValue();
}
const TimeSpan timeSinceEpoch(0, 0, fileInfo.st_mtime);
const TimeSpan timeSinceEpoch(0, 0, 0, fileInfo.st_mtime);
const DateTime unixEpoch(1970, 1, 1);
return unixEpoch + timeSinceEpoch;
}

View File

@@ -46,6 +46,12 @@ struct RendererDirectionalLightData
float ShadowsDistance;
int32 CascadeCount;
float Cascade1Spacing;
float Cascade2Spacing;
float Cascade3Spacing;
float Cascade4Spacing;
PartitionMode PartitionMode;
float ContactShadowsLength;
ShadowsCastingMode ShadowsMode;

View File

@@ -247,19 +247,12 @@ void ShadowsPass::SetupLight(RenderContext& renderContext, RenderContextBatch& r
minDistance = cameraNear;
maxDistance = cameraNear + shadowsDistance;
// TODO: expose partition mode?
enum class PartitionMode
{
Manual = 0,
Logarithmic = 1,
PSSM = 2,
};
PartitionMode partitionMode = PartitionMode::Manual;
PartitionMode partitionMode = light.PartitionMode;
float pssmFactor = 0.5f;
float splitDistance0 = 0.05f;
float splitDistance1 = 0.15f;
float splitDistance2 = 0.50f;
float splitDistance3 = 1.00f;
float splitDistance0 = light.Cascade1Spacing;
float splitDistance1 = Math::Max(splitDistance0, light.Cascade2Spacing);
float splitDistance2 = Math::Max(splitDistance1, light.Cascade3Spacing);
float splitDistance3 = Math::Max(splitDistance2, light.Cascade4Spacing);
// Compute the split distances based on the partitioning mode
if (partitionMode == PartitionMode::Manual)

View File

@@ -5,11 +5,11 @@ using System;
namespace FlaxEngine
{
/// <summary>
/// Shows property/field in the editor only if the specified member has a given value. Can be used to hide properties based on other properties (also private properties). The given member has to be bool type.
/// Shows property/field in the editor only if the specified member has a given value. Can be used to hide properties based on other properties (also private properties). The given member has to be bool type. Multiple VisibleIf attributes can be added for additional conditions to be met.
/// </summary>
/// <seealso cref="System.Attribute" />
[Serializable]
[AttributeUsage(AttributeTargets.Field | AttributeTargets.Property)]
[AttributeUsage(AttributeTargets.Field | AttributeTargets.Property, AllowMultiple = true)]
public sealed class VisibleIfAttribute : Attribute
{
/// <summary>

View File

@@ -0,0 +1,26 @@
// Copyright (c) 2012-2023 Wojciech Figat. All rights reserved.
#include "Engine/Core/Types/DateTime.h"
#include <ThirdParty/catch2/catch.hpp>
TEST_CASE("DateTime")
{
SECTION("Test Convertion")
{
constexpr int year = 2023;
constexpr int month = 12;
constexpr int day = 16;
constexpr int hour = 23;
constexpr int minute = 50;
constexpr int second = 13;
constexpr int millisecond = 5;
const DateTime dt1(year, month, day, hour, minute, second, millisecond);
CHECK(dt1.GetYear() == year);
CHECK(dt1.GetMonth() == month);
CHECK(dt1.GetDay() == day);
CHECK(dt1.GetHour() == hour);
CHECK(dt1.GetMinute() == minute);
CHECK(dt1.GetSecond() == second);
CHECK(dt1.GetMillisecond() == millisecond);
}
}

View File

@@ -584,7 +584,6 @@ void MaterialGenerator::ProcessGroupTextures(Box* box, Node* node, Value& value)
{
// Procedural Texture Sample
textureBox->Cache = writeLocal(Value::InitForZero(ValueType::Float4), node);
createGradients(node);
auto proceduralSample = String::Format(TEXT(
" {{\n"
" float3 weights;\n"
@@ -613,19 +612,19 @@ void MaterialGenerator::ProcessGroupTextures(Box* box, Node* node, Value& value)
" uv1 = {0} + frac(sin(mul(float2x2(127.1, 311.7, 269.5, 183.3), vertex1)) * 43758.5453);\n"
" uv2 = {0} + frac(sin(mul(float2x2(127.1, 311.7, 269.5, 183.3), vertex2)) * 43758.5453);\n"
" uv3 = {0} + frac(sin(mul(float2x2(127.1, 311.7, 269.5, 183.3), vertex3)) * 43758.5453);\n"
" float4 tex1 = {1}.SampleGrad({4}, uv1, {2}, {3}, {6}) * weights.x;\n"
" float4 tex2 = {1}.SampleGrad({4}, uv2, {2}, {3}, {6}) * weights.y;\n"
" float4 tex3 = {1}.SampleGrad({4}, uv3, {2}, {3}, {6}) * weights.z;\n"
" {5} = tex1 + tex2 + tex3;\n"
" float2 fdx = ddx({0});\n"
" float2 fdy = ddy({0});\n"
" float4 tex1 = {1}.SampleGrad({2}, uv1, fdx, fdy, {4}) * weights.x;\n"
" float4 tex2 = {1}.SampleGrad({2}, uv2, fdx, fdy, {4}) * weights.y;\n"
" float4 tex3 = {1}.SampleGrad({2}, uv3, fdx, fdy, {4}) * weights.z;\n"
" {3} = tex1 + tex2 + tex3;\n"
" }}\n"
),
uvs.Value, // {0}
texture.Value, // {1}
_ddx.Value, // {2}
_ddy.Value, // {3}
samplerName, // {4}
textureBox->Cache.Value, // {5}
offset.Value // {6}
samplerName, // {2}
textureBox->Cache.Value, // {3}
offset.Value // {4}
);
_writer.Write(*proceduralSample);

View File

@@ -374,6 +374,7 @@ void ModelTool::Options::Serialize(SerializeStream& stream, const void* otherObj
SERIALIZE(InstanceToImportAs);
SERIALIZE(ImportTextures);
SERIALIZE(RestoreMaterialsOnReimport);
SERIALIZE(SkipExistingMaterialsOnReimport);
SERIALIZE(GenerateSDF);
SERIALIZE(SDFResolution);
SERIALIZE(SplitObjects);
@@ -422,6 +423,7 @@ void ModelTool::Options::Deserialize(DeserializeStream& stream, ISerializeModifi
DESERIALIZE(InstanceToImportAs);
DESERIALIZE(ImportTextures);
DESERIALIZE(RestoreMaterialsOnReimport);
DESERIALIZE(SkipExistingMaterialsOnReimport);
DESERIALIZE(GenerateSDF);
DESERIALIZE(SDFResolution);
DESERIALIZE(SplitObjects);
@@ -1154,6 +1156,18 @@ bool ModelTool::ImportModel(const String& path, ModelData& data, Options& option
continue;
}
// Skip any materials that already exist from the model.
// This allows the use of "import as material instances" without material properties getting overridden on each import.
if (options.SkipExistingMaterialsOnReimport)
{
AssetInfo info;
if (Content::GetAssetInfo(assetPath, info))
{
material.AssetID = info.ID;
continue;
}
}
if (options.ImportMaterialsAsInstances)
{
// Create material instance

View File

@@ -260,17 +260,20 @@ public:
API_FIELD(Attributes="EditorOrder(400), EditorDisplay(\"Materials\"), VisibleIf(nameof(ShowGeometry))")
bool ImportMaterials = true;
// If checked, the importer will create the model's materials as instances of a base material.
API_FIELD(Attributes = "EditorOrder(401), EditorDisplay(\"Materials\"), VisibleIf(nameof(ImportMaterials))")
API_FIELD(Attributes = "EditorOrder(401), EditorDisplay(\"Materials\"), VisibleIf(nameof(ImportMaterials)), VisibleIf(nameof(ShowGeometry))")
bool ImportMaterialsAsInstances = false;
// The material used as the base material that will be instanced as the imported model's material.
API_FIELD(Attributes = "EditorOrder(402), EditorDisplay(\"Materials\"), VisibleIf(nameof(ImportMaterialsAsInstances))")
API_FIELD(Attributes = "EditorOrder(402), EditorDisplay(\"Materials\"), VisibleIf(nameof(ImportMaterialsAsInstances)), VisibleIf(nameof(ShowGeometry))")
AssetReference<MaterialBase> InstanceToImportAs;
// If checked, the importer will import texture files used by the model and any embedded texture resources.
API_FIELD(Attributes="EditorOrder(410), EditorDisplay(\"Materials\"), VisibleIf(nameof(ShowGeometry))")
bool ImportTextures = true;
// If checked, the importer will try to keep the model's current material slots, instead of importing materials from the source file.
API_FIELD(Attributes="EditorOrder(420), EditorDisplay(\"Materials\", \"Keep Material Slots on Reimport\"), VisibleIf(nameof(ShowGeometry))")
// If checked, the importer will try to keep the model's current overridden material slots, instead of importing materials from the source file.
API_FIELD(Attributes="EditorOrder(420), EditorDisplay(\"Materials\", \"Keep Overridden Materials\"), VisibleIf(nameof(ShowGeometry))")
bool RestoreMaterialsOnReimport = true;
// If checked, the importer will not reimport any material from this model which already exist in the sub-asset folder.
API_FIELD(Attributes = "EditorOrder(421), EditorDisplay(\"Materials\", \"Skip Existing Materials\"), VisibleIf(nameof(ShowGeometry))")
bool SkipExistingMaterialsOnReimport = true;
public: // SDF

View File

@@ -179,7 +179,10 @@ namespace Flax.Build.Projects.VisualStudio
customizer.WriteVisualStudioBuildProperties(vsProject, platform, toolchain, configuration, vcProjectFileContent, vcFiltersFileContent, vcUserFileContent);
vcProjectFileContent.AppendLine(string.Format(" <IntDir>{0}</IntDir>", targetBuildOptions.IntermediateFolder));
vcProjectFileContent.AppendLine(string.Format(" <OutDir>{0}</OutDir>", targetBuildOptions.OutputFolder));
vcProjectFileContent.AppendLine(" <IncludePath />");
if (includePaths.Count != 0)
vcProjectFileContent.AppendLine(string.Format(" <IncludePath>$(IncludePath);{0}</IncludePath>", string.Join(";", includePaths)));
else
vcProjectFileContent.AppendLine(" <IncludePath />");
vcProjectFileContent.AppendLine(" <ReferencePath />");
vcProjectFileContent.AppendLine(" <LibraryPath />");
vcProjectFileContent.AppendLine(" <LibraryWPath />");