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

# Conflicts:
#	Source/Engine/Renderer/RenderList.cpp
#	Source/Engine/Renderer/RenderList.h
This commit is contained in:
Wojtek Figat
2024-04-17 09:58:59 +02:00
99 changed files with 11337 additions and 1004 deletions

View File

@@ -79,6 +79,9 @@
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=SDK/@EntryIndexedValue">SDK</s:String>
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=VS/@EntryIndexedValue">VS</s:String>
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/PredefinedNamingRules/=PrivateStaticReadonly/@EntryIndexedValue">&lt;Policy Inspect="True" Prefix="_" Suffix="" Style="aaBb" /&gt;</s:String>
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/UserRules/=15b5b1f1_002D457c_002D4ca6_002Db278_002D5615aedc07d3/@EntryIndexedValue">&lt;Policy&gt;&lt;Descriptor Staticness="Static" AccessRightKinds="Private" Description="Static readonly fields (private)"&gt;&lt;ElementKinds&gt;&lt;Kind Name="READONLY_FIELD" /&gt;&lt;/ElementKinds&gt;&lt;/Descriptor&gt;&lt;Policy Inspect="True" Prefix="_" Suffix="" Style="aaBb" /&gt;&lt;/Policy&gt;</s:String>
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/UserRules/=53eecf85_002Dd821_002D40e8_002Dac97_002Dfdb734542b84/@EntryIndexedValue">&lt;Policy&gt;&lt;Descriptor Staticness="Instance" AccessRightKinds="Protected, ProtectedInternal, Internal, Public, PrivateProtected" Description="Instance fields (not private)"&gt;&lt;ElementKinds&gt;&lt;Kind Name="FIELD" /&gt;&lt;Kind Name="READONLY_FIELD" /&gt;&lt;/ElementKinds&gt;&lt;/Descriptor&gt;&lt;Policy Inspect="True" Prefix="" Suffix="" Style="AaBb"&gt;&lt;ExtraRule Prefix="_" Suffix="" Style="aaBb" /&gt;&lt;/Policy&gt;&lt;/Policy&gt;</s:String>
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/UserRules/=70345118_002D4b40_002D4ece_002D937c_002Dbbeb7a0b2e70/@EntryIndexedValue">&lt;Policy&gt;&lt;Descriptor Staticness="Static" AccessRightKinds="Protected, ProtectedInternal, Internal, Public, PrivateProtected" Description="Static fields (not private)"&gt;&lt;ElementKinds&gt;&lt;Kind Name="FIELD" /&gt;&lt;/ElementKinds&gt;&lt;/Descriptor&gt;&lt;Policy Inspect="True" Prefix="" Suffix="" Style="AaBb"&gt;&lt;ExtraRule Prefix="_" Suffix="" Style="aaBb" /&gt;&lt;/Policy&gt;&lt;/Policy&gt;</s:String>
<s:String x:Key="/Default/CodeStyle/Naming/JavaScriptNaming/UserRules/=JS_005FBLOCK_005FSCOPE_005FCONSTANT/@EntryIndexedValue">&lt;Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /&gt;</s:String>
<s:String x:Key="/Default/CodeStyle/Naming/JavaScriptNaming/UserRules/=JS_005FBLOCK_005FSCOPE_005FFUNCTION/@EntryIndexedValue">&lt;Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /&gt;</s:String>
<s:String x:Key="/Default/CodeStyle/Naming/JavaScriptNaming/UserRules/=JS_005FBLOCK_005FSCOPE_005FVARIABLE/@EntryIndexedValue">&lt;Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /&gt;</s:String>
@@ -220,6 +223,7 @@
<s:Boolean x:Key="/Default/Environment/SettingsMigration/IsMigratorApplied/=JetBrains_002EReSharper_002EPsi_002ECSharp_002ECodeStyle_002ESettingsUpgrade_002ECSharpPlaceAttributeOnSameLineMigration/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/Environment/SettingsMigration/IsMigratorApplied/=JetBrains_002EReSharper_002EPsi_002ECSharp_002ECodeStyle_002ESettingsUpgrade_002EMigrateBlankLinesAroundFieldToBlankLinesAroundProperty/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/Environment/SettingsMigration/IsMigratorApplied/=JetBrains_002EReSharper_002EPsi_002ECSharp_002ECodeStyle_002ESettingsUpgrade_002EMigrateThisQualifierSettings/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/Environment/SettingsMigration/IsMigratorApplied/=JetBrains_002EReSharper_002EPsi_002ECSharp_002ECodeStyle_002ESettingsUpgrade_002EPredefinedNamingRulesToUserRulesUpgrade/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/Environment/SettingsMigration/IsMigratorApplied/=JetBrains_002EReSharper_002EPsi_002EXml_002ECodeStyle_002EFormatSettingsUpgrade_002EXmlMoveToCommonFormatterSettingsUpgrade/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/Environment/SettingsMigration/IsMigratorApplied/=JetBrains_002EReSharper_002EFeature_002EServices_002ECpp_002ECodeStyle_002ESettingsUpgrade_002EFunctionReturnStyleSettingsUpgrader/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/Environment/SettingsMigration/IsMigratorApplied/=JetBrains_002EReSharper_002EFeature_002EServices_002ECpp_002ECodeStyle_002ESettingsUpgrade_002ENamespaceIndentationSettingsUpgrader/@EntryIndexedValue">True</s:Boolean>

View File

@@ -133,6 +133,7 @@ namespace FlaxEditor.Content.Import
FileTypes["dds"] = ImportTexture;
FileTypes["hdr"] = ImportTexture;
FileTypes["raw"] = ImportTexture;
FileTypes["exr"] = ImportTexture;
// Models
FileTypes["obj"] = ImportModel;

View File

@@ -128,6 +128,11 @@ namespace FlaxEditor.Content.Import
_settings.Settings.Type = TextureFormatType.HdrRGBA;
_settings.Settings.Compress = false;
}
else if (extension == ".exr")
{
// HDR image
_settings.Settings.Type = TextureFormatType.HdrRGBA;
}
else if (extension == ".hdr")
{
// HDR sky texture

View File

@@ -2,6 +2,7 @@
using System;
using System.Collections.Generic;
using System.Linq;
using FlaxEditor.Actions;
using FlaxEditor.CustomEditors.Editors;
using FlaxEditor.CustomEditors.Elements;
@@ -9,6 +10,8 @@ using FlaxEditor.GUI;
using FlaxEditor.GUI.ContextMenu;
using FlaxEditor.GUI.Tree;
using FlaxEditor.Scripting;
using FlaxEditor.Windows;
using FlaxEditor.Windows.Assets;
using FlaxEngine;
using FlaxEngine.GUI;
using FlaxEngine.Json;
@@ -111,6 +114,38 @@ namespace FlaxEditor.CustomEditors.Dedicated
var actor = (Actor)Values[0];
var scriptType = TypeUtils.GetType(actor.TypeName);
var item = scriptType.ContentItem;
if (Presenter.Owner is PropertiesWindow propertiesWindow)
{
var lockButton = cm.AddButton(propertiesWindow.LockObjects ? "Unlock" : "Lock");
lockButton.ButtonClicked += button =>
{
propertiesWindow.LockObjects = !propertiesWindow.LockObjects;
// Reselect current selection
if (!propertiesWindow.LockObjects && Editor.Instance.SceneEditing.SelectionCount > 0)
{
var cachedSelection = Editor.Instance.SceneEditing.Selection.ToArray();
Editor.Instance.SceneEditing.Select(null);
Editor.Instance.SceneEditing.Select(cachedSelection);
}
};
}
else if (Presenter.Owner is PrefabWindow prefabWindow)
{
var lockButton = cm.AddButton(prefabWindow.LockSelectedObjects ? "Unlock" : "Lock");
lockButton.ButtonClicked += button =>
{
prefabWindow.LockSelectedObjects = !prefabWindow.LockSelectedObjects;
// Reselect current selection
if (!prefabWindow.LockSelectedObjects && prefabWindow.Selection.Count > 0)
{
var cachedSelection = prefabWindow.Selection.ToList();
prefabWindow.Select(null);
prefabWindow.Select(cachedSelection);
}
};
}
cm.AddButton("Copy ID", OnClickCopyId);
cm.AddButton("Edit actor type", OnClickEditActorType).Enabled = item != null;
var showButton = cm.AddButton("Show in content window", OnClickShowActorType);

View File

@@ -37,6 +37,8 @@ namespace FlaxEditor.CustomEditors.Dedicated
return;
}
EvaluateVisibleIf(itemLayout, item, GetLabelIndex(itemLayout, item));
// Add labels with a check box
var label = new CheckablePropertyNameLabel(item.DisplayName);
label.CheckBox.Tag = setting.Bit;

View File

@@ -582,55 +582,13 @@ namespace FlaxEditor.CustomEditors.Editors
}
/// <summary>
/// Spawns the property for the given item.
/// Evaluate the <see cref="VisibleIfAttribute"/> cache for a given property item.
/// </summary>
/// <param name="itemLayout">The item layout.</param>
/// <param name="itemValues">The item values.</param>
/// <param name="item">The item.</param>
protected virtual void SpawnProperty(LayoutElementsContainer itemLayout, ValueContainer itemValues, ItemInfo item)
/// <param name="labelIndex">The label index.</param>
protected virtual void EvaluateVisibleIf(LayoutElementsContainer itemLayout, ItemInfo item, int labelIndex)
{
int labelIndex = 0;
if ((item.IsReadOnly || item.VisibleIfs.Length > 0) &&
itemLayout.Children.Count > 0 &&
itemLayout.Children[itemLayout.Children.Count - 1] is PropertiesListElement propertiesListElement)
{
labelIndex = propertiesListElement.Labels.Count;
}
itemLayout.Property(item.DisplayName, itemValues, item.OverrideEditor, item.TooltipText);
if (item.IsReadOnly && itemLayout.Children.Count > 0)
{
PropertiesListElement list = null;
int firstChildControlIndex = 0;
bool disableSingle = true;
var control = itemLayout.Children[itemLayout.Children.Count - 1];
if (control is GroupElement group && group.Children.Count > 0)
{
list = group.Children[0] as PropertiesListElement;
disableSingle = false; // Disable all nested editors
}
else if (control is PropertiesListElement list1)
{
list = list1;
firstChildControlIndex = list.Labels[labelIndex].FirstChildControlIndex;
}
if (list != null)
{
// Disable controls added to the editor
var count = list.Properties.Children.Count;
for (int j = firstChildControlIndex; j < count; j++)
{
var child = list.Properties.Children[j];
if (disableSingle && child is PropertyNameLabel)
break;
if (child != null)
child.Enabled = false;
}
}
}
if (item.VisibleIfs.Length > 0 && itemLayout.Children.Count > 0)
{
PropertiesListElement list = null;
@@ -669,6 +627,73 @@ namespace FlaxEditor.CustomEditors.Editors
}
}
/// <summary>
/// Get the label index.
/// </summary>
/// <param name="itemLayout">The item layout.</param>
/// <param name="item">The item.</param>
/// <returns>The label index.</returns>
protected virtual int GetLabelIndex(LayoutElementsContainer itemLayout, ItemInfo item)
{
int labelIndex = 0;
if ((item.IsReadOnly || item.VisibleIfs.Length > 0) &&
itemLayout.Children.Count > 0 &&
itemLayout.Children[itemLayout.Children.Count - 1] is PropertiesListElement propertiesListElement)
{
labelIndex = propertiesListElement.Labels.Count;
}
return labelIndex;
}
/// <summary>
/// Spawns the property for the given item.
/// </summary>
/// <param name="itemLayout">The item layout.</param>
/// <param name="itemValues">The item values.</param>
/// <param name="item">The item.</param>
protected virtual void SpawnProperty(LayoutElementsContainer itemLayout, ValueContainer itemValues, ItemInfo item)
{
int labelIndex = GetLabelIndex(itemLayout, item);
itemLayout.Property(item.DisplayName, itemValues, item.OverrideEditor, item.TooltipText);
if (item.IsReadOnly && itemLayout.Children.Count > 0)
{
PropertiesListElement list = null;
int firstChildControlIndex = 0;
bool disableSingle = true;
var control = itemLayout.Children[itemLayout.Children.Count - 1];
if (control is GroupElement group && group.Children.Count > 0)
{
list = group.Children[0] as PropertiesListElement;
disableSingle = false; // Disable all nested editors
}
else if (control is PropertiesListElement list1)
{
list = list1;
firstChildControlIndex = list.Labels[labelIndex].FirstChildControlIndex;
}
if (list != null)
{
// Disable controls added to the editor
var count = list.Properties.Children.Count;
for (int j = firstChildControlIndex; j < count; j++)
{
var child = list.Properties.Children[j];
if (disableSingle && child is PropertyNameLabel)
break;
if (child != null)
child.Enabled = false;
}
}
}
EvaluateVisibleIf(itemLayout, item, labelIndex);
}
/// <inheritdoc />
internal override void Initialize(CustomEditorPresenter presenter, LayoutElementsContainer layout, ValueContainer values)
{

View File

@@ -38,7 +38,7 @@ namespace FlaxEditor.CustomEditors.Editors
_comboBox.SelectedIndexChanged += OnSelectedIndexChanged;
}
private void OnSetupContextMenu(PropertyNameLabel label, ContextMenu menu, CustomEditor linkededitor)
private void OnSetupContextMenu(PropertyNameLabel label, ContextMenu menu, CustomEditor linkedEditor)
{
var button = menu.AddButton("Set to null");
button.Clicked += () => _comboBox.SelectedItem = null;
@@ -106,7 +106,7 @@ namespace FlaxEditor.CustomEditors.Editors
_comboBox.SelectedIndexChanged += OnSelectedIndexChanged;
}
private void OnSetupContextMenu(PropertyNameLabel label, ContextMenu menu, CustomEditor linkededitor)
private void OnSetupContextMenu(PropertyNameLabel label, ContextMenu menu, CustomEditor linkedEditor)
{
var button = menu.AddButton("Set to null");
button.Clicked += () => _comboBox.SelectedItem = null;

View File

@@ -79,7 +79,7 @@ namespace FlaxEditor.CustomEditors
var theFirstType = TypeUtils.GetObjectType(this[0]);
for (int i = 1; i < Count; i++)
{
if (theFirstType != TypeUtils.GetObjectType(this[1]))
if (theFirstType != TypeUtils.GetObjectType(this[i]))
return true;
}
return false;

View File

@@ -47,7 +47,7 @@ namespace FlaxEditor.GUI.Timeline.GUI
break;
default: throw new ArgumentOutOfRangeException();
}
var color = (_timeline.IsMovingPositionHandle ? style.ProgressNormal : style.Foreground).AlphaMultiplied(0.6f);
var color = (_timeline.IsMovingPositionHandle ? style.SelectionBorder : style.Foreground).AlphaMultiplied(0.6f);
Matrix3x3.RotationZ(Mathf.PiOverTwo, out var m1);
var m2 = Matrix3x3.Translation2D(0, timeAxisHeaderOffset);
Matrix3x3.Multiply(ref m1, ref m2, out var m3);
@@ -61,7 +61,8 @@ namespace FlaxEditor.GUI.Timeline.GUI
Render2D.DrawText(style.FontSmall, labelText, style.Foreground, new Float2(2, -6));
Render2D.PopTransform();
Render2D.FillRectangle(new Rectangle(Width * 0.5f, Height + timeAxisHeaderOffset, 1, _timeline.MediaPanel.Height - timeAxisHeaderOffset - timeAxisOverlap), _timeline.IsMovingPositionHandle ? style.ProgressNormal : style.Foreground.RGBMultiplied(0.8f));
color = _timeline.IsMovingPositionHandle ? style.SelectionBorder : style.Foreground.RGBMultiplied(0.8f);
Render2D.FillRectangle(new Rectangle(Width * 0.5f, Height + timeAxisHeaderOffset, 1, _timeline.MediaPanel.Height - timeAxisHeaderOffset - timeAxisOverlap), color);
base.Draw();
}

View File

@@ -42,7 +42,7 @@ namespace FlaxEditor.GUI.Timeline.GUI
var timeAxisOverlap = Timeline.HeaderTopAreaHeight * 0.5f;
var timeAxisHeaderOffset = -_timeline.MediaBackground.ViewOffset.Y - timeAxisOverlap;
var moveColor = style.ProgressNormal;
var moveColor = style.SelectionBorder;
var thickness = 2.0f;
var borderColor = _isMoving ? moveColor : (IsMouseOver && _canEdit ? Color.Yellow : style.BorderNormal);
Render2D.FillRectangle(new Rectangle((Width - thickness) * 0.5f, timeAxisHeaderOffset, thickness, Height - timeAxisHeaderOffset), borderColor);

View File

@@ -100,7 +100,7 @@ namespace FlaxEditor.GUI.Timeline
private Track _tack;
private int _startFrame, _durationFrames;
private Float2 _mouseLocation = Float2.Minimum;
private bool _isMoving;
internal bool _isMoving;
private Float2 _startMoveLocation;
private int _startMoveStartFrame;
private int _startMoveDuration;
@@ -347,7 +347,7 @@ namespace FlaxEditor.GUI.Timeline
var isMovingWholeMedia = _isMoving && !_startMoveRightEdge && !_startMoveLeftEdge;
var borderHighlightColor = style.BorderHighlighted;
var moveColor = style.ProgressNormal;
var moveColor = style.SelectionBorder;
var selectedColor = style.BackgroundSelected;
var moveThickness = 2.0f;
var borderColor = isMovingWholeMedia ? moveColor : (Timeline.SelectedMedia.Contains(this) ? selectedColor : (IsMouseOver ? borderHighlightColor : style.BorderNormal));

View File

@@ -1,6 +1,5 @@
// Copyright (c) 2012-2024 Wojciech Figat. All rights reserved.
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
@@ -183,6 +182,49 @@ namespace FlaxEditor.GUI.Timeline.Tracks
base.OnDurationFramesChanged();
}
/// <inheritdoc />
public override bool ContainsPoint(ref Float2 location, bool precise = false)
{
if (Timeline.Zoom > 0.5f && !IsContinuous)
{
// Hit-test dot
var size = Height - 2.0f;
var rect = new Rectangle(new Float2(size * -0.5f) + Size * 0.5f, new Float2(size));
return rect.Contains(ref location);
}
return base.ContainsPoint(ref location, precise);
}
/// <inheritdoc />
public override void Draw()
{
if (Timeline.Zoom > 0.5f && !IsContinuous)
{
// Draw more visible dot for the event that maintains size even when zooming out
var style = Style.Current;
var icon = Editor.Instance.Icons.VisjectBoxClosed32;
var size = Height - 2.0f;
var rect = new Rectangle(new Float2(size * -0.5f) + Size * 0.5f, new Float2(size));
var outline = Color.Black; // Shadow
if (_isMoving)
outline = style.SelectionBorder;
else if (IsMouseOver)
outline = style.BorderHighlighted;
else if (Timeline.SelectedMedia.Contains(this))
outline = style.BackgroundSelected;
Render2D.DrawSprite(icon, rect.MakeExpanded(6.0f), outline);
Render2D.DrawSprite(icon, rect, BackgroundColor);
DrawChildren();
}
else
{
// Default drawing
base.Draw();
}
}
/// <inheritdoc />
public override void OnDestroy()
{

View File

@@ -717,19 +717,6 @@ namespace FlaxEditor.Modules
_toolStripScale = (ToolStripButton)ToolStrip.AddButton(Editor.Icons.Scale32, () => Editor.MainTransformGizmo.ActiveMode = TransformGizmoBase.Mode.Scale).LinkTooltip($"Change Gizmo tool mode to Scale ({inputOptions.ScaleMode})");
ToolStrip.AddSeparator();
// Build scenes
_toolStripBuildScenes = (ToolStripButton)ToolStrip.AddButton(Editor.Icons.Build64, Editor.BuildScenesOrCancel).LinkTooltip($"Build scenes data - CSG, navmesh, static lighting, env probes - configurable via Build Actions in editor options ({inputOptions.BuildScenesData})");
// Cook and run
_toolStripCook = (ToolStripButton)ToolStrip.AddButton(Editor.Icons.ShipIt64, Editor.Windows.GameCookerWin.BuildAndRun).LinkTooltip($"Cook & Run - build game for the current platform and run it locally ({inputOptions.CookAndRun})");
_toolStripCook.ContextMenu = new ContextMenu();
_toolStripCook.ContextMenu.AddButton("Run cooked game", Editor.Windows.GameCookerWin.RunCooked);
_toolStripCook.ContextMenu.AddSeparator();
var numberOfClientsMenu = _toolStripCook.ContextMenu.AddChildMenu("Number of game clients");
_numberOfClientsGroup.AddItemsToContextMenu(numberOfClientsMenu.ContextMenu);
ToolStrip.AddSeparator();
// Play
_toolStripPlay = (ToolStripButton)ToolStrip.AddButton(Editor.Icons.Play64, Editor.Simulation.DelegatePlayOrStopPlayInEditor).LinkTooltip($"Play In Editor ({inputOptions.Play})");
_toolStripPlay.ContextMenu = new ContextMenu();
@@ -741,7 +728,6 @@ namespace FlaxEditor.Modules
playActionGroup.Selected = Editor.Options.Options.Interface.PlayButtonAction;
playActionGroup.SelectedChanged = SetPlayAction;
Editor.Options.OptionsChanged += options => { playActionGroup.Selected = options.Interface.PlayButtonAction; };
var windowModesGroup = new ContextMenuSingleSelectGroup<InterfaceOptions.GameWindowMode>();
var windowTypeMenu = _toolStripPlay.ContextMenu.AddChildMenu("Game window mode");
windowModesGroup.AddItem("Docked", InterfaceOptions.GameWindowMode.Docked, null, "Shows the game window docked, inside the editor");
@@ -754,7 +740,20 @@ namespace FlaxEditor.Modules
Editor.Options.OptionsChanged += options => { windowModesGroup.Selected = options.Interface.DefaultGameWindowMode; };
_toolStripPause = (ToolStripButton)ToolStrip.AddButton(Editor.Icons.Pause64, Editor.Simulation.RequestResumeOrPause).LinkTooltip($"Pause/Resume game ({inputOptions.Pause})");
_toolStripStep = (ToolStripButton)ToolStrip.AddButton(Editor.Icons.Skip64, Editor.Simulation.RequestPlayOneFrame).LinkTooltip("Step one frame in game");
_toolStripStep = (ToolStripButton)ToolStrip.AddButton(Editor.Icons.Skip64, Editor.Simulation.RequestPlayOneFrame).LinkTooltip($"Step one frame in game ({inputOptions.StepFrame})");
ToolStrip.AddSeparator();
// Build scenes
_toolStripBuildScenes = (ToolStripButton)ToolStrip.AddButton(Editor.Icons.Build64, Editor.BuildScenesOrCancel).LinkTooltip($"Build scenes data - CSG, navmesh, static lighting, env probes - configurable via Build Actions in editor options ({inputOptions.BuildScenesData})");
// Cook and run
_toolStripCook = (ToolStripButton)ToolStrip.AddButton(Editor.Icons.ShipIt64, Editor.Windows.GameCookerWin.BuildAndRun).LinkTooltip($"Cook & Run - build game for the current platform and run it locally ({inputOptions.CookAndRun})");
_toolStripCook.ContextMenu = new ContextMenu();
_toolStripCook.ContextMenu.AddButton("Run cooked game", Editor.Windows.GameCookerWin.RunCooked);
_toolStripCook.ContextMenu.AddSeparator();
var numberOfClientsMenu = _toolStripCook.ContextMenu.AddChildMenu("Number of game clients");
_numberOfClientsGroup.AddItemsToContextMenu(numberOfClientsMenu.ContextMenu);
UpdateToolstrip();
}

