Merge branch 'master' into vehicle-physics
This commit is contained in:
@@ -22,6 +22,22 @@ namespace FlaxEditor.Content
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public abstract string TypeName { get; }
|
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>
|
/// <summary>
|
||||||
/// Checks if this proxy supports the given asset type id at the given path.
|
/// Checks if this proxy supports the given asset type id at the given path.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
// Copyright (c) 2012-2023 Wojciech Figat. All rights reserved.
|
// Copyright (c) 2012-2023 Wojciech Figat. All rights reserved.
|
||||||
|
|
||||||
using FlaxEditor.CustomEditors;
|
using FlaxEditor.CustomEditors;
|
||||||
|
using FlaxEditor.GUI.Docking;
|
||||||
using FlaxEditor.Windows;
|
using FlaxEditor.Windows;
|
||||||
using FlaxEngine.GUI;
|
using FlaxEngine.GUI;
|
||||||
using DockState = FlaxEditor.GUI.Docking.DockState;
|
|
||||||
|
|
||||||
namespace FlaxEditor
|
namespace FlaxEditor
|
||||||
{
|
{
|
||||||
@@ -97,9 +97,12 @@ namespace FlaxEditor
|
|||||||
/// Shows the window.
|
/// Shows the window.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="state">Initial window state.</param>
|
/// <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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -88,6 +88,8 @@ namespace FlaxEditor.CustomEditors.Editors
|
|||||||
_element.Value = asFloat;
|
_element.Value = asFloat;
|
||||||
else if (value is double asDouble)
|
else if (value is double asDouble)
|
||||||
_element.Value = (float)asDouble;
|
_element.Value = (float)asDouble;
|
||||||
|
else if (value is int asInt)
|
||||||
|
_element.Value = (float)asInt;
|
||||||
else
|
else
|
||||||
throw new Exception(string.Format("Invalid value type {0}.", value?.GetType().ToString() ?? "<null>"));
|
throw new Exception(string.Format("Invalid value type {0}.", value?.GetType().ToString() ?? "<null>"));
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -66,9 +66,9 @@ namespace FlaxEditor.CustomEditors.Editors
|
|||||||
public HeaderAttribute Header;
|
public HeaderAttribute Header;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The visible if attribute.
|
/// The visible if attributes.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public VisibleIfAttribute VisibleIf;
|
public VisibleIfAttribute[] VisibleIfs;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The read-only attribute usage flag.
|
/// The read-only attribute usage flag.
|
||||||
@@ -128,7 +128,7 @@ namespace FlaxEditor.CustomEditors.Editors
|
|||||||
CustomEditorAlias = (CustomEditorAliasAttribute)attributes.FirstOrDefault(x => x is CustomEditorAliasAttribute);
|
CustomEditorAlias = (CustomEditorAliasAttribute)attributes.FirstOrDefault(x => x is CustomEditorAliasAttribute);
|
||||||
Space = (SpaceAttribute)attributes.FirstOrDefault(x => x is SpaceAttribute);
|
Space = (SpaceAttribute)attributes.FirstOrDefault(x => x is SpaceAttribute);
|
||||||
Header = (HeaderAttribute)attributes.FirstOrDefault(x => x is HeaderAttribute);
|
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;
|
IsReadOnly = attributes.FirstOrDefault(x => x is ReadOnlyAttribute) != null;
|
||||||
ExpandGroups = attributes.FirstOrDefault(x => x is ExpandGroupsAttribute) != null;
|
ExpandGroups = attributes.FirstOrDefault(x => x is ExpandGroupsAttribute) != null;
|
||||||
|
|
||||||
@@ -210,17 +210,24 @@ namespace FlaxEditor.CustomEditors.Editors
|
|||||||
private struct VisibleIfCache
|
private struct VisibleIfCache
|
||||||
{
|
{
|
||||||
public ScriptMemberInfo Target;
|
public ScriptMemberInfo Target;
|
||||||
public ScriptMemberInfo Source;
|
public ScriptMemberInfo[] Sources;
|
||||||
public PropertiesListElement PropertiesList;
|
public PropertiesListElement PropertiesList;
|
||||||
public GroupElement Group;
|
public GroupElement Group;
|
||||||
public bool Invert;
|
public bool[] InversionList;
|
||||||
public int LabelIndex;
|
public int LabelIndex;
|
||||||
|
|
||||||
public bool GetValue(object instance)
|
public bool GetValue(object instance)
|
||||||
{
|
{
|
||||||
var value = (bool)Source.GetValue(instance);
|
bool value = true;
|
||||||
if (Invert)
|
|
||||||
value = !value;
|
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;
|
return value;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -298,40 +305,48 @@ namespace FlaxEditor.CustomEditors.Editors
|
|||||||
return items;
|
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);
|
ScriptMemberInfo[] members = Array.Empty<ScriptMemberInfo>();
|
||||||
if (property != ScriptMemberInfo.Null)
|
|
||||||
|
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);
|
if (!property.HasGet)
|
||||||
return ScriptMemberInfo.Null;
|
{
|
||||||
|
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);
|
if (field.ValueType.Type != typeof(bool))
|
||||||
return ScriptMemberInfo.Null;
|
{
|
||||||
|
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);
|
return members;
|
||||||
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;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void GroupPanelCheckIfCanRevert(LayoutElementsContainer layout, ref bool canRevertReference, ref bool canRevertDefault)
|
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)
|
protected virtual void SpawnProperty(LayoutElementsContainer itemLayout, ValueContainer itemValues, ItemInfo item)
|
||||||
{
|
{
|
||||||
int labelIndex = 0;
|
int labelIndex = 0;
|
||||||
if ((item.IsReadOnly || item.VisibleIf != null) &&
|
if ((item.IsReadOnly || item.VisibleIfs.Length > 0) &&
|
||||||
itemLayout.Children.Count > 0 &&
|
itemLayout.Children.Count > 0 &&
|
||||||
itemLayout.Children[itemLayout.Children.Count - 1] is PropertiesListElement propertiesListElement)
|
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;
|
PropertiesListElement list = null;
|
||||||
GroupElement group = null;
|
GroupElement group = null;
|
||||||
@@ -628,8 +643,8 @@ namespace FlaxEditor.CustomEditors.Editors
|
|||||||
return;
|
return;
|
||||||
|
|
||||||
// Get source member used to check rule
|
// Get source member used to check rule
|
||||||
var sourceMember = GetVisibleIfSource(item.Info.DeclaringType, item.VisibleIf);
|
var sourceMembers = GetVisibleIfSources(item.Info.DeclaringType, item.VisibleIfs);
|
||||||
if (sourceMember == ScriptType.Null)
|
if (sourceMembers.Length == 0)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
// Resize cache
|
// Resize cache
|
||||||
@@ -645,11 +660,11 @@ namespace FlaxEditor.CustomEditors.Editors
|
|||||||
_visibleIfCaches[count] = new VisibleIfCache
|
_visibleIfCaches[count] = new VisibleIfCache
|
||||||
{
|
{
|
||||||
Target = item.Info,
|
Target = item.Info,
|
||||||
Source = sourceMember,
|
Sources = sourceMembers,
|
||||||
PropertiesList = list,
|
PropertiesList = list,
|
||||||
Group = group,
|
Group = group,
|
||||||
LabelIndex = labelIndex,
|
LabelIndex = labelIndex,
|
||||||
Invert = item.VisibleIf.Invert,
|
InversionList = item.VisibleIfs.Select((x, i) => x.Invert).ToArray(),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,8 +1,12 @@
|
|||||||
// Copyright (c) 2012-2023 Wojciech Figat. All rights reserved.
|
// Copyright (c) 2012-2023 Wojciech Figat. All rights reserved.
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
|
using System.Linq;
|
||||||
using FlaxEditor.CustomEditors.Elements;
|
using FlaxEditor.CustomEditors.Elements;
|
||||||
|
using FlaxEditor.GUI;
|
||||||
|
using FlaxEditor.Scripting;
|
||||||
using FlaxEngine;
|
using FlaxEngine;
|
||||||
|
using FlaxEngine.Utilities;
|
||||||
|
|
||||||
namespace FlaxEditor.CustomEditors.Editors
|
namespace FlaxEditor.CustomEditors.Editors
|
||||||
{
|
{
|
||||||
@@ -13,6 +17,9 @@ namespace FlaxEditor.CustomEditors.Editors
|
|||||||
public sealed class GuidEditor : CustomEditor
|
public sealed class GuidEditor : CustomEditor
|
||||||
{
|
{
|
||||||
private TextBoxElement _element;
|
private TextBoxElement _element;
|
||||||
|
private AssetPicker _picker;
|
||||||
|
private bool _isReference;
|
||||||
|
private bool _isRefreshing;
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public override DisplayStyle Style => DisplayStyle.Inline;
|
public override DisplayStyle Style => DisplayStyle.Inline;
|
||||||
@@ -20,8 +27,55 @@ namespace FlaxEditor.CustomEditors.Editors
|
|||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public override void Initialize(LayoutElementsContainer layout)
|
public override void Initialize(LayoutElementsContainer layout)
|
||||||
{
|
{
|
||||||
_element = layout.TextBox();
|
var attributes = Values.GetAttributes();
|
||||||
_element.TextBox.EditEnd += OnEditEnd;
|
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()
|
private void OnEditEnd()
|
||||||
@@ -36,17 +90,32 @@ namespace FlaxEditor.CustomEditors.Editors
|
|||||||
public override void Refresh()
|
public override void Refresh()
|
||||||
{
|
{
|
||||||
base.Refresh();
|
base.Refresh();
|
||||||
|
_isRefreshing = true;
|
||||||
if (HasDifferentValues)
|
if (HasDifferentValues)
|
||||||
{
|
{
|
||||||
_element.TextBox.Text = string.Empty;
|
if (_isReference)
|
||||||
_element.TextBox.WatermarkText = "Different values";
|
{
|
||||||
|
// Not supported
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
_element.TextBox.Text = string.Empty;
|
||||||
|
_element.TextBox.WatermarkText = "Different values";
|
||||||
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
_element.TextBox.Text = ((Guid)Values[0]).ToString("D");
|
if (_isReference)
|
||||||
_element.TextBox.WatermarkText = string.Empty;
|
{
|
||||||
|
_picker.Validator.SelectedID = (Guid)Values[0];
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
_element.TextBox.Text = ((Guid)Values[0]).ToString("D");
|
||||||
|
_element.TextBox.WatermarkText = string.Empty;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
_isRefreshing = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -408,9 +408,9 @@ namespace FlaxEditor.GUI.ContextMenu
|
|||||||
{
|
{
|
||||||
foreach (var child in _panel.Children)
|
foreach (var child in _panel.Children)
|
||||||
{
|
{
|
||||||
if (child is ContextMenuChildMenu item && item.Visible)
|
if (child is ContextMenuButton item && item.Visible)
|
||||||
{
|
{
|
||||||
item.AdjustArrowAmount = -_panel.VScrollBar.Width;
|
item.ExtraAdjustmentAmount = -_panel.VScrollBar.Width;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -14,6 +14,11 @@ namespace FlaxEditor.GUI.ContextMenu
|
|||||||
public class ContextMenuButton : ContextMenuItem
|
public class ContextMenuButton : ContextMenuItem
|
||||||
{
|
{
|
||||||
private bool _isMouseDown;
|
private bool _isMouseDown;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The amount to adjust the short keys and arrow image by in x coordinates.
|
||||||
|
/// </summary>
|
||||||
|
public float ExtraAdjustmentAmount = 0;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Event fired when user clicks on the button.
|
/// Event fired when user clicks on the button.
|
||||||
@@ -133,7 +138,7 @@ namespace FlaxEditor.GUI.ContextMenu
|
|||||||
if (!string.IsNullOrEmpty(ShortKeys))
|
if (!string.IsNullOrEmpty(ShortKeys))
|
||||||
{
|
{
|
||||||
// Draw short keys
|
// Draw short keys
|
||||||
Render2D.DrawText(style.FontMedium, ShortKeys, textRect, textColor, TextAlignment.Far, TextAlignment.Center);
|
Render2D.DrawText(style.FontMedium, ShortKeys, new Rectangle(textRect.X + ExtraAdjustmentAmount, textRect.Y, textRect.Width, textRect.Height), textColor, TextAlignment.Far, TextAlignment.Center);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Draw icon
|
// Draw icon
|
||||||
|
|||||||
@@ -17,11 +17,6 @@ namespace FlaxEditor.GUI.ContextMenu
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public readonly ContextMenu ContextMenu = new ContextMenu();
|
public readonly ContextMenu ContextMenu = new ContextMenu();
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// The amount to adjust the arrow image by in x coordinates.
|
|
||||||
/// </summary>
|
|
||||||
public float AdjustArrowAmount = 0;
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Initializes a new instance of the <see cref="ContextMenuChildMenu"/> class.
|
/// Initializes a new instance of the <see cref="ContextMenuChildMenu"/> class.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@@ -49,7 +44,7 @@ namespace FlaxEditor.GUI.ContextMenu
|
|||||||
|
|
||||||
// Draw arrow
|
// Draw arrow
|
||||||
if (ContextMenu.HasChildren)
|
if (ContextMenu.HasChildren)
|
||||||
Render2D.DrawSprite(style.ArrowRight, new Rectangle(Width - 15 + AdjustArrowAmount, (Height - 12) / 2, 12, 12), Enabled ? isCMopened ? style.BackgroundSelected : style.Foreground : style.ForegroundDisabled);
|
Render2D.DrawSprite(style.ArrowRight, new Rectangle(Width - 15 + ExtraAdjustmentAmount, (Height - 12) / 2, 12, 12), Enabled ? isCMopened ? style.BackgroundSelected : style.Foreground : style.ForegroundDisabled);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
|
|||||||
@@ -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>
|
/// <summary>
|
||||||
@@ -528,7 +528,9 @@ namespace FlaxEditor.GUI.Docking
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="state">The state.</param>
|
/// <param name="state">The state.</param>
|
||||||
/// <param name="window">The window.</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();
|
CreateTabsProxy();
|
||||||
|
|
||||||
@@ -536,12 +538,12 @@ namespace FlaxEditor.GUI.Docking
|
|||||||
if (state == DockState.DockFill)
|
if (state == DockState.DockFill)
|
||||||
{
|
{
|
||||||
// Add tab
|
// Add tab
|
||||||
AddTab(window);
|
AddTab(window, autoSelect);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
// Create child panel
|
// Create child panel
|
||||||
var dockPanel = CreateChildPanel(state, DefaultSplitterValue);
|
var dockPanel = CreateChildPanel(state, splitterValue ?? DefaultSplitterValue);
|
||||||
|
|
||||||
// Dock window as a tab in a child panel
|
// Dock window as a tab in a child panel
|
||||||
dockPanel.DockWindow(DockState.DockFill, window);
|
dockPanel.DockWindow(DockState.DockFill, window);
|
||||||
|
|||||||
@@ -214,7 +214,9 @@ namespace FlaxEditor.GUI.Docking
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="state">Initial window state.</param>
|
/// <param name="state">Initial window state.</param>
|
||||||
/// <param name="toDock">Panel to dock to it.</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)
|
if (state == DockState.Hidden)
|
||||||
{
|
{
|
||||||
@@ -232,7 +234,7 @@ namespace FlaxEditor.GUI.Docking
|
|||||||
Undock();
|
Undock();
|
||||||
|
|
||||||
// Then dock
|
// Then dock
|
||||||
(toDock ?? _masterPanel).DockWindowInternal(state, this);
|
(toDock ?? _masterPanel).DockWindowInternal(state, this, autoSelect, splitterValue);
|
||||||
OnShow();
|
OnShow();
|
||||||
PerformLayout();
|
PerformLayout();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -196,6 +196,25 @@ namespace FlaxEditor.Modules
|
|||||||
|
|
||||||
return null;
|
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>
|
/// <summary>
|
||||||
/// Refreshes the given item folder. Tries to find new content items and remove not existing ones.
|
/// 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);
|
item = proxy?.ConstructItem(path, assetInfo.TypeName, ref assetInfo.ID);
|
||||||
}
|
}
|
||||||
if (item == null)
|
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
|
// Link
|
||||||
item.ParentFolder = parent.Folder;
|
item.ParentFolder = parent.Folder;
|
||||||
|
|||||||
@@ -36,6 +36,22 @@ namespace FlaxEditor.Modules
|
|||||||
{
|
{
|
||||||
public string AssemblyName;
|
public string AssemblyName;
|
||||||
public string TypeName;
|
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>();
|
private readonly List<WindowRestoreData> _restoreWindows = new List<WindowRestoreData>();
|
||||||
@@ -802,10 +818,38 @@ namespace FlaxEditor.Modules
|
|||||||
if (constructor == null || type.IsGenericType)
|
if (constructor == null || type.IsGenericType)
|
||||||
return;
|
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.AssemblyName = type.Assembly.GetName().Name;
|
||||||
winData.TypeName = type.FullName;
|
winData.TypeName = type.FullName;
|
||||||
// TODO: cache and restore docking info
|
|
||||||
_restoreWindows.Add(winData);
|
_restoreWindows.Add(winData);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -824,7 +868,24 @@ namespace FlaxEditor.Modules
|
|||||||
if (type != null)
|
if (type != null)
|
||||||
{
|
{
|
||||||
var win = (CustomEditorWindow)Activator.CreateInstance(type);
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -113,6 +113,25 @@ namespace FlaxEditor.Surface
|
|||||||
ScriptsBuilder.ScriptsReloadBegin += OnScriptsReloadBegin;
|
ScriptsBuilder.ScriptsReloadBegin += OnScriptsReloadBegin;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
internal AnimGraphTraceEvent[] LastTraceEvents;
|
||||||
|
|
||||||
|
internal bool TryGetTraceEvent(SurfaceNode node, out AnimGraphTraceEvent traceEvent)
|
||||||
|
{
|
||||||
|
if (LastTraceEvents != null)
|
||||||
|
{
|
||||||
|
foreach (var e in LastTraceEvents)
|
||||||
|
{
|
||||||
|
if (e.NodeId == node.ID)
|
||||||
|
{
|
||||||
|
traceEvent = e;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
traceEvent = default;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
private static SurfaceStyle CreateStyle()
|
private static SurfaceStyle CreateStyle()
|
||||||
{
|
{
|
||||||
var editor = Editor.Instance;
|
var editor = Editor.Instance;
|
||||||
@@ -383,6 +402,7 @@ namespace FlaxEditor.Surface
|
|||||||
}
|
}
|
||||||
ScriptsBuilder.ScriptsReloadBegin -= OnScriptsReloadBegin;
|
ScriptsBuilder.ScriptsReloadBegin -= OnScriptsReloadBegin;
|
||||||
_nodesCache.Wait();
|
_nodesCache.Wait();
|
||||||
|
LastTraceEvents = null;
|
||||||
|
|
||||||
base.OnDestroy();
|
base.OnDestroy();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1583,14 +1583,24 @@ namespace FlaxEditor.Surface.Archetypes
|
|||||||
None = 0,
|
None = 0,
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Transition rule will be rechecked during active transition with option to interrupt transition.
|
/// Transition rule will be rechecked during active transition with option to interrupt transition (to go back to the source state).
|
||||||
/// </summary>
|
/// </summary>
|
||||||
RuleRechecking = 1,
|
RuleRechecking = 1,
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Interrupted transition is immediately stopped without blending out.
|
/// Interrupted transition is immediately stopped without blending out (back to the source/destination state).
|
||||||
/// </summary>
|
/// </summary>
|
||||||
Instant = 2,
|
Instant = 2,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Enables checking other transitions in the source state that might interrupt this one.
|
||||||
|
/// </summary>
|
||||||
|
SourceState = 4,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Enables checking transitions in the destination state that might interrupt this one.
|
||||||
|
/// </summary>
|
||||||
|
DestinationState = 8,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -1613,6 +1623,8 @@ namespace FlaxEditor.Surface.Archetypes
|
|||||||
UseDefaultRule = 4,
|
UseDefaultRule = 4,
|
||||||
InterruptionRuleRechecking = 8,
|
InterruptionRuleRechecking = 8,
|
||||||
InterruptionInstant = 16,
|
InterruptionInstant = 16,
|
||||||
|
InterruptionSourceState = 32,
|
||||||
|
InterruptionDestinationState = 64,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -1773,7 +1785,7 @@ namespace FlaxEditor.Surface.Archetypes
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Transition interruption options.
|
/// Transition interruption options (flags, can select multiple values).
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[EditorOrder(70), DefaultValue(InterruptionFlags.None)]
|
[EditorOrder(70), DefaultValue(InterruptionFlags.None)]
|
||||||
public InterruptionFlags Interruption
|
public InterruptionFlags Interruption
|
||||||
@@ -1785,12 +1797,18 @@ namespace FlaxEditor.Surface.Archetypes
|
|||||||
flags |= InterruptionFlags.RuleRechecking;
|
flags |= InterruptionFlags.RuleRechecking;
|
||||||
if (_data.HasFlag(Data.FlagTypes.InterruptionInstant))
|
if (_data.HasFlag(Data.FlagTypes.InterruptionInstant))
|
||||||
flags |= InterruptionFlags.Instant;
|
flags |= InterruptionFlags.Instant;
|
||||||
|
if (_data.HasFlag(Data.FlagTypes.InterruptionSourceState))
|
||||||
|
flags |= InterruptionFlags.SourceState;
|
||||||
|
if (_data.HasFlag(Data.FlagTypes.InterruptionDestinationState))
|
||||||
|
flags |= InterruptionFlags.DestinationState;
|
||||||
return flags;
|
return flags;
|
||||||
}
|
}
|
||||||
set
|
set
|
||||||
{
|
{
|
||||||
_data.SetFlag(Data.FlagTypes.InterruptionRuleRechecking, value.HasFlag(InterruptionFlags.RuleRechecking));
|
_data.SetFlag(Data.FlagTypes.InterruptionRuleRechecking, value.HasFlag(InterruptionFlags.RuleRechecking));
|
||||||
_data.SetFlag(Data.FlagTypes.InterruptionInstant, value.HasFlag(InterruptionFlags.Instant));
|
_data.SetFlag(Data.FlagTypes.InterruptionInstant, value.HasFlag(InterruptionFlags.Instant));
|
||||||
|
_data.SetFlag(Data.FlagTypes.InterruptionSourceState, value.HasFlag(InterruptionFlags.SourceState));
|
||||||
|
_data.SetFlag(Data.FlagTypes.InterruptionDestinationState, value.HasFlag(InterruptionFlags.DestinationState));
|
||||||
SourceState.SaveTransitions(true);
|
SourceState.SaveTransitions(true);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -36,6 +36,7 @@ namespace FlaxEditor.Surface.Archetypes
|
|||||||
{
|
{
|
||||||
private AssetSelect _assetSelect;
|
private AssetSelect _assetSelect;
|
||||||
private Box _assetBox;
|
private Box _assetBox;
|
||||||
|
private ProgressBar _playbackPos;
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public Sample(uint id, VisjectSurfaceContext context, NodeArchetype nodeArch, GroupArchetype groupArch)
|
public Sample(uint id, VisjectSurfaceContext context, NodeArchetype nodeArch, GroupArchetype groupArch)
|
||||||
@@ -93,6 +94,36 @@ namespace FlaxEditor.Surface.Archetypes
|
|||||||
_assetSelect.Visible = !box.HasAnyConnection;
|
_assetSelect.Visible = !box.HasAnyConnection;
|
||||||
UpdateTitle();
|
UpdateTitle();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public override void Update(float deltaTime)
|
||||||
|
{
|
||||||
|
// Debug current playback position
|
||||||
|
if (((AnimGraphSurface)Surface).TryGetTraceEvent(this, out var traceEvent) && traceEvent.Asset is FlaxEngine.Animation anim)
|
||||||
|
{
|
||||||
|
if (_playbackPos == null)
|
||||||
|
{
|
||||||
|
_playbackPos = new ProgressBar
|
||||||
|
{
|
||||||
|
SmoothingScale = 0.0f,
|
||||||
|
Offsets = Margin.Zero,
|
||||||
|
AnchorPreset = AnchorPresets.HorizontalStretchBottom,
|
||||||
|
Parent = this,
|
||||||
|
Height = 12.0f,
|
||||||
|
};
|
||||||
|
_playbackPos.Y -= 16.0f;
|
||||||
|
}
|
||||||
|
_playbackPos.Visible = true;
|
||||||
|
_playbackPos.Maximum = anim.Duration;
|
||||||
|
_playbackPos.Value = traceEvent.Value; // AnimGraph reports position in animation frames, not time
|
||||||
|
}
|
||||||
|
else if (_playbackPos != null)
|
||||||
|
{
|
||||||
|
_playbackPos.Visible = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
base.Update(deltaTime);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|||||||
@@ -24,6 +24,8 @@ namespace FlaxEditor.Surface.Undo
|
|||||||
|
|
||||||
public ConnectBoxesAction(InputBox iB, OutputBox oB, bool connect)
|
public ConnectBoxesAction(InputBox iB, OutputBox oB, bool connect)
|
||||||
{
|
{
|
||||||
|
if (iB == null || oB == null || iB.ParentNode == null || oB.ParentNode == null)
|
||||||
|
throw new System.ArgumentNullException();
|
||||||
_surface = iB.Surface;
|
_surface = iB.Surface;
|
||||||
_context = new ContextHandle(iB.ParentNode.Context);
|
_context = new ContextHandle(iB.ParentNode.Context);
|
||||||
_connect = connect;
|
_connect = connect;
|
||||||
|
|||||||
@@ -1310,8 +1310,17 @@ namespace FlaxEditor.Utilities
|
|||||||
inputActions.Add(options => options.BuildSDF, Editor.Instance.BuildAllMeshesSDF);
|
inputActions.Add(options => options.BuildSDF, Editor.Instance.BuildAllMeshesSDF);
|
||||||
inputActions.Add(options => options.TakeScreenshot, Editor.Instance.Windows.TakeScreenshot);
|
inputActions.Add(options => options.TakeScreenshot, Editor.Instance.Windows.TakeScreenshot);
|
||||||
inputActions.Add(options => options.ProfilerWindow, () => Editor.Instance.Windows.ProfilerWin.FocusOrShow());
|
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.ProfilerStartStop, () =>
|
||||||
inputActions.Add(options => options.ProfilerClear, () => { Editor.Instance.Windows.ProfilerWin.Clear(); Editor.Instance.UI.AddStatusMessage($"Profiling results cleared."); });
|
{
|
||||||
|
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.SaveScenes, () => Editor.Instance.Scene.SaveScenes());
|
||||||
inputActions.Add(options => options.CloseScenes, () => Editor.Instance.Scene.CloseAllScenes());
|
inputActions.Add(options => options.CloseScenes, () => Editor.Instance.Scene.CloseAllScenes());
|
||||||
inputActions.Add(options => options.OpenScriptsProject, () => Editor.Instance.CodeEditing.OpenSolution());
|
inputActions.Add(options => options.OpenScriptsProject, () => Editor.Instance.CodeEditing.OpenSolution());
|
||||||
|
|||||||
@@ -1113,7 +1113,7 @@ namespace FlaxEditor.Viewport
|
|||||||
private void OnFarPlaneChanged(FloatValueBox control)
|
private void OnFarPlaneChanged(FloatValueBox control)
|
||||||
{
|
{
|
||||||
_farPlane = control.Value;
|
_farPlane = control.Value;
|
||||||
_editor.ProjectCache.SetCustomData("CameraNearPlaneValue", _farPlane.ToString());
|
_editor.ProjectCache.SetCustomData("CameraFarPlaneValue", _farPlane.ToString());
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|||||||
@@ -396,6 +396,16 @@ namespace FlaxEditor.Windows.Assets
|
|||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public override void OnUpdate()
|
public override void OnUpdate()
|
||||||
{
|
{
|
||||||
|
// Extract animations playback state from the events tracing
|
||||||
|
var debugActor = _debugPicker.Value as AnimatedModel;
|
||||||
|
if (debugActor == null)
|
||||||
|
debugActor = _preview.PreviewActor;
|
||||||
|
if (debugActor != null)
|
||||||
|
{
|
||||||
|
debugActor.EnableTracing = true;
|
||||||
|
Surface.LastTraceEvents = debugActor.TraceEvents;
|
||||||
|
}
|
||||||
|
|
||||||
base.OnUpdate();
|
base.OnUpdate();
|
||||||
|
|
||||||
// Update graph execution flow debugging visualization
|
// Update graph execution flow debugging visualization
|
||||||
@@ -416,6 +426,8 @@ namespace FlaxEditor.Windows.Assets
|
|||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public override void OnDestroy()
|
public override void OnDestroy()
|
||||||
{
|
{
|
||||||
|
if (IsDisposing)
|
||||||
|
return;
|
||||||
Animations.DebugFlow -= OnDebugFlow;
|
Animations.DebugFlow -= OnDebugFlow;
|
||||||
|
|
||||||
_properties = null;
|
_properties = null;
|
||||||
|
|||||||
@@ -321,8 +321,7 @@ namespace FlaxEditor.Windows.Profiler
|
|||||||
var data = _events.Get(_drawTimeCPU.SelectedSampleIndex);
|
var data = _events.Get(_drawTimeCPU.SelectedSampleIndex);
|
||||||
if (data == null || data.Length == 0)
|
if (data == null || data.Length == 0)
|
||||||
return;
|
return;
|
||||||
|
float totalTimeMs = _drawTimeGPU.SelectedSample;
|
||||||
float totalTimeMs = _drawTimeCPU.SelectedSample;
|
|
||||||
|
|
||||||
// Add rows
|
// Add rows
|
||||||
var rowColor2 = Style.Current.Background * 1.4f;
|
var rowColor2 = Style.Current.Background * 1.4f;
|
||||||
@@ -343,14 +342,19 @@ namespace FlaxEditor.Windows.Profiler
|
|||||||
row = new Row
|
row = new Row
|
||||||
{
|
{
|
||||||
Values = new object[6],
|
Values = new object[6],
|
||||||
|
BackgroundColors = new Color[6],
|
||||||
};
|
};
|
||||||
|
for (int k = 0; k < row.BackgroundColors.Length; k++)
|
||||||
|
row.BackgroundColors[k] = Color.Transparent;
|
||||||
}
|
}
|
||||||
{
|
{
|
||||||
// Event
|
// Event
|
||||||
row.Values[0] = name;
|
row.Values[0] = name;
|
||||||
|
|
||||||
// Total (%)
|
// 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
|
// GPU ms
|
||||||
row.Values[2] = (e.Time * 10000.0f) / 10000.0f;
|
row.Values[2] = (e.Time * 10000.0f) / 10000.0f;
|
||||||
|
|||||||
@@ -38,6 +38,7 @@ namespace FlaxEditor.Windows.Profiler
|
|||||||
if (value != LiveRecording)
|
if (value != LiveRecording)
|
||||||
{
|
{
|
||||||
_liveRecordingButton.Checked = value;
|
_liveRecordingButton.Checked = value;
|
||||||
|
OnLiveRecordingChanged();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -99,10 +99,7 @@ void BlendPoseBucketInit(AnimGraphInstanceData::Bucket& bucket)
|
|||||||
|
|
||||||
void StateMachineBucketInit(AnimGraphInstanceData::Bucket& bucket)
|
void StateMachineBucketInit(AnimGraphInstanceData::Bucket& bucket)
|
||||||
{
|
{
|
||||||
bucket.StateMachine.LastUpdateFrame = 0;
|
Platform::MemoryClear(&bucket.StateMachine, sizeof(bucket.StateMachine));
|
||||||
bucket.StateMachine.CurrentState = nullptr;
|
|
||||||
bucket.StateMachine.ActiveTransition = nullptr;
|
|
||||||
bucket.StateMachine.TransitionPosition = 0.0f;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void SlotBucketInit(AnimGraphInstanceData::Bucket& bucket)
|
void SlotBucketInit(AnimGraphInstanceData::Bucket& bucket)
|
||||||
|
|||||||
@@ -39,6 +39,7 @@ void AnimGraphImpulse::SetNodeModelTransformation(SkeletonData& skeleton, int32
|
|||||||
void AnimGraphInstanceData::Clear()
|
void AnimGraphInstanceData::Clear()
|
||||||
{
|
{
|
||||||
ClearState();
|
ClearState();
|
||||||
|
Slots.Clear();
|
||||||
Parameters.Resize(0);
|
Parameters.Resize(0);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -55,7 +56,7 @@ void AnimGraphInstanceData::ClearState()
|
|||||||
RootMotion = Transform::Identity;
|
RootMotion = Transform::Identity;
|
||||||
State.Resize(0);
|
State.Resize(0);
|
||||||
NodesPose.Resize(0);
|
NodesPose.Resize(0);
|
||||||
Slots.Clear();
|
TraceEvents.Clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
void AnimGraphInstanceData::Invalidate()
|
void AnimGraphInstanceData::Invalidate()
|
||||||
@@ -238,6 +239,7 @@ void AnimGraphExecutor::Update(AnimGraphInstanceData& data, float dt)
|
|||||||
}
|
}
|
||||||
for (auto& e : data.ActiveEvents)
|
for (auto& e : data.ActiveEvents)
|
||||||
e.Hit = false;
|
e.Hit = false;
|
||||||
|
data.TraceEvents.Clear();
|
||||||
|
|
||||||
// Init empty nodes data
|
// Init empty nodes data
|
||||||
context.EmptyNodes.RootMotion = Transform::Identity;
|
context.EmptyNodes.RootMotion = Transform::Identity;
|
||||||
@@ -282,6 +284,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;
|
SkeletonData* animResultSkeleton = &skeleton;
|
||||||
|
|
||||||
// Retarget animation when using output pose from other skeleton
|
// Retarget animation when using output pose from other skeleton
|
||||||
|
|||||||
@@ -129,6 +129,8 @@ public:
|
|||||||
UseDefaultRule = 4,
|
UseDefaultRule = 4,
|
||||||
InterruptionRuleRechecking = 8,
|
InterruptionRuleRechecking = 8,
|
||||||
InterruptionInstant = 16,
|
InterruptionInstant = 16,
|
||||||
|
InterruptionSourceState = 32,
|
||||||
|
InterruptionDestinationState = 64,
|
||||||
};
|
};
|
||||||
|
|
||||||
public:
|
public:
|
||||||
@@ -197,6 +199,22 @@ struct FLAXENGINE_API AnimGraphSlot
|
|||||||
float BlendOutTime = 0.0f;
|
float BlendOutTime = 0.0f;
|
||||||
int32 LoopCount = 0;
|
int32 LoopCount = 0;
|
||||||
bool Pause = false;
|
bool Pause = false;
|
||||||
|
bool Reset = false;
|
||||||
|
};
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The animation graph state container for a single node playback trace (eg. animation sample info or state transition). Can be used by Anim Graph debugging or custom scripting.
|
||||||
|
/// </summary>
|
||||||
|
API_STRUCT() struct FLAXENGINE_API AnimGraphTraceEvent
|
||||||
|
{
|
||||||
|
DECLARE_SCRIPTING_TYPE_MINIMAL(AnimGraphTraceEvent);
|
||||||
|
|
||||||
|
// Contextual asset used. For example, sampled animation.
|
||||||
|
API_FIELD() Asset* Asset = nullptr;
|
||||||
|
// Generic value contextual to playback type (eg. animation sample position).
|
||||||
|
API_FIELD() float Value = 0;
|
||||||
|
// Identifier of the node in the graph.
|
||||||
|
API_FIELD() uint32 NodeId = 0;
|
||||||
};
|
};
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -240,7 +258,10 @@ public:
|
|||||||
uint64 LastUpdateFrame;
|
uint64 LastUpdateFrame;
|
||||||
AnimGraphNode* CurrentState;
|
AnimGraphNode* CurrentState;
|
||||||
AnimGraphStateTransition* ActiveTransition;
|
AnimGraphStateTransition* ActiveTransition;
|
||||||
|
AnimGraphStateTransition* BaseTransition;
|
||||||
|
AnimGraphNode* BaseTransitionState;
|
||||||
float TransitionPosition;
|
float TransitionPosition;
|
||||||
|
float BaseTransitionPosition;
|
||||||
};
|
};
|
||||||
|
|
||||||
struct SlotBucket
|
struct SlotBucket
|
||||||
@@ -357,6 +378,12 @@ public:
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
void InvokeAnimEvents();
|
void InvokeAnimEvents();
|
||||||
|
|
||||||
|
public:
|
||||||
|
// Anim Graph logic tracing feature that allows to collect insights of animations sampling and skeleton poses operations.
|
||||||
|
bool EnableTracing = false;
|
||||||
|
// Trace events collected when using EnableTracing option.
|
||||||
|
Array<AnimGraphTraceEvent> TraceEvents;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
struct OutgoingEvent
|
struct OutgoingEvent
|
||||||
{
|
{
|
||||||
@@ -836,7 +863,7 @@ public:
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Resets all the state bucket used by the given graph including sub-graphs (total). Can eb used to reset the animation state of the nested graph (including children).
|
/// Resets all the state bucket used by the given graph including sub-graphs (total). Can be used to reset the animation state of the nested graph (including children).
|
||||||
/// </summary>
|
/// </summary>
|
||||||
void ResetBuckets(AnimGraphContext& context, AnimGraphBase* graph);
|
void ResetBuckets(AnimGraphContext& context, AnimGraphBase* graph);
|
||||||
|
|
||||||
@@ -865,5 +892,7 @@ private:
|
|||||||
Variant SampleAnimationsWithBlend(AnimGraphNode* node, bool loop, float length, float startTimePos, float prevTimePos, float& newTimePos, Animation* animA, Animation* animB, Animation* animC, float speedA, float speedB, float speedC, float alphaA, float alphaB, float alphaC);
|
Variant SampleAnimationsWithBlend(AnimGraphNode* node, bool loop, float length, float startTimePos, float prevTimePos, float& newTimePos, Animation* animA, Animation* animB, Animation* animC, float speedA, float speedB, float speedC, float alphaA, float alphaB, float alphaC);
|
||||||
Variant Blend(AnimGraphNode* node, const Value& poseA, const Value& poseB, float alpha, AlphaBlendMode alphaMode);
|
Variant Blend(AnimGraphNode* node, const Value& poseA, const Value& poseB, float alpha, AlphaBlendMode alphaMode);
|
||||||
Variant SampleState(AnimGraphNode* state);
|
Variant SampleState(AnimGraphNode* state);
|
||||||
|
void InitStateTransition(AnimGraphContext& context, AnimGraphInstanceData::StateMachineBucket& stateMachineBucket, AnimGraphStateTransition* transition = nullptr);
|
||||||
|
AnimGraphStateTransition* UpdateStateTransitions(AnimGraphContext& context, const AnimGraphNode::StateMachineData& stateMachineData, AnimGraphNode* state, AnimGraphNode* ignoreState = nullptr);
|
||||||
void UpdateStateTransitions(AnimGraphContext& context, const AnimGraphNode::StateMachineData& stateMachineData, AnimGraphInstanceData::StateMachineBucket& stateMachineBucket, const AnimGraphNode::StateBaseData& stateData);
|
void UpdateStateTransitions(AnimGraphContext& context, const AnimGraphNode::StateMachineData& stateMachineData, AnimGraphInstanceData::StateMachineBucket& stateMachineBucket, const AnimGraphNode::StateBaseData& stateData);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -219,6 +219,16 @@ void AnimGraphExecutor::ProcessAnimation(AnimGraphImpulse* nodes, AnimGraphNode*
|
|||||||
const float animPos = GetAnimSamplePos(length, anim, pos, speed);
|
const float animPos = GetAnimSamplePos(length, anim, pos, speed);
|
||||||
const float animPrevPos = GetAnimSamplePos(length, anim, prevPos, speed);
|
const float animPrevPos = GetAnimSamplePos(length, anim, prevPos, speed);
|
||||||
|
|
||||||
|
// Add to trace
|
||||||
|
auto& context = Context.Get();
|
||||||
|
if (context.Data->EnableTracing)
|
||||||
|
{
|
||||||
|
auto& trace = context.Data->TraceEvents.AddOne();
|
||||||
|
trace.Asset = anim;
|
||||||
|
trace.Value = animPos;
|
||||||
|
trace.NodeId = node->ID;
|
||||||
|
}
|
||||||
|
|
||||||
// Evaluate nested animations
|
// Evaluate nested animations
|
||||||
bool hasNested = false;
|
bool hasNested = false;
|
||||||
if (anim->NestedAnims.Count() != 0)
|
if (anim->NestedAnims.Count() != 0)
|
||||||
@@ -460,10 +470,12 @@ Variant AnimGraphExecutor::Blend(AnimGraphNode* node, const Value& poseA, const
|
|||||||
{
|
{
|
||||||
ANIM_GRAPH_PROFILE_EVENT("Blend Pose");
|
ANIM_GRAPH_PROFILE_EVENT("Blend Pose");
|
||||||
|
|
||||||
|
if (isnan(alpha) || isinf(alpha))
|
||||||
|
alpha = 0;
|
||||||
|
alpha = Math::Saturate(alpha);
|
||||||
alpha = AlphaBlend::Process(alpha, alphaMode);
|
alpha = AlphaBlend::Process(alpha, alphaMode);
|
||||||
|
|
||||||
const auto nodes = node->GetNodes(this);
|
const auto nodes = node->GetNodes(this);
|
||||||
|
|
||||||
auto nodesA = static_cast<AnimGraphImpulse*>(poseA.AsPointer);
|
auto nodesA = static_cast<AnimGraphImpulse*>(poseA.AsPointer);
|
||||||
auto nodesB = static_cast<AnimGraphImpulse*>(poseB.AsPointer);
|
auto nodesB = static_cast<AnimGraphImpulse*>(poseB.AsPointer);
|
||||||
if (!ANIM_GRAPH_IS_VALID_PTR(poseA))
|
if (!ANIM_GRAPH_IS_VALID_PTR(poseA))
|
||||||
@@ -484,32 +496,40 @@ Variant AnimGraphExecutor::Blend(AnimGraphNode* node, const Value& poseA, const
|
|||||||
|
|
||||||
Variant AnimGraphExecutor::SampleState(AnimGraphNode* state)
|
Variant AnimGraphExecutor::SampleState(AnimGraphNode* state)
|
||||||
{
|
{
|
||||||
// Prepare
|
|
||||||
auto& data = state->Data.State;
|
auto& data = state->Data.State;
|
||||||
if (data.Graph == nullptr || data.Graph->GetRootNode() == nullptr)
|
if (data.Graph == nullptr || data.Graph->GetRootNode() == nullptr)
|
||||||
{
|
|
||||||
// Invalid state graph
|
|
||||||
return Value::Null;
|
return Value::Null;
|
||||||
}
|
|
||||||
|
|
||||||
ANIM_GRAPH_PROFILE_EVENT("Evaluate State");
|
ANIM_GRAPH_PROFILE_EVENT("Evaluate State");
|
||||||
|
|
||||||
// Evaluate state
|
|
||||||
auto rootNode = data.Graph->GetRootNode();
|
auto rootNode = data.Graph->GetRootNode();
|
||||||
auto result = eatBox((Node*)rootNode, &rootNode->Boxes[0]);
|
return eatBox((Node*)rootNode, &rootNode->Boxes[0]);
|
||||||
|
|
||||||
return result;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void AnimGraphExecutor::UpdateStateTransitions(AnimGraphContext& context, const AnimGraphNode::StateMachineData& stateMachineData, AnimGraphInstanceData::StateMachineBucket& stateMachineBucket, const AnimGraphNode::StateBaseData& stateData)
|
void AnimGraphExecutor::InitStateTransition(AnimGraphContext& context, AnimGraphInstanceData::StateMachineBucket& stateMachineBucket, AnimGraphStateTransition* transition)
|
||||||
|
{
|
||||||
|
// Reset transiton
|
||||||
|
stateMachineBucket.ActiveTransition = transition;
|
||||||
|
stateMachineBucket.TransitionPosition = 0.0f;
|
||||||
|
|
||||||
|
// End base transition
|
||||||
|
if (stateMachineBucket.BaseTransition)
|
||||||
|
{
|
||||||
|
ResetBuckets(context, stateMachineBucket.BaseTransitionState->Data.State.Graph);
|
||||||
|
stateMachineBucket.BaseTransition = nullptr;
|
||||||
|
stateMachineBucket.BaseTransitionState = nullptr;
|
||||||
|
stateMachineBucket.BaseTransitionPosition = 0.0f;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
AnimGraphStateTransition* AnimGraphExecutor::UpdateStateTransitions(AnimGraphContext& context, const AnimGraphNode::StateMachineData& stateMachineData, AnimGraphNode* state, AnimGraphNode* ignoreState)
|
||||||
{
|
{
|
||||||
int32 transitionIndex = 0;
|
int32 transitionIndex = 0;
|
||||||
|
const AnimGraphNode::StateBaseData& stateData = state->Data.State;
|
||||||
while (transitionIndex < ANIM_GRAPH_MAX_STATE_TRANSITIONS && stateData.Transitions[transitionIndex] != AnimGraphNode::StateData::InvalidTransitionIndex)
|
while (transitionIndex < ANIM_GRAPH_MAX_STATE_TRANSITIONS && stateData.Transitions[transitionIndex] != AnimGraphNode::StateData::InvalidTransitionIndex)
|
||||||
{
|
{
|
||||||
const uint16 idx = stateData.Transitions[transitionIndex];
|
const uint16 idx = stateData.Transitions[transitionIndex];
|
||||||
ASSERT(idx < stateMachineData.Graph->StateTransitions.Count());
|
ASSERT(idx < stateMachineData.Graph->StateTransitions.Count());
|
||||||
auto& transition = stateMachineData.Graph->StateTransitions[idx];
|
auto& transition = stateMachineData.Graph->StateTransitions[idx];
|
||||||
if (transition.Destination == stateMachineBucket.CurrentState)
|
if (transition.Destination == state || transition.Destination == ignoreState)
|
||||||
{
|
{
|
||||||
// Ignore transition to the current state
|
// Ignore transition to the current state
|
||||||
transitionIndex++;
|
transitionIndex++;
|
||||||
@@ -517,7 +537,7 @@ void AnimGraphExecutor::UpdateStateTransitions(AnimGraphContext& context, const
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Evaluate source state transition data (position, length, etc.)
|
// Evaluate source state transition data (position, length, etc.)
|
||||||
const Value sourceStatePtr = SampleState(stateMachineBucket.CurrentState);
|
const Value sourceStatePtr = SampleState(state);
|
||||||
auto& transitionData = context.TransitionData; // Note: this could support nested transitions but who uses state machine inside transition rule?
|
auto& transitionData = context.TransitionData; // Note: this could support nested transitions but who uses state machine inside transition rule?
|
||||||
if (ANIM_GRAPH_IS_VALID_PTR(sourceStatePtr))
|
if (ANIM_GRAPH_IS_VALID_PTR(sourceStatePtr))
|
||||||
{
|
{
|
||||||
@@ -538,6 +558,7 @@ void AnimGraphExecutor::UpdateStateTransitions(AnimGraphContext& context, const
|
|||||||
if (transition.RuleGraph && !useDefaultRule)
|
if (transition.RuleGraph && !useDefaultRule)
|
||||||
{
|
{
|
||||||
// Execute transition rule
|
// Execute transition rule
|
||||||
|
ANIM_GRAPH_PROFILE_EVENT("Rule");
|
||||||
auto rootNode = transition.RuleGraph->GetRootNode();
|
auto rootNode = transition.RuleGraph->GetRootNode();
|
||||||
ASSERT(rootNode);
|
ASSERT(rootNode);
|
||||||
if (!(bool)eatBox((Node*)rootNode, &rootNode->Boxes[0]))
|
if (!(bool)eatBox((Node*)rootNode, &rootNode->Boxes[0]))
|
||||||
@@ -560,10 +581,7 @@ void AnimGraphExecutor::UpdateStateTransitions(AnimGraphContext& context, const
|
|||||||
canEnter = true;
|
canEnter = true;
|
||||||
if (canEnter)
|
if (canEnter)
|
||||||
{
|
{
|
||||||
// Start transition
|
return &transition;
|
||||||
stateMachineBucket.ActiveTransition = &transition;
|
|
||||||
stateMachineBucket.TransitionPosition = 0.0f;
|
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Skip after Solo transition
|
// Skip after Solo transition
|
||||||
@@ -573,6 +591,18 @@ void AnimGraphExecutor::UpdateStateTransitions(AnimGraphContext& context, const
|
|||||||
|
|
||||||
transitionIndex++;
|
transitionIndex++;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// No transition
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
void AnimGraphExecutor::UpdateStateTransitions(AnimGraphContext& context, const AnimGraphNode::StateMachineData& stateMachineData, AnimGraphInstanceData::StateMachineBucket& stateMachineBucket, const AnimGraphNode::StateBaseData& stateData)
|
||||||
|
{
|
||||||
|
AnimGraphStateTransition* transition = UpdateStateTransitions(context, stateMachineData, stateMachineBucket.CurrentState);
|
||||||
|
if (transition)
|
||||||
|
{
|
||||||
|
InitStateTransition(context, stateMachineBucket, transition);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void ComputeMultiBlendLength(float& length, AnimGraphNode* node)
|
void ComputeMultiBlendLength(float& length, AnimGraphNode* node)
|
||||||
@@ -1367,33 +1397,29 @@ void AnimGraphExecutor::ProcessGroupAnimation(Box* boxBase, Node* nodeBase, Valu
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Use 1D blend if points are on the same line (degenerated triangle)
|
// 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 (v1.Y >= v0.Y)
|
||||||
{
|
{
|
||||||
if (p.Y < v0.Y && v1.Y >= v0.Y)
|
if (p.Y < v0.Y && v1.Y >= v0.Y)
|
||||||
{
|
blendData = { p.Y, v0.Y, aAnim, bAnim, &aData, &bData };
|
||||||
const float alpha = p.Y / v0.Y;
|
|
||||||
value = SampleAnimationsWithBlend(node, loop, data.Length, startTimePos, bucket.TimePosition, newTimePos, aAnim, bAnim, aData.W, bData.W, alpha);
|
|
||||||
}
|
|
||||||
else
|
else
|
||||||
{
|
blendData = { p.Y - v0.Y, v1.Y - v0.Y, bAnim, cAnim, &bData, &cData };
|
||||||
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);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
if (p.Y < v1.Y)
|
if (p.Y < v1.Y)
|
||||||
{
|
blendData = { p.Y, v1.Y, aAnim, cAnim, &aData, &cData };
|
||||||
const float alpha = p.Y / v1.Y;
|
|
||||||
value = SampleAnimationsWithBlend(node, loop, data.Length, startTimePos, bucket.TimePosition, newTimePos, aAnim, cAnim, aData.W, cData.W, alpha);
|
|
||||||
}
|
|
||||||
else
|
else
|
||||||
{
|
blendData = { p.Y - v1.Y, v0.Y - v1.Y, cAnim, bAnim, &cData, &bData };
|
||||||
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);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
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
|
else
|
||||||
{
|
{
|
||||||
@@ -1505,10 +1531,9 @@ void AnimGraphExecutor::ProcessGroupAnimation(Box* boxBase, Node* nodeBase, Valu
|
|||||||
|
|
||||||
// Blend two animations
|
// Blend two animations
|
||||||
{
|
{
|
||||||
const float alpha = Math::Saturate(bucket.TransitionPosition / blendDuration);
|
const float alpha = bucket.TransitionPosition / blendDuration;
|
||||||
const auto valueA = tryGetValue(node->GetBox(FirstBlendPoseBoxIndex + bucket.PreviousBlendPoseIndex), Value::Null);
|
const auto valueA = tryGetValue(node->GetBox(FirstBlendPoseBoxIndex + bucket.PreviousBlendPoseIndex), Value::Null);
|
||||||
const auto valueB = tryGetValue(node->GetBox(FirstBlendPoseBoxIndex + poseIndex), Value::Null);
|
const auto valueB = tryGetValue(node->GetBox(FirstBlendPoseBoxIndex + poseIndex), Value::Null);
|
||||||
|
|
||||||
value = Blend(node, valueA, valueB, alpha, mode);
|
value = Blend(node, valueA, valueB, alpha, mode);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1614,22 +1639,21 @@ void AnimGraphExecutor::ProcessGroupAnimation(Box* boxBase, Node* nodeBase, Valu
|
|||||||
|
|
||||||
// Enter to the first state pointed by the Entry node (without transitions)
|
// Enter to the first state pointed by the Entry node (without transitions)
|
||||||
bucket.CurrentState = data.Graph->GetRootNode();
|
bucket.CurrentState = data.Graph->GetRootNode();
|
||||||
bucket.ActiveTransition = nullptr;
|
InitStateTransition(context, bucket);
|
||||||
bucket.TransitionPosition = 0.0f;
|
|
||||||
|
|
||||||
// Reset all state buckets pof the graphs and nodes included inside the state machine
|
// Reset all state buckets of the graphs and nodes included inside the state machine
|
||||||
ResetBuckets(context, data.Graph);
|
ResetBuckets(context, data.Graph);
|
||||||
}
|
}
|
||||||
#define END_TRANSITION() \
|
#define END_TRANSITION() \
|
||||||
ResetBuckets(context, bucket.CurrentState->Data.State.Graph); \
|
ResetBuckets(context, bucket.CurrentState->Data.State.Graph); \
|
||||||
bucket.CurrentState = bucket.ActiveTransition->Destination; \
|
bucket.CurrentState = bucket.ActiveTransition->Destination; \
|
||||||
bucket.ActiveTransition = nullptr; \
|
InitStateTransition(context, bucket)
|
||||||
bucket.TransitionPosition = 0.0f
|
|
||||||
|
|
||||||
// Update the active transition
|
// Update the active transition
|
||||||
if (bucket.ActiveTransition)
|
if (bucket.ActiveTransition)
|
||||||
{
|
{
|
||||||
bucket.TransitionPosition += context.DeltaTime;
|
bucket.TransitionPosition += context.DeltaTime;
|
||||||
|
ASSERT(bucket.CurrentState);
|
||||||
|
|
||||||
// Check for transition end
|
// Check for transition end
|
||||||
if (bucket.TransitionPosition >= bucket.ActiveTransition->BlendDuration)
|
if (bucket.TransitionPosition >= bucket.ActiveTransition->BlendDuration)
|
||||||
@@ -1637,38 +1661,70 @@ void AnimGraphExecutor::ProcessGroupAnimation(Box* boxBase, Node* nodeBase, Valu
|
|||||||
END_TRANSITION();
|
END_TRANSITION();
|
||||||
}
|
}
|
||||||
// Check for transition interruption
|
// Check for transition interruption
|
||||||
else if (EnumHasAnyFlags(bucket.ActiveTransition->Flags, AnimGraphStateTransition::FlagTypes::InterruptionRuleRechecking))
|
else if (EnumHasAnyFlags(bucket.ActiveTransition->Flags, AnimGraphStateTransition::FlagTypes::InterruptionRuleRechecking) &&
|
||||||
|
EnumHasNoneFlags(bucket.ActiveTransition->Flags, AnimGraphStateTransition::FlagTypes::UseDefaultRule) &&
|
||||||
|
bucket.ActiveTransition->RuleGraph)
|
||||||
{
|
{
|
||||||
const bool useDefaultRule = EnumHasAnyFlags(bucket.ActiveTransition->Flags, AnimGraphStateTransition::FlagTypes::UseDefaultRule);
|
// Execute transition rule
|
||||||
if (bucket.ActiveTransition->RuleGraph && !useDefaultRule)
|
auto rootNode = bucket.ActiveTransition->RuleGraph->GetRootNode();
|
||||||
|
if (!(bool)eatBox((Node*)rootNode, &rootNode->Boxes[0]))
|
||||||
{
|
{
|
||||||
// Execute transition rule
|
bool cancelTransition = false;
|
||||||
auto rootNode = bucket.ActiveTransition->RuleGraph->GetRootNode();
|
if (EnumHasAnyFlags(bucket.ActiveTransition->Flags, AnimGraphStateTransition::FlagTypes::InterruptionInstant))
|
||||||
if (!(bool)eatBox((Node*)rootNode, &rootNode->Boxes[0]))
|
|
||||||
{
|
{
|
||||||
bool cancelTransition = false;
|
cancelTransition = true;
|
||||||
if (EnumHasAnyFlags(bucket.ActiveTransition->Flags, AnimGraphStateTransition::FlagTypes::InterruptionInstant))
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// Blend back to the source state (remove currently applied delta and rewind transition)
|
||||||
|
bucket.TransitionPosition -= context.DeltaTime;
|
||||||
|
bucket.TransitionPosition -= context.DeltaTime;
|
||||||
|
if (bucket.TransitionPosition <= ZeroTolerance)
|
||||||
{
|
{
|
||||||
cancelTransition = true;
|
cancelTransition = true;
|
||||||
}
|
}
|
||||||
else
|
|
||||||
{
|
|
||||||
// Blend back to the source state (remove currently applied delta and rewind transition)
|
|
||||||
bucket.TransitionPosition -= context.DeltaTime;
|
|
||||||
bucket.TransitionPosition -= context.DeltaTime;
|
|
||||||
if (bucket.TransitionPosition <= ZeroTolerance)
|
|
||||||
{
|
|
||||||
cancelTransition = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (cancelTransition)
|
|
||||||
{
|
|
||||||
// Go back to the source state
|
|
||||||
ResetBuckets(context, bucket.CurrentState->Data.State.Graph);
|
|
||||||
bucket.ActiveTransition = nullptr;
|
|
||||||
bucket.TransitionPosition = 0.0f;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
if (cancelTransition)
|
||||||
|
{
|
||||||
|
// Go back to the source state
|
||||||
|
ResetBuckets(context, bucket.CurrentState->Data.State.Graph);
|
||||||
|
InitStateTransition(context, bucket);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (bucket.ActiveTransition && !bucket.BaseTransition && EnumHasAnyFlags(bucket.ActiveTransition->Flags, AnimGraphStateTransition::FlagTypes::InterruptionSourceState))
|
||||||
|
{
|
||||||
|
// Try to interrupt with any other transition in the source state (except the current transition)
|
||||||
|
if (AnimGraphStateTransition* transition = UpdateStateTransitions(context, data, bucket.CurrentState, bucket.ActiveTransition->Destination))
|
||||||
|
{
|
||||||
|
// Change active transition to the interrupted one
|
||||||
|
if (EnumHasNoneFlags(bucket.ActiveTransition->Flags, AnimGraphStateTransition::FlagTypes::InterruptionInstant))
|
||||||
|
{
|
||||||
|
// Cache the current blending state to be used as a base when blending towards new destination state (seamless blending after interruption)
|
||||||
|
bucket.BaseTransition = bucket.ActiveTransition;
|
||||||
|
bucket.BaseTransitionState = bucket.CurrentState;
|
||||||
|
bucket.BaseTransitionPosition = bucket.TransitionPosition;
|
||||||
|
}
|
||||||
|
bucket.ActiveTransition = transition;
|
||||||
|
bucket.TransitionPosition = 0.0f;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (bucket.ActiveTransition && !bucket.BaseTransition && EnumHasAnyFlags(bucket.ActiveTransition->Flags, AnimGraphStateTransition::FlagTypes::InterruptionDestinationState))
|
||||||
|
{
|
||||||
|
// Try to interrupt with any other transition in the destination state (except the transition back to the current state if exists)
|
||||||
|
if (AnimGraphStateTransition* transition = UpdateStateTransitions(context, data, bucket.ActiveTransition->Destination, bucket.CurrentState))
|
||||||
|
{
|
||||||
|
// Change active transition to the interrupted one
|
||||||
|
if (EnumHasNoneFlags(bucket.ActiveTransition->Flags, AnimGraphStateTransition::FlagTypes::InterruptionInstant))
|
||||||
|
{
|
||||||
|
// Cache the current blending state to be used as a base when blending towards new destination state (seamless blending after interruption)
|
||||||
|
bucket.BaseTransition = bucket.ActiveTransition;
|
||||||
|
bucket.BaseTransitionState = bucket.CurrentState;
|
||||||
|
bucket.BaseTransitionPosition = bucket.TransitionPosition;
|
||||||
|
}
|
||||||
|
bucket.CurrentState = bucket.ActiveTransition->Destination;
|
||||||
|
bucket.ActiveTransition = transition;
|
||||||
|
bucket.TransitionPosition = 0.0f;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1697,9 +1753,23 @@ void AnimGraphExecutor::ProcessGroupAnimation(Box* boxBase, Node* nodeBase, Valu
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Sample the current state
|
if (bucket.BaseTransitionState)
|
||||||
const auto currentState = SampleState(bucket.CurrentState);
|
{
|
||||||
value = currentState;
|
// Sample the other state (eg. when blending from interrupted state to the another state from the old destination)
|
||||||
|
value = SampleState(bucket.BaseTransitionState);
|
||||||
|
if (bucket.BaseTransition)
|
||||||
|
{
|
||||||
|
// Evaluate the base pose from the time when transition was interrupted
|
||||||
|
const auto destinationState = SampleState(bucket.BaseTransition->Destination);
|
||||||
|
const float alpha = bucket.BaseTransitionPosition / bucket.BaseTransition->BlendDuration;
|
||||||
|
value = Blend(node, value, destinationState, alpha, bucket.BaseTransition->BlendMode);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// Sample the current state
|
||||||
|
value = SampleState(bucket.CurrentState);
|
||||||
|
}
|
||||||
|
|
||||||
// Handle active transition blending
|
// Handle active transition blending
|
||||||
if (bucket.ActiveTransition)
|
if (bucket.ActiveTransition)
|
||||||
@@ -1708,14 +1778,12 @@ void AnimGraphExecutor::ProcessGroupAnimation(Box* boxBase, Node* nodeBase, Valu
|
|||||||
const auto destinationState = SampleState(bucket.ActiveTransition->Destination);
|
const auto destinationState = SampleState(bucket.ActiveTransition->Destination);
|
||||||
|
|
||||||
// Perform blending
|
// Perform blending
|
||||||
const float alpha = Math::Saturate(bucket.TransitionPosition / bucket.ActiveTransition->BlendDuration);
|
const float alpha = bucket.TransitionPosition / bucket.ActiveTransition->BlendDuration;
|
||||||
value = Blend(node, currentState, destinationState, alpha, bucket.ActiveTransition->BlendMode);
|
value = Blend(node, value, destinationState, alpha, bucket.ActiveTransition->BlendMode);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Update bucket
|
|
||||||
bucket.LastUpdateFrame = context.CurrentFrameIndex;
|
bucket.LastUpdateFrame = context.CurrentFrameIndex;
|
||||||
#undef END_TRANSITION
|
#undef END_TRANSITION
|
||||||
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
// Entry
|
// Entry
|
||||||
@@ -2102,6 +2170,12 @@ void AnimGraphExecutor::ProcessGroupAnimation(Box* boxBase, Node* nodeBase, Valu
|
|||||||
auto& slot = slots[bucket.Index];
|
auto& slot = slots[bucket.Index];
|
||||||
Animation* anim = slot.Animation;
|
Animation* anim = slot.Animation;
|
||||||
ASSERT(slot.Animation && slot.Animation->IsLoaded());
|
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 deltaTime = slot.Pause ? 0.0f : context.DeltaTime * slot.Speed;
|
||||||
const float length = anim->GetLength();
|
const float length = anim->GetLength();
|
||||||
const bool loop = bucket.LoopsLeft != 0;
|
const bool loop = bucket.LoopsLeft != 0;
|
||||||
@@ -2130,7 +2204,7 @@ void AnimGraphExecutor::ProcessGroupAnimation(Box* boxBase, Node* nodeBase, Valu
|
|||||||
// Blend out
|
// Blend out
|
||||||
auto input = tryGetValue(node->GetBox(1), Value::Null);
|
auto input = tryGetValue(node->GetBox(1), Value::Null);
|
||||||
bucket.BlendOutPosition += deltaTime;
|
bucket.BlendOutPosition += deltaTime;
|
||||||
const float alpha = Math::Saturate(bucket.BlendOutPosition / slot.BlendOutTime);
|
const float alpha = bucket.BlendOutPosition / slot.BlendOutTime;
|
||||||
value = Blend(node, value, input, alpha, AlphaBlendMode::HermiteCubic);
|
value = Blend(node, value, input, alpha, AlphaBlendMode::HermiteCubic);
|
||||||
}
|
}
|
||||||
else if (bucket.LoopsDone == 0 && slot.BlendInTime > 0.0f && bucket.BlendInPosition < slot.BlendInTime)
|
else if (bucket.LoopsDone == 0 && slot.BlendInTime > 0.0f && bucket.BlendInPosition < slot.BlendInTime)
|
||||||
@@ -2138,7 +2212,7 @@ void AnimGraphExecutor::ProcessGroupAnimation(Box* boxBase, Node* nodeBase, Valu
|
|||||||
// Blend in
|
// Blend in
|
||||||
auto input = tryGetValue(node->GetBox(1), Value::Null);
|
auto input = tryGetValue(node->GetBox(1), Value::Null);
|
||||||
bucket.BlendInPosition += deltaTime;
|
bucket.BlendInPosition += deltaTime;
|
||||||
const float alpha = Math::Saturate(bucket.BlendInPosition / slot.BlendInTime);
|
const float alpha = bucket.BlendInPosition / slot.BlendInTime;
|
||||||
value = Blend(node, input, value, alpha, AlphaBlendMode::HermiteCubic);
|
value = Blend(node, input, value, alpha, AlphaBlendMode::HermiteCubic);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
|||||||
@@ -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)
|
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));
|
ASSERT_LOW_LAYER(Validate(year, month, day, hour, minute, second, millisecond));
|
||||||
int32 totalDays = 0;
|
int32 daysSum = 0;
|
||||||
if (month > 2 && IsLeapYear(year))
|
if (month > 2 && IsLeapYear(year))
|
||||||
totalDays++;
|
daysSum++;
|
||||||
year--;
|
year--;
|
||||||
month--;
|
month--;
|
||||||
totalDays += year * 365 + year / 4 - year / 100 + year / 400 + CachedDaysToMonth[month] + day - 1;
|
daysSum += year * 365 + year / 4 - year / 100 + year / 400 + CachedDaysToMonth[month] + day - 1;
|
||||||
Ticks = totalDays * Constants::TicksPerDay
|
Ticks = daysSum * TimeSpan::TicksPerDay
|
||||||
+ hour * Constants::TicksPerHour
|
+ hour * TimeSpan::TicksPerHour
|
||||||
+ minute * Constants::TicksPerMinute
|
+ minute * TimeSpan::TicksPerMinute
|
||||||
+ second * Constants::TicksPerSecond
|
+ second * TimeSpan::TicksPerSecond
|
||||||
+ millisecond * Constants::TicksPerMillisecond;
|
+ millisecond * TimeSpan::TicksPerMillisecond;
|
||||||
}
|
}
|
||||||
|
|
||||||
DateTime DateTime::GetDate() const
|
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
|
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:
|
// Based on:
|
||||||
// Fliegel, H. F. and van Flandern, T. C.,
|
// Fliegel, H. F. and van Flandern, T. C.,
|
||||||
// Communications of the ACM, Vol. 11, No. 10 (October 1968).
|
// Communications of the ACM, Vol. 11, No. 10 (October 1968).
|
||||||
|
int32 l = Math::FloorToInt((float)(GetDate().GetJulianDay() + 0.5)) + 68569;
|
||||||
int32 l = Math::FloorToInt(static_cast<float>(GetJulianDay() + 0.5)) + 68569;
|
|
||||||
const int32 n = 4 * l / 146097;
|
const int32 n = 4 * l / 146097;
|
||||||
l = l - (146097 * n + 3) / 4;
|
l = l - (146097 * n + 3) / 4;
|
||||||
int32 i = 4000 * (l + 1) / 1461001;
|
int32 i = 4000 * (l + 1) / 1461001;
|
||||||
@@ -46,7 +45,6 @@ void DateTime::GetDate(int32& year, int32& month, int32& day) const
|
|||||||
l = j / 11;
|
l = j / 11;
|
||||||
j = j + 2 - 12 * l;
|
j = j + 2 - 12 * l;
|
||||||
i = 100 * (n - 49) + i + l;
|
i = 100 * (n - 49) + i + l;
|
||||||
|
|
||||||
year = i;
|
year = i;
|
||||||
month = j;
|
month = j;
|
||||||
day = k;
|
day = k;
|
||||||
@@ -61,7 +59,7 @@ int32 DateTime::GetDay() const
|
|||||||
|
|
||||||
DayOfWeek DateTime::GetDayOfWeek() 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
|
int32 DateTime::GetDayOfYear() const
|
||||||
@@ -75,7 +73,7 @@ int32 DateTime::GetDayOfYear() const
|
|||||||
|
|
||||||
int32 DateTime::GetHour() 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
|
int32 DateTime::GetHour12() const
|
||||||
@@ -90,7 +88,7 @@ int32 DateTime::GetHour12() const
|
|||||||
|
|
||||||
double DateTime::GetJulianDay() 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
|
double DateTime::GetModifiedJulianDay() const
|
||||||
@@ -100,12 +98,12 @@ double DateTime::GetModifiedJulianDay() const
|
|||||||
|
|
||||||
int32 DateTime::GetMillisecond() 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
|
int32 DateTime::GetMinute() const
|
||||||
{
|
{
|
||||||
return static_cast<int32>(Ticks / Constants::TicksPerMinute % 60);
|
return static_cast<int32>(Ticks / TimeSpan::TicksPerMinute % 60);
|
||||||
}
|
}
|
||||||
|
|
||||||
int32 DateTime::GetMonth() const
|
int32 DateTime::GetMonth() const
|
||||||
@@ -122,12 +120,12 @@ MonthOfYear DateTime::GetMonthOfYear() const
|
|||||||
|
|
||||||
int32 DateTime::GetSecond() 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
|
TimeSpan DateTime::GetTimeOfDay() const
|
||||||
{
|
{
|
||||||
return TimeSpan(Ticks % Constants::TicksPerDay);
|
return TimeSpan(Ticks % TimeSpan::TicksPerDay);
|
||||||
}
|
}
|
||||||
|
|
||||||
int32 DateTime::GetYear() const
|
int32 DateTime::GetYear() const
|
||||||
@@ -137,11 +135,6 @@ int32 DateTime::GetYear() const
|
|||||||
return year;
|
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)
|
int32 DateTime::DaysInMonth(int32 year, int32 month)
|
||||||
{
|
{
|
||||||
ASSERT_LOW_LAYER((month >= 1) && (month <= 12));
|
ASSERT_LOW_LAYER((month >= 1) && (month <= 12));
|
||||||
@@ -155,16 +148,6 @@ int32 DateTime::DaysInYear(int32 year)
|
|||||||
return IsLeapYear(year) ? 366 : 365;
|
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)
|
bool DateTime::IsLeapYear(int32 year)
|
||||||
{
|
{
|
||||||
if ((year % 4) == 0)
|
if ((year % 4) == 0)
|
||||||
@@ -176,7 +159,7 @@ bool DateTime::IsLeapYear(int32 year)
|
|||||||
|
|
||||||
DateTime DateTime::MaxValue()
|
DateTime DateTime::MaxValue()
|
||||||
{
|
{
|
||||||
return DateTime(3652059 * Constants::TicksPerDay - 1);
|
return DateTime(3652059 * TimeSpan::TicksPerDay - 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
DateTime DateTime::Now()
|
DateTime DateTime::Now()
|
||||||
|
|||||||
@@ -199,11 +199,6 @@ public:
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
int32 GetYear() const;
|
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:
|
public:
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets the number of days in the year and month.
|
/// Gets the number of days in the year and month.
|
||||||
@@ -220,20 +215,6 @@ public:
|
|||||||
/// <returns>The number of days.</returns>
|
/// <returns>The number of days.</returns>
|
||||||
static int32 DaysInYear(int32 year);
|
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>
|
/// <summary>
|
||||||
/// Determines whether the specified year is a leap year.
|
/// Determines whether the specified year is a leap year.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|||||||
@@ -6,38 +6,53 @@
|
|||||||
TimeSpan TimeSpan::FromDays(double days)
|
TimeSpan TimeSpan::FromDays(double days)
|
||||||
{
|
{
|
||||||
ASSERT_LOW_LAYER((days >= MinValue().GetTotalDays()) && (days <= MaxValue().GetTotalDays()));
|
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)
|
TimeSpan TimeSpan::FromHours(double hours)
|
||||||
{
|
{
|
||||||
ASSERT_LOW_LAYER((hours >= MinValue().GetTotalHours()) && (hours <= MaxValue().GetTotalHours()));
|
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)
|
TimeSpan TimeSpan::FromMilliseconds(double milliseconds)
|
||||||
{
|
{
|
||||||
ASSERT_LOW_LAYER((milliseconds >= MinValue().GetTotalMilliseconds()) && (milliseconds <= MaxValue().GetTotalMilliseconds()));
|
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)
|
TimeSpan TimeSpan::FromMinutes(double minutes)
|
||||||
{
|
{
|
||||||
ASSERT_LOW_LAYER((minutes >= MinValue().GetTotalMinutes()) && (minutes <= MaxValue().GetTotalMinutes()));
|
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)
|
TimeSpan TimeSpan::FromSeconds(double seconds)
|
||||||
{
|
{
|
||||||
ASSERT_LOW_LAYER((seconds >= MinValue().GetTotalSeconds()) && (seconds <= MaxValue().GetTotalSeconds()));
|
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)
|
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;
|
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()));
|
ASSERT_LOW_LAYER((totalMs >= MinValue().GetTotalMilliseconds()) && (totalMs <= MaxValue().GetTotalMilliseconds()));
|
||||||
Ticks = totalMs * Constants::TicksPerMillisecond;
|
Ticks = totalMs * TicksPerMillisecond;
|
||||||
}
|
}
|
||||||
|
|
||||||
String TimeSpan::ToString() const
|
String TimeSpan::ToString() const
|
||||||
|
|||||||
@@ -6,32 +6,30 @@
|
|||||||
#include "Engine/Core/Formatting.h"
|
#include "Engine/Core/Formatting.h"
|
||||||
#include "Engine/Core/Templates.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>
|
/// <summary>
|
||||||
/// Represents the difference between two dates and times.
|
/// Represents the difference between two dates and times.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
API_STRUCT(InBuild, Namespace="System") struct FLAXENGINE_API TimeSpan
|
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:
|
public:
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Time span in 100 nanoseconds resolution.
|
/// Time span in 100 nanoseconds resolution.
|
||||||
@@ -170,7 +168,7 @@ public:
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
FORCE_INLINE int32 GetDays() const
|
FORCE_INLINE int32 GetDays() const
|
||||||
{
|
{
|
||||||
return (int32)(Ticks / Constants::TicksPerDay);
|
return (int32)(Ticks / TicksPerDay);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -186,7 +184,7 @@ public:
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
FORCE_INLINE int32 GetHours() const
|
FORCE_INLINE int32 GetHours() const
|
||||||
{
|
{
|
||||||
return (int32)(Ticks / Constants::TicksPerHour % 24);
|
return (int32)(Ticks / TicksPerHour % 24);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -194,7 +192,7 @@ public:
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
FORCE_INLINE int32 GetMilliseconds() const
|
FORCE_INLINE int32 GetMilliseconds() const
|
||||||
{
|
{
|
||||||
return (int32)(Ticks / Constants::TicksPerMillisecond % 1000);
|
return (int32)(Ticks / TicksPerMillisecond % 1000);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -202,7 +200,7 @@ public:
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
FORCE_INLINE int32 GetMinutes() const
|
FORCE_INLINE int32 GetMinutes() const
|
||||||
{
|
{
|
||||||
return (int32)(Ticks / Constants::TicksPerMinute % 60);
|
return (int32)(Ticks / TicksPerMinute % 60);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -210,7 +208,7 @@ public:
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
FORCE_INLINE int32 GetSeconds() const
|
FORCE_INLINE int32 GetSeconds() const
|
||||||
{
|
{
|
||||||
return (int32)(Ticks / Constants::TicksPerSecond % 60);
|
return (int32)(Ticks / TicksPerSecond % 60);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -218,7 +216,7 @@ public:
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
FORCE_INLINE double GetTotalDays() const
|
FORCE_INLINE double GetTotalDays() const
|
||||||
{
|
{
|
||||||
return (double)Ticks / Constants::TicksPerDay;
|
return (double)Ticks / TicksPerDay;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -226,7 +224,7 @@ public:
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
FORCE_INLINE double GetTotalHours() const
|
FORCE_INLINE double GetTotalHours() const
|
||||||
{
|
{
|
||||||
return (double)Ticks / Constants::TicksPerHour;
|
return (double)Ticks / TicksPerHour;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -234,7 +232,7 @@ public:
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
FORCE_INLINE double GetTotalMilliseconds() const
|
FORCE_INLINE double GetTotalMilliseconds() const
|
||||||
{
|
{
|
||||||
return (double)Ticks / Constants::TicksPerMillisecond;
|
return (double)Ticks / TicksPerMillisecond;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -242,7 +240,7 @@ public:
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
FORCE_INLINE double GetTotalMinutes() const
|
FORCE_INLINE double GetTotalMinutes() const
|
||||||
{
|
{
|
||||||
return (double)Ticks / Constants::TicksPerMinute;
|
return (double)Ticks / TicksPerMinute;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -250,7 +248,7 @@ public:
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
FORCE_INLINE float GetTotalSeconds() const
|
FORCE_INLINE float GetTotalSeconds() const
|
||||||
{
|
{
|
||||||
return static_cast<float>(Ticks) / Constants::TicksPerSecond;
|
return static_cast<float>(Ticks) / TicksPerSecond;
|
||||||
}
|
}
|
||||||
|
|
||||||
public:
|
public:
|
||||||
@@ -293,29 +291,17 @@ public:
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// Returns the maximum time span value.
|
/// Returns the maximum time span value.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <returns>The time span.</returns>
|
static TimeSpan MaxValue();
|
||||||
static TimeSpan MaxValue()
|
|
||||||
{
|
|
||||||
return TimeSpan(9223372036854775807);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Returns the minimum time span value.
|
/// Returns the minimum time span value.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <returns>The time span.</returns>
|
static TimeSpan MinValue();
|
||||||
static TimeSpan MinValue()
|
|
||||||
{
|
|
||||||
return TimeSpan(-9223372036854775807 - 1);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Returns the zero time span value.
|
/// Returns the zero time span value.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <returns>The time span.</returns>
|
static TimeSpan Zero();
|
||||||
static TimeSpan Zero()
|
|
||||||
{
|
|
||||||
return TimeSpan(0);
|
|
||||||
}
|
|
||||||
|
|
||||||
private:
|
private:
|
||||||
void Set(int32 days, int32 hours, int32 minutes, int32 seconds, int32 milliseconds);
|
void Set(int32 days, int32 hours, int32 minutes, int32 seconds, int32 milliseconds);
|
||||||
|
|||||||
@@ -139,6 +139,24 @@ VariantType::VariantType(const StringAnsiView& typeName)
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
{
|
||||||
|
// Aliases
|
||||||
|
if (typeName == "FlaxEngine.Vector2")
|
||||||
|
{
|
||||||
|
new(this) VariantType(Vector2);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (typeName == "FlaxEngine.Vector3")
|
||||||
|
{
|
||||||
|
new(this) VariantType(Vector3);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (typeName == "FlaxEngine.Vector4")
|
||||||
|
{
|
||||||
|
new(this) VariantType(Vector4);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Check case for array
|
// Check case for array
|
||||||
if (typeName.EndsWith(StringAnsiView("[]"), StringSearchCase::CaseSensitive))
|
if (typeName.EndsWith(StringAnsiView("[]"), StringSearchCase::CaseSensitive))
|
||||||
@@ -3985,15 +4003,32 @@ void Variant::CopyStructure(void* src)
|
|||||||
{
|
{
|
||||||
if (AsBlob.Data && src)
|
if (AsBlob.Data && src)
|
||||||
{
|
{
|
||||||
const ScriptingTypeHandle typeHandle = Scripting::FindScriptingType(StringAnsiView(Type.TypeName));
|
const StringAnsiView typeName(Type.TypeName);
|
||||||
|
const ScriptingTypeHandle typeHandle = Scripting::FindScriptingType(typeName);
|
||||||
if (typeHandle)
|
if (typeHandle)
|
||||||
{
|
{
|
||||||
auto& type = typeHandle.GetType();
|
auto& type = typeHandle.GetType();
|
||||||
type.Struct.Copy(AsBlob.Data, src);
|
type.Struct.Copy(AsBlob.Data, src);
|
||||||
}
|
}
|
||||||
|
#if USE_CSHARP
|
||||||
|
else if (const auto mclass = Scripting::FindClass(typeName))
|
||||||
|
{
|
||||||
|
// Fallback to C#-only types
|
||||||
|
MCore::Thread::Attach();
|
||||||
|
if (MANAGED_GC_HANDLE && mclass->IsValueType())
|
||||||
|
{
|
||||||
|
MObject* instance = MCore::GCHandle::GetTarget(MANAGED_GC_HANDLE);
|
||||||
|
void* data = MCore::Object::Unbox(instance);
|
||||||
|
Platform::MemoryCopy(data, src, mclass->GetInstanceSize());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#endif
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
Platform::MemoryCopy(AsBlob.Data, src, AsBlob.Length);
|
if (typeName.Length() != 0)
|
||||||
|
{
|
||||||
|
LOG(Warning, "Missing scripting type \'{0}\'", String(typeName));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -67,7 +67,7 @@ void Time::TickData::OnBeforeRun(float targetFps, double currentTime)
|
|||||||
{
|
{
|
||||||
Time = UnscaledTime = TimeSpan::Zero();
|
Time = UnscaledTime = TimeSpan::Zero();
|
||||||
DeltaTime = UnscaledDeltaTime = targetFps > ZeroTolerance ? TimeSpan::FromSeconds(1.0f / targetFps) : 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;
|
LastBegin = currentTime - LastLength;
|
||||||
LastEnd = currentTime;
|
LastEnd = currentTime;
|
||||||
NextBegin = targetFps > ZeroTolerance ? LastBegin + (1.0f / targetFps) : 0.0;
|
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)
|
void Time::TickData::OnReset(float targetFps, double currentTime)
|
||||||
{
|
{
|
||||||
DeltaTime = UnscaledDeltaTime = targetFps > ZeroTolerance ? TimeSpan::FromSeconds(1.0f / targetFps) : 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;
|
LastBegin = currentTime - LastLength;
|
||||||
LastEnd = currentTime;
|
LastEnd = currentTime;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -236,6 +236,27 @@ API_ENUM(Attributes="Flags") enum class ShadowsCastingMode
|
|||||||
|
|
||||||
DECLARE_ENUM_OPERATORS(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>
|
/// <summary>
|
||||||
/// Identifies expected GPU resource use during rendering. The usage directly reflects whether a resource is accessible by the CPU and/or the GPU.
|
/// Identifies expected GPU resource use during rendering. The usage directly reflects whether a resource is accessible by the CPU and/or the GPU.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|||||||
@@ -1359,7 +1359,7 @@ bool Actor::IsPrefabRoot() const
|
|||||||
Actor* Actor::FindActor(const StringView& name) const
|
Actor* Actor::FindActor(const StringView& name) const
|
||||||
{
|
{
|
||||||
Actor* result = nullptr;
|
Actor* result = nullptr;
|
||||||
if (StringUtils::Compare(*_name, *name) == 0)
|
if (_name == name)
|
||||||
{
|
{
|
||||||
result = const_cast<Actor*>(this);
|
result = const_cast<Actor*>(this);
|
||||||
}
|
}
|
||||||
@@ -1393,7 +1393,7 @@ Actor* Actor::FindActor(const MClass* type) const
|
|||||||
Actor* Actor::FindActor(const MClass* type, const StringView& name) const
|
Actor* Actor::FindActor(const MClass* type, const StringView& name) const
|
||||||
{
|
{
|
||||||
CHECK_RETURN(type, nullptr);
|
CHECK_RETURN(type, nullptr);
|
||||||
if (GetClass()->IsSubClassOf(type) && StringUtils::Compare(*_name, *name) == 0)
|
if (GetClass()->IsSubClassOf(type) && _name == name)
|
||||||
return const_cast<Actor*>(this);
|
return const_cast<Actor*>(this);
|
||||||
for (auto child : Children)
|
for (auto child : Children)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -225,6 +225,17 @@ void AnimatedModel::SetMasterPoseModel(AnimatedModel* masterPose)
|
|||||||
_masterPose->AnimationUpdated.Bind<AnimatedModel, &AnimatedModel::OnAnimationUpdated>(this);
|
_masterPose->AnimationUpdated.Bind<AnimatedModel, &AnimatedModel::OnAnimationUpdated>(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const Array<AnimGraphTraceEvent>& AnimatedModel::GetTraceEvents() const
|
||||||
|
{
|
||||||
|
#if !BUILD_RELEASE
|
||||||
|
if (!GetEnableTracing())
|
||||||
|
{
|
||||||
|
LOG(Warning, "Accessing AnimatedModel.TraceEvents with tracing disabled.");
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
return GraphInstance.TraceEvents;
|
||||||
|
}
|
||||||
|
|
||||||
#define CHECK_ANIM_GRAPH_PARAM_ACCESS() \
|
#define CHECK_ANIM_GRAPH_PARAM_ACCESS() \
|
||||||
if (!AnimationGraph) \
|
if (!AnimationGraph) \
|
||||||
{ \
|
{ \
|
||||||
@@ -494,6 +505,7 @@ void AnimatedModel::StopSlotAnimation(const StringView& slotName, Animation* ani
|
|||||||
if (slot.Animation == anim && slot.Name == slotName)
|
if (slot.Animation == anim && slot.Name == slotName)
|
||||||
{
|
{
|
||||||
slot.Animation = nullptr;
|
slot.Animation = nullptr;
|
||||||
|
slot.Reset = true;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -259,6 +259,27 @@ public:
|
|||||||
/// <param name="masterPose">The master pose actor to use.</param>
|
/// <param name="masterPose">The master pose actor to use.</param>
|
||||||
API_FUNCTION() void SetMasterPoseModel(AnimatedModel* masterPose);
|
API_FUNCTION() void SetMasterPoseModel(AnimatedModel* masterPose);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Enables extracting animation playback insights for debugging or custom scripting.
|
||||||
|
/// </summary>
|
||||||
|
API_PROPERTY(Attributes="HideInEditor, NoSerialize") bool GetEnableTracing() const
|
||||||
|
{
|
||||||
|
return GraphInstance.EnableTracing;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Enables extracting animation playback insights for debugging or custom scripting.
|
||||||
|
/// </summary>
|
||||||
|
API_PROPERTY() void SetEnableTracing(bool value)
|
||||||
|
{
|
||||||
|
GraphInstance.EnableTracing = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the trace events from the last animation update. Valid only when EnableTracing is active.
|
||||||
|
/// </summary>
|
||||||
|
API_PROPERTY(Attributes="HideInEditor, NoSerialize") const Array<AnimGraphTraceEvent>& GetTraceEvents() const;
|
||||||
|
|
||||||
public:
|
public:
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets the anim graph instance parameters collection.
|
/// Gets the anim graph instance parameters collection.
|
||||||
|
|||||||
@@ -41,6 +41,12 @@ void DirectionalLight::Draw(RenderContext& renderContext)
|
|||||||
data.RenderedVolumetricFog = 0;
|
data.RenderedVolumetricFog = 0;
|
||||||
data.ShadowsMode = ShadowsMode;
|
data.ShadowsMode = ShadowsMode;
|
||||||
data.CascadeCount = CascadeCount;
|
data.CascadeCount = CascadeCount;
|
||||||
|
data.Cascade1Spacing = Cascade1Spacing;
|
||||||
|
data.Cascade2Spacing = Cascade2Spacing;
|
||||||
|
data.Cascade3Spacing = Cascade3Spacing;
|
||||||
|
data.Cascade4Spacing = Cascade4Spacing;
|
||||||
|
|
||||||
|
data.PartitionMode = PartitionMode;
|
||||||
data.ContactShadowsLength = ContactShadowsLength;
|
data.ContactShadowsLength = ContactShadowsLength;
|
||||||
data.StaticFlags = GetStaticFlags();
|
data.StaticFlags = GetStaticFlags();
|
||||||
data.ID = GetID();
|
data.ID = GetID();
|
||||||
@@ -56,6 +62,12 @@ void DirectionalLight::Serialize(SerializeStream& stream, const void* otherObj)
|
|||||||
SERIALIZE_GET_OTHER_OBJ(DirectionalLight);
|
SERIALIZE_GET_OTHER_OBJ(DirectionalLight);
|
||||||
|
|
||||||
SERIALIZE(CascadeCount);
|
SERIALIZE(CascadeCount);
|
||||||
|
SERIALIZE(Cascade1Spacing);
|
||||||
|
SERIALIZE(Cascade2Spacing);
|
||||||
|
SERIALIZE(Cascade3Spacing);
|
||||||
|
SERIALIZE(Cascade4Spacing);
|
||||||
|
|
||||||
|
SERIALIZE(PartitionMode);
|
||||||
}
|
}
|
||||||
|
|
||||||
void DirectionalLight::Deserialize(DeserializeStream& stream, ISerializeModifier* modifier)
|
void DirectionalLight::Deserialize(DeserializeStream& stream, ISerializeModifier* modifier)
|
||||||
@@ -64,6 +76,12 @@ void DirectionalLight::Deserialize(DeserializeStream& stream, ISerializeModifier
|
|||||||
LightWithShadow::Deserialize(stream, modifier);
|
LightWithShadow::Deserialize(stream, modifier);
|
||||||
|
|
||||||
DESERIALIZE(CascadeCount);
|
DESERIALIZE(CascadeCount);
|
||||||
|
DESERIALIZE(Cascade1Spacing);
|
||||||
|
DESERIALIZE(Cascade2Spacing);
|
||||||
|
DESERIALIZE(Cascade3Spacing);
|
||||||
|
DESERIALIZE(Cascade4Spacing);
|
||||||
|
|
||||||
|
DESERIALIZE(PartitionMode);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool DirectionalLight::IntersectsItself(const Ray& ray, Real& distance, Vector3& normal)
|
bool DirectionalLight::IntersectsItself(const Ray& ray, Real& distance, Vector3& normal)
|
||||||
|
|||||||
@@ -12,12 +12,42 @@ class FLAXENGINE_API DirectionalLight : public LightWithShadow
|
|||||||
{
|
{
|
||||||
DECLARE_SCENE_OBJECT(DirectionalLight);
|
DECLARE_SCENE_OBJECT(DirectionalLight);
|
||||||
public:
|
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>
|
/// <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.
|
/// 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>
|
/// </summary>
|
||||||
API_FIELD(Attributes="EditorOrder(65), DefaultValue(4), Limit(1, 4), EditorDisplay(\"Shadow\")")
|
API_FIELD(Attributes="EditorOrder(65), DefaultValue(4), Limit(1, 4), EditorDisplay(\"Shadow\")")
|
||||||
int32 CascadeCount = 4;
|
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:
|
public:
|
||||||
// [LightWithShadow]
|
// [LightWithShadow]
|
||||||
void Draw(RenderContext& renderContext) override;
|
void Draw(RenderContext& renderContext) override;
|
||||||
|
|||||||
10
Source/Engine/Level/DirectionalLight.cs
Normal file
10
Source/Engine/Level/DirectionalLight.cs
Normal 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -43,7 +43,7 @@ API_STRUCT(Namespace="FlaxEngine.Networking") struct FLAXENGINE_API NetworkConfi
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
/// <remarks>Object is managed by the created network peer (will be deleted on peer shutdown).</remarks>
|
/// <remarks>Object is managed by the created network peer (will be deleted on peer shutdown).</remarks>
|
||||||
API_FIELD()
|
API_FIELD()
|
||||||
ScriptingObject* NetworkDriver;
|
ScriptingObject* NetworkDriver = nullptr;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The upper limit on how many peers can join when we're listening.
|
/// The upper limit on how many peers can join when we're listening.
|
||||||
|
|||||||
@@ -2151,6 +2151,18 @@ void NetworkInternal::OnNetworkMessageObjectRpc(NetworkEvent& event, NetworkClie
|
|||||||
NetworkMessageObjectRpc msgData;
|
NetworkMessageObjectRpc msgData;
|
||||||
event.Message.ReadStructure(msgData);
|
event.Message.ReadStructure(msgData);
|
||||||
ScopeLock lock(ObjectsLock);
|
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);
|
NetworkReplicatedObject* e = ResolveObject(msgData.ObjectId, msgData.ParentId, msgData.ObjectTypeName);
|
||||||
if (e)
|
if (e)
|
||||||
{
|
{
|
||||||
@@ -2159,17 +2171,7 @@ void NetworkInternal::OnNetworkMessageObjectRpc(NetworkEvent& event, NetworkClie
|
|||||||
if (!obj)
|
if (!obj)
|
||||||
return;
|
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
|
// Validate RPC
|
||||||
if (info->Server && NetworkManager::IsClient())
|
if (info->Server && NetworkManager::IsClient())
|
||||||
{
|
{
|
||||||
@@ -2192,7 +2194,7 @@ void NetworkInternal::OnNetworkMessageObjectRpc(NetworkEvent& event, NetworkClie
|
|||||||
// Execute RPC
|
// Execute RPC
|
||||||
info->Execute(obj, stream, info->Tag);
|
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));
|
NETWORK_REPLICATOR_LOG(Error, "[NetworkReplicator] Unknown object {} RPC {}::{}", msgData.ObjectId, String(msgData.RpcTypeName), String(msgData.RpcName));
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2414,7 +2414,13 @@ void PhysicsBackend::SetRigidActorPose(void* actor, const Vector3& position, con
|
|||||||
if (kinematic)
|
if (kinematic)
|
||||||
{
|
{
|
||||||
auto actorPhysX = (PxRigidDynamic*)actor;
|
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
|
else
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -517,7 +517,7 @@ DateTime AndroidFileSystem::GetFileLastEditTime(const StringView& path)
|
|||||||
const StringAsANSI<> pathANSI(*path, path.Length());
|
const StringAsANSI<> pathANSI(*path, path.Length());
|
||||||
if (stat(pathANSI.Get(), &fileInfo) == -1)
|
if (stat(pathANSI.Get(), &fileInfo) == -1)
|
||||||
return DateTime::MinValue();
|
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);
|
const DateTime UnixEpoch(1970, 1, 1);
|
||||||
return UnixEpoch + timeSinceEpoch;
|
return UnixEpoch + timeSinceEpoch;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -498,7 +498,7 @@ DateTime AppleFileSystem::GetFileLastEditTime(const StringView& path)
|
|||||||
return DateTime::MinValue();
|
return DateTime::MinValue();
|
||||||
}
|
}
|
||||||
|
|
||||||
const TimeSpan timeSinceEpoch(0, 0, fileInfo.st_mtime);
|
const TimeSpan timeSinceEpoch(0, 0, 0, fileInfo.st_mtime);
|
||||||
return UnixEpoch + timeSinceEpoch;
|
return UnixEpoch + timeSinceEpoch;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -657,7 +657,7 @@ DateTime LinuxFileSystem::GetFileLastEditTime(const StringView& path)
|
|||||||
return DateTime::MinValue();
|
return DateTime::MinValue();
|
||||||
}
|
}
|
||||||
|
|
||||||
const TimeSpan timeSinceEpoch(0, 0, fileInfo.st_mtime);
|
const TimeSpan timeSinceEpoch(0, 0, 0, fileInfo.st_mtime);
|
||||||
return UnixEpoch + timeSinceEpoch;
|
return UnixEpoch + timeSinceEpoch;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -137,7 +137,7 @@ DateTime UnixFile::GetLastWriteTime() const
|
|||||||
{
|
{
|
||||||
return DateTime::MinValue();
|
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);
|
const DateTime unixEpoch(1970, 1, 1);
|
||||||
return unixEpoch + timeSinceEpoch;
|
return unixEpoch + timeSinceEpoch;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -46,6 +46,12 @@ struct RendererDirectionalLightData
|
|||||||
|
|
||||||
float ShadowsDistance;
|
float ShadowsDistance;
|
||||||
int32 CascadeCount;
|
int32 CascadeCount;
|
||||||
|
float Cascade1Spacing;
|
||||||
|
float Cascade2Spacing;
|
||||||
|
float Cascade3Spacing;
|
||||||
|
float Cascade4Spacing;
|
||||||
|
|
||||||
|
PartitionMode PartitionMode;
|
||||||
float ContactShadowsLength;
|
float ContactShadowsLength;
|
||||||
ShadowsCastingMode ShadowsMode;
|
ShadowsCastingMode ShadowsMode;
|
||||||
|
|
||||||
|
|||||||
@@ -247,19 +247,12 @@ void ShadowsPass::SetupLight(RenderContext& renderContext, RenderContextBatch& r
|
|||||||
minDistance = cameraNear;
|
minDistance = cameraNear;
|
||||||
maxDistance = cameraNear + shadowsDistance;
|
maxDistance = cameraNear + shadowsDistance;
|
||||||
|
|
||||||
// TODO: expose partition mode?
|
PartitionMode partitionMode = light.PartitionMode;
|
||||||
enum class PartitionMode
|
|
||||||
{
|
|
||||||
Manual = 0,
|
|
||||||
Logarithmic = 1,
|
|
||||||
PSSM = 2,
|
|
||||||
};
|
|
||||||
PartitionMode partitionMode = PartitionMode::Manual;
|
|
||||||
float pssmFactor = 0.5f;
|
float pssmFactor = 0.5f;
|
||||||
float splitDistance0 = 0.05f;
|
float splitDistance0 = light.Cascade1Spacing;
|
||||||
float splitDistance1 = 0.15f;
|
float splitDistance1 = Math::Max(splitDistance0, light.Cascade2Spacing);
|
||||||
float splitDistance2 = 0.50f;
|
float splitDistance2 = Math::Max(splitDistance1, light.Cascade3Spacing);
|
||||||
float splitDistance3 = 1.00f;
|
float splitDistance3 = Math::Max(splitDistance2, light.Cascade4Spacing);
|
||||||
|
|
||||||
// Compute the split distances based on the partitioning mode
|
// Compute the split distances based on the partitioning mode
|
||||||
if (partitionMode == PartitionMode::Manual)
|
if (partitionMode == PartitionMode::Manual)
|
||||||
|
|||||||
@@ -5,11 +5,11 @@ using System;
|
|||||||
namespace FlaxEngine
|
namespace FlaxEngine
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <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>
|
/// </summary>
|
||||||
/// <seealso cref="System.Attribute" />
|
/// <seealso cref="System.Attribute" />
|
||||||
[Serializable]
|
[Serializable]
|
||||||
[AttributeUsage(AttributeTargets.Field | AttributeTargets.Property)]
|
[AttributeUsage(AttributeTargets.Field | AttributeTargets.Property, AllowMultiple = true)]
|
||||||
public sealed class VisibleIfAttribute : Attribute
|
public sealed class VisibleIfAttribute : Attribute
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|||||||
26
Source/Engine/Tests/TestTime.cpp
Normal file
26
Source/Engine/Tests/TestTime.cpp
Normal 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -584,7 +584,6 @@ void MaterialGenerator::ProcessGroupTextures(Box* box, Node* node, Value& value)
|
|||||||
{
|
{
|
||||||
// Procedural Texture Sample
|
// Procedural Texture Sample
|
||||||
textureBox->Cache = writeLocal(Value::InitForZero(ValueType::Float4), node);
|
textureBox->Cache = writeLocal(Value::InitForZero(ValueType::Float4), node);
|
||||||
createGradients(node);
|
|
||||||
auto proceduralSample = String::Format(TEXT(
|
auto proceduralSample = String::Format(TEXT(
|
||||||
" {{\n"
|
" {{\n"
|
||||||
" float3 weights;\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"
|
" 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"
|
" 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"
|
" 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"
|
" float2 fdx = ddx({0});\n"
|
||||||
" float4 tex2 = {1}.SampleGrad({4}, uv2, {2}, {3}, {6}) * weights.y;\n"
|
" float2 fdy = ddy({0});\n"
|
||||||
" float4 tex3 = {1}.SampleGrad({4}, uv3, {2}, {3}, {6}) * weights.z;\n"
|
" float4 tex1 = {1}.SampleGrad({2}, uv1, fdx, fdy, {4}) * weights.x;\n"
|
||||||
" {5} = tex1 + tex2 + tex3;\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"
|
" }}\n"
|
||||||
),
|
),
|
||||||
uvs.Value, // {0}
|
uvs.Value, // {0}
|
||||||
texture.Value, // {1}
|
texture.Value, // {1}
|
||||||
_ddx.Value, // {2}
|
samplerName, // {2}
|
||||||
_ddy.Value, // {3}
|
textureBox->Cache.Value, // {3}
|
||||||
samplerName, // {4}
|
offset.Value // {4}
|
||||||
textureBox->Cache.Value, // {5}
|
|
||||||
offset.Value // {6}
|
|
||||||
);
|
);
|
||||||
|
|
||||||
_writer.Write(*proceduralSample);
|
_writer.Write(*proceduralSample);
|
||||||
|
|||||||
@@ -374,6 +374,7 @@ void ModelTool::Options::Serialize(SerializeStream& stream, const void* otherObj
|
|||||||
SERIALIZE(InstanceToImportAs);
|
SERIALIZE(InstanceToImportAs);
|
||||||
SERIALIZE(ImportTextures);
|
SERIALIZE(ImportTextures);
|
||||||
SERIALIZE(RestoreMaterialsOnReimport);
|
SERIALIZE(RestoreMaterialsOnReimport);
|
||||||
|
SERIALIZE(SkipExistingMaterialsOnReimport);
|
||||||
SERIALIZE(GenerateSDF);
|
SERIALIZE(GenerateSDF);
|
||||||
SERIALIZE(SDFResolution);
|
SERIALIZE(SDFResolution);
|
||||||
SERIALIZE(SplitObjects);
|
SERIALIZE(SplitObjects);
|
||||||
@@ -422,6 +423,7 @@ void ModelTool::Options::Deserialize(DeserializeStream& stream, ISerializeModifi
|
|||||||
DESERIALIZE(InstanceToImportAs);
|
DESERIALIZE(InstanceToImportAs);
|
||||||
DESERIALIZE(ImportTextures);
|
DESERIALIZE(ImportTextures);
|
||||||
DESERIALIZE(RestoreMaterialsOnReimport);
|
DESERIALIZE(RestoreMaterialsOnReimport);
|
||||||
|
DESERIALIZE(SkipExistingMaterialsOnReimport);
|
||||||
DESERIALIZE(GenerateSDF);
|
DESERIALIZE(GenerateSDF);
|
||||||
DESERIALIZE(SDFResolution);
|
DESERIALIZE(SDFResolution);
|
||||||
DESERIALIZE(SplitObjects);
|
DESERIALIZE(SplitObjects);
|
||||||
@@ -1154,6 +1156,18 @@ bool ModelTool::ImportModel(const String& path, ModelData& data, Options& option
|
|||||||
continue;
|
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)
|
if (options.ImportMaterialsAsInstances)
|
||||||
{
|
{
|
||||||
// Create material instance
|
// Create material instance
|
||||||
|
|||||||
@@ -260,17 +260,20 @@ public:
|
|||||||
API_FIELD(Attributes="EditorOrder(400), EditorDisplay(\"Materials\"), VisibleIf(nameof(ShowGeometry))")
|
API_FIELD(Attributes="EditorOrder(400), EditorDisplay(\"Materials\"), VisibleIf(nameof(ShowGeometry))")
|
||||||
bool ImportMaterials = true;
|
bool ImportMaterials = true;
|
||||||
// If checked, the importer will create the model's materials as instances of a base material.
|
// 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;
|
bool ImportMaterialsAsInstances = false;
|
||||||
// The material used as the base material that will be instanced as the imported model's material.
|
// 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;
|
AssetReference<MaterialBase> InstanceToImportAs;
|
||||||
// If checked, the importer will import texture files used by the model and any embedded texture resources.
|
// 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))")
|
API_FIELD(Attributes="EditorOrder(410), EditorDisplay(\"Materials\"), VisibleIf(nameof(ShowGeometry))")
|
||||||
bool ImportTextures = true;
|
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.
|
// 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 Material Slots on Reimport\"), VisibleIf(nameof(ShowGeometry))")
|
API_FIELD(Attributes="EditorOrder(420), EditorDisplay(\"Materials\", \"Keep Overridden Materials\"), VisibleIf(nameof(ShowGeometry))")
|
||||||
bool RestoreMaterialsOnReimport = true;
|
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
|
public: // SDF
|
||||||
|
|
||||||
|
|||||||
@@ -685,9 +685,9 @@ void VisjectExecutor::ProcessGroupPacking(Box* box, Node* node, Value& value)
|
|||||||
case 36:
|
case 36:
|
||||||
{
|
{
|
||||||
// Get value with structure data
|
// Get value with structure data
|
||||||
Variant structureValue = eatBox(node, node->GetBox(0)->FirstConnection());
|
|
||||||
if (!node->GetBox(0)->HasConnection())
|
if (!node->GetBox(0)->HasConnection())
|
||||||
return;
|
return;
|
||||||
|
Variant structureValue = eatBox(node, node->GetBox(0)->FirstConnection());
|
||||||
|
|
||||||
// Find type
|
// Find type
|
||||||
const StringView typeName(node->Values[0]);
|
const StringView typeName(node->Values[0]);
|
||||||
@@ -741,6 +741,12 @@ void VisjectExecutor::ProcessGroupPacking(Box* box, Node* node, Value& value)
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const ScriptingType& type = typeHandle.GetType();
|
const ScriptingType& type = typeHandle.GetType();
|
||||||
|
if (structureValue.Type.Type != VariantType::Structure) // If structureValue is eg. Float we can try to cast it to a required structure type
|
||||||
|
{
|
||||||
|
VariantType typeVariantType(typeNameAnsiView);
|
||||||
|
if (Variant::CanCast(structureValue, typeVariantType))
|
||||||
|
structureValue = Variant::Cast(structureValue, typeVariantType);
|
||||||
|
}
|
||||||
structureValue.InvertInline(); // Extract any Float3/Int32 into Structure type from inlined format
|
structureValue.InvertInline(); // Extract any Float3/Int32 into Structure type from inlined format
|
||||||
const ScriptingTypeHandle structureValueTypeHandle = Scripting::FindScriptingType(structureValue.Type.GetTypeName());
|
const ScriptingTypeHandle structureValueTypeHandle = Scripting::FindScriptingType(structureValue.Type.GetTypeName());
|
||||||
if (structureValue.Type.Type != VariantType::Structure || typeHandle != structureValueTypeHandle)
|
if (structureValue.Type.Type != VariantType::Structure || typeHandle != structureValueTypeHandle)
|
||||||
|
|||||||
@@ -179,7 +179,10 @@ namespace Flax.Build.Projects.VisualStudio
|
|||||||
customizer.WriteVisualStudioBuildProperties(vsProject, platform, toolchain, configuration, vcProjectFileContent, vcFiltersFileContent, vcUserFileContent);
|
customizer.WriteVisualStudioBuildProperties(vsProject, platform, toolchain, configuration, vcProjectFileContent, vcFiltersFileContent, vcUserFileContent);
|
||||||
vcProjectFileContent.AppendLine(string.Format(" <IntDir>{0}</IntDir>", targetBuildOptions.IntermediateFolder));
|
vcProjectFileContent.AppendLine(string.Format(" <IntDir>{0}</IntDir>", targetBuildOptions.IntermediateFolder));
|
||||||
vcProjectFileContent.AppendLine(string.Format(" <OutDir>{0}</OutDir>", targetBuildOptions.OutputFolder));
|
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(" <ReferencePath />");
|
||||||
vcProjectFileContent.AppendLine(" <LibraryPath />");
|
vcProjectFileContent.AppendLine(" <LibraryPath />");
|
||||||
vcProjectFileContent.AppendLine(" <LibraryWPath />");
|
vcProjectFileContent.AppendLine(" <LibraryWPath />");
|
||||||
|
|||||||
@@ -296,7 +296,7 @@ namespace Flax.Build.Projects.VisualStudio
|
|||||||
var folderIdMatches = new Regex("Project\\(\"{2150E333-8FDC-42A3-9474-1A3956D46DE8}\"\\) = \"(.*?)\", \"(.*?)\", \"{(.*?)}\"").Matches(contents);
|
var folderIdMatches = new Regex("Project\\(\"{2150E333-8FDC-42A3-9474-1A3956D46DE8}\"\\) = \"(.*?)\", \"(.*?)\", \"{(.*?)}\"").Matches(contents);
|
||||||
foreach (Match match in folderIdMatches)
|
foreach (Match match in folderIdMatches)
|
||||||
{
|
{
|
||||||
var folder = match.Groups[1].Value;
|
var folder = match.Groups[2].Value;
|
||||||
var folderId = Guid.ParseExact(match.Groups[3].Value, "D");
|
var folderId = Guid.ParseExact(match.Groups[3].Value, "D");
|
||||||
folderIds[folder] = folderId;
|
folderIds[folder] = folderId;
|
||||||
}
|
}
|
||||||
@@ -385,8 +385,7 @@ namespace Flax.Build.Projects.VisualStudio
|
|||||||
{
|
{
|
||||||
if (!folderIds.TryGetValue(folderPath, out project.FolderGuid))
|
if (!folderIds.TryGetValue(folderPath, out project.FolderGuid))
|
||||||
{
|
{
|
||||||
if (!folderIds.TryGetValue(folderParents[i], out project.FolderGuid))
|
project.FolderGuid = Guid.NewGuid();
|
||||||
project.FolderGuid = Guid.NewGuid();
|
|
||||||
folderIds.Add(folderPath, project.FolderGuid);
|
folderIds.Add(folderPath, project.FolderGuid);
|
||||||
}
|
}
|
||||||
folderNames.Add(folderPath);
|
folderNames.Add(folderPath);
|
||||||
@@ -401,7 +400,7 @@ namespace Flax.Build.Projects.VisualStudio
|
|||||||
var lastSplit = folder.LastIndexOf('\\');
|
var lastSplit = folder.LastIndexOf('\\');
|
||||||
var name = lastSplit != -1 ? folder.Substring(lastSplit + 1) : folder;
|
var name = lastSplit != -1 ? folder.Substring(lastSplit + 1) : folder;
|
||||||
|
|
||||||
vcSolutionFileContent.AppendLine(string.Format("Project(\"{0}\") = \"{1}\", \"{2}\", \"{3}\"", typeGuid, name, name, folderGuid));
|
vcSolutionFileContent.AppendLine(string.Format("Project(\"{0}\") = \"{1}\", \"{2}\", \"{3}\"", typeGuid, name, folder, folderGuid));
|
||||||
vcSolutionFileContent.AppendLine("EndProject");
|
vcSolutionFileContent.AppendLine("EndProject");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user