View File

@@ -17,6 +17,11 @@ namespace FlaxEditor.Surface
/// </summary>
public readonly InputActionsContainer InputActions;
/// <summary>
/// Optional feature.
/// </summary>
public bool PanWithMiddleMouse = false;
private string _currentInputText = string.Empty;
private Float2 _movingNodesDelta;
private HashSet<SurfaceNode> _movingNodes;
@@ -223,15 +228,18 @@ namespace FlaxEditor.Surface
if (_middleMouseDown)
{
// Calculate delta
var delta = location - _middleMouseDownPos;
if (delta.LengthSquared > 0.01f)
if (PanWithMiddleMouse)
{
// Move view
_mouseMoveAmount += delta.Length;
_rootControl.Location += delta;
_middleMouseDownPos = location;
Cursor = CursorType.SizeAll;
// Calculate delta
var delta = location - _middleMouseDownPos;
if (delta.LengthSquared > 0.01f)
{
// Move view
_mouseMoveAmount += delta.Length;
_rootControl.Location += delta;
_middleMouseDownPos = location;
Cursor = CursorType.SizeAll;
}
}
// Handled
@@ -300,7 +308,8 @@ namespace FlaxEditor.Surface
if (_middleMouseDown)
{
_middleMouseDown = false;
Cursor = CursorType.Default;
if (PanWithMiddleMouse)
Cursor = CursorType.Default;
}
_isMovingSelection = false;
ConnectingEnd(null);
@@ -483,7 +492,7 @@ namespace FlaxEditor.Surface
Focus();
return true;
}
if (_rightMouseDown || _middleMouseDown)
if (_rightMouseDown || (_middleMouseDown && _middleMouseDown))
{
// Start navigating
StartMouseCapture();
@@ -555,9 +564,12 @@ namespace FlaxEditor.Surface
if (_middleMouseDown && button == MouseButton.Middle)
{
_middleMouseDown = false;
EndMouseCapture();
Cursor = CursorType.Default;
if (_mouseMoveAmount > 0)
if (_middleMouseDown)
{
EndMouseCapture();
Cursor = CursorType.Default;
}
if (_mouseMoveAmount > 0 && _middleMouseDown)
_mouseMoveAmount = 0;
else if (CanEdit)
{

View File

@@ -66,8 +66,8 @@ namespace FlaxEditor.Tools.Terrain
[EditorOrder(410), EditorDisplay("Transform", "Rotation"), DefaultValue(typeof(Quaternion), "0,0,0,1"), Tooltip("Orientation of the terrain")]
public Quaternion Orientation = Quaternion.Identity;
[EditorOrder(420), EditorDisplay("Transform", "Scale"), DefaultValue(typeof(Float3), "1,1,1"), Limit(float.MinValue, float.MaxValue, 0.01f), Tooltip("Scale of the terrain")]
public Float3 Scale = Float3.One;
[EditorOrder(420), EditorDisplay("Transform", "Scale"), DefaultValue(1.0f), Limit(0.0001f, float.MaxValue, 0.01f), Tooltip("Scale of the terrain")]
public float Scale = 1.0f;
}
private readonly Options _options = new Options();
@@ -147,7 +147,7 @@ namespace FlaxEditor.Tools.Terrain
// Create terrain object and setup some options
var terrain = new FlaxEngine.Terrain();
terrain.Setup(_options.LODCount, (int)_options.ChunkSize);
terrain.Transform = new Transform(_options.Position, _options.Orientation, _options.Scale);
terrain.Transform = new Transform(_options.Position, _options.Orientation, new Float3(_options.Scale));
terrain.Material = _options.Material;
terrain.CollisionLOD = _options.CollisionLOD;
if (_options.Heightmap)
@@ -238,24 +238,5 @@ namespace FlaxEditor.Tools.Terrain
return base.CanCloseWindow(reason);
}
/// <inheritdoc />
public override bool OnKeyDown(KeyboardKeys key)
{
if (_isWorking)
return true;
switch (key)
{
case KeyboardKeys.Escape:
OnCancel();
return true;
case KeyboardKeys.Return:
OnSubmit();
return true;
}
return base.OnKeyDown(key);
}
}
}

View File

@@ -80,7 +80,7 @@ struct TextureDataResult
}
};
bool GetTextureDataForSampling(Texture* texture, TextureDataResult& data)
bool GetTextureDataForSampling(Texture* texture, TextureDataResult& data, bool hdr = false)
{
// Lock asset chunks (if not virtual)
data.Lock = texture->LockData();
@@ -103,7 +103,7 @@ bool GetTextureDataForSampling(Texture* texture, TextureDataResult& data)
// Decompress or convert data if need to
data.Mip0DataPtr = &data.Mip0Data;
if (PixelFormatExtensions::IsCompressed(data.Format))
if (PixelFormatExtensions::IsCompressed(data.Format) || TextureTool::GetSampler(data.Format) == nullptr)
{
PROFILE_CPU_NAMED("Decompress");
@@ -122,7 +122,7 @@ bool GetTextureDataForSampling(Texture* texture, TextureDataResult& data)
srcMip.Lines = src.Height;
// Decompress texture
if (TextureTool::Convert(data.Tmp, src, PixelFormat::R8G8B8A8_UNorm))
if (TextureTool::Convert(data.Tmp, src, hdr ? PixelFormat::R16G16B16A16_Float : PixelFormat::R8G8B8A8_UNorm))
{
LOG(Warning, "Failed to decompress data.");
return true;
@@ -134,7 +134,6 @@ bool GetTextureDataForSampling(Texture* texture, TextureDataResult& data)
data.SlicePitch = data.Tmp.Items[0].Mips[0].DepthPitch;
data.Mip0DataPtr = &data.Tmp.Items[0].Mips[0].Data;
}
// TODO: convert to RGBA from other formats that cannot be sampled?
// Check if can even sample the given format
const auto sampler = TextureTool::GetSampler(data.Format);
@@ -155,7 +154,6 @@ bool TerrainTools::GenerateTerrain(Terrain* terrain, const Int2& numberOfPatches
LOG(Warning, "Cannot setup terain with no patches.");
return false;
}
PROFILE_CPU_NAMED("Terrain.GenerateTerrain");
// Wait for assets to be loaded
@@ -188,7 +186,7 @@ bool TerrainTools::GenerateTerrain(Terrain* terrain, const Int2& numberOfPatches
{
// Get data
TextureDataResult dataHeightmap;
if (GetTextureDataForSampling(heightmap, dataHeightmap))
if (GetTextureDataForSampling(heightmap, dataHeightmap, true))
return true;
const auto sampler = TextureTool::GetSampler(dataHeightmap.Format);
@@ -198,7 +196,6 @@ bool TerrainTools::GenerateTerrain(Terrain* terrain, const Int2& numberOfPatches
for (int32 patchIndex = 0; patchIndex < terrain->GetPatchesCount(); patchIndex++)
{
auto patch = terrain->GetPatch(patchIndex);
const Vector2 uvStart = Vector2((float)patch->GetX(), (float)patch->GetZ()) * uvPerPatch;
// Sample heightmap pixels with interpolation to get actual heightmap vertices locations

View File

@@ -76,7 +76,7 @@ namespace FlaxEditor.Viewport
public bool SnapToVertex => ContainsFocus && Editor.Instance.Options.Options.Input.SnapToVertex.Process(Root);
/// <inheritdoc />
public Float2 MouseDelta => _mouseDelta * 1000;
public Float2 MouseDelta => _mouseDelta;
/// <inheritdoc />
public bool UseSnapping => Root?.GetKey(KeyboardKeys.Control) ?? false;

View File

@@ -65,6 +65,11 @@ namespace FlaxEditor.Viewport
/// </summary>
public bool IsAltDown;
/// <summary>
/// The is alt down flag cached from the previous input. Used to make <see cref="IsControllingMouse"/> consistent when user releases Alt while orbiting with Alt+LMB.
/// </summary>
public bool WasAltDownBefore;
/// <summary>
/// The is mouse right down flag.
/// </summary>
@@ -88,18 +93,20 @@ namespace FlaxEditor.Viewport
/// <summary>
/// Gets a value indicating whether use is controlling mouse.
/// </summary>
public bool IsControllingMouse => IsMouseMiddleDown || IsMouseRightDown || (IsAltDown && IsMouseLeftDown) || Mathf.Abs(MouseWheelDelta) > 0.1f;
public bool IsControllingMouse => IsMouseMiddleDown || IsMouseRightDown || ((IsAltDown || WasAltDownBefore) && IsMouseLeftDown) || Mathf.Abs(MouseWheelDelta) > 0.1f;
/// <summary>
/// Gathers input from the specified window.
/// </summary>
/// <param name="window">The window.</param>
/// <param name="useMouse">True if use mouse input, otherwise will skip mouse.</param>
public void Gather(Window window, bool useMouse)
/// <param name="prevInput">Previous input state.</param>
public void Gather(Window window, bool useMouse, ref Input prevInput)
{
IsControlDown = window.GetKey(KeyboardKeys.Control);
IsShiftDown = window.GetKey(KeyboardKeys.Shift);
IsAltDown = window.GetKey(KeyboardKeys.Alt);
WasAltDownBefore = prevInput.WasAltDownBefore || prevInput.IsAltDown;
IsMouseRightDown = useMouse && window.GetMouseButton(MouseButton.Right);
IsMouseMiddleDown = useMouse && window.GetMouseButton(MouseButton.Middle);
@@ -114,6 +121,7 @@ namespace FlaxEditor.Viewport
IsControlDown = false;
IsShiftDown = false;
IsAltDown = false;
WasAltDownBefore = false;
IsMouseRightDown = false;
IsMouseMiddleDown = false;
@@ -1540,7 +1548,7 @@ namespace FlaxEditor.Viewport
_prevInput = _input;
var hit = GetChildAt(_viewMousePos, c => c.Visible && !(c is CanvasRootControl) && !(c is UIEditorRoot));
if (canUseInput && ContainsFocus && hit == null)
_input.Gather(win.Window, useMouse);
_input.Gather(win.Window, useMouse, ref _prevInput);
else
_input.Clear();
@@ -1663,8 +1671,7 @@ namespace FlaxEditor.Viewport
{
offset.X = offset.X > 0 ? Mathf.Floor(offset.X) : Mathf.Ceil(offset.X);
offset.Y = offset.Y > 0 ? Mathf.Floor(offset.Y) : Mathf.Ceil(offset.Y);
_mouseDelta = offset / size;
_mouseDelta.Y *= size.Y / size.X;
_mouseDelta = offset;
// Update delta filtering buffer
_deltaFilteringBuffer[_deltaFilteringStep] = _mouseDelta;
@@ -1682,8 +1689,7 @@ namespace FlaxEditor.Viewport
}
else
{
_mouseDelta = offset / size;
_mouseDelta.Y *= size.Y / size.X;
_mouseDelta = offset;
mouseDelta = _mouseDelta;
}
@@ -1697,7 +1703,7 @@ namespace FlaxEditor.Viewport
// Update
moveDelta *= dt * (60.0f * 4.0f);
mouseDelta *= 200.0f * MouseSpeed * _mouseSensitivity;
mouseDelta *= 0.1833f * MouseSpeed * _mouseSensitivity;
UpdateView(dt, ref moveDelta, ref mouseDelta, out var centerMouse);
// Move mouse back to the root position
@@ -1723,7 +1729,7 @@ namespace FlaxEditor.Viewport
var offset = _viewMousePos - _startPos;
offset.X = offset.X > 0 ? Mathf.Floor(offset.X) : Mathf.Ceil(offset.X);
offset.Y = offset.Y > 0 ? Mathf.Floor(offset.Y) : Mathf.Ceil(offset.Y);
_mouseDelta = offset / size;
_mouseDelta = offset;
_startPos = _viewMousePos;
}
else

View File

@@ -291,7 +291,7 @@ namespace FlaxEditor.Viewport
public bool SnapToVertex => ContainsFocus && Editor.Instance.Options.Options.Input.SnapToVertex.Process(Root);
/// <inheritdoc />
public Float2 MouseDelta => _mouseDelta * 1000;
public Float2 MouseDelta => _mouseDelta;
/// <inheritdoc />
public bool UseSnapping => Root.GetKey(KeyboardKeys.Control);

View File

@@ -336,7 +336,8 @@ namespace FlaxEditor.Viewport.Previews
if (_showNodes)
{
// Draw bounding box at the node locations
var localBox = new OrientedBoundingBox(new Vector3(-1.0f), new Vector3(1.0f));
var boxSize = Mathf.Min(1.0f, _previewModel.Sphere.Radius / 100.0f);
var localBox = new OrientedBoundingBox(new Vector3(-boxSize), new Vector3(boxSize));
for (int nodeIndex = 0; nodeIndex < pose.Length; nodeIndex++)
{
if (nodesMask != null && !nodesMask[nodeIndex])

View File

@@ -494,6 +494,7 @@ namespace FlaxEditor.Windows.Assets
_undo.Enabled = false;
_undo.Clear();
_propertiesEditor.BuildLayoutOnUpdate();
UpdateToolstrip();
}
/// <inheritdoc />
@@ -504,6 +505,7 @@ namespace FlaxEditor.Windows.Assets
_undo.Enabled = true;
_undo.Clear();
_propertiesEditor.BuildLayoutOnUpdate();
UpdateToolstrip();
}
/// <inheritdoc />

View File

@@ -54,6 +54,9 @@ namespace FlaxEditor.Windows.Assets
/// <param name="before">The selection before the change.</param>
public void OnSelectionChanged(SceneGraphNode[] before)
{
if (LockSelectedObjects)
return;
Undo.AddAction(new SelectionChangeAction(before, Selection.ToArray(), OnSelectionUndo));
OnSelectionChanges();

View File

@@ -68,6 +68,11 @@ namespace FlaxEditor.Windows.Assets
/// </summary>
public readonly LocalSceneGraph Graph;
/// <summary>
/// Indication of if the prefab window selection is locked on specific objects.
/// </summary>
public bool LockSelectedObjects = false;
/// <summary>
/// Gets or sets a value indicating whether use live reloading for the prefab changes (applies prefab changes on modification by auto).
/// </summary>

View File

@@ -665,7 +665,16 @@ namespace FlaxEditor.Windows
// Scroll to the new entry (if any added to view)
if (scrollView && anyVisible)
{
panelScroll.ScrollViewTo(newEntry);
bool scrollViewNew = (panelScroll.VScrollBar.Maximum - panelScroll.VScrollBar.TargetValue) < LogEntry.DefaultHeight * 1.5f;
if (scrollViewNew != scrollView)
{
// Make sure scrolling doesn't stop in case too many entries were added at once
panelScroll.ScrollViewTo(new Float2(float.MaxValue, float.MaxValue));
}
}
}
}

View File

@@ -37,6 +37,11 @@ namespace FlaxEditor.Windows
/// </summary>
public bool UIPivotRelative = true;
/// <summary>
/// Indication of if the properties window is locked on specific objects.
/// </summary>
public bool LockObjects = false;
/// <summary>
/// Initializes a new instance of the <see cref="PropertiesWindow"/> class.
/// </summary>
@@ -62,6 +67,9 @@ namespace FlaxEditor.Windows
private void OnSelectionChanged()
{
if (LockObjects)
return;
// Update selected objects
// TODO: use cached collection for less memory allocations
undoRecordObjects = Editor.SceneEditing.Selection.ConvertAll(x => x.UndoRecordObject).Distinct();

View File

@@ -196,6 +196,7 @@ namespace FlaxEditor.Windows
{
if (actorType.IsAbstract)
continue;
_groupSearch.AddChild(CreateActorItem(Utilities.Utils.GetPropertyNameUI(actorType.Name), actorType));
ActorToolboxAttribute attribute = null;
foreach (var e in actorType.GetAttributes(false))
{
@@ -235,6 +236,7 @@ namespace FlaxEditor.Windows
group.AddChild(string.IsNullOrEmpty(attribute.Name) ? CreateActorItem(Utilities.Utils.GetPropertyNameUI(actorType.Name), actorType) : CreateActorItem(attribute.Name, actorType));
group.SortChildren();
}
_groupSearch.SortChildren();
}
private void OnSearchBoxTextChanged()
@@ -260,6 +262,10 @@ namespace FlaxEditor.Windows
}
var text = (attribute == null) ? actorType.Name : string.IsNullOrEmpty(attribute.Name) ? actorType.Name : attribute.Name;
// Display all actors on no search
if (string.IsNullOrEmpty(filterText))
_groupSearch.AddChild(CreateActorItem(Utilities.Utils.GetPropertyNameUI(text), actorType));
if (!QueryFilterHelper.Match(filterText, text, out QueryFilterHelper.Range[] ranges))
continue;
@@ -278,6 +284,9 @@ namespace FlaxEditor.Windows
}
item.SetHighlights(highlights);
}
if (string.IsNullOrEmpty(filterText))
_groupSearch.SortChildren();
_groupSearch.UnlockChildrenRecursive();
PerformLayout();

View File

@@ -3,6 +3,7 @@
#pragma once
#include "Engine/Core/Types/String.h"
#include "Engine/Core/Types/Pair.h"
#include "Engine/Core/Math/Transform.h"
#include "Engine/Animations/Curve.h"
@@ -68,6 +69,16 @@ public:
uint64 GetMemoryUsage() const;
};
/// <summary>
/// Single track with events.
/// </summary>
struct EventAnimationData
{
float Duration = 0.0f;
StringAnsi TypeName;
StringAnsi JsonData;
};
/// <summary>
/// Root Motion modes that can be applied by the animation. Used as flags for selective behavior.
/// </summary>
@@ -120,10 +131,15 @@ struct AnimationData
String RootNodeName;
/// <summary>
/// The per skeleton node animation channels.
/// The per-skeleton node animation channels.
/// </summary>
Array<NodeAnimationData> Channels;
/// <summary>
/// The animation event tracks.
/// </summary>
Array<Pair<String, StepCurve<EventAnimationData>>> Events;
public:
/// <summary>
/// Gets the length of the animation (in seconds).

View File

@@ -5,6 +5,7 @@
#include "Engine/Visject/VisjectGraph.h"
#include "Engine/Content/Assets/Animation.h"
#include "Engine/Core/Collections/ChunkedArray.h"
#include "Engine/Core/Collections/BitArray.h"
#include "Engine/Animations/AlphaBlend.h"
#include "Engine/Core/Math/Matrix.h"
#include "../Config.h"
@@ -892,7 +893,7 @@ private:
int32 GetRootNodeIndex(Animation* anim);
void ProcessAnimEvents(AnimGraphNode* node, bool loop, float length, float animPos, float animPrevPos, Animation* anim, float speed);
void ProcessAnimation(AnimGraphImpulse* nodes, AnimGraphNode* node, bool loop, float length, float pos, float prevPos, Animation* anim, float speed, float weight = 1.0f, ProcessAnimationMode mode = ProcessAnimationMode::Override);
void ProcessAnimation(AnimGraphImpulse* nodes, AnimGraphNode* node, bool loop, float length, float pos, float prevPos, Animation* anim, float speed, float weight = 1.0f, ProcessAnimationMode mode = ProcessAnimationMode::Override, BitArray<InlinedAllocation<8>>* usedNodes = nullptr);
Variant SampleAnimation(AnimGraphNode* node, bool loop, float length, float startTimePos, float prevTimePos, float& newTimePos, Animation* anim, float speed);
Variant SampleAnimationsWithBlend(AnimGraphNode* node, bool loop, float length, float startTimePos, float prevTimePos, float& newTimePos, Animation* animA, Animation* animB, float speedA, float speedB, float alpha);
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);

View File

@@ -25,7 +25,7 @@ namespace
{
for (int32 i = 0; i < nodes->Nodes.Count(); i++)
{
nodes->Nodes[i].Orientation.Normalize();
nodes->Nodes.Get()[i].Orientation.Normalize();
}
if (rootMotionMode != RootMotionExtraction::NoExtraction)
{
@@ -222,7 +222,7 @@ FORCE_INLINE void GetAnimSamplePos(bool loop, float length, float startTimePos,
prevPos = GetAnimPos(prevTimePos, startTimePos, loop, length);
}
void AnimGraphExecutor::ProcessAnimation(AnimGraphImpulse* nodes, AnimGraphNode* node, bool loop, float length, float pos, float prevPos, Animation* anim, float speed, float weight, ProcessAnimationMode mode)
void AnimGraphExecutor::ProcessAnimation(AnimGraphImpulse* nodes, AnimGraphNode* node, bool loop, float length, float pos, float prevPos, Animation* anim, float speed, float weight, ProcessAnimationMode mode, BitArray<InlinedAllocation<8>>* usedNodes)
{
PROFILE_CPU_ASSET(anim);
@@ -240,9 +240,16 @@ void AnimGraphExecutor::ProcessAnimation(AnimGraphImpulse* nodes, AnimGraphNode*
}
// Evaluate nested animations
bool hasNested = false;
BitArray<InlinedAllocation<8>> usedNodesThis;
if (anim->NestedAnims.Count() != 0)
{
if (usedNodes == nullptr)
{
// Per-channel bit to indicate which channels were used by nested
usedNodesThis.Resize(nodes->Nodes.Count());
usedNodes = &usedNodesThis;
}
for (auto& e : anim->NestedAnims)
{
const auto& nestedAnim = e.Second;
@@ -262,8 +269,7 @@ void AnimGraphExecutor::ProcessAnimation(AnimGraphImpulse* nodes, AnimGraphNode*
nestedAnimPrevPos = nestedAnimPrevPos * frameRateMatchScale;
GetAnimSamplePos(nestedAnim.Loop, nestedAnimLength, nestedAnim.StartTime, nestedAnimPrevPos, nestedAnimPos, nestedAnimPos, nestedAnimPrevPos);
ProcessAnimation(nodes, node, true, nestedAnimLength, nestedAnimPos, nestedAnimPrevPos, nestedAnim.Anim, 1.0f, weight, mode);
hasNested = true;
ProcessAnimation(nodes, node, true, nestedAnimLength, nestedAnimPos, nestedAnimPrevPos, nestedAnim.Anim, 1.0f, weight, mode, usedNodes);
}
}
}
@@ -295,6 +301,15 @@ void AnimGraphExecutor::ProcessAnimation(AnimGraphImpulse* nodes, AnimGraphNode*
{
RetargetSkeletonNode(mapping.SourceSkeleton->Skeleton, mapping.TargetSkeleton->Skeleton, sourceMapping, srcNode, i);
}
// Mark node as used
if (usedNodes)
usedNodes->Set(i, true);
}
else if (usedNodes && usedNodes != &usedNodesThis)
{
// Skip for nested animations so other one or top-level anim will update remaining nodes
continue;
}
// Blend node
@@ -316,7 +331,7 @@ void AnimGraphExecutor::ProcessAnimation(AnimGraphImpulse* nodes, AnimGraphNode*
dstNode.Scale = srcNode.Scale * weight;
dstNode.Orientation = srcNode.Orientation * weight;
}
else if (!hasNested)
else
{
dstNode = srcNode;
}
@@ -1177,14 +1192,12 @@ void AnimGraphExecutor::ProcessGroupAnimation(Box* boxBase, Node* nodeBase, Valu
{
const float alpha = Math::Saturate((float)tryGetValue(node->GetBox(3), node->Values[0]));
auto mask = node->Assets[0].As<SkeletonMask>();
auto maskAssetBox = node->GetBox(4); // 4 is the id of skeleton mask parameter node.
// Check if have some mask asset connected with the mask node
if (maskAssetBox->HasConnection())
// Use the mask connected with this node instead of default mask asset
auto maskAssetBox = node->TryGetBox(4);
if (maskAssetBox && maskAssetBox->HasConnection())
{
const Value assetBoxValue = tryGetValue(maskAssetBox, Value::Null);
// Use the mask connected with this node instead of default mask asset
if (assetBoxValue != Value::Null)
mask = (SkeletonMask*)assetBoxValue.AsAsset;
}

View File

@@ -10,79 +10,102 @@ void InverseKinematics::SolveAimIK(const Transform& node, const Vector3& target,
Quaternion::FindBetween(fromNode, toTarget, outNodeCorrection);
}
void InverseKinematics::SolveTwoBoneIK(Transform& rootNode, Transform& jointNode, Transform& targetNode, const Vector3& target, const Vector3& jointTarget, bool allowStretching, float maxStretchScale)
{
Real lowerLimbLength = (targetNode.Translation - jointNode.Translation).Length();
Real upperLimbLength = (jointNode.Translation - rootNode.Translation).Length();
Vector3 jointPos = jointNode.Translation;
Vector3 desiredDelta = target - rootNode.Translation;
Real desiredLength = desiredDelta.Length();
Real limbLengthLimit = lowerLimbLength + upperLimbLength;
Vector3 desiredDir;
if (desiredLength < ZeroTolerance)
void InverseKinematics::SolveTwoBoneIK(Transform& rootTransform, Transform& midJointTransform, Transform& endEffectorTransform, const Vector3& targetPosition, const Vector3& poleVector, bool allowStretching, float maxStretchScale)
{
// Calculate limb segment lengths
Real lowerLimbLength = (endEffectorTransform.Translation - midJointTransform.Translation).Length();
Real upperLimbLength = (midJointTransform.Translation - rootTransform.Translation).Length();
Vector3 midJointPos = midJointTransform.Translation;
// Calculate the direction and length towards the target
Vector3 toTargetVector = targetPosition - rootTransform.Translation;
Real toTargetLength = toTargetVector.Length();
Real totalLimbLength = lowerLimbLength + upperLimbLength;
// Normalize the direction vector or set a default direction if too small
Vector3 toTargetDir;
if (toTargetLength < ZeroTolerance)
{
desiredLength = ZeroTolerance;
desiredDir = Vector3(1, 0, 0);
toTargetLength = ZeroTolerance;
toTargetDir = Vector3(1, 0, 0);
}
else
{
desiredDir = desiredDelta.GetNormalized();
toTargetDir = toTargetVector.GetNormalized();
}
Vector3 jointTargetDelta = jointTarget - rootNode.Translation;
const Real jointTargetLengthSqr = jointTargetDelta.LengthSquared();
// Calculate the pole vector direction
Vector3 poleVectorDelta = poleVector - rootTransform.Translation;
const Real poleVectorLengthSqr = poleVectorDelta.LengthSquared();
Vector3 jointPlaneNormal, jointBendDir;
if (jointTargetLengthSqr < ZeroTolerance * ZeroTolerance)
Vector3 jointPlaneNormal, bendDirection;
if (poleVectorLengthSqr < ZeroTolerance * ZeroTolerance)
{
jointBendDir = Vector3::Forward;
bendDirection = Vector3::Forward;
jointPlaneNormal = Vector3::Up;
}
else
{
jointPlaneNormal = desiredDir ^ jointTargetDelta;
jointPlaneNormal = toTargetDir ^ poleVectorDelta;
if (jointPlaneNormal.LengthSquared() < ZeroTolerance * ZeroTolerance)
{
desiredDir.FindBestAxisVectors(jointPlaneNormal, jointBendDir);
toTargetDir.FindBestAxisVectors(jointPlaneNormal, bendDirection);
}
else
{
jointPlaneNormal.Normalize();
jointBendDir = jointTargetDelta - (jointTargetDelta | desiredDir) * desiredDir;
jointBendDir.Normalize();
bendDirection = poleVectorDelta - (poleVectorDelta | toTargetDir) * toTargetDir;
bendDirection.Normalize();
}
}
// Handle limb stretching if allowed
if (allowStretching)
{
const Real initialStretchRatio = 1.0f;
const Real range = maxStretchScale - initialStretchRatio;
if (range > ZeroTolerance && limbLengthLimit > ZeroTolerance)
const Real stretchRange = maxStretchScale - initialStretchRatio;
if (stretchRange > ZeroTolerance && totalLimbLength > ZeroTolerance)
{
const Real reachRatio = desiredLength / limbLengthLimit;
const Real scalingFactor = (maxStretchScale - 1.0f) * Math::Saturate((reachRatio - initialStretchRatio) / range);
const Real reachRatio = toTargetLength / totalLimbLength;
const Real scalingFactor = (maxStretchScale - 1.0f) * Math::Saturate((reachRatio - initialStretchRatio) / stretchRange);
if (scalingFactor > ZeroTolerance)
{
lowerLimbLength *= 1.0f + scalingFactor;
upperLimbLength *= 1.0f + scalingFactor;
limbLengthLimit *= 1.0f + scalingFactor;
totalLimbLength *= 1.0f + scalingFactor;
}
}
}
Vector3 resultEndPos = target;
Vector3 resultJointPos = jointPos;
// Calculate new positions for joint and end effector
Vector3 newEndEffectorPos = targetPosition;
Vector3 newMidJointPos = midJointPos;
if (desiredLength >= limbLengthLimit)
{
resultEndPos = rootNode.Translation + limbLengthLimit * desiredDir;
resultJointPos = rootNode.Translation + upperLimbLength * desiredDir;
if (toTargetLength >= totalLimbLength) {
// Target is beyond the reach of the limb
Vector3 rootToEnd = (targetPosition - rootTransform.Translation).GetNormalized();
// Calculate the slight offset towards the pole vector
Vector3 rootToPole = (poleVector - rootTransform.Translation).GetNormalized();
Vector3 slightBendDirection = Vector3::Cross(rootToEnd, rootToPole);
if (slightBendDirection.LengthSquared() < ZeroTolerance * ZeroTolerance) {
slightBendDirection = Vector3::Up;
}
else {
slightBendDirection.Normalize();
}
// Calculate the direction from root to mid joint with a slight offset towards the pole vector
Vector3 midJointDirection = Vector3::Cross(slightBendDirection, rootToEnd).GetNormalized();
Real slightOffset = upperLimbLength * 0.01f; // Small percentage of the limb length for slight offset
newMidJointPos = rootTransform.Translation + rootToEnd * (upperLimbLength - slightOffset) + midJointDirection * slightOffset;
}
else
{
const Real twoAb = 2.0f * upperLimbLength * desiredLength;
const Real cosAngle = twoAb > ZeroTolerance ? (upperLimbLength * upperLimbLength + desiredLength * desiredLength - lowerLimbLength * lowerLimbLength) / twoAb : 0.0f;
// Target is within reach, calculate joint position
const Real twoAb = 2.0f * upperLimbLength * toTargetLength;
const Real cosAngle = twoAb > ZeroTolerance ? (upperLimbLength * upperLimbLength + toTargetLength * toTargetLength - lowerLimbLength * lowerLimbLength) / twoAb : 0.0f;
const bool reverseUpperBone = cosAngle < 0.0f;
const Real angle = Math::Acos(cosAngle);
const Real jointLineDist = upperLimbLength * Math::Sin(angle);
@@ -90,23 +113,66 @@ void InverseKinematics::SolveTwoBoneIK(Transform& rootNode, Transform& jointNode
Real projJointDist = projJointDistSqr > 0.0f ? Math::Sqrt(projJointDistSqr) : 0.0f;
if (reverseUpperBone)
projJointDist *= -1.0f;
resultJointPos = rootNode.Translation + projJointDist * desiredDir + jointLineDist * jointBendDir;
newMidJointPos = rootTransform.Translation + projJointDist * toTargetDir + jointLineDist * bendDirection;
}
// Update root joint orientation
{
const Vector3 oldDir = (jointPos - rootNode.Translation).GetNormalized();
const Vector3 newDir = (resultJointPos - rootNode.Translation).GetNormalized();
const Quaternion deltaRotation = Quaternion::FindBetween(oldDir, newDir);
rootNode.Orientation = deltaRotation * rootNode.Orientation;
// Vector from root joint to mid joint (local Y-axis direction)
Vector3 localY = (newMidJointPos - rootTransform.Translation).GetNormalized();
// Vector from mid joint to end effector (used to calculate plane normal)
Vector3 midToEnd = (newEndEffectorPos - newMidJointPos).GetNormalized();
// Calculate the plane normal (local Z-axis direction)
Vector3 localZ = Vector3::Cross(localY, midToEnd).GetNormalized();
// Calculate the local X-axis direction, should be perpendicular to the Y and Z axes
Vector3 localX = Vector3::Cross(localY, localZ).GetNormalized();
// Correct the local Z-axis direction based on the cross product of X and Y to ensure orthogonality
localZ = Vector3::Cross(localX, localY).GetNormalized();
// Construct a rotation from the orthogonal basis vectors
Quaternion newRootJointOrientation = Quaternion::LookRotation(localZ, localY);
// Apply the new rotation to the root joint
rootTransform.Orientation = newRootJointOrientation;
}
// Update mid joint orientation to point Y-axis towards the end effector and Z-axis perpendicular to the IK plane
{
const Vector3 oldDir = (targetNode.Translation - jointPos).GetNormalized();
const Vector3 newDir = (resultEndPos - resultJointPos).GetNormalized();
const Quaternion deltaRotation = Quaternion::FindBetween(oldDir, newDir);
jointNode.Orientation = deltaRotation * jointNode.Orientation;
jointNode.Translation = resultJointPos;
// Vector from mid joint to end effector (local Y-axis direction after rotation)
Vector3 midToEnd = (newEndEffectorPos - newMidJointPos).GetNormalized();
// Calculate the plane normal using the root, mid joint, and end effector positions (will be the local Z-axis direction)
Vector3 rootToMid = (newMidJointPos - rootTransform.Translation).GetNormalized();
Vector3 planeNormal = Vector3::Cross(rootToMid, midToEnd).GetNormalized();
// Vector from mid joint to end effector (local Y-axis direction)
Vector3 localY = (newEndEffectorPos - newMidJointPos).GetNormalized();
// Calculate the plane normal using the root, mid joint, and end effector positions (local Z-axis direction)
Vector3 localZ = Vector3::Cross(rootToMid, localY).GetNormalized();
//// Calculate the local X-axis direction, should be perpendicular to the Y and Z axes
Vector3 localX = Vector3::Cross(localY, localZ).GetNormalized();
// Correct the local Z-axis direction based on the cross product of X and Y to ensure orthogonality
localZ = Vector3::Cross(localX, localY).GetNormalized();
// Construct a rotation from the orthogonal basis vectors
// The axes are used differently here than a standard LookRotation to align Z towards the end and Y perpendicular
Quaternion newMidJointOrientation = Quaternion::LookRotation(localZ, localY); // Assuming FromLookRotation creates a rotation with the first vector as forward and the second as up
// Apply the new rotation to the mid joint
midJointTransform.Orientation = newMidJointOrientation;
midJointTransform.Translation = newMidJointPos;
}
targetNode.Translation = resultEndPos;
// Update end effector transform
endEffectorTransform.Translation = newEndEffectorPos;
}

View File

@@ -152,12 +152,17 @@ void AudioSource::Play()
RequestStreamingBuffersUpdate();
}
}
else
else if (SourceIDs.HasItems())
{
// Play it right away
SetNonStreamingBuffer();
PlayInternal();
}
else
{
// Source was nt properly added to the Audio Backend
LOG(Warning, "Cannot play unitialized audio source.");
}
}
void AudioSource::Pause()

View File

@@ -184,9 +184,8 @@ void SoftAssetReferenceBase::OnUnloaded(Asset* asset)
Asset::Asset(const SpawnParams& params, const AssetInfo* info)
: ManagedScriptingObject(params)
, _refCount(0)
, _loadingTask(nullptr)
, _isLoaded(false)
, _loadFailed(false)
, _loadState(0)
, _loadingTask(0)
, _deleteFileOnUnload(false)
, _isVirtual(false)
{
@@ -225,10 +224,10 @@ void Asset::OnDeleteObject()
// Unload asset data (in a safe way to protect asset data)
Locker.Lock();
if (_isLoaded)
if (IsLoaded())
{
unload(false);
_isLoaded = false;
Platform::AtomicStore(&_loadState, (int64)LoadState::Unloaded);
}
Locker.Unlock();
@@ -319,11 +318,6 @@ void Asset::ChangeID(const Guid& newId)
Content::onAssetChangeId(this, oldId, newId);
}
bool Asset::LastLoadFailed() const
{
return _loadFailed != 0;
}
#if USE_EDITOR
bool Asset::ShouldDeleteFileOnUnload() const
@@ -337,7 +331,7 @@ uint64 Asset::GetMemoryUsage() const
{
uint64 result = sizeof(Asset);
Locker.Lock();
if (_loadingTask)
if (Platform::AtomicRead(&_loadingTask))
result += sizeof(ContentLoadTask);
result += (OnLoaded.Capacity() + OnReloading.Capacity() + OnUnloaded.Capacity()) * sizeof(EventType::FunctionType);
Locker.Unlock();
@@ -368,7 +362,7 @@ void Asset::Reload()
{
// Unload current data
unload(true);
_isLoaded = false;
Platform::AtomicStore(&_loadState, (int64)LoadState::Unloaded);
}
// Start reloading process
@@ -426,7 +420,7 @@ bool Asset::WaitForLoaded(double timeoutInMilliseconds) const
// Check if has missing loading task
Platform::MemoryBarrier();
const auto loadingTask = _loadingTask;
const auto loadingTask = (ContentLoadTask*)Platform::AtomicRead(&_loadingTask);
if (loadingTask == nullptr)
{
LOG(Warning, "WaitForLoaded asset \'{0}\' failed. No loading task attached and asset is not loaded.", ToString());
@@ -516,7 +510,7 @@ bool Asset::WaitForLoaded(double timeoutInMilliseconds) const
Content::tryCallOnLoaded((Asset*)this);
}
return _isLoaded == 0;
return !IsLoaded();
}
void Asset::InitAsVirtual()
@@ -525,14 +519,14 @@ void Asset::InitAsVirtual()
_isVirtual = true;
// Be a loaded thing
_isLoaded = true;
Platform::AtomicStore(&_loadState, (int64)LoadState::Loaded);
}
void Asset::CancelStreaming()
{
// Cancel loading task but go over asset locker to prevent case if other load threads still loads asset while it's reimported on other thread
Locker.Lock();
ContentLoadTask* loadTask = _loadingTask;
auto loadTask = (ContentLoadTask*)Platform::AtomicRead(&_loadingTask);
Locker.Unlock();
if (loadTask)
{
@@ -575,10 +569,11 @@ ContentLoadTask* Asset::createLoadingTask()
void Asset::startLoading()
{
ASSERT(!IsLoaded());
ASSERT(_loadingTask == nullptr);
_loadingTask = createLoadingTask();
ASSERT(_loadingTask != nullptr);
_loadingTask->Start();
ASSERT(Platform::AtomicRead(&_loadingTask) == 0);
auto loadingTask = createLoadingTask();
ASSERT(loadingTask != nullptr);
Platform::AtomicStore(&_loadingTask, (intptr)loadingTask);
loadingTask->Start();
}
void Asset::releaseStorage()
@@ -593,7 +588,7 @@ bool Asset::IsInternalType() const
bool Asset::onLoad(LoadAssetTask* task)
{
// It may fail when task is cancelled and new one is created later (don't crash but just end with an error)
if (task->Asset.Get() != this || _loadingTask == nullptr)
if (task->Asset.Get() != this || Platform::AtomicRead(&_loadingTask) == 0)
return true;
Locker.Lock();
@@ -606,15 +601,15 @@ bool Asset::onLoad(LoadAssetTask* task)
}
const bool isLoaded = result == LoadResult::Ok;
const bool failed = !isLoaded;
_loadFailed = failed;
_isLoaded = !failed;
LoadState state = LoadState::Loaded;
Platform::AtomicStore(&_loadState, (int64)(isLoaded ? LoadState::Loaded : LoadState::LoadFailed));
if (failed)
{
LOG(Error, "Loading asset \'{0}\' result: {1}.", ToString(), ToString(result));
}
// Unlink task
_loadingTask = nullptr;
Platform::AtomicStore(&_loadingTask, 0);
ASSERT(failed || IsLoaded() == isLoaded);
Locker.Unlock();
@@ -663,12 +658,12 @@ void Asset::onUnload_MainThread()
OnUnloaded(this);
// Check if is during loading
if (_loadingTask != nullptr)
auto loadingTask = (ContentLoadTask*)Platform::AtomicRead(&_loadingTask);
if (loadingTask != nullptr)
{
// Cancel loading
auto task = _loadingTask;
_loadingTask = nullptr;
Platform::AtomicStore(&_loadingTask, 0);
LOG(Warning, "Cancel loading task for \'{0}\'", ToString());
task->Cancel();
loadingTask->Cancel();
}
}

View File

@@ -34,11 +34,17 @@ public:
DECLARE_ENUM_7(LoadResult, Ok, Failed, MissingDataChunk, CannotLoadData, CannotLoadStorage, CannotLoadInitData, InvalidData);
protected:
volatile int64 _refCount;
ContentLoadTask* _loadingTask;
enum class LoadState : int64
{
Unloaded,
Loaded,
LoadFailed,
};
volatile int64 _refCount;
volatile int64 _loadState;
volatile intptr _loadingTask;
int8 _isLoaded : 1; // Indicates that asset is loaded
int8 _loadFailed : 1; // Indicates that last asset loading has failed
int8 _deleteFileOnUnload : 1; // Indicates that asset source file should be removed on asset unload
int8 _isVirtual : 1; // Indicates that asset is pure virtual (generated or temporary, has no storage so won't be saved)
@@ -111,13 +117,16 @@ public:
/// </summary>
API_PROPERTY() FORCE_INLINE bool IsLoaded() const
{
return _isLoaded != 0;
return Platform::AtomicRead(&_loadState) == (int64)LoadState::Loaded;
}
/// <summary>
/// Returns true if last asset loading failed, otherwise false.
/// </summary>
API_PROPERTY() bool LastLoadFailed() const;
API_PROPERTY() bool LastLoadFailed() const
{
return Platform::AtomicRead(&_loadState) == (int64)LoadState::LoadFailed;
}
/// <summary>
/// Determines whether this asset is virtual (generated or temporary, has no storage so it won't be saved).

View File

@@ -1434,8 +1434,7 @@ Asset::LoadResult VisualScript::load()
if (_instances.HasItems())
{
// Mark as already loaded so any WaitForLoaded checks during GetDefaultInstance bellow will handle this Visual Script as ready to use
_loadFailed = false;
_isLoaded = true;
Platform::AtomicStore(&_loadState, (int64)LoadState::Loaded);
// Setup scripting type
CacheScriptingType();

View File

@@ -14,6 +14,7 @@
#include "Engine/Engine/EngineService.h"
#include "Engine/Platform/FileSystem.h"
#include "Engine/Threading/Threading.h"
#include "Engine/Threading/MainThreadTask.h"
#include "Engine/Graphics/Graphics.h"
#include "Engine/Engine/Time.h"
#include "Engine/Engine/Globals.h"
@@ -688,101 +689,135 @@ bool Content::FastTmpAssetClone(const StringView& path, String& resultPath)
return false;
}
bool Content::CloneAssetFile(const StringView& dstPath, const StringView& srcPath, const Guid& dstId)
class CloneAssetFileTask : public MainThreadTask
{
PROFILE_CPU();
ASSERT(FileSystem::AreFilePathsEqual(srcPath, dstPath) == false && dstId.IsValid());
public:
StringView dstPath;
StringView srcPath;
Guid dstId;
bool* output;
LOG(Info, "Cloning asset \'{0}\' to \'{1}\'({2}).", srcPath, dstPath, dstId);
// Check source file
if (!FileSystem::FileExists(srcPath))
protected:
bool Run() override
{
LOG(Warning, "Missing source file.");
return true;
}
// Special case for json resources
if (JsonStorageProxy::IsValidExtension(FileSystem::GetExtension(srcPath).ToLower()))
{
if (FileSystem::CopyFile(dstPath, srcPath))
{
LOG(Warning, "Cannot copy file to destination.");
return true;
}
if (JsonStorageProxy::ChangeId(dstPath, dstId))
{
LOG(Warning, "Cannot change asset ID.");
return true;
}
*output = Content::CloneAssetFile(dstPath, srcPath, dstId);
return false;
}
};
// Check if destination file is missing
if (!FileSystem::FileExists(dstPath))
bool Content::CloneAssetFile(const StringView& dstPath, const StringView& srcPath, const Guid& dstId)
{
// Best to run this on the main thread to avoid clone conflicts.
if (IsInMainThread())
{
// Use quick file copy
if (FileSystem::CopyFile(dstPath, srcPath))
PROFILE_CPU();
ASSERT(FileSystem::AreFilePathsEqual(srcPath, dstPath) == false && dstId.IsValid());
LOG(Info, "Cloning asset \'{0}\' to \'{1}\'({2}).", srcPath, dstPath, dstId);
// Check source file
if (!FileSystem::FileExists(srcPath))
{
LOG(Warning, "Cannot copy file to destination.");
LOG(Warning, "Missing source file.");
return true;
}
// Change ID
auto storage = ContentStorageManager::GetStorage(dstPath);
FlaxStorage::Entry e;
storage->GetEntry(0, e);
if (storage == nullptr || storage->ChangeAssetID(e, dstId))
// Special case for json resources
if (JsonStorageProxy::IsValidExtension(FileSystem::GetExtension(srcPath).ToLower()))
{
LOG(Warning, "Cannot change asset ID.");
return true;
if (FileSystem::CopyFile(dstPath, srcPath))
{
LOG(Warning, "Cannot copy file to destination.");
return true;
}
if (JsonStorageProxy::ChangeId(dstPath, dstId))
{
LOG(Warning, "Cannot change asset ID.");
return true;
}
return false;
}
// Check if destination file is missing
if (!FileSystem::FileExists(dstPath))
{
// Use quick file copy
if (FileSystem::CopyFile(dstPath, srcPath))
{
LOG(Warning, "Cannot copy file to destination.");
return true;
}
// Change ID
auto storage = ContentStorageManager::GetStorage(dstPath);
FlaxStorage::Entry e;
storage->GetEntry(0, e);
if (storage == nullptr || storage->ChangeAssetID(e, dstId))
{
LOG(Warning, "Cannot change asset ID.");
return true;
}
}
else
{
// Use temporary file
String tmpPath = Globals::TemporaryFolder / Guid::New().ToString(Guid::FormatType::D);
if (FileSystem::CopyFile(tmpPath, srcPath))
{
LOG(Warning, "Cannot copy file.");
return true;
}
// Change asset ID
{
auto storage = ContentStorageManager::GetStorage(tmpPath);
if (!storage)
{
LOG(Warning, "Cannot change asset ID.");
return true;
}
FlaxStorage::Entry e;
storage->GetEntry(0, e);
if (storage->ChangeAssetID(e, dstId))
{
LOG(Warning, "Cannot change asset ID.");
return true;
}
}
// Unlock destination file
ContentStorageManager::EnsureAccess(dstPath);
// Copy temp file to the destination
if (FileSystem::CopyFile(dstPath, tmpPath))
{
LOG(Warning, "Cannot copy file to destination.");
return true;
}
// Cleanup
FileSystem::DeleteFile(tmpPath);
// Reload storage
if (auto storage = ContentStorageManager::GetStorage(dstPath))
{
storage->Reload();
}
}
}
else
{
// Use temporary file
String tmpPath = Globals::TemporaryFolder / Guid::New().ToString(Guid::FormatType::D);
if (FileSystem::CopyFile(tmpPath, srcPath))
{
LOG(Warning, "Cannot copy file.");
return true;
}
CloneAssetFileTask* task = New<CloneAssetFileTask>();
task->dstId = dstId;
task->dstPath = dstPath;
task->srcPath = srcPath;
// Change asset ID
{
auto storage = ContentStorageManager::GetStorage(tmpPath);
if (!storage)
{
LOG(Warning, "Cannot change asset ID.");
return true;
}
FlaxStorage::Entry e;
storage->GetEntry(0, e);
if (storage->ChangeAssetID(e, dstId))
{
LOG(Warning, "Cannot change asset ID.");
return true;
}
}
bool result = false;
task->output = &result;
task->Start();
task->Wait();
// Unlock destination file
ContentStorageManager::EnsureAccess(dstPath);
// Copy temp file to the destination
if (FileSystem::CopyFile(dstPath, tmpPath))
{
LOG(Warning, "Cannot copy file to destination.");
return true;
}
// Cleanup
FileSystem::DeleteFile(tmpPath);
// Reload storage
if (auto storage = ContentStorageManager::GetStorage(dstPath))
{
storage->Reload();
}
return result;
}
return false;

View File

@@ -31,12 +31,11 @@ public:
if (Asset)
{
Asset->Locker.Lock();
if (Asset->_loadingTask == this)
if (Platform::AtomicRead(&Asset->_loadingTask) == (intptr)this)
{
Asset->_loadFailed = true;
Asset->_isLoaded = false;
Platform::AtomicStore(&Asset->_loadState, (int64)Asset::LoadState::LoadFailed);
Platform::AtomicStore(&Asset->_loadingTask, 0);
LOG(Error, "Loading asset \'{0}\' result: {1}.", ToString(), ToString(Result::TaskFailed));
Asset->_loadingTask = nullptr;
}
Asset->Locker.Unlock();
}
@@ -77,8 +76,8 @@ protected:
if (Asset)
{
Asset->Locker.Lock();
if (Asset->_loadingTask == this)
Asset->_loadingTask = nullptr;
if (Platform::AtomicRead(&Asset->_loadingTask) == (intptr)this)
Platform::AtomicStore(&Asset->_loadingTask, 0);
Asset->Locker.Unlock();
Asset = nullptr;
}
@@ -91,8 +90,8 @@ protected:
if (Asset)
{
Asset->Locker.Lock();
if (Asset->_loadingTask == this)
Asset->_loadingTask = nullptr;
if (Platform::AtomicRead(&Asset->_loadingTask) == (intptr)this)
Platform::AtomicStore(&Asset->_loadingTask, 0);
Asset->Locker.Unlock();
Asset = nullptr;
}

View File

@@ -413,6 +413,7 @@ bool AssetsImportingManagerService::Init()
{ TEXT("jpg"), ASSET_FILES_EXTENSION, ImportTexture::Import },
{ TEXT("hdr"), ASSET_FILES_EXTENSION, ImportTexture::Import },
{ TEXT("raw"), ASSET_FILES_EXTENSION, ImportTexture::Import },
{ TEXT("exr"), ASSET_FILES_EXTENSION, ImportTexture::Import },
// IES Profiles
{ TEXT("ies"), ASSET_FILES_EXTENSION, ImportTexture::ImportIES },

View File

@@ -15,6 +15,7 @@
#include "Engine/Content/Storage/ContentStorageManager.h"
#include "Engine/Content/Assets/Animation.h"
#include "Engine/Content/Content.h"
#include "Engine/Animations/AnimEvent.h"
#include "Engine/Level/Actors/EmptyActor.h"
#include "Engine/Level/Actors/StaticModel.h"
#include "Engine/Level/Prefabs/Prefab.h"
@@ -141,48 +142,6 @@ void RepackMeshLightmapUVs(ModelData& data)
}
}
void TryRestoreMaterials(CreateAssetContext& context, ModelData& modelData)
{
// Skip if file is missing
if (!FileSystem::FileExists(context.TargetAssetPath))
return;
// Try to load asset that gets reimported
AssetReference<Asset> asset = Content::LoadAsync<Asset>(context.TargetAssetPath);
if (asset == nullptr)
return;
if (asset->WaitForLoaded())
return;
// Get model object
ModelBase* model = nullptr;
if (asset.Get()->GetTypeName() == Model::TypeName)
{
model = ((Model*)asset.Get());
}
else if (asset.Get()->GetTypeName() == SkinnedModel::TypeName)
{
model = ((SkinnedModel*)asset.Get());
}
if (!model)
return;
// Peek materials
for (int32 i = 0; i < modelData.Materials.Count(); i++)
{
auto& dstSlot = modelData.Materials[i];
if (model->MaterialSlots.Count() > i)
{
auto& srcSlot = model->MaterialSlots[i];
dstSlot.Name = srcSlot.Name;
dstSlot.ShadowsMode = srcSlot.ShadowsMode;
dstSlot.AssetID = srcSlot.Material.GetID();
}
}
}
void SetupMaterialSlots(ModelData& data, const Array<MaterialSlotEntry>& materials)
{
Array<int32> materialSlotsTable;
@@ -458,10 +417,62 @@ CreateAssetResult ImportModel::Import(CreateAssetContext& context)
data = &dataThis;
}
// Check if restore materials on model reimport
if (options.RestoreMaterialsOnReimport && data->Materials.HasItems())
// Check if restore local changes on asset reimport
constexpr bool RestoreAnimEventsOnReimport = true;
const bool restoreMaterials = options.RestoreMaterialsOnReimport && data->Materials.HasItems();
const bool restoreAnimEvents = RestoreAnimEventsOnReimport && options.Type == ModelTool::ModelType::Animation && data->Animations.HasItems();
if ((restoreMaterials || restoreAnimEvents) && FileSystem::FileExists(context.TargetAssetPath))
{
TryRestoreMaterials(context, *data);
AssetReference<Asset> asset = Content::LoadAsync<Asset>(context.TargetAssetPath);
if (asset && !asset->WaitForLoaded())
{
auto* model = ScriptingObject::Cast<ModelBase>(asset);
auto* animation = ScriptingObject::Cast<Animation>(asset);
if (restoreMaterials && model)
{
// Copy material settings
for (int32 i = 0; i < data->Materials.Count(); i++)
{
auto& dstSlot = data->Materials[i];
if (model->MaterialSlots.Count() > i)
{
auto& srcSlot = model->MaterialSlots[i];
dstSlot.Name = srcSlot.Name;
dstSlot.ShadowsMode = srcSlot.ShadowsMode;
dstSlot.AssetID = srcSlot.Material.GetID();
}
}
}
if (restoreAnimEvents && animation)
{
// Copy anim event tracks
for (const auto& e : animation->Events)
{
auto& clone = data->Animations[0].Events.AddOne();
clone.First = e.First;
const auto& eKeys = e.Second.GetKeyframes();
auto& cloneKeys = clone.Second.GetKeyframes();
clone.Second.Resize(eKeys.Count());
for (int32 i = 0; i < eKeys.Count(); i++)
{
const auto& eKey = eKeys[i];
auto& cloneKey = cloneKeys[i];
cloneKey.Time = eKey.Time;
cloneKey.Value.Duration = eKey.Value.Duration;
if (eKey.Value.Instance)
{
cloneKey.Value.TypeName = eKey.Value.Instance->GetType().Fullname;
rapidjson_flax::StringBuffer buffer;
CompactJsonWriter writer(buffer);
writer.StartObject();
eKey.Value.Instance->Serialize(writer, nullptr);
writer.EndObject();
cloneKey.Value.JsonData.Set(buffer.GetString(), buffer.GetSize());
}
}
}
}
}
}
// When using generated lightmap UVs those coordinates needs to be moved so all meshes are in unique locations in [0-1]x[0-1] coordinates space

View File

@@ -44,6 +44,49 @@ public:
}
};
private:
template<typename T>
static void Merge(T* data, T* tmp, int32 start, int32 mid, int32 end)
{
int32 h = start;
int32 i = start;
int32 j = mid + 1;
while (h <= mid && j <= end)
{
if (data[h] < data[j])
tmp[i] = data[h++];
else
tmp[i] = data[j++];
i++;
}
if (h > mid)
{
for (int32 k = j; k <= end; k++)
tmp[i++] = data[k];
}
else
{
for (int32 k = h; k <= mid; k++)
tmp[i++] = data[k];
}
for (int32 k = start; k <= end; k++)
data[k] = tmp[k];
}
template<typename T>
static void MergeSort(T* data, T* tmp, int32 start, int32 end)
{
if (start >= end)
return;
const int32 mid = (start + end) / 2;
MergeSort(data, tmp, start, mid);
MergeSort(data, tmp, mid + 1, end);
Merge(data, tmp, start, mid, end);
}
public:
/// <summary>
/// Sorts the linear data array using Quick Sort algorithm (non recursive version, uses temporary stack collection).
@@ -263,6 +306,33 @@ public:
}
}
/// <summary>
/// Sorts the linear data array using Merge Sort algorithm (recursive version, uses temporary memory).
/// </summary>
/// <param name="data">The data pointer.</param>
/// <param name="count">The elements count.</param>
/// <param name="tmp">The additional temporary memory buffer for sorting data. If null then will be automatically allocated within this function call.</param>
template<typename T>
static void MergeSort(T* data, int32 count, T* tmp = nullptr)
{
if (count < 2)
return;
const bool alloc = tmp == nullptr;
if (alloc)
tmp = (T*)Platform::Allocate(sizeof(T) * count, 16);
MergeSort(data, tmp, 0, count - 1);
if (alloc)
Platform::Free(tmp);
}
template<typename T, typename AllocationType = HeapAllocation>
FORCE_INLINE static void MergeSort(Array<T, AllocationType>& data, Array<T, AllocationType>* tmp = nullptr)
{
if (tmp)
tmp->Resize(data.Count());
MergeSort(data.Get(), data.Count(), tmp ? tmp->Get() : nullptr);
}
/// <summary>
/// Sorts the linear data array using Radix Sort algorithm (uses temporary keys collection).
/// </summary>

View File

@@ -14,6 +14,8 @@
#define FORCE_INLINE inline
#define FORCE_NOINLINE __attribute__((noinline))
#define NO_RETURN __attribute__((noreturn))
#define NO_SANITIZE_ADDRESS __attribute__((no_sanitize_address))
#define NO_SANITIZE_THREAD __attribute__((no_sanitize_thread))
#define PACK_BEGIN()
#define PACK_END() __attribute__((__packed__))
#define ALIGN_BEGIN(_align)
@@ -44,6 +46,8 @@
#define FORCE_INLINE inline
#define FORCE_NOINLINE __attribute__((noinline))
#define NO_RETURN __attribute__((noreturn))
#define NO_SANITIZE_ADDRESS __attribute__((no_sanitize_address))
#define NO_SANITIZE_THREAD __attribute__((no_sanitize_thread))
#define PACK_BEGIN()
#define PACK_END() __attribute__((__packed__))
#define ALIGN_BEGIN(_align)
@@ -69,6 +73,8 @@
#define FORCE_INLINE __forceinline
#define FORCE_NOINLINE __declspec(noinline)
#define NO_RETURN __declspec(noreturn)
#define NO_SANITIZE_ADDRESS
#define NO_SANITIZE_THREAD
#define PACK_BEGIN() __pragma(pack(push, 1))
#define PACK_END() ; __pragma(pack(pop))
#define ALIGN_BEGIN(_align) __declspec(align(_align))

View File

@@ -94,4 +94,15 @@ namespace Utilities
return (x * 0x01010101) >> 24;
#endif
}
// Copy memory region but ignoring address sanatizer checks for memory regions.
NO_SANITIZE_ADDRESS static void UnsafeMemoryCopy(void* dst, const void* src, uint64 size)
{
#if BUILD_RELEASE
memcpy(dst, src, static_cast<size_t>(size));
#else
for (uint64 i = 0; i < size; i++)
((byte*)dst)[i] = ((byte*)src)[i];
#endif
}
}

View File

@@ -129,7 +129,7 @@ PACK_STRUCT(struct Data {
Matrix ViewProjection;
Float2 Padding;
float ClipPosZBias;
bool EnableDepthTest;
uint32 EnableDepthTest;
});
struct PsData

View File

@@ -2,6 +2,7 @@
#include "CommandLine.h"
#include "Engine/Core/Collections/Array.h"
#include "Engine/Core/Utilities.h"
#include <iostream>
CommandLine::OptionsData CommandLine::Options;
@@ -81,7 +82,7 @@ bool CommandLine::Parse(const Char* cmdLine)
if (pos) \
{ \
len = ARRAY_COUNT(text) - 1; \
Platform::MemoryCopy(pos, pos + len, (end - pos - len) * 2); \
Utilities::UnsafeMemoryCopy(pos, pos + len, (end - pos - len) * 2); \
*(end - len) = 0; \
end -= len; \
Options.field = true; \
@@ -98,7 +99,7 @@ bool CommandLine::Parse(const Char* cmdLine)
} \
Options.field = String(argStart, static_cast<int32>(argEnd - argStart)); \
len = static_cast<int32>((argEnd - pos) + 1); \
Platform::MemoryCopy(pos, pos + len, (end - pos - len) * 2); \
Utilities::UnsafeMemoryCopy(pos, pos + len, (end - pos - len) * 2); \
*(end - len) = 0; \
end -= len; \
}
@@ -114,7 +115,7 @@ bool CommandLine::Parse(const Char* cmdLine)
{ \
Options.field = String(argStart, static_cast<int32>(argEnd - argStart)); \
len = static_cast<int32>((argEnd - pos) + 1); \
Platform::MemoryCopy(pos, pos + len, (end - pos - len) * 2); \
Utilities::UnsafeMemoryCopy(pos, pos + len, (end - pos - len) * 2); \
*(end - len) = 0; \
end -= len; \
} \

View File

@@ -324,10 +324,12 @@ void Engine::OnUpdate()
// Call event
Update();
UpdateGraph->Execute();
// Update services
EngineService::OnUpdate();
// Run async
UpdateGraph->Execute();
}
void Engine::OnLateUpdate()

View File

@@ -221,9 +221,7 @@ Asset::LoadResult GameplayGlobals::load()
// Get data
const auto chunk = GetChunk(0);
if (!chunk || !chunk->IsLoaded())
{
return LoadResult::MissingDataChunk;
}
MemoryReadStream stream(chunk->Get(), chunk->Size());
// Load all variables
@@ -234,15 +232,16 @@ Asset::LoadResult GameplayGlobals::load()
for (int32 i = 0; i < count; i++)
{
stream.ReadString(&name, 71);
if (name.IsEmpty())
{
LOG(Warning, "Empty variable name");
return LoadResult::InvalidData;
}
auto& e = Variables[name];
stream.ReadVariant(&e.DefaultValue);
e.Value = e.DefaultValue;
}
if (stream.HasError())
{
// Failed to load data
Variables.Clear();
return LoadResult::InvalidData;
}
return LoadResult::Ok;
}

View File

@@ -115,7 +115,7 @@ public:
// Rollback state and cancel
_context = nullptr;
_state = TaskState::Queued;
SetState(TaskState::Queued);
Cancel();
}
@@ -148,8 +148,7 @@ protected:
ASSERT(_context != nullptr);
_context->OnCancelSync(this);
_context = nullptr;
_state = TaskState::Canceled;
SetState(TaskState::Canceled);
}
else
{

View File

@@ -9,9 +9,8 @@
void GPUTask::Execute(GPUTasksContext* context)
{
// Begin
ASSERT(IsQueued() && _context == nullptr);
_state = TaskState::Running;
SetState(TaskState::Running);
// Perform an operation
const auto result = run(context);
@@ -19,7 +18,7 @@ void GPUTask::Execute(GPUTasksContext* context)
// Process result
if (IsCancelRequested())
{
_state = TaskState::Canceled;
SetState(TaskState::Canceled);
}
else if (result != Result::Ok)
{

View File

@@ -941,7 +941,19 @@ bool ModelData::Pack2AnimationHeader(WriteStream* stream, int32 animIndex) const
}
// Animation events
stream->WriteInt32(0);
stream->WriteInt32(anim.Events.Count());
for (auto& e : anim.Events)
{
stream->WriteString(e.First, 172);
stream->WriteInt32(e.Second.GetKeyframes().Count());
for (const auto& k : e.Second.GetKeyframes())
{
stream->WriteFloat(k.Time);
stream->WriteFloat(k.Value.Duration);
stream->WriteStringAnsi(k.Value.TypeName, 17);
stream->WriteJson(k.Value.JsonData);
}
}
// Nested animations
stream->WriteInt32(0);

View File

@@ -13,4 +13,12 @@ namespace FlaxEngine
Bit = bit;
}
}
public partial struct AntiAliasingSettings
{
/// <summary>
/// Whether or not to show the TAA settings.
/// </summary>
public bool ShowTAASettings => (Mode == AntialiasingMode.TemporalAntialiasing);
}
}

View File

@@ -1888,25 +1888,25 @@ API_STRUCT() struct FLAXENGINE_API AntiAliasingSettings : ISerializable
/// <summary>
/// The diameter (in texels) inside which jitter samples are spread. Smaller values result in crisper but more aliased output, while larger values result in more stable but blurrier output.
/// </summary>
API_FIELD(Attributes="Limit(0.1f, 1f, 0.001f), EditorOrder(1), PostProcessSetting((int)AntiAliasingSettingsOverride.TAA_JitterSpread), EditorDisplay(null, \"TAA Jitter Spread\")")
API_FIELD(Attributes="Limit(0.1f, 1f, 0.001f), EditorOrder(1), PostProcessSetting((int)AntiAliasingSettingsOverride.TAA_JitterSpread), EditorDisplay(null, \"TAA Jitter Spread\"), VisibleIf(nameof(ShowTAASettings))")
float TAA_JitterSpread = 1.0f;
/// <summary>
/// Controls the amount of sharpening applied to the color buffer. TAA can induce a slight loss of details in high frequency regions. Sharpening alleviates this issue. High values may introduce dark-border artifacts.
/// </summary>
API_FIELD(Attributes="Limit(0, 3f, 0.001f), EditorOrder(2), PostProcessSetting((int)AntiAliasingSettingsOverride.TAA_Sharpness), EditorDisplay(null, \"TAA Sharpness\")")
API_FIELD(Attributes="Limit(0, 3f, 0.001f), EditorOrder(2), PostProcessSetting((int)AntiAliasingSettingsOverride.TAA_Sharpness), EditorDisplay(null, \"TAA Sharpness\"), VisibleIf(nameof(ShowTAASettings))")
float TAA_Sharpness = 0.1f;
/// <summary>
/// The blend coefficient for stationary fragments. Controls the percentage of history samples blended into the final color for fragments with minimal active motion.
/// </summary>
API_FIELD(Attributes="Limit(0, 0.99f, 0.001f), EditorOrder(3), PostProcessSetting((int)AntiAliasingSettingsOverride.TAA_StationaryBlending), EditorDisplay(null, \"TAA Stationary Blending\")")
API_FIELD(Attributes="Limit(0, 0.99f, 0.001f), EditorOrder(3), PostProcessSetting((int)AntiAliasingSettingsOverride.TAA_StationaryBlending), EditorDisplay(null, \"TAA Stationary Blending\"), VisibleIf(nameof(ShowTAASettings))")
float TAA_StationaryBlending = 0.95f;
/// <summary>
/// The blending coefficient for moving fragments. Controls the percentage of history samples blended into the final color for fragments with significant active motion.
/// </summary>
API_FIELD(Attributes="Limit(0, 0.99f, 0.001f), EditorOrder(4), PostProcessSetting((int)AntiAliasingSettingsOverride.TAA_MotionBlending), EditorDisplay(null, \"TAA Motion Blending\")")
API_FIELD(Attributes="Limit(0, 0.99f, 0.001f), EditorOrder(4), PostProcessSetting((int)AntiAliasingSettingsOverride.TAA_MotionBlending), EditorDisplay(null, \"TAA Motion Blending\"), VisibleIf(nameof(ShowTAASettings))")
float TAA_MotionBlending = 0.85f;
public:

View File

@@ -10,6 +10,7 @@
#include "GPUTimerQueryVulkan.h"
#endif
#include "DescriptorSetVulkan.h"
#include "Engine/Engine/Engine.h"
#include "Engine/Profiler/ProfilerCPU.h"
void CmdBufferVulkan::AddWaitSemaphore(VkPipelineStageFlags waitFlags, SemaphoreVulkan* waitSemaphore)
@@ -32,7 +33,7 @@ void CmdBufferVulkan::Begin()
// Acquire a descriptor pool set on
if (_descriptorPoolSetContainer == nullptr)
{
_descriptorPoolSetContainer = &_device->DescriptorPoolsManager->AcquirePoolSetContainer();
_descriptorPoolSetContainer = _device->DescriptorPoolsManager->AcquirePoolSetContainer();
}
_state = State::IsInsideBegin;
@@ -138,7 +139,7 @@ void CmdBufferVulkan::RefreshFenceStatus()
if (_descriptorPoolSetContainer)
{
_device->DescriptorPoolsManager->ReleasePoolSet(*_descriptorPoolSetContainer);
_descriptorPoolSetContainer->LastFrameUsed = Engine::FrameCount;
_descriptorPoolSetContainer = nullptr;
}
}
@@ -279,6 +280,7 @@ void CmdBufferManagerVulkan::WaitForCmdBuffer(CmdBufferVulkan* cmdBuffer, float
void CmdBufferManagerVulkan::PrepareForNewActiveCommandBuffer()
{
PROFILE_CPU();
ASSERT_LOW_LAYER(_activeCmdBuffer == nullptr)
for (int32 i = 0; i < _pool._cmdBuffers.Count(); i++)
{
auto cmdBuffer = _pool._cmdBuffers.Get()[i];
@@ -286,8 +288,7 @@ void CmdBufferManagerVulkan::PrepareForNewActiveCommandBuffer()
if (cmdBuffer->GetState() == CmdBufferVulkan::State::ReadyForBegin)
{
_activeCmdBuffer = cmdBuffer;
_activeCmdBuffer->Begin();
return;
break;
}
else
{
@@ -295,8 +296,12 @@ void CmdBufferManagerVulkan::PrepareForNewActiveCommandBuffer()
}
}
// Always begin fresh command buffer for rendering
_activeCmdBuffer = _pool.Create();
if (_activeCmdBuffer == nullptr)
{
// Always begin fresh command buffer for rendering
_activeCmdBuffer = _pool.Create();
}
_activeCmdBuffer->Begin();
#if VULKAN_USE_QUERIES

View File

@@ -247,8 +247,7 @@ void TypedDescriptorPoolSetVulkan::Reset()
DescriptorPoolSetContainerVulkan::DescriptorPoolSetContainerVulkan(GPUDeviceVulkan* device)
: _device(device)
, _lastFrameUsed(Engine::FrameCount)
, _used(true)
, LastFrameUsed(Engine::FrameCount)
{
}
@@ -278,12 +277,6 @@ void DescriptorPoolSetContainerVulkan::Reset()
}
}
void DescriptorPoolSetContainerVulkan::SetUsed(bool used)
{
_used = used;
_lastFrameUsed = used ? Engine::FrameCount : _lastFrameUsed;
}
DescriptorPoolsManagerVulkan::DescriptorPoolsManagerVulkan(GPUDeviceVulkan* device)
: _device(device)
{
@@ -294,26 +287,21 @@ DescriptorPoolsManagerVulkan::~DescriptorPoolsManagerVulkan()
_poolSets.ClearDelete();
}
DescriptorPoolSetContainerVulkan& DescriptorPoolsManagerVulkan::AcquirePoolSetContainer()
DescriptorPoolSetContainerVulkan* DescriptorPoolsManagerVulkan::AcquirePoolSetContainer()
{
ScopeLock lock(_locker);
for (auto* poolSet : _poolSets)
{
if (poolSet->IsUnused())
if (poolSet->Refs == 0)
{
poolSet->SetUsed(true);
poolSet->LastFrameUsed = Engine::FrameCount;
poolSet->Reset();
return *poolSet;
return poolSet;
}
}
const auto poolSet = New<DescriptorPoolSetContainerVulkan>(_device);
_poolSets.Add(poolSet);
return *poolSet;
}
void DescriptorPoolsManagerVulkan::ReleasePoolSet(DescriptorPoolSetContainerVulkan& poolSet)
{
poolSet.SetUsed(false);
return poolSet;
}
void DescriptorPoolsManagerVulkan::GC()
@@ -322,7 +310,7 @@ void DescriptorPoolsManagerVulkan::GC()
for (int32 i = _poolSets.Count() - 1; i >= 0; i--)
{
const auto poolSet = _poolSets[i];
if (poolSet->IsUnused() && Engine::FrameCount - poolSet->GetLastFrameUsed() > VULKAN_RESOURCE_DELETE_SAFE_FRAMES_COUNT)
if (poolSet->Refs == 0 && Engine::FrameCount - poolSet->LastFrameUsed > VULKAN_RESOURCE_DELETE_SAFE_FRAMES_COUNT)
{
_poolSets.RemoveAt(i);
Delete(poolSet);

View File

@@ -212,8 +212,6 @@ class DescriptorPoolSetContainerVulkan
private:
GPUDeviceVulkan* _device;
Dictionary<uint32, TypedDescriptorPoolSetVulkan*> _typedDescriptorPools;
uint64 _lastFrameUsed;
bool _used;
public:
DescriptorPoolSetContainerVulkan(GPUDeviceVulkan* device);
@@ -222,17 +220,9 @@ public:
public:
TypedDescriptorPoolSetVulkan* AcquireTypedPoolSet(const DescriptorSetLayoutVulkan& layout);
void Reset();
void SetUsed(bool used);
bool IsUnused() const
{
return !_used;
}
uint64 GetLastFrameUsed() const
{
return _lastFrameUsed;
}
mutable uint64 Refs = 0;
mutable uint64 LastFrameUsed;
};
class DescriptorPoolsManagerVulkan
@@ -246,8 +236,7 @@ public:
DescriptorPoolsManagerVulkan(GPUDeviceVulkan* device);
~DescriptorPoolsManagerVulkan();
DescriptorPoolSetContainerVulkan& AcquirePoolSetContainer();
void ReleasePoolSet(DescriptorPoolSetContainerVulkan& poolSet);
DescriptorPoolSetContainerVulkan* AcquirePoolSetContainer();
void GC();
};

View File

@@ -112,7 +112,11 @@ ComputePipelineStateVulkan::ComputePipelineStateVulkan(GPUDeviceVulkan* device,
ComputePipelineStateVulkan::~ComputePipelineStateVulkan()
{
DSWriteContainer.Release();
CurrentTypedDescriptorPoolSet = nullptr;
if (CurrentTypedDescriptorPoolSet)
{
CurrentTypedDescriptorPoolSet->GetOwner()->Refs--;
CurrentTypedDescriptorPoolSet = nullptr;
}
DescriptorSetsLayout = nullptr;
DescriptorSetHandles.Resize(0);
DynamicOffsets.Resize(0);
@@ -206,7 +210,11 @@ VkPipeline GPUPipelineStateVulkan::GetState(RenderPassVulkan* renderPass)
void GPUPipelineStateVulkan::OnReleaseGPU()
{
DSWriteContainer.Release();
CurrentTypedDescriptorPoolSet = nullptr;
if (CurrentTypedDescriptorPoolSet)
{
CurrentTypedDescriptorPoolSet->GetOwner()->Refs--;
CurrentTypedDescriptorPoolSet = nullptr;
}
DescriptorSetsLayout = nullptr;
DescriptorSetHandles.Resize(0);
DynamicOffsets.Resize(0);

View File

@@ -41,17 +41,17 @@ public:
DescriptorPoolSetContainerVulkan* cmdBufferPoolSet = cmdBuffer->GetDescriptorPoolSet();
if (CurrentTypedDescriptorPoolSet == nullptr || CurrentTypedDescriptorPoolSet->GetOwner() != cmdBufferPoolSet)
{
ASSERT(cmdBufferPoolSet);
if (CurrentTypedDescriptorPoolSet)
CurrentTypedDescriptorPoolSet->GetOwner()->Refs--;
CurrentTypedDescriptorPoolSet = cmdBufferPoolSet->AcquireTypedPoolSet(*DescriptorSetsLayout);
CurrentTypedDescriptorPoolSet->GetOwner()->Refs++;
return true;
}
return false;
}
inline bool AllocateDescriptorSets()
{
ASSERT(CurrentTypedDescriptorPoolSet);
return CurrentTypedDescriptorPoolSet->AllocateDescriptorSets(*DescriptorSetsLayout, DescriptorSetHandles.Get());
}
@@ -165,7 +165,10 @@ public:
DescriptorPoolSetContainerVulkan* cmdBufferPoolSet = cmdBuffer->GetDescriptorPoolSet();
if (CurrentTypedDescriptorPoolSet == nullptr || CurrentTypedDescriptorPoolSet->GetOwner() != cmdBufferPoolSet)
{
if (CurrentTypedDescriptorPoolSet)
CurrentTypedDescriptorPoolSet->GetOwner()->Refs--;
CurrentTypedDescriptorPoolSet = cmdBufferPoolSet->AcquireTypedPoolSet(*DescriptorSetsLayout);
CurrentTypedDescriptorPoolSet->GetOwner()->Refs++;
return true;
}
return false;

View File

@@ -7,6 +7,8 @@
#include "Engine/Animations/Animations.h"
#include "Engine/Engine/Engine.h"
#if USE_EDITOR
#include "Engine/Core/Math/OrientedBoundingBox.h"
#include "Engine/Core/Math/Matrix3x3.h"
#include "Editor/Editor.h"
#endif
#include "Engine/Graphics/GPUContext.h"
@@ -1018,6 +1020,45 @@ void AnimatedModel::OnDebugDrawSelected()
ModelInstanceActor::OnDebugDrawSelected();
}
void AnimatedModel::OnDebugDraw()
{
if (ShowDebugDrawSkeleton && SkinnedModel && AnimationGraph)
{
if (GraphInstance.NodesPose.IsEmpty())
PreInitSkinningData();
Matrix world;
GetLocalToWorldMatrix(world);
// Draw bounding box at the node locations
const float boxSize = Math::Min(1.0f, (float)_sphere.Radius / 100.0f);
OrientedBoundingBox localBox(Vector3(-boxSize), Vector3(boxSize));
for (int32 nodeIndex = 0; nodeIndex < GraphInstance.NodesPose.Count(); nodeIndex++)
{
Matrix transform = GraphInstance.NodesPose[nodeIndex] * world;
Float3 scale, translation;
Matrix3x3 rotation;
transform.Decompose(scale, rotation, translation);
transform = Matrix::Invert(Matrix::Scaling(scale)) * transform;
OrientedBoundingBox box = localBox * transform;
DEBUG_DRAW_WIRE_BOX(box, Color::Green, 0, false);
}
// Nodes connections
for (int32 nodeIndex = 0; nodeIndex < SkinnedModel->Skeleton.Nodes.Count(); nodeIndex++)
{
int32 parentIndex = SkinnedModel->Skeleton.Nodes[nodeIndex].ParentIndex;
if (parentIndex != -1)
{
Float3 parentPos = (GraphInstance.NodesPose[parentIndex] * world).GetTranslation();
Float3 bonePos = (GraphInstance.NodesPose[nodeIndex] * world).GetTranslation();
DEBUG_DRAW_LINE(parentPos, bonePos, Color::Green, 0, false);
}
}
}
ModelInstanceActor::OnDebugDraw();
}
BoundingBox AnimatedModel::GetEditorBox() const
{
if (SkinnedModel)

View File

@@ -168,6 +168,13 @@ public:
API_FIELD(Attributes="EditorOrder(120), DefaultValue(null), EditorDisplay(\"Skinned Model\")")
ScriptingObjectReference<Actor> RootMotionTarget;
#if USE_EDITOR
/// <summary>
/// If checked, the skeleton pose will be shawn during debug shapes drawing.
/// </summary>
API_FIELD(Attributes="EditorOrder(200), EditorDisplay(\"Skinned Model\")") bool ShowDebugDrawSkeleton = false;
#endif
public:
/// <summary>
/// The graph instance data container. For dynamic usage only at runtime, not serialized.
@@ -416,6 +423,7 @@ public:
void Draw(RenderContextBatch& renderContextBatch) override;
#if USE_EDITOR
void OnDebugDrawSelected() override;
void OnDebugDraw() override;
BoundingBox GetEditorBox() const override;
#endif
bool IntersectsItself(const Ray& ray, Real& distance, Vector3& normal) override;

View File

@@ -105,10 +105,8 @@ void NavMesh::OnDataAssetLoaded()
if (Data.Tiles.HasItems())
return;
const bool isEnabled = IsDuringPlay() && IsActiveInHierarchy();
// Remove added tiles
if (isEnabled)
if (_navMeshActive)
{
RemoveTiles();
}
@@ -120,7 +118,7 @@ void NavMesh::OnDataAssetLoaded()
IsDataDirty = false;
// Add loaded tiles
if (isEnabled)
if (_navMeshActive)
{
AddTiles();
}
@@ -156,15 +154,36 @@ void NavMesh::OnEnable()
// Base
Actor::OnEnable();
GetScene()->Navigation.Meshes.Add(this);
AddTiles();
if (!_navMeshActive)
{
GetScene()->Navigation.Meshes.Add(this);
AddTiles();
_navMeshActive = true;
}
}
void NavMesh::OnDisable()
{
RemoveTiles();
GetScene()->Navigation.Meshes.Remove(this);
if (_navMeshActive)
{
RemoveTiles();
GetScene()->Navigation.Meshes.Remove(this);
_navMeshActive = false;
}
// Base
Actor::OnDisable();
}
void NavMesh::Initialize()
{
// Base
Actor::Initialize();
if (!_navMeshActive && IsActiveInHierarchy())
{
GetScene()->Navigation.Meshes.Add(this);
AddTiles();
_navMeshActive = true;
}
}

View File

@@ -68,6 +68,9 @@ private:
void RemoveTiles();
void OnDataAssetLoaded();
private:
bool _navMeshActive = false;
public:
// [Actor]
void Serialize(SerializeStream& stream, const void* otherObj) override;
@@ -77,4 +80,5 @@ protected:
// [Actor]
void OnEnable() override;
void OnDisable() override;
void Initialize() override;
};

View File

@@ -55,13 +55,13 @@ public:
{
return __sync_fetch_and_add(dst, value);
}
FORCE_INLINE static int32 AtomicRead(int32 volatile* dst)
FORCE_INLINE static int32 AtomicRead(int32 const volatile* dst)
{
int32 result;
__atomic_load(dst, &result, __ATOMIC_RELAXED);
return result;
}
FORCE_INLINE static int64 AtomicRead(int64 volatile* dst)
FORCE_INLINE static int64 AtomicRead(int64 const volatile* dst)
{
int64 result;
__atomic_load(dst, &result, __ATOMIC_RELAXED);

View File

@@ -45,21 +45,21 @@ public:
{
return __sync_fetch_and_add(dst, value);
}
FORCE_INLINE static int32 AtomicRead(int32 volatile* dst)
FORCE_INLINE static int32 AtomicRead(int32 const volatile* dst)
{
return __atomic_load_n(dst, __ATOMIC_RELAXED);
}
FORCE_INLINE static int64 AtomicRead(int64 volatile* dst)
FORCE_INLINE static int64 AtomicRead(int64 const volatile* dst)
{
return __atomic_load_n(dst, __ATOMIC_RELAXED);
}
FORCE_INLINE static void AtomicStore(int32 volatile* dst, int32 value)
{
__atomic_store(dst, &value, __ATOMIC_SEQ_CST);
__atomic_store_n((volatile int32*)dst, value, __ATOMIC_RELAXED);
}
FORCE_INLINE static void AtomicStore(int64 volatile* dst, int64 value)
{
__atomic_store(dst, &value, __ATOMIC_SEQ_CST);
__atomic_store_n((volatile int64*)dst, value, __ATOMIC_RELAXED);
}
FORCE_INLINE static void Prefetch(void const* ptr)
{

View File

@@ -270,14 +270,14 @@ public:
/// </summary>
/// <param name="dst">A pointer to the destination value.</param>
/// <returns>The function returns the value of the destination parameter.</returns>
static int32 AtomicRead(int32 volatile* dst) = delete;
static int32 AtomicRead(int32 const volatile* dst) = delete;
/// <summary>
/// Performs an atomic 64-bit variable read operation on the specified values.
/// </summary>
/// <param name="dst">A pointer to the destination value.</param>
/// <returns>The function returns the value of the destination parameter.</returns>
static int64 AtomicRead(int64 volatile* dst) = delete;
static int64 AtomicRead(int64 const volatile* dst) = delete;
/// <summary>
/// Sets a 32-bit variable to the specified value as an atomic operation.

View File

@@ -69,13 +69,13 @@ public:
{
return __sync_fetch_and_add(dst, value);
}
FORCE_INLINE static int32 AtomicRead(int32 volatile* dst)
FORCE_INLINE static int32 AtomicRead(int32 const volatile* dst)
{
int32 result;
__atomic_load(dst, &result, __ATOMIC_SEQ_CST);
return result;
}
FORCE_INLINE static int64 AtomicRead(int64 volatile* dst)
FORCE_INLINE static int64 AtomicRead(int64 const volatile* dst)
{
int64 result;
__atomic_load(dst, &result, __ATOMIC_SEQ_CST);

View File

@@ -57,7 +57,7 @@ public:
/// <summary>
/// Locks the critical section.
/// </summary>
void Lock() const
NO_SANITIZE_THREAD void Lock() const
{
pthread_mutex_lock(_mutexPtr);
#if BUILD_DEBUG
@@ -69,7 +69,7 @@ public:
/// Attempts to enter a critical section without blocking. If the call is successful, the calling thread takes ownership of the critical section.
/// </summary>
/// <returns>True if calling thread took ownership of the critical section.</returns>
bool TryLock() const
NO_SANITIZE_THREAD bool TryLock() const
{
return pthread_mutex_trylock(_mutexPtr) == 0;
}
@@ -77,7 +77,7 @@ public:
/// <summary>
/// Releases the lock on the critical section.
/// </summary>
void Unlock() const
NO_SANITIZE_THREAD void Unlock() const
{
#if BUILD_DEBUG
((UnixCriticalSection*)this)->_owningThreadId = 0;

View File

@@ -63,13 +63,13 @@ public:
return _interlockedexchangeadd64(dst, value);
#endif
}
static int32 AtomicRead(int32 volatile* dst)
static int32 AtomicRead(int32 const volatile* dst)
{
return (int32)_InterlockedCompareExchange((long volatile*)dst, 0, 0);
}
static int64 AtomicRead(int64 volatile* dst)
static int64 AtomicRead(int64 const volatile* dst)
{
return _InterlockedCompareExchange64(dst, 0, 0);
return _InterlockedCompareExchange64((int64 volatile*)dst, 0, 0);
}
static void AtomicStore(int32 volatile* dst, int32 value)
{

View File

@@ -27,6 +27,7 @@ namespace
// Cached data for the draw calls sorting
Array<uint64> SortingKeys[2];
Array<int32> SortingIndices;
Array<DrawBatch> SortingBatches;
Array<RenderList*> FreeRenderList;
struct MemPoolEntry
@@ -594,12 +595,13 @@ namespace
}
}
void RenderList::SortDrawCalls(const RenderContext& renderContext, bool reverseDistance, DrawCallsList& list, const RenderListBuffer<DrawCall>& drawCalls, DrawPass pass)
void RenderList::SortDrawCalls(const RenderContext& renderContext, bool reverseDistance, DrawCallsList& list, const RenderListBuffer<DrawCall>& drawCalls, DrawPass pass, bool stable)
{
PROFILE_CPU();
const auto* drawCallsData = drawCalls.Get();
const auto* listData = list.Indices.Get();
const int32 listSize = list.Indices.Count();
ZoneValue(listSize);
// Peek shared memory
#define PREPARE_CACHE(list) (list).Clear(); (list).Resize(listSize)
@@ -656,6 +658,7 @@ void RenderList::SortDrawCalls(const RenderContext& renderContext, bool reverseD
}
DrawBatch batch;
static_assert(sizeof(DrawBatch) == sizeof(uint64) * 2, "Fix the size of draw batch to optimize memory access.");
batch.SortKey = sortedKeys[i];
batch.StartIndex = i;
batch.BatchSize = batchSize;
@@ -665,8 +668,12 @@ void RenderList::SortDrawCalls(const RenderContext& renderContext, bool reverseD
i += batchSize;
}
// Sort draw calls batches by depth
Sorting::QuickSort(list.Batches);
// When using depth buffer draw calls are already almost ideally sorted by Radix Sort but transparency needs more stability to prevent flickering
if (stable)
{
// Sort draw calls batches by depth
Sorting::MergeSort(list.Batches, &SortingBatches);
}
}
FORCE_INLINE bool CanUseInstancing(DrawPass pass)

View File

@@ -217,17 +217,17 @@ struct DrawBatch
/// <summary>
/// The first draw call index.
/// </summary>
int32 StartIndex;
uint16 StartIndex;
/// <summary>
/// A number of draw calls to be submitted at once.
/// </summary>
int32 BatchSize;
uint16 BatchSize;
/// <summary>
/// The total amount of instances (sum from all draw calls in this batch).
/// </summary>
int32 InstanceCount;
uint32 InstanceCount;
bool operator<(const DrawBatch& other) const
{
@@ -525,7 +525,8 @@ public:
/// <param name="pass">The draw pass (optional).</param>
API_FUNCTION() FORCE_INLINE void SortDrawCalls(API_PARAM(Ref) const RenderContext& renderContext, bool reverseDistance, DrawCallsListType listType, DrawPass pass = DrawPass::All)
{
SortDrawCalls(renderContext, reverseDistance, DrawCallsLists[(int32)listType], DrawCalls, pass);
const bool stable = listType == DrawCallsListType::Forward;
SortDrawCalls(renderContext, reverseDistance, DrawCallsLists[(int32)listType], DrawCalls, pass, stable);
}
/// <summary>
@@ -536,7 +537,8 @@ public:
/// <param name="list">The collected draw calls indices list.</param>
/// <param name="drawCalls">The collected draw calls list.</param>
/// <param name="pass">The draw pass (optional).</param>
void SortDrawCalls(const RenderContext& renderContext, bool reverseDistance, DrawCallsList& list, const RenderListBuffer<DrawCall>& drawCalls, DrawPass pass = DrawPass::All);
/// <param name="stable">If set to <c>true</c> draw batches will be additionally sorted to prevent any flickering, otherwise Depth Buffer will smooth out any non-stability in sorting.</param>
void SortDrawCalls(const RenderContext& renderContext, bool reverseDistance, DrawCallsList& list, const RenderListBuffer<DrawCall>& drawCalls, DrawPass pass = DrawPass::All, bool stable = false);
/// <summary>
/// Executes the collected draw calls.

View File

@@ -3,6 +3,7 @@
#include "BinaryModule.h"
#include "ScriptingObject.h"
#include "Engine/Core/Log.h"
#include "Engine/Core/Utilities.h"
#include "Engine/Threading/Threading.h"
#include "Engine/Profiler/ProfilerCPU.h"
#include "ManagedCLR/MAssembly.h"
@@ -436,6 +437,7 @@ void ScriptingType::SetupScriptVTable(ScriptingTypeHandle baseTypeHandle)
}
}
NO_SANITIZE_ADDRESS
void ScriptingType::SetupScriptObjectVTable(void* object, ScriptingTypeHandle baseTypeHandle, int32 wrapperIndex)
{
// Analyze vtable size
@@ -475,7 +477,7 @@ void ScriptingType::SetupScriptObjectVTable(void* object, ScriptingTypeHandle ba
// Duplicate vtable
Script.VTable = (void**)((byte*)Platform::Allocate(totalSize, 16) + prefixSize);
Platform::MemoryCopy((byte*)Script.VTable - prefixSize, (byte*)vtable - prefixSize, prefixSize + size);
Utilities::UnsafeMemoryCopy((byte*)Script.VTable - prefixSize, (byte*)vtable - prefixSize, prefixSize + size);
// Override vtable entries
if (interfacesCount)
@@ -508,7 +510,7 @@ void ScriptingType::SetupScriptObjectVTable(void* object, ScriptingTypeHandle ba
const int32 interfaceSize = interfaceCount * sizeof(void*);
// Duplicate interface vtable
Platform::MemoryCopy((byte*)Script.VTable + interfaceOffset, (byte*)vtableInterface - prefixSize, prefixSize + interfaceSize);
Utilities::UnsafeMemoryCopy((byte*)Script.VTable + interfaceOffset, (byte*)vtableInterface - prefixSize, prefixSize + interfaceSize);
// Override interface vtable entries
const auto scriptOffset = interfaces->ScriptVTableOffset;

View File

@@ -497,7 +497,9 @@ void ReadStream::Read(Variant& data)
break;
}
default:
CRASH;
_hasError = true;
LOG(Error, "Invalid Variant type. Corrupted data.");
break;
}
}
@@ -946,6 +948,13 @@ void WriteStream::WriteJson(ISerializable* obj, const void* otherObj)
WriteInt32(0);
}
void WriteStream::WriteJson(const StringAnsiView& json)
{
WriteInt32(FLAXENGINE_VERSION_BUILD);
WriteInt32((int32)json.Length());
WriteBytes((byte*)json.Get(), (int32)json.Length());
}
void WriteStream::WriteString(const StringView& data)
{
Write(data);

View File

@@ -233,6 +233,7 @@ public:
/// <param name="obj">The object to serialize.</param>
/// <param name="otherObj">The instance of the object to compare with and serialize only the modified properties. If null, then serialize all properties.</param>
void WriteJson(ISerializable* obj, const void* otherObj = nullptr);
void WriteJson(const StringAnsiView& json);
public:
// Writes String to the stream

View File

@@ -11,13 +11,13 @@
void Task::Start()
{
if (_state != TaskState::Created)
if (GetState() != TaskState::Created)
return;
OnStart();
// Change state
_state = TaskState::Queued;
SetState(TaskState::Queued);
// Add task to the execution queue
Enqueue();
@@ -110,7 +110,6 @@ Task* Task::ContinueWith(const Function<bool()>& action, Object* target)
Task* Task::StartNew(Task* task)
{
ASSERT(task);
task->Start();
return task;
}
@@ -137,11 +136,10 @@ Task* Task::StartNew(Function<bool()>::Signature& action, Object* target)
void Task::Execute()
{
// Begin
if (IsCanceled())
return;
ASSERT(IsQueued());
_state = TaskState::Running;
SetState(TaskState::Running);
// Perform an operation
bool failed = Run();
@@ -149,7 +147,7 @@ void Task::Execute()
// Process result
if (IsCancelRequested())
{
_state = TaskState::Canceled;
SetState(TaskState::Canceled);
}
else if (failed)
{
@@ -167,10 +165,8 @@ void Task::OnStart()
void Task::OnFinish()
{
ASSERT(IsRunning());
ASSERT(!IsCancelRequested());
_state = TaskState::Finished;
ASSERT(IsRunning() && !IsCancelRequested());
SetState(TaskState::Finished);
// Send event further
if (_continueWith)
@@ -181,7 +177,7 @@ void Task::OnFinish()
void Task::OnFail()
{
_state = TaskState::Failed;
SetState(TaskState::Failed);
// Send event further
if (_continueWith)
@@ -209,8 +205,7 @@ void Task::OnCancel()
const auto state = GetState();
if (state != TaskState::Finished && state != TaskState::Failed)
{
_state = TaskState::Canceled;
SetState(TaskState::Canceled);
OnEnd();
}
}

View File

@@ -49,7 +49,6 @@ class FLAXENGINE_API Task : public Object, public NonCopyable
//
protected:
/// <summary>
/// The cancel flag used to indicate that there is request to cancel task operation.
/// </summary>
@@ -65,14 +64,18 @@ protected:
/// </summary>
Task* _continueWith = nullptr;
public:
void SetState(TaskState state)
{
Platform::AtomicStore((int64 volatile*)&_state, (uint64)state);
}
public:
/// <summary>
/// Gets the task state.
/// </summary>
FORCE_INLINE TaskState GetState() const
{
return static_cast<TaskState>(Platform::AtomicRead((int64 volatile*)&_state));
return (TaskState)Platform::AtomicRead((int64 const volatile*)&_state);
}
/// <summary>
@@ -94,7 +97,6 @@ public:
}
public:
/// <summary>
/// Checks if operation failed.
/// </summary>
@@ -153,7 +155,6 @@ public:
}
public:
/// <summary>
/// Starts this task execution (and will continue with all children).
/// </summary>
@@ -199,7 +200,6 @@ public:
}
public:
/// <summary>
/// Continues that task execution with a given task (will call Start on given task after finishing that one).
/// </summary>
@@ -232,7 +232,6 @@ public:
Task* ContinueWith(const Function<bool()>& action, Object* target = nullptr);
public:
/// <summary>
/// Starts the new task.
/// </summary>
@@ -312,7 +311,6 @@ public:
}
protected:
/// <summary>
/// Executes this task.
/// It should be called by the task consumer (thread pool or other executor of this task type).
@@ -328,7 +326,6 @@ protected:
virtual bool Run() = 0;
protected:
virtual void Enqueue() = 0;
virtual void OnStart();
virtual void OnFinish();

View File

@@ -9,6 +9,7 @@
#include "Engine/Platform/FileSystem.h"
#include "Engine/Platform/File.h"
#include "Engine/Tools/TextureTool/TextureTool.h"
#include "Engine/Utilities/AnsiPathTempFile.h"
// Import Assimp library
// Source: https://github.com/assimp/assimp
@@ -157,7 +158,7 @@ struct AssimpImporterData
Array<AssimpBone> Bones;
Dictionary<int32, Array<int32>> MeshIndexToNodeIndex;
AssimpImporterData(const char* path, const ModelTool::Options& options)
AssimpImporterData(const StringView& path, const ModelTool::Options& options)
: Path(path)
, Options(options)
{
@@ -735,7 +736,7 @@ void ImportAnimation(int32 index, ModelData& data, AssimpImporterData& importerD
}
}
bool ModelTool::ImportDataAssimp(const char* path, ModelData& data, Options& options, String& errorMsg)
bool ModelTool::ImportDataAssimp(const String& path, ModelData& data, Options& options, String& errorMsg)
{
static bool AssimpInited = false;
if (!AssimpInited)
@@ -784,7 +785,10 @@ bool ModelTool::ImportDataAssimp(const char* path, ModelData& data, Options& opt
context.AssimpImporter.SetPropertyBool(AI_CONFIG_IMPORT_FBX_OPTIMIZE_EMPTY_ANIMATION_CURVES, true);
// Import file
context.Scene = context.AssimpImporter.ReadFile(path, flags);
{
AnsiPathTempFile tempFile(path);
context.Scene = context.AssimpImporter.ReadFile(tempFile.Path.Get(), flags);
}
if (context.Scene == nullptr)
{
LOG_STR(Warning, String(context.AssimpImporter.GetErrorString()));

View File

@@ -816,7 +816,7 @@ void BakeTransforms(FbxScene* scene)
scene->GetRootNode()->ConvertPivotAnimationRecursive(nullptr, FbxNode::eDestinationPivot, frameRate, false);
}
bool ModelTool::ImportDataAutodeskFbxSdk(const char* path, ImportedModelData& data, Options& options, String& errorMsg)
bool ModelTool::ImportDataAutodeskFbxSdk(const String& path, ImportedModelData& data, Options& options, String& errorMsg)
{
ScopeLock lock(FbxSdkManager::Locker);
@@ -836,7 +836,7 @@ bool ModelTool::ImportDataAutodeskFbxSdk(const char* path, ImportedModelData& da
auto ios = FbxSdkManager::Manager->GetIOSettings();
ios->SetBoolProp(IMP_FBX_MODEL, importMeshes);
ios->SetBoolProp(IMP_FBX_ANIMATION, importAnimations);
if (!importer->Initialize(path, -1, ios))
if (!importer->Initialize(StringAnsi(path), -1, ios))
{
errorMsg = String::Format(TEXT("Failed to initialize FBX importer. {0}"), String(importer->GetStatus().GetErrorString()));
return false;

View File

@@ -103,7 +103,7 @@ struct OpenFbxImporterData
Array<const ofbx::Material*> Materials;
Array<MaterialSlotEntry> ImportedMaterials;
OpenFbxImporterData(const char* path, const ModelTool::Options& options, ofbx::IScene* scene)
OpenFbxImporterData(const String& path, const ModelTool::Options& options, ofbx::IScene* scene)
: Scene(scene)
, ScenePtr(scene)
, Path(path)
@@ -1114,11 +1114,11 @@ static Float3 FbxVectorFromAxisAndSign(int axis, int sign)
return { 0.f, 0.f, 0.f };
}
bool ModelTool::ImportDataOpenFBX(const char* path, ModelData& data, Options& options, String& errorMsg)
bool ModelTool::ImportDataOpenFBX(const String& path, ModelData& data, Options& options, String& errorMsg)
{
// Import file
Array<byte> fileData;
if (File::ReadAllBytes(String(path), fileData))
if (File::ReadAllBytes(path, fileData))
{
errorMsg = TEXT("Cannot load file.");
return true;

View File

@@ -471,64 +471,37 @@ bool ModelTool::ImportData(const String& path, ModelData& data, Options& options
options.MergeMeshes = false; // Meshes merging doesn't make sense when we want to import each mesh individually
// TODO: maybe we could update meshes merger to collapse meshes within the same name if splitting is enabled?
// Validate path
// Note: Assimp/Autodesk supports only ANSI characters in imported file path
StringAnsi importPath;
String tmpPath;
if (path.IsANSI() == false)
{
// Use temporary file
LOG(Warning, "Model Tool doesn't support importing files from paths using non ASNI characters. Using temporary file.");
FileSystem::GetTempFilePath(tmpPath);
if (tmpPath.IsANSI() == false || FileSystem::CopyFile(tmpPath, path))
{
errorMsg = TEXT("Path with non ANSI characters is invalid.");
return true;
}
importPath = tmpPath.ToStringAnsi();
}
else
{
importPath = path.ToStringAnsi();
}
// Call importing backend
#if (USE_AUTODESK_FBX_SDK || USE_OPEN_FBX) && USE_ASSIMP
if (path.EndsWith(TEXT(".fbx"), StringSearchCase::IgnoreCase))
{
#if USE_AUTODESK_FBX_SDK
if (ImportDataAutodeskFbxSdk(importPath.Get(), data, options, errorMsg))
if (ImportDataAutodeskFbxSdk(path, data, options, errorMsg))
return true;
#elif USE_OPEN_FBX
if (ImportDataOpenFBX(importPath.Get(), data, options, errorMsg))
if (ImportDataOpenFBX(path, data, options, errorMsg))
return true;
#endif
}
else
{
if (ImportDataAssimp(importPath.Get(), data, options, errorMsg))
if (ImportDataAssimp(path, data, options, errorMsg))
return true;
}
#elif USE_ASSIMP
if (ImportDataAssimp(importPath.Get(), data, options, errorMsg))
if (ImportDataAssimp(path, data, options, errorMsg))
return true;
#elif USE_AUTODESK_FBX_SDK
if (ImportDataAutodeskFbxSdk(importPath.Get(), data, options, errorMsg))
if (ImportDataAutodeskFbxSdk(path, data, options, errorMsg))
return true;
#elif USE_OPEN_FBX
if (ImportDataOpenFBX(importPath.Get(), data, options, errorMsg))
if (ImportDataOpenFBX(path, data, options, errorMsg))
return true;
#else
LOG(Error, "Compiled without model importing backend.");
return true;
#endif
// Remove temporary file
if (tmpPath.HasChars() && FileSystem::FileExists(tmpPath))
{
FileSystem::DeleteFile(tmpPath);
}
// Remove namespace prefixes from the nodes names
{
for (auto& node : data.Nodes)
@@ -1248,7 +1221,7 @@ bool ModelTool::ImportModel(const String& path, ModelData& data, Options& option
}
// Apply the import transformation
if (!importTransform.IsIdentity())
if (!importTransform.IsIdentity() && data.Nodes.HasItems())
{
if (options.Type == ModelType::SkinnedModel)
{

View File

@@ -386,13 +386,13 @@ public:
private:
static void CalculateBoneOffsetMatrix(const Array<SkeletonNode>& nodes, Matrix& offsetMatrix, int32 nodeIndex);
#if USE_ASSIMP
static bool ImportDataAssimp(const char* path, ModelData& data, Options& options, String& errorMsg);
static bool ImportDataAssimp(const String& path, ModelData& data, Options& options, String& errorMsg);
#endif
#if USE_AUTODESK_FBX_SDK
static bool ImportDataAutodeskFbxSdk(const char* path, ModelData& data, Options& options, String& errorMsg);
static bool ImportDataAutodeskFbxSdk(const String& path, ModelData& data, Options& options, String& errorMsg);
#endif
#if USE_OPEN_FBX
static bool ImportDataOpenFBX(const char* path, ModelData& data, Options& options, String& errorMsg);
static bool ImportDataOpenFBX(const String& path, ModelData& data, Options& options, String& errorMsg);
#endif
#endif
};

View File

@@ -21,6 +21,7 @@ public class TextureTool : EngineModule
bool useDirectXTex = false;
bool useStb = false;
bool useExr = options.Target.IsEditor;
switch (options.Platform.Target)
{
@@ -58,6 +59,10 @@ public class TextureTool : EngineModule
options.PrivateDependencies.Add("bc7enc16");
}
}
if (useExr)
{
options.PrivateDependencies.Add("tinyexr");
}
if (options.Target.IsEditor && astc.IsSupported(options))
{
// ASTC for mobile (iOS and Android)

View File

@@ -15,6 +15,7 @@
#if USE_EDITOR
#include "Engine/Graphics/GPUDevice.h"
#endif
#include "Engine/Utilities/AnsiPathTempFile.h"
// Import DirectXTex library
// Source: https://github.com/Microsoft/DirectXTex
@@ -24,6 +25,19 @@ DECLARE_HANDLE(HMONITOR);
#endif
#include <ThirdParty/DirectXTex/DirectXTex.h>
#if USE_EDITOR
// Import tinyexr library
// Source: https://github.com/syoyo/tinyexr
#define TINYEXR_IMPLEMENTATION
#define TINYEXR_USE_MINIZ 1
#define TINYEXR_USE_STB_ZLIB 0
#define TINYEXR_USE_THREAD 0
#define TINYEXR_USE_OPENMP 0
#undef min
#undef max
#include <ThirdParty/tinyexr/tinyexr.h>
#endif
namespace
{
FORCE_INLINE PixelFormat ToPixelFormat(const DXGI_FORMAT format)
@@ -36,7 +50,7 @@ namespace
return static_cast<DXGI_FORMAT>(format);
}
HRESULT Compress(const DirectX::Image* srcImages, size_t nimages, const DirectX::TexMetadata& metadata, DXGI_FORMAT format, DWORD compress, float threshold, DirectX::ScratchImage& cImages)
HRESULT Compress(const DirectX::Image* srcImages, size_t nimages, const DirectX::TexMetadata& metadata, DXGI_FORMAT format, DirectX::TEX_COMPRESS_FLAGS compress, float threshold, DirectX::ScratchImage& cImages)
{
#if USE_EDITOR
if ((format == DXGI_FORMAT_BC7_UNORM || format == DXGI_FORMAT_BC7_UNORM_SRGB || format == DXGI_FORMAT_BC6H_UF16 || format == DXGI_FORMAT_BC6H_SF16) &&
@@ -60,12 +74,12 @@ namespace
size_t _nimages;
const DirectX::TexMetadata& _metadata;
DXGI_FORMAT _format;
DWORD _compress;
DirectX::TEX_COMPRESS_FLAGS _compress;
DirectX::ScratchImage& _cImages;
public:
HRESULT CompressResult = E_FAIL;
GPUCompressTask(ConditionVariable& signal, const DirectX::Image* srcImages, size_t nimages, const DirectX::TexMetadata& metadata, DXGI_FORMAT format, DWORD compress, DirectX::ScratchImage& cImages)
GPUCompressTask(ConditionVariable& signal, const DirectX::Image* srcImages, size_t nimages, const DirectX::TexMetadata& metadata, DXGI_FORMAT format, DirectX::TEX_COMPRESS_FLAGS compress, DirectX::ScratchImage& cImages)
: GPUTask(Type::Custom)
, _signal(&signal)
, _srcImages(srcImages)
@@ -276,6 +290,46 @@ HRESULT LoadFromRAWFile(const StringView& path, DirectX::ScratchImage& image)
return image.InitializeFromImage(img);
}
HRESULT LoadFromEXRFile(const StringView& path, DirectX::ScratchImage& image)
{
#if USE_EDITOR
// Load exr file
AnsiPathTempFile tempFile(path);
float* pixels;
int width, height;
const char* err = nullptr;
int ret = LoadEXR(&pixels, &width, &height, tempFile.Path.Get(), &err);
if (ret != TINYEXR_SUCCESS)
{
if (err)
{
LOG_STR(Warning, String(err));
FreeEXRErrorMessage(err);
}
return S_FALSE;
}
// Setup image
DirectX::Image img;
img.format = DXGI_FORMAT_R32G32B32A32_FLOAT;
img.width = width;
img.height = height;
img.rowPitch = width * sizeof(Float4);
img.slicePitch = img.rowPitch * height;
// Link data
img.pixels = (uint8_t*)pixels;
// Init
HRESULT result = image.InitializeFromImage(img);
free(pixels);
return result;
#else
LOG(Warning, "EXR format is not supported.");
return S_FALSE;
#endif
}
bool TextureTool::ImportTextureDirectXTex(ImageType type, const StringView& path, TextureData& textureData, bool& hasAlpha)
{
// Load image data
@@ -302,6 +356,9 @@ bool TextureTool::ImportTextureDirectXTex(ImageType type, const StringView& path
case ImageType::RAW:
result = LoadFromRAWFile(path, image);
break;
case ImageType::EXR:
result = LoadFromEXRFile(path, image);
break;
default:
result = DXGI_ERROR_INVALID_CALL;
break;
@@ -518,6 +575,9 @@ bool TextureTool::ImportTextureDirectXTex(ImageType type, const StringView& path
case ImageType::RAW:
result = LoadFromRAWFile(path, image1);
break;
case ImageType::EXR:
result = LoadFromEXRFile(path, image1);
break;
case ImageType::Internal:
{
if (options.InternalLoad.IsBinded())
@@ -688,7 +748,7 @@ bool TextureTool::ImportTextureDirectXTex(ImageType type, const StringView& path
if (!keepAsIs && options.FlipY)
{
auto& tmpImg = GET_TMP_IMG();
DWORD flags = DirectX::TEX_FR_FLIP_VERTICAL;
DirectX::TEX_FR_FLAGS flags = DirectX::TEX_FR_FLIP_VERTICAL;
result = FlipRotate(currentImage->GetImages(), currentImage->GetImageCount(), currentImage->GetMetadata(), flags, tmpImg);
if (FAILED(result))
{
@@ -698,7 +758,7 @@ bool TextureTool::ImportTextureDirectXTex(ImageType type, const StringView& path
SET_CURRENT_IMG(tmpImg);
}
// Check if it invert green channel
// Check if invert green channel
if (!keepAsIs && options.InvertGreenChannel)
{
auto& timage = GET_TMP_IMG();

View File

@@ -767,6 +767,10 @@ bool TextureTool::GetImageType(const StringView& path, ImageType& type)
{
type = ImageType::RAW;
}
else if (extension == TEXT("exr"))
{
type = ImageType::EXR;
}
else
{
LOG(Warning, "Unknown file type.");

View File

@@ -252,6 +252,7 @@ private:
JPEG,
HDR,
RAW,
EXR,
Internal,
};

View File

@@ -10,6 +10,7 @@
#include "Engine/Graphics/RenderTools.h"
#include "Engine/Graphics/Textures/TextureData.h"
#include "Engine/Graphics/PixelFormatExtensions.h"
#include "Engine/Utilities/AnsiPathTempFile.h"
#include "Engine/Platform/File.h"
#define STBI_ASSERT(x) ASSERT(x)
@@ -48,6 +49,18 @@
// Compression libs for Editor
#include <ThirdParty/detex/detex.h>
#include <ThirdParty/bc7enc16/bc7enc16.h>
// Import tinyexr library
// Source: https://github.com/syoyo/tinyexr
#define TINYEXR_IMPLEMENTATION
#define TINYEXR_USE_MINIZ 1
#define TINYEXR_USE_STB_ZLIB 0
#define TINYEXR_USE_THREAD 0
#define TINYEXR_USE_OPENMP 0
#undef min
#undef max
#include <ThirdParty/tinyexr/tinyexr.h>
#endif
static void stbWrite(void* context, void* data, int size)
@@ -173,7 +186,7 @@ bool TextureTool::ExportTextureStb(ImageType type, const StringView& path, const
{
if (textureData.GetArraySize() != 1)
{
LOG(Warning, "Exporting texture arrays and cubemaps is not supported by stb library.");
LOG(Warning, "Exporting texture arrays and cubemaps is not supported.");
}
TextureData const* texture = &textureData;
@@ -189,7 +202,7 @@ bool TextureTool::ExportTextureStb(ImageType type, const StringView& path, const
const auto sampler = GetSampler(texture->Format);
if (sampler == nullptr)
{
LOG(Warning, "Texture data format {0} is not supported by stb library.", (int32)textureData.Format);
LOG(Warning, "Texture data format {0} is not supported.", (int32)textureData.Format);
return true;
}
const auto srcData = texture->GetData(0, 0);
@@ -272,16 +285,19 @@ bool TextureTool::ExportTextureStb(ImageType type, const StringView& path, const
break;
}
case ImageType::GIF:
LOG(Warning, "GIF format is not supported by stb library.");
LOG(Warning, "GIF format is not supported.");
break;
case ImageType::TIFF:
LOG(Warning, "GIF format is not supported by stb library.");
LOG(Warning, "GIF format is not supported.");
break;
case ImageType::DDS:
LOG(Warning, "DDS format is not supported by stb library.");
LOG(Warning, "DDS format is not supported.");
break;
case ImageType::RAW:
LOG(Warning, "RAW format is not supported by stb library.");
LOG(Warning, "RAW format is not supported.");
break;
case ImageType::EXR:
LOG(Warning, "EXR format is not supported.");
break;
default:
LOG(Warning, "Unknown format.");
@@ -383,11 +399,49 @@ bool TextureTool::ImportTextureStb(ImageType type, const StringView& path, Textu
break;
}
case ImageType::EXR:
{
#if USE_EDITOR
// Load exr file
AnsiPathTempFile tempFile(path);
float* pixels;
int width, height;
const char* err = nullptr;
int ret = LoadEXR(&pixels, &width, &height, tempFile.Path.Get(), &err);
if (ret != TINYEXR_SUCCESS)
{
if (err)
{
LOG_STR(Warning, String(err));
FreeEXRErrorMessage(err);
}
return true;
}
// Setup texture data
textureData.Width = width;
textureData.Height = height;
textureData.Depth = 1;
textureData.Format = PixelFormat::R32G32B32A32_Float;
textureData.Items.Resize(1);
textureData.Items[0].Mips.Resize(1);
auto& mip = textureData.Items[0].Mips[0];
mip.RowPitch = width * sizeof(Float4);
mip.DepthPitch = mip.RowPitch * height;
mip.Lines = height;
mip.Data.Copy((const byte*)pixels, mip.DepthPitch);
free(pixels);
#else
LOG(Warning, "EXR format is not supported.");
#endif
break;
}
case ImageType::DDS:
LOG(Warning, "DDS format is not supported by stb library.");
LOG(Warning, "DDS format is not supported.");
break;
case ImageType::TIFF:
LOG(Warning, "TIFF format is not supported by stb library.");
LOG(Warning, "TIFF format is not supported.");
break;
default:
LOG(Warning, "Unknown format.");

View File

@@ -290,7 +290,7 @@ namespace FlaxEngine.GUI
{
var leftEdge = selection.StartIndex <= textBlock.Range.StartIndex ? textBlock.Bounds.UpperLeft : font.GetCharPosition(_text, selection.StartIndex);
var rightEdge = selection.EndIndex >= textBlock.Range.EndIndex ? textBlock.Bounds.UpperRight : font.GetCharPosition(_text, selection.EndIndex);
float height = font.Height / DpiScale;
float height = font.Height;
float alpha = Mathf.Min(1.0f, Mathf.Cos(_animateTime * BackgroundSelectedFlashSpeed) * 0.5f + 1.3f);
alpha *= alpha;
Color selectionColor = Color.White * alpha;
@@ -340,7 +340,7 @@ namespace FlaxEngine.GUI
if (textBlock.Style.UnderlineBrush != null)
{
var underLineHeight = 2.0f;
var height = font.Height / DpiScale;
var height = font.Height;
var underlineRect = new Rectangle(textBlock.Bounds.Location.X, textBlock.Bounds.Location.Y + height - underLineHeight * 0.5f, textBlock.Bounds.Width, underLineHeight);
textBlock.Style.UnderlineBrush.Draw(underlineRect, textBlock.Style.Color);
}

View File

@@ -0,0 +1,37 @@
// Copyright (c) 2012-2024 Wojciech Figat. All rights reserved.
#pragma once
#include "Engine/Core/Types/String.h"
#include "Engine/Platform/FileSystem.h"
// Small utility that uses temporary file to properly handle non-ANSI paths for 3rd party libs.
struct AnsiPathTempFile
{
StringAnsi Path;
String TempPath;
bool Temp;
AnsiPathTempFile(const String& path)
{
if (path.IsANSI() == false)
{
// Use temporary file
FileSystem::GetTempFilePath(TempPath);
if (TempPath.IsANSI() && !FileSystem::CopyFile(TempPath, path))
{
Path = TempPath.ToStringAnsi();
return;
}
TempPath.Clear();
}
Path = path.ToStringAnsi();
}
~AnsiPathTempFile()
{
// Cleanup temporary file after use
if (TempPath.HasChars())
FileSystem::DeleteFile(TempPath);
}
};

View File

@@ -47,6 +47,12 @@ namespace FlaxEngine.Utilities
/// True if this tag contained a leading or trailing forward slash.
/// </summary>
public bool IsSlash => IsLeadingSlash || IsEndingSlash;
/// <inheritdoc />
public override string ToString()
{
return Name;
}
};
/// <summary>
@@ -231,6 +237,9 @@ namespace FlaxEngine.Utilities
tag.Attributes[s] = value;
}
}
if (EOF)
return false;
}
// Skip over closing '>'
@@ -264,8 +273,13 @@ namespace FlaxEngine.Utilities
private string ParseAttributeName()
{
int start = _pos;
while (!EOF && char.IsLetterOrDigit(Peek()))
while (!EOF)
{
var c = Peek();
if (!char.IsLetterOrDigit(c) && c != '-')
break;
Move();
}
return _html.Substring(start, _pos - start);
}

Binary file not shown.

Binary file not shown.

File diff suppressed because it is too large Load Diff

View File

@@ -1,9 +1,9 @@
//-------------------------------------------------------------------------------------
// DirectXTex.inl
//
//
// DirectX Texture Library
//
// Copyright (c) Microsoft Corporation. All rights reserved.
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
//
// http://go.microsoft.com/fwlink/?LinkId=248926
@@ -11,81 +11,109 @@
#pragma once
//=====================================================================================
// Bitmask flags enumerator operators
//=====================================================================================
DEFINE_ENUM_FLAG_OPERATORS(CP_FLAGS);
DEFINE_ENUM_FLAG_OPERATORS(DDS_FLAGS);
DEFINE_ENUM_FLAG_OPERATORS(TGA_FLAGS);
DEFINE_ENUM_FLAG_OPERATORS(WIC_FLAGS);
DEFINE_ENUM_FLAG_OPERATORS(TEX_FR_FLAGS);
DEFINE_ENUM_FLAG_OPERATORS(TEX_FILTER_FLAGS);
DEFINE_ENUM_FLAG_OPERATORS(TEX_PMALPHA_FLAGS);
DEFINE_ENUM_FLAG_OPERATORS(TEX_COMPRESS_FLAGS);
DEFINE_ENUM_FLAG_OPERATORS(CNMAP_FLAGS);
DEFINE_ENUM_FLAG_OPERATORS(CMSE_FLAGS);
DEFINE_ENUM_FLAG_OPERATORS(CREATETEX_FLAGS);
// WIC_FILTER modes match TEX_FILTER modes
constexpr WIC_FLAGS operator|(WIC_FLAGS a, TEX_FILTER_FLAGS b) { return static_cast<WIC_FLAGS>(static_cast<unsigned long>(a) | static_cast<unsigned long>(b & TEX_FILTER_MODE_MASK)); }
constexpr WIC_FLAGS operator|(TEX_FILTER_FLAGS a, WIC_FLAGS b) { return static_cast<WIC_FLAGS>(static_cast<unsigned long>(a & TEX_FILTER_MODE_MASK) | static_cast<unsigned long>(b)); }
// TEX_PMALPHA_SRGB match TEX_FILTER_SRGB
constexpr TEX_PMALPHA_FLAGS operator|(TEX_PMALPHA_FLAGS a, TEX_FILTER_FLAGS b) { return static_cast<TEX_PMALPHA_FLAGS>(static_cast<unsigned long>(a) | static_cast<unsigned long>(b & TEX_FILTER_SRGB_MASK)); }
constexpr TEX_PMALPHA_FLAGS operator|(TEX_FILTER_FLAGS a, TEX_PMALPHA_FLAGS b) { return static_cast<TEX_PMALPHA_FLAGS>(static_cast<unsigned long>(a & TEX_FILTER_SRGB_MASK) | static_cast<unsigned long>(b)); }
// TEX_COMPRESS_SRGB match TEX_FILTER_SRGB
constexpr TEX_COMPRESS_FLAGS operator|(TEX_COMPRESS_FLAGS a, TEX_FILTER_FLAGS b) { return static_cast<TEX_COMPRESS_FLAGS>(static_cast<unsigned long>(a) | static_cast<unsigned long>(b & TEX_FILTER_SRGB_MASK)); }
constexpr TEX_COMPRESS_FLAGS operator|(TEX_FILTER_FLAGS a, TEX_COMPRESS_FLAGS b) { return static_cast<TEX_COMPRESS_FLAGS>(static_cast<unsigned long>(a & TEX_FILTER_SRGB_MASK) | static_cast<unsigned long>(b)); }
//=====================================================================================
// DXGI Format Utilities
//=====================================================================================
_Use_decl_annotations_
inline bool __cdecl IsValid(DXGI_FORMAT fmt)
constexpr bool __cdecl IsValid(DXGI_FORMAT fmt) noexcept
{
return (static_cast<size_t>(fmt) >= 1 && static_cast<size_t>(fmt) <= 190);
return (static_cast<size_t>(fmt) >= 1 && static_cast<size_t>(fmt) <= 191);
}
_Use_decl_annotations_
inline bool __cdecl IsCompressed(DXGI_FORMAT fmt)
inline bool __cdecl IsCompressed(DXGI_FORMAT fmt) noexcept
{
switch (fmt)
{
case DXGI_FORMAT_BC1_TYPELESS:
case DXGI_FORMAT_BC1_UNORM:
case DXGI_FORMAT_BC1_UNORM_SRGB:
case DXGI_FORMAT_BC2_TYPELESS:
case DXGI_FORMAT_BC2_UNORM:
case DXGI_FORMAT_BC2_UNORM_SRGB:
case DXGI_FORMAT_BC3_TYPELESS:
case DXGI_FORMAT_BC3_UNORM:
case DXGI_FORMAT_BC3_UNORM_SRGB:
case DXGI_FORMAT_BC4_TYPELESS:
case DXGI_FORMAT_BC4_UNORM:
case DXGI_FORMAT_BC4_SNORM:
case DXGI_FORMAT_BC5_TYPELESS:
case DXGI_FORMAT_BC5_UNORM:
case DXGI_FORMAT_BC5_SNORM:
case DXGI_FORMAT_BC6H_TYPELESS:
case DXGI_FORMAT_BC6H_UF16:
case DXGI_FORMAT_BC6H_SF16:
case DXGI_FORMAT_BC7_TYPELESS:
case DXGI_FORMAT_BC7_UNORM:
case DXGI_FORMAT_BC7_UNORM_SRGB:
return true;
case DXGI_FORMAT_BC1_TYPELESS:
case DXGI_FORMAT_BC1_UNORM:
case DXGI_FORMAT_BC1_UNORM_SRGB:
case DXGI_FORMAT_BC2_TYPELESS:
case DXGI_FORMAT_BC2_UNORM:
case DXGI_FORMAT_BC2_UNORM_SRGB:
case DXGI_FORMAT_BC3_TYPELESS:
case DXGI_FORMAT_BC3_UNORM:
case DXGI_FORMAT_BC3_UNORM_SRGB:
case DXGI_FORMAT_BC4_TYPELESS:
case DXGI_FORMAT_BC4_UNORM:
case DXGI_FORMAT_BC4_SNORM:
case DXGI_FORMAT_BC5_TYPELESS:
case DXGI_FORMAT_BC5_UNORM:
case DXGI_FORMAT_BC5_SNORM:
case DXGI_FORMAT_BC6H_TYPELESS:
case DXGI_FORMAT_BC6H_UF16:
case DXGI_FORMAT_BC6H_SF16:
case DXGI_FORMAT_BC7_TYPELESS:
case DXGI_FORMAT_BC7_UNORM:
case DXGI_FORMAT_BC7_UNORM_SRGB:
return true;
default:
return false;
default:
return false;
}
}
_Use_decl_annotations_
inline bool __cdecl IsPalettized(DXGI_FORMAT fmt)
inline bool __cdecl IsPalettized(DXGI_FORMAT fmt) noexcept
{
switch (fmt)
{
case DXGI_FORMAT_AI44:
case DXGI_FORMAT_IA44:
case DXGI_FORMAT_P8:
case DXGI_FORMAT_A8P8:
return true;
case DXGI_FORMAT_AI44:
case DXGI_FORMAT_IA44:
case DXGI_FORMAT_P8:
case DXGI_FORMAT_A8P8:
return true;
default:
return false;
default:
return false;
}
}
_Use_decl_annotations_
inline bool __cdecl IsSRGB(DXGI_FORMAT fmt)
inline bool __cdecl IsSRGB(DXGI_FORMAT fmt) noexcept
{
switch (fmt)
{
case DXGI_FORMAT_R8G8B8A8_UNORM_SRGB:
case DXGI_FORMAT_BC1_UNORM_SRGB:
case DXGI_FORMAT_BC2_UNORM_SRGB:
case DXGI_FORMAT_BC3_UNORM_SRGB:
case DXGI_FORMAT_B8G8R8A8_UNORM_SRGB:
case DXGI_FORMAT_B8G8R8X8_UNORM_SRGB:
case DXGI_FORMAT_BC7_UNORM_SRGB:
return true;
case DXGI_FORMAT_R8G8B8A8_UNORM_SRGB:
case DXGI_FORMAT_BC1_UNORM_SRGB:
case DXGI_FORMAT_BC2_UNORM_SRGB:
case DXGI_FORMAT_BC3_UNORM_SRGB:
case DXGI_FORMAT_B8G8R8A8_UNORM_SRGB:
case DXGI_FORMAT_B8G8R8X8_UNORM_SRGB:
case DXGI_FORMAT_BC7_UNORM_SRGB:
return true;
default:
return false;
default:
return false;
}
}
@@ -94,7 +122,7 @@ inline bool __cdecl IsSRGB(DXGI_FORMAT fmt)
// Image I/O
//=====================================================================================
_Use_decl_annotations_
inline HRESULT __cdecl SaveToDDSMemory(const Image& image, DWORD flags, Blob& blob)
inline HRESULT __cdecl SaveToDDSMemory(const Image& image, DDS_FLAGS flags, Blob& blob) noexcept
{
TexMetadata mdata = {};
mdata.width = image.width;
@@ -109,7 +137,7 @@ inline HRESULT __cdecl SaveToDDSMemory(const Image& image, DWORD flags, Blob& bl
}
_Use_decl_annotations_
inline HRESULT __cdecl SaveToDDSFile(const Image& image, DWORD flags, const wchar_t* szFile)
inline HRESULT __cdecl SaveToDDSFile(const Image& image, DDS_FLAGS flags, const wchar_t* szFile) noexcept
{
TexMetadata mdata = {};
mdata.width = image.width;
@@ -122,3 +150,43 @@ inline HRESULT __cdecl SaveToDDSFile(const Image& image, DWORD flags, const wcha
return SaveToDDSFile(&image, 1, mdata, flags, szFile);
}
//=====================================================================================
// Compatability helpers
//=====================================================================================
_Use_decl_annotations_
inline HRESULT __cdecl GetMetadataFromTGAMemory(const void* pSource, size_t size, TexMetadata& metadata) noexcept
{
return GetMetadataFromTGAMemory(pSource, size, TGA_FLAGS_NONE, metadata);
}
_Use_decl_annotations_
inline HRESULT __cdecl GetMetadataFromTGAFile(const wchar_t* szFile, TexMetadata& metadata) noexcept
{
return GetMetadataFromTGAFile(szFile, TGA_FLAGS_NONE, metadata);
}
_Use_decl_annotations_
inline HRESULT __cdecl LoadFromTGAMemory(const void* pSource, size_t size, TexMetadata* metadata, ScratchImage& image) noexcept
{
return LoadFromTGAMemory(pSource, size, TGA_FLAGS_NONE, metadata, image);
}
_Use_decl_annotations_
inline HRESULT __cdecl LoadFromTGAFile(const wchar_t* szFile, TexMetadata* metadata, ScratchImage& image) noexcept
{
return LoadFromTGAFile(szFile, TGA_FLAGS_NONE, metadata, image);
}
_Use_decl_annotations_
inline HRESULT __cdecl SaveToTGAMemory(const Image& image, Blob& blob, const TexMetadata* metadata) noexcept
{
return SaveToTGAMemory(image, TGA_FLAGS_NONE, blob, metadata);
}
_Use_decl_annotations_
inline HRESULT __cdecl SaveToTGAFile(const Image& image, const wchar_t* szFile, const TexMetadata* metadata) noexcept
{
return SaveToTGAFile(image, TGA_FLAGS_NONE, szFile, metadata);
}

View File

@@ -1,21 +1,21 @@
The MIT License (MIT)
MIT License
Copyright (c) 2011-2019 Microsoft Corp
Copyright (c) Microsoft Corporation.
Permission is hereby granted, free of charge, to any person obtaining a copy of this
software and associated documentation files (the "Software"), to deal in the Software
without restriction, including without limitation the rights to use, copy, modify,
merge, publish, distribute, sublicense, and/or sell copies of the Software, and to
permit persons to whom the Software is furnished to do so, subject to the following
conditions:
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies
or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF
CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE
OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE

5
Source/ThirdParty/tinyexr/LICENSE vendored Normal file
View File

@@ -0,0 +1,5 @@
3-clause BSD
tinyexr uses miniz, which is developed by Rich Geldreich richgel99@gmail.com, and licensed under public domain.
tinyexr tools uses stb, which is licensed under public domain: https://github.com/nothings/stb tinyexr uses some code from OpenEXR, which is licensed under 3-clause BSD license. tinyexr uses nanozlib and wuffs, whose are licensed unnder Apache 2.0 license.

View File

@@ -0,0 +1,22 @@
// Copyright (c) 2012-2024 Wojciech Figat. All rights reserved.
using Flax.Build;
using Flax.Build.NativeCpp;
/// <summary>
/// https://github.com/syoyo/tinyexr
/// </summary>
public class tinyexr : HeaderOnlyModule
{
/// <inheritdoc />
public override void Init()
{
base.Init();
LicenseType = LicenseTypes.BSD3Clause;
LicenseFilePath = "LICENSE";
// Merge third-party modules into engine binary
BinaryModuleName = "FlaxEngine";
}
}

9307
Source/ThirdParty/tinyexr/tinyexr.h vendored Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -269,8 +269,9 @@ namespace Flax.Build
dotnetSdkVersions = MergeVersions(dotnetSdkVersions, GetVersions(Path.Combine(dotnetPath, "sdk")));
dotnetRuntimeVersions = MergeVersions(dotnetRuntimeVersions, GetVersions(Path.Combine(dotnetPath, "shared", "Microsoft.NETCore.App")));
dotnetSdkVersions = dotnetSdkVersions.Where(x => IsValidVersion(Path.Combine(dotnetPath, "sdk", x)));
dotnetRuntimeVersions = dotnetRuntimeVersions.Where(x => IsValidVersion(Path.Combine(dotnetPath, "shared", "Microsoft.NETCore.App", x)));
dotnetSdkVersions = dotnetSdkVersions.Where(x => File.Exists(Path.Combine(dotnetPath, "sdk", x, ".version")));
dotnetRuntimeVersions = dotnetRuntimeVersions.Where(x => File.Exists(Path.Combine(dotnetPath, "shared", "Microsoft.NETCore.App", x, ".version")));
dotnetRuntimeVersions = dotnetRuntimeVersions.Where(x => Directory.Exists(Path.Combine(dotnetPath, "packs", "Microsoft.NETCore.App.Ref", x)));
dotnetSdkVersions = dotnetSdkVersions.OrderByDescending(ParseVersion);
dotnetRuntimeVersions = dotnetRuntimeVersions.OrderByDescending(ParseVersion);
@@ -543,11 +544,6 @@ namespace Flax.Build
return null;
}
private static bool IsValidVersion(string versionPath)
{
return File.Exists(Path.Combine(versionPath, ".version"));
}
private static string SearchForDotnetLocationLinux()
{
if (File.Exists("/etc/dotnet/install_location")) // Officially recommended dotnet location file

View File

@@ -26,6 +26,38 @@ namespace Flax.Build.NativeCpp
SmallCode
}
/// <summary>
/// The code sanitizers for core errors detection by compiler-supported checks.
/// </summary>
[Flags]
public enum Sanitizer
{
/// <summary>
/// No sanitizers in use.
/// </summary>
None = 0,
/// <summary>
/// Memory errors detector,
/// </summary>
Address = 1,
/// <summary>
/// Data races and deadlocks detector.
/// </summary>
Thread = 2,
/// <summary>
/// Uninitialized memory reads detector.
/// </summary>
Memory = 4,
/// <summary>
/// Undefined behavior (UB) detector.
/// </summary>
Undefined = 8,
}
/// <summary>
/// The compilation optimization hint.
/// </summary>
@@ -67,6 +99,11 @@ namespace Flax.Build.NativeCpp
/// </summary>
public FavorSizeOrSpeed FavorSizeOrSpeed = FavorSizeOrSpeed.Neither;
/// <summary>
/// Selects a sanitizers to use (as flags).
/// </summary>
public Sanitizer Sanitizers = Sanitizer.None;
/// <summary>
/// Enables exceptions support.
/// </summary>
@@ -184,6 +221,7 @@ namespace Flax.Build.NativeCpp
{
CppVersion = CppVersion,
FavorSizeOrSpeed = FavorSizeOrSpeed,
Sanitizers = Sanitizers,
EnableExceptions = EnableExceptions,
RuntimeTypeInfo = RuntimeTypeInfo,
Inlining = Inlining,

View File

@@ -249,6 +249,7 @@ namespace Flax.Build
}
options.CompileEnv.EnableExceptions = true; // TODO: try to disable this!
options.CompileEnv.Sanitizers = Configuration.Sanitizers;
switch (options.Configuration)
{
case TargetConfiguration.Debug:

View File

@@ -225,6 +225,12 @@ namespace Flax.Build
[CommandLine("compiler", "<name>", "Overrides the compiler to use for building. Eg. v140 overrides the toolset when building for Windows.")]
public static string Compiler = null;
/// <summary>
/// Selects a sanitizers to use (as flags). Options: Address, Thread.
/// </summary>
[CommandLine("sanitizers", "<name>", "Selects a sanitizers to use (as flags). Options: Address, Thread.")]
public static Flax.Build.NativeCpp.Sanitizer Sanitizers = Flax.Build.NativeCpp.Sanitizer.None;
/// <summary>
/// Specifies the dotnet SDK version to use for the build. Eg. set to '7' to use .NET 7 even if .NET 8 is installed.
/// </summary>
@@ -242,6 +248,8 @@ namespace Flax.Build
cmdLine += " -compiler=" + Compiler;
if (!string.IsNullOrEmpty(Dotnet))
cmdLine += " -dotnet=" + Dotnet;
if (Sanitizers != Flax.Build.NativeCpp.Sanitizer.None)
cmdLine += " -sanitizers=" + Sanitizers.ToString();
}
}

View File

@@ -44,7 +44,7 @@ namespace Flax.Deps.Dependencies
// Get the source
CloneGitRepo(root, "https://github.com/Microsoft/DirectXTex.git");
GitCheckout(root, "master", "9a417f506c43e087b84c017260ad673abd6c64e1");
GitCheckout(root, "main", "5cfd711dc5d64cde1e8b27670036535df5c3f922");
foreach (var platform in options.Platforms)
{
@@ -52,60 +52,39 @@ namespace Flax.Deps.Dependencies
{
case TargetPlatform.Windows:
{
var solutionPath = Path.Combine(root, "DirectXTex_Desktop_2015.sln");
var binFolder = Path.Combine(root, "DirectXTex", "Bin", "Desktop_2015");
var solutionPath = Path.Combine(root, "DirectXTex_Desktop_2022.sln");
var binFolder = Path.Combine(root, "DirectXTex", "Bin", "Desktop_2022");
Deploy.VCEnvironment.BuildSolution(solutionPath, configuration, "x64");
var depsFolder = GetThirdPartyFolder(options, platform, TargetArchitecture.x64);
foreach (var file in outputFileNames)
{
Utilities.FileCopy(Path.Combine(binFolder, "x64", configuration, file), Path.Combine(depsFolder, file));
}
break;
}
case TargetPlatform.UWP:
{
var solutionPath = Path.Combine(root, "DirectXTex_Windows10_2017.sln");
var binFolder = Path.Combine(root, "DirectXTex", "Bin", "Windows10_2017");
var solutionPath = Path.Combine(root, "DirectXTex_Windows10_2019.sln");
var binFolder = Path.Combine(root, "DirectXTex", "Bin", "Windows10_2019");
Deploy.VCEnvironment.BuildSolution(solutionPath, configuration, "x64");
var depsFolder = GetThirdPartyFolder(options, platform, TargetArchitecture.x64);
foreach (var file in outputFileNames)
{
Utilities.FileCopy(Path.Combine(binFolder, "x64", configuration, file), Path.Combine(depsFolder, file));
}
break;
}
case TargetPlatform.XboxOne:
{
var solutionPath = Path.Combine(root, "DirectXTex_GXDK_2017.sln");
File.Copy(Path.Combine(GetBinariesFolder(options, platform), "DirectXTex_GXDK_2017.sln"), solutionPath, true);
var projectFileContents = File.ReadAllText(Path.Combine(GetBinariesFolder(options, platform), "DirectXTex_GXDK_2017.vcxproj"));
projectFileContents = projectFileContents.Replace("___VS_TOOLSET___", "v142");
var projectPath = Path.Combine(root, "DirectXTex", "DirectXTex_GXDK_2017.vcxproj");
File.WriteAllText(projectPath, projectFileContents);
var binFolder = Path.Combine(root, "DirectXTex", "Bin", "GXDK_2017");
Deploy.VCEnvironment.BuildSolution(solutionPath, configuration, "Gaming.Xbox.XboxOne.x64");
var depsFolder = GetThirdPartyFolder(options, platform, TargetArchitecture.x64);
foreach (var file in outputFileNames)
{
Utilities.FileCopy(Path.Combine(binFolder, "Gaming.Xbox.XboxOne.x64", configuration, file), Path.Combine(depsFolder, file));
}
break;
}
case TargetPlatform.XboxScarlett:
{
var solutionPath = Path.Combine(root, "DirectXTex_GXDK_2017.sln");
File.Copy(Path.Combine(GetBinariesFolder(options, platform), "DirectXTex_GXDK_2017.sln"), solutionPath, true);
var projectFileContents = File.ReadAllText(Path.Combine(GetBinariesFolder(options, platform), "DirectXTex_GXDK_2017.vcxproj"));
var solutionPath = Path.Combine(root, "DirectXTex_GXDK_2019.sln");
File.Copy(Path.Combine(GetBinariesFolder(options, platform), "DirectXTex_GXDK_2019.sln"), solutionPath, true);
var projectFileContents = File.ReadAllText(Path.Combine(GetBinariesFolder(options, platform), "DirectXTex_GXDK_2019.vcxproj"));
projectFileContents = projectFileContents.Replace("___VS_TOOLSET___", "v142");
var projectPath = Path.Combine(root, "DirectXTex", "DirectXTex_GXDK_2017.vcxproj");
var projectPath = Path.Combine(root, "DirectXTex", "DirectXTex_GXDK_2019.vcxproj");
File.WriteAllText(projectPath, projectFileContents);
var binFolder = Path.Combine(root, "DirectXTex", "Bin", "GXDK_2017");
Deploy.VCEnvironment.BuildSolution(solutionPath, configuration, "Gaming.Xbox.Scarlett.x64");
var binFolder = Path.Combine(root, "DirectXTex", "Bin", "GXDK_2019");
var xboxName = platform == TargetPlatform.XboxOne ? "Gaming.Xbox.XboxOne.x64" : "Gaming.Xbox.Scarlett.x64";
Deploy.VCEnvironment.BuildSolution(solutionPath, configuration, xboxName);
var depsFolder = GetThirdPartyFolder(options, platform, TargetArchitecture.x64);
foreach (var file in outputFileNames)
{
Utilities.FileCopy(Path.Combine(binFolder, "Gaming.Xbox.Scarlett.x64", configuration, file), Path.Combine(depsFolder, file));
}
Utilities.FileCopy(Path.Combine(binFolder, xboxName, configuration, file), Path.Combine(depsFolder, file));
break;
}
}

View File

@@ -105,6 +105,7 @@ namespace Flax.Build.Platforms
commonArgs.Add("objective-c++");
commonArgs.Add("-stdlib=libc++");
AddArgsCommon(options, commonArgs);
AddArgsSanitizer(compileEnvironment.Sanitizers, commonArgs);
switch (compileEnvironment.CppVersion)
{
@@ -155,14 +156,24 @@ namespace Flax.Build.Platforms
commonArgs.Add("-pthread");
if (compileEnvironment.FavorSizeOrSpeed == FavorSizeOrSpeed.FastCode)
commonArgs.Add("-Ofast");
else if (compileEnvironment.FavorSizeOrSpeed == FavorSizeOrSpeed.SmallCode)
commonArgs.Add("-Os");
if (compileEnvironment.Optimization)
commonArgs.Add("-O3");
if (compileEnvironment.Sanitizers.HasFlag(Sanitizer.Address))
{
commonArgs.Add("-fno-optimize-sibling-calls");
commonArgs.Add("-fno-omit-frame-pointer");
if (compileEnvironment.Optimization)
commonArgs.Add("-O1");
}
else
commonArgs.Add("-O0");
{
if (compileEnvironment.FavorSizeOrSpeed == FavorSizeOrSpeed.FastCode)
commonArgs.Add("-Ofast");
else if (compileEnvironment.FavorSizeOrSpeed == FavorSizeOrSpeed.SmallCode)
commonArgs.Add("-Os");
if (compileEnvironment.Optimization)
commonArgs.Add("-O3");
else
commonArgs.Add("-O0");
}
if (compileEnvironment.BufferSecurityCheck)
commonArgs.Add("-fstack-protector");
@@ -240,6 +251,7 @@ namespace Flax.Build.Platforms
{
args.Add(string.Format("-o \"{0}\"", outputFilePath));
AddArgsCommon(options, args);
AddArgsSanitizer(options.CompileEnv.Sanitizers, args);
if (isArchive)
{
@@ -426,5 +438,15 @@ namespace Flax.Build.Platforms
break;
}
}
protected void AddArgsSanitizer(Sanitizer sanitizers, List<string> args)
{
if (sanitizers.HasFlag(Sanitizer.Address))
args.Add("-fsanitize=address");
if (sanitizers.HasFlag(Sanitizer.Thread))
args.Add("-fsanitize=thread");
if (sanitizers.HasFlag(Sanitizer.Undefined))
args.Add("-fsanitize=undefined");
}
}
}