diff --git a/Content/Editor/MaterialTemplates/Surface.shader b/Content/Editor/MaterialTemplates/Surface.shader index f206d58fb..4b3581cee 100644 --- a/Content/Editor/MaterialTemplates/Surface.shader +++ b/Content/Editor/MaterialTemplates/Surface.shader @@ -430,7 +430,9 @@ float3x4 GetPrevBoneMatrix(int index) float3 SkinPrevPosition(ModelInput_Skinned input) { float4 position = float4(input.Position.xyz, 1); - float3x4 boneMatrix = input.BlendWeights.x * GetPrevBoneMatrix(input.BlendIndices.x); + float weightsSum = input.BlendWeights.x + input.BlendWeights.y + input.BlendWeights.z + input.BlendWeights.w; + float mainWeight = input.BlendWeights.x + (1.0f - weightsSum); // Re-normalize to account for 16-bit weights encoding erros + float3x4 boneMatrix = mainWeight * GetPrevBoneMatrix(input.BlendIndices.x); boneMatrix += input.BlendWeights.y * GetPrevBoneMatrix(input.BlendIndices.y); boneMatrix += input.BlendWeights.z * GetPrevBoneMatrix(input.BlendIndices.z); boneMatrix += input.BlendWeights.w * GetPrevBoneMatrix(input.BlendIndices.w); @@ -439,12 +441,6 @@ float3 SkinPrevPosition(ModelInput_Skinned input) #endif -// Cached skinning data to avoid multiple calculation -struct SkinningData -{ - float3x4 BlendMatrix; -}; - // Calculates the transposed transform matrix for the given bone index float3x4 GetBoneMatrix(int index) { @@ -457,7 +453,9 @@ float3x4 GetBoneMatrix(int index) // Calculates the transposed transform matrix for the given vertex (uses blending) float3x4 GetBoneMatrix(ModelInput_Skinned input) { - float3x4 boneMatrix = input.BlendWeights.x * GetBoneMatrix(input.BlendIndices.x); + float weightsSum = input.BlendWeights.x + input.BlendWeights.y + input.BlendWeights.z + input.BlendWeights.w; + float mainWeight = input.BlendWeights.x + (1.0f - weightsSum); // Re-normalize to account for 16-bit weights encoding erros + float3x4 boneMatrix = mainWeight * GetBoneMatrix(input.BlendIndices.x); boneMatrix += input.BlendWeights.y * GetBoneMatrix(input.BlendIndices.y); boneMatrix += input.BlendWeights.z * GetBoneMatrix(input.BlendIndices.z); boneMatrix += input.BlendWeights.w * GetBoneMatrix(input.BlendIndices.w); @@ -465,13 +463,13 @@ float3x4 GetBoneMatrix(ModelInput_Skinned input) } // Transforms the vertex position by weighted sum of the skinning matrices -float3 SkinPosition(ModelInput_Skinned input, SkinningData data) +float3 SkinPosition(ModelInput_Skinned input, float3x4 boneMatrix) { - return mul(data.BlendMatrix, float4(input.Position.xyz, 1)); + return mul(boneMatrix, float4(input.Position.xyz, 1)); } // Transforms the vertex position by weighted sum of the skinning matrices -float3x3 SkinTangents(ModelInput_Skinned input, SkinningData data) +float3x3 SkinTangents(ModelInput_Skinned input, float3x4 boneMatrix) { // Unpack vertex tangent frame float bitangentSign = input.Tangent.w ? -1.0f : +1.0f; @@ -479,10 +477,10 @@ float3x3 SkinTangents(ModelInput_Skinned input, SkinningData data) float3 tangent = input.Tangent.xyz * 2.0 - 1.0; // Apply skinning - tangent = mul(data.BlendMatrix, float4(tangent, 0)); - normal = mul(data.BlendMatrix, float4(normal, 0)); + tangent = normalize(mul(boneMatrix, float4(tangent, 0))); + normal = normalize(mul(boneMatrix, float4(normal, 0))); - float3 bitangent = cross(normal, tangent) * bitangentSign; + float3 bitangent = normalize(cross(normal, tangent) * bitangentSign); return float3x3(tangent, bitangent, normal); } @@ -501,10 +499,9 @@ VertexOutput VS_Skinned(ModelInput_Skinned input) VertexOutput output; // Perform skinning - SkinningData data; - data.BlendMatrix = GetBoneMatrix(input); - float3 position = SkinPosition(input, data); - float3x3 tangentToLocal = SkinTangents(input, data); + float3x4 boneMatrix = GetBoneMatrix(input); + float3 position = SkinPosition(input, boneMatrix); + float3x3 tangentToLocal = SkinTangents(input, boneMatrix); // Compute world space vertex position CalculateInstanceTransform(input); diff --git a/Flax.sln.DotSettings b/Flax.sln.DotSettings index 6d72e44ed..96456601b 100644 --- a/Flax.sln.DotSettings +++ b/Flax.sln.DotSettings @@ -236,6 +236,7 @@ True True True + DisabledByUser True Blue True diff --git a/Source/Editor/Content/Items/VisualScriptItem.cs b/Source/Editor/Content/Items/VisualScriptItem.cs index 1d2e6a0bc..b99125f9b 100644 --- a/Source/Editor/Content/Items/VisualScriptItem.cs +++ b/Source/Editor/Content/Items/VisualScriptItem.cs @@ -249,6 +249,7 @@ namespace FlaxEditor.Content private ScriptMemberInfo[] _parameters; private ScriptMemberInfo[] _methods; private object[] _attributes; + private List> _disposing; /// /// Gets the Visual Script asset that contains this type. @@ -310,6 +311,13 @@ namespace FlaxEditor.Content internal void Dispose() { + if (_disposing != null) + { + foreach (var e in _disposing) + e(new ScriptType(this)); + _disposing.Clear(); + _disposing = null; + } if (_parameters != null) { OnAssetReloading(_asset); @@ -510,6 +518,14 @@ namespace FlaxEditor.Content } return _methods; } + + /// + public void TrackLifetime(Action disposing) + { + if (_disposing == null) + _disposing = new List>(); + _disposing.Add(disposing); + } } /// diff --git a/Source/Editor/CustomEditors/Dedicated/ScriptsEditor.cs b/Source/Editor/CustomEditors/Dedicated/ScriptsEditor.cs index fe068e4de..9227dac06 100644 --- a/Source/Editor/CustomEditors/Dedicated/ScriptsEditor.cs +++ b/Source/Editor/CustomEditors/Dedicated/ScriptsEditor.cs @@ -209,16 +209,15 @@ namespace FlaxEditor.CustomEditors.Dedicated public override DragDropEffect OnDragMove(ref Float2 location, DragData data) { var result = base.OnDragMove(ref location, data); - if (result != DragDropEffect.None) + if (result != DragDropEffect.None || _dragHandlers == null) return result; - return _dragHandlers.Effect; } /// public override void OnDragLeave() { - _dragHandlers.OnDragLeave(); + _dragHandlers?.OnDragLeave(); base.OnDragLeave(); } diff --git a/Source/Editor/CustomEditors/Editors/CollectionEditor.cs b/Source/Editor/CustomEditors/Editors/CollectionEditor.cs index 41715d654..d7d16083e 100644 --- a/Source/Editor/CustomEditors/Editors/CollectionEditor.cs +++ b/Source/Editor/CustomEditors/Editors/CollectionEditor.cs @@ -585,7 +585,7 @@ namespace FlaxEditor.CustomEditors.Editors public override DragDropEffect OnDragMove(ref Float2 location, DragData data) { var result = base.OnDragMove(ref location, data); - if (result != DragDropEffect.None) + if (result != DragDropEffect.None || _dragHandlers == null) return result; return _dragHandlers.Effect; @@ -594,7 +594,7 @@ namespace FlaxEditor.CustomEditors.Editors /// public override void OnDragLeave() { - _dragHandlers.OnDragLeave(); + _dragHandlers?.OnDragLeave(); base.OnDragLeave(); } diff --git a/Source/Editor/GUI/ComboBox.cs b/Source/Editor/GUI/ComboBox.cs index bb767040d..a7058ebc8 100644 --- a/Source/Editor/GUI/ComboBox.cs +++ b/Source/Editor/GUI/ComboBox.cs @@ -554,7 +554,7 @@ namespace FlaxEditor.GUI // Check if has selected item if (_selectedIndices != null && _selectedIndices.Count > 0) { - string text = _selectedIndices.Count == 1 ? _items[_selectedIndices[0]] : "Multiple Values"; + string text = _selectedIndices.Count == 1 ? (_selectedIndices[0] >= 0 && _selectedIndices[0] < _items.Count ? _items[_selectedIndices[0]] : "") : "Multiple Values"; // Draw text of the selected item float textScale = Height / DefaultHeight; diff --git a/Source/Editor/GUI/ContextMenu/ContextMenu.cs b/Source/Editor/GUI/ContextMenu/ContextMenu.cs index 05d62d3f2..cb197e141 100644 --- a/Source/Editor/GUI/ContextMenu/ContextMenu.cs +++ b/Source/Editor/GUI/ContextMenu/ContextMenu.cs @@ -42,15 +42,14 @@ namespace FlaxEditor.GUI.ContextMenu // Arrange controls Margin margin = _menu._itemsMargin; - float y = margin.Top; - float x = margin.Left; + float y = 0; float width = Width - margin.Width; for (int i = 0; i < _children.Count; i++) { if (_children[i] is ContextMenuItem item && item.Visible) { var height = item.Height; - item.Bounds = new Rectangle(x, y, width, height); + item.Bounds = new Rectangle(margin.Left, y, width, height); y += height + margin.Height; } } @@ -300,7 +299,6 @@ namespace FlaxEditor.GUI.ContextMenu if (_panel.Children[i] is ContextMenuChildMenu menu && menu.Text == text) return menu; } - return null; } @@ -319,7 +317,6 @@ namespace FlaxEditor.GUI.ContextMenu Parent = _panel }; } - return item; } @@ -396,10 +393,12 @@ namespace FlaxEditor.GUI.ContextMenu float height = _itemsAreaMargin.Height; int itemsLeft = MaximumItemsInViewCount; int overflowItemCount = 0; + int itemsCount = 0; for (int i = 0; i < _panel.Children.Count; i++) { if (_panel.Children[i] is ContextMenuItem item && item.Visible) { + itemsCount++; if (itemsLeft > 0) { height += item.Height + _itemsMargin.Height; @@ -412,6 +411,8 @@ namespace FlaxEditor.GUI.ContextMenu maxWidth = Mathf.Max(maxWidth, item.MinimumWidth); } } + if (itemsCount != 0) + height -= _itemsMargin.Height; // Remove item margin from top and bottom maxWidth = Mathf.Max(maxWidth + 20, MinimumWidth); // Move child arrows to accommodate scroll bar showing diff --git a/Source/Editor/GUI/ContextMenu/ContextMenuChildMenu.cs b/Source/Editor/GUI/ContextMenu/ContextMenuChildMenu.cs index 3e422f98f..af0308184 100644 --- a/Source/Editor/GUI/ContextMenu/ContextMenuChildMenu.cs +++ b/Source/Editor/GUI/ContextMenu/ContextMenuChildMenu.cs @@ -29,6 +29,15 @@ namespace FlaxEditor.GUI.ContextMenu CloseMenuOnClick = false; } + private void ShowChild(ContextMenu parentContextMenu) + { + // Hide parent CM popups and set itself as child + var vAlign = parentContextMenu.ItemsAreaMargin.Top; + var location = new Float2(Width, -vAlign); + location = PointToParent(parentContextMenu, location); + parentContextMenu.ShowChild(ContextMenu, location); + } + /// public override void Draw() { @@ -58,14 +67,12 @@ namespace FlaxEditor.GUI.ContextMenu var parentContextMenu = ParentContextMenu; if (parentContextMenu == ContextMenu) return; - if (ContextMenu.IsOpened) return; base.OnMouseEnter(location); - // Hide parent CM popups and set itself as child - parentContextMenu.ShowChild(ContextMenu, PointToParent(ParentContextMenu, new Float2(Width, 0))); + ShowChild(parentContextMenu); } /// @@ -78,8 +85,7 @@ namespace FlaxEditor.GUI.ContextMenu if (ContextMenu.IsOpened) return true; - // Hide parent CM popups and set itself as child - parentContextMenu.ShowChild(ContextMenu, PointToParent(ParentContextMenu, new Float2(Width, 0))); + ShowChild(parentContextMenu); return base.OnMouseUp(location, button); } } diff --git a/Source/Editor/GUI/Timeline/Tracks/AnimationEventTrack.cs b/Source/Editor/GUI/Timeline/Tracks/AnimationEventTrack.cs index ef0cef279..bf46e36ab 100644 --- a/Source/Editor/GUI/Timeline/Tracks/AnimationEventTrack.cs +++ b/Source/Editor/GUI/Timeline/Tracks/AnimationEventTrack.cs @@ -99,6 +99,7 @@ namespace FlaxEditor.GUI.Timeline.Tracks if (Type == ScriptType.Null) { Editor.LogError("Missing anim event type " + _instanceTypeName); + InitMissing(); return; } Instance = (AnimEvent)Type.CreateInstance(); @@ -125,20 +126,37 @@ namespace FlaxEditor.GUI.Timeline.Tracks _isRegisteredForScriptsReload = true; ScriptsBuilder.ScriptsReloadBegin += OnScriptsReloadBegin; } + Type.TrackLifetime(OnTypeDisposing); + } + + private void OnTypeDisposing(ScriptType type) + { + if (Type == type && !IsDisposing) + { + // Turn into missing script + OnScriptsReloadBegin(); + ScriptsBuilder.ScriptsReloadEnd -= OnScriptsReloadEnd; + InitMissing(); + } + } + + private void InitMissing() + { + CanDelete = true; + CanSplit = false; + CanResize = false; + TooltipText = $"Missing Anim Event Type '{_instanceTypeName}'"; + BackgroundColor = Color.Red; + Type = ScriptType.Null; + Instance = null; } internal void InitMissing(string typeName, byte[] data) { - Type = ScriptType.Null; IsContinuous = false; - CanDelete = true; - CanSplit = false; - CanResize = false; - TooltipText = $"Missing Anim Event Type '{typeName}'"; - Instance = null; - BackgroundColor = Color.Red; _instanceTypeName = typeName; _instanceData = data; + InitMissing(); } internal void Load(BinaryReader stream) diff --git a/Source/Editor/Managed/ManagedEditor.cpp b/Source/Editor/Managed/ManagedEditor.cpp index 7a9e55b88..c4c71bdfd 100644 --- a/Source/Editor/Managed/ManagedEditor.cpp +++ b/Source/Editor/Managed/ManagedEditor.cpp @@ -16,6 +16,7 @@ #include "Engine/Engine/CommandLine.h" #include "Engine/Renderer/ProbesRenderer.h" #include "Engine/Animations/Graph/AnimGraph.h" +#include "Engine/Core/ObjectsRemovalService.h" ManagedEditor::InternalOptions ManagedEditor::ManagedEditorOptions; @@ -572,6 +573,29 @@ bool ManagedEditor::EvaluateVisualScriptLocal(VisualScript* script, VisualScript return false; } +void ManagedEditor::WipeOutLeftoverSceneObjects() +{ + Array objects = Scripting::GetObjects(); + bool removedAny = false; + for (ScriptingObject* object : objects) + { + if (EnumHasAllFlags(object->Flags, ObjectFlags::IsDuringPlay) && EnumHasNoneFlags(object->Flags, ObjectFlags::WasMarkedToDelete)) + { + if (auto* sceneObject = Cast(object)) + { + if (sceneObject->HasParent()) + continue; // Skip sub-objects + + LOG(Error, "Object '{}' (ID={}, Type={}) is still in memory after play end but should be destroyed (memory leak).", sceneObject->GetNamePath(), sceneObject->GetID(), sceneObject->GetType().ToString()); + sceneObject->DeleteObject(); + removedAny = true; + } + } + } + if (removedAny) + ObjectsRemovalService::Flush(); +} + void ManagedEditor::OnEditorAssemblyLoaded(MAssembly* assembly) { ASSERT(!HasManagedInstance()); diff --git a/Source/Editor/Managed/ManagedEditor.h b/Source/Editor/Managed/ManagedEditor.h index 601b4f2cf..e51749e5f 100644 --- a/Source/Editor/Managed/ManagedEditor.h +++ b/Source/Editor/Managed/ManagedEditor.h @@ -241,6 +241,7 @@ public: API_FUNCTION(Internal) static VisualScriptStackFrame GetVisualScriptPreviousScopeFrame(); API_FUNCTION(Internal) static Array GetVisualScriptLocals(); API_FUNCTION(Internal) static bool EvaluateVisualScriptLocal(VisualScript* script, API_PARAM(Ref) VisualScriptLocal& local); + API_FUNCTION(Internal) static void WipeOutLeftoverSceneObjects(); private: void OnEditorAssemblyLoaded(MAssembly* assembly); diff --git a/Source/Editor/Scripting/ScriptType.Custom.cs b/Source/Editor/Scripting/ScriptType.Custom.cs index 6da34f448..49bdd6013 100644 --- a/Source/Editor/Scripting/ScriptType.Custom.cs +++ b/Source/Editor/Scripting/ScriptType.Custom.cs @@ -144,5 +144,11 @@ namespace FlaxEditor.Scripting { return Utils.GetEmptyArray(); } + + /// + public void TrackLifetime(Action disposing) + { + ElementType.TrackLifetime(disposing); + } } } diff --git a/Source/Editor/Scripting/ScriptType.Interfaces.cs b/Source/Editor/Scripting/ScriptType.Interfaces.cs index 2aac4babc..01a4755ec 100644 --- a/Source/Editor/Scripting/ScriptType.Interfaces.cs +++ b/Source/Editor/Scripting/ScriptType.Interfaces.cs @@ -167,6 +167,12 @@ namespace FlaxEditor.Scripting /// A bitmask comprised of one or more that specify how the search is conducted.-or- Zero (), to return an empty array. /// An array of member objects representing all methods defined for the current type that match the specified binding constraints.-or- An empty array of type member, if no methods are defined for the current type, or if none of the defined methods match the binding constraints. ScriptMemberInfo[] GetMethods(BindingFlags bindingAttr); + + /// + /// Registers delegate to be invoked upon script type disposal (except hot-reload in Editor via ). For example, can happen when user deleted Visual Script asset. + /// + /// Event to call when script type gets disposed (eg. removed asset). + void TrackLifetime(Action disposing); } /// diff --git a/Source/Editor/Scripting/ScriptType.cs b/Source/Editor/Scripting/ScriptType.cs index 5a5a3ac93..848fda30e 100644 --- a/Source/Editor/Scripting/ScriptType.cs +++ b/Source/Editor/Scripting/ScriptType.cs @@ -1395,11 +1395,21 @@ namespace FlaxEditor.Scripting } /// - /// Basic check to see if a type could be casted to another type + /// Registers delegate to be invoked upon script type disposal (except hot-reload in Editor via ). For example, can happen when user deleted Visual Script asset. + /// + /// Event to call when script type gets disposed (eg. removed asset). + public void TrackLifetime(Action disposing) + { + if (_custom != null) + _custom.TrackLifetime(disposing); + } + + /// + /// Basic check to see if a type could be cast to another type /// /// Source type /// Target type - /// True if the type can be casted + /// True if the type can be cast. public static bool CanCast(ScriptType from, ScriptType to) { if (from == to) @@ -1412,10 +1422,10 @@ namespace FlaxEditor.Scripting } /// - /// Basic check to see if this type could be casted to another type + /// Basic check to see if this type could be cast to another type /// /// Target type - /// True if the type can be casted + /// True if the type can be cast. public bool CanCastTo(ScriptType to) { return CanCast(this, to); diff --git a/Source/Editor/States/PlayingState.cs b/Source/Editor/States/PlayingState.cs index 1be2a1816..69a11cf1b 100644 --- a/Source/Editor/States/PlayingState.cs +++ b/Source/Editor/States/PlayingState.cs @@ -163,6 +163,8 @@ namespace FlaxEditor.States Editor.OnPlayBegin(); IsPlayModeStarting = false; Profiler.EndEvent(); + + Time.Synchronize(); } private void SetupEditorEnvOptions() @@ -192,7 +194,7 @@ namespace FlaxEditor.States // Restore editor scene SceneRestoring?.Invoke(); - _duplicateScenes.DeletedScenes(); + _duplicateScenes.UnloadScenes(); PluginManager.Internal_DeinitializeGamePlugins(); Editor.Internal_SetPlayMode(false); _duplicateScenes.RestoreSceneData(); @@ -209,6 +211,8 @@ namespace FlaxEditor.States Editor.OnPlayEnd(); IsPlayModeEnding = false; Profiler.EndEvent(); + + Time.Synchronize(); } } } diff --git a/Source/Editor/Surface/Archetypes/Animation.MultiBlend.cs b/Source/Editor/Surface/Archetypes/Animation.MultiBlend.cs index 1178f54de..80419ba90 100644 --- a/Source/Editor/Surface/Archetypes/Animation.MultiBlend.cs +++ b/Source/Editor/Surface/Archetypes/Animation.MultiBlend.cs @@ -6,6 +6,7 @@ using System.IO; using FlaxEditor.GUI; using FlaxEditor.GUI.Input; using FlaxEditor.Scripting; +using FlaxEditor.Surface.Undo; using FlaxEngine; using FlaxEngine.GUI; @@ -16,14 +17,13 @@ namespace FlaxEditor.Surface.Archetypes /// /// [HideInEditor] - public abstract class BlendPointsEditor : ContainerControl + public class BlendPointsEditor : ContainerControl { + private readonly Animation.MultiBlend _node; private readonly bool _is2D; - private Float2 _rangeX; - private Float2 _rangeY; - private readonly BlendPoint[] _blendPoints = new BlendPoint[Animation.MultiBlend.MaxAnimationsCount]; - private readonly Guid[] _pointsAnims = new Guid[Animation.MultiBlend.MaxAnimationsCount]; - private readonly Float2[] _pointsLocations = new Float2[Animation.MultiBlend.MaxAnimationsCount]; + private Float2 _rangeX, _rangeY; + private Float2 _debugPos = Float2.Minimum; + private readonly List _blendPoints = new List(); /// /// Represents single blend point. @@ -31,16 +31,22 @@ namespace FlaxEditor.Surface.Archetypes /// protected class BlendPoint : Control { - private static Matrix3x3 _transform = Matrix3x3.RotationZ(45.0f * Mathf.DegreesToRadians) * Matrix3x3.Translation2D(4.0f, 0.5f); private readonly BlendPointsEditor _editor; private readonly int _index; - private bool _isMouseDown; + private Float2 _mousePosOffset; + private bool _isMouseDown, _mouseMoved; + private object[] _mouseMoveStartValues; /// /// The default size for the blend points. /// public const float DefaultSize = 8.0f; + /// + /// Blend point index. + /// + public int Index => _index; + /// /// Initializes a new instance of the class. /// @@ -53,24 +59,45 @@ namespace FlaxEditor.Surface.Archetypes _index = index; } + private void EndMove() + { + _isMouseDown = false; + EndMouseCapture(); + if (_mouseMoveStartValues != null) + { + // Add undo action + _editor._node.Surface.AddBatchedUndoAction(new EditNodeValuesAction(_editor._node, _mouseMoveStartValues, true)); + _mouseMoveStartValues = null; + } + if (_mouseMoved) + _editor._node.Surface.MarkAsEdited(); + } + /// public override void OnGotFocus() { base.OnGotFocus(); - _editor.SelectedIndex = _index; + _editor._node.SelectedAnimationIndex = _index; } /// public override void Draw() { - // Cache data - var isSelected = _editor.SelectedIndex == _index; - - // Draw rotated rectangle - Render2D.PushTransform(ref _transform); - Render2D.FillRectangle(new Rectangle(0, 0, 5, 5), isSelected ? Color.Orange : Color.BlueViolet); - Render2D.PopTransform(); + // Draw dot with outline + var style = Style.Current; + var icon = Editor.Instance.Icons.VisjectBoxClosed32; + var size = Height; + var rect = new Rectangle(new Float2(size * -0.5f) + Size * 0.5f, new Float2(size)); + var outline = Color.Black; // Shadow + if (_isMouseDown) + outline = style.SelectionBorder; + else if (IsMouseOver) + outline = style.BorderHighlighted; + else if (_editor._node.SelectedAnimationIndex == _index) + outline = style.BackgroundSelected; + Render2D.DrawSprite(icon, rect.MakeExpanded(4.0f), outline); + Render2D.DrawSprite(icon, rect, style.Foreground); } /// @@ -80,6 +107,9 @@ namespace FlaxEditor.Surface.Archetypes { Focus(); _isMouseDown = true; + _mouseMoved = false; + _mouseMoveStartValues = null; + _mousePosOffset = -location; StartMouseCapture(); return true; } @@ -92,8 +122,7 @@ namespace FlaxEditor.Surface.Archetypes { if (button == MouseButton.Left && _isMouseDown) { - _isMouseDown = false; - EndMouseCapture(); + EndMove(); return true; } @@ -103,9 +132,26 @@ namespace FlaxEditor.Surface.Archetypes /// public override void OnMouseMove(Float2 location) { - if (_isMouseDown) + if (_isMouseDown && (_mouseMoved || Float2.DistanceSquared(location, _mousePosOffset) > 16.0f)) { - _editor.SetLocation(_index, _editor.BlendPointPosToBlendSpacePos(Location + location)); + if (!_mouseMoved) + { + // Capture initial state for undo + _mouseMoved = true; + _mouseMoveStartValues = _editor._node.Surface.Undo != null ? (object[])_editor._node.Values.Clone() : null; + } + + var newLocation = Location + location + _mousePosOffset; + newLocation = _editor.BlendPointPosToBlendSpacePos(newLocation); + if (Root != null && Root.GetKey(KeyboardKeys.Control)) + { + var data0 = (Float4)_editor._node.Values[0]; + var rangeX = new Float2(data0.X, data0.Y); + var rangeY = _editor._is2D ? new Float2(data0.Z, data0.W) : Float2.One; + var grid = new Float2(Mathf.Abs(rangeX.Y - rangeX.X) * 0.01f, Mathf.Abs(rangeY.X - rangeY.Y) * 0.01f); + newLocation = Float2.SnapToGrid(newLocation, grid); + } + _editor.SetLocation(_index, newLocation); } base.OnMouseMove(location); @@ -116,8 +162,7 @@ namespace FlaxEditor.Surface.Archetypes { if (_isMouseDown) { - _isMouseDown = false; - EndMouseCapture(); + EndMove(); } base.OnMouseLeave(); @@ -128,8 +173,7 @@ namespace FlaxEditor.Surface.Archetypes { if (_isMouseDown) { - _isMouseDown = false; - EndMouseCapture(); + EndMove(); } base.OnLostFocus(); @@ -149,40 +193,130 @@ namespace FlaxEditor.Surface.Archetypes /// public bool Is2D => _is2D; + /// + /// Blend points count. + /// + public int PointsCount => (_node.Values.Length - 4) / 2; // 4 node values + 2 per blend point + /// /// Initializes a new instance of the class. /// + /// The node. /// The value indicating whether blend space is 2D, otherwise it is 1D. /// The X location. /// The Y location. /// The width. /// The height. - public BlendPointsEditor(bool is2D, float x, float y, float width, float height) + public BlendPointsEditor(Animation.MultiBlend node, bool is2D, float x, float y, float width, float height) : base(x, y, width, height) { + _node = node; _is2D = is2D; } - /// - /// Gets the blend space data. - /// - /// The space range for X axis (X-width, Y-height). - /// The space range for Y axis (X-width, Y-height). - /// The points anims (input array to fill of size equal 14). - /// The points locations (input array to fill of size equal 14). - public abstract void GetData(out Float2 rangeX, out Float2 rangeY, Guid[] pointsAnims, Float2[] pointsLocations); + internal void AddPoint() + { + // Add random point within range + var rand = new Float2(Mathf.Frac((float)Platform.TimeSeconds), (Platform.TimeCycles % 10000) / 10000.0f); + AddPoint(Float2.Lerp(new Float2(_rangeX.X, _rangeY.X), new Float2(_rangeX.Y, _rangeY.Y), rand)); + } + + private void AddPoint(Float2 location) + { + // Reuse existing animation + var count = PointsCount; + Guid id = Guid.Empty; + for (int i = 0; i < count; i++) + { + id = (Guid)_node.Values[5 + i * 2]; + if (id != Guid.Empty) + break; + } + if (id == Guid.Empty) + { + // Just use the first anim from project, user will change it + var ids = FlaxEngine.Content.GetAllAssetsByType(typeof(FlaxEngine.Animation)); + if (ids.Length != 0) + id = ids[0]; + else + return; + } + + AddPoint(id, location); + } /// - /// Gets or sets the index of the selected blend point. + /// Sets the blend point asset. /// - public abstract int SelectedIndex { get; set; } + /// The asset. + /// The location. + public void AddPoint(Guid asset, Float2 location) + { + // Find the first free slot + var count = PointsCount; + if (count == Animation.MultiBlend.MaxAnimationsCount) + return; + var values = (object[])_node.Values.Clone(); + var index = 0; + for (; index < count; index++) + { + var dataB = (Guid)_node.Values[5 + index * 2]; + if (dataB == Guid.Empty) + break; + } + if (index == count) + { + // Add another blend point + Array.Resize(ref values, values.Length + 2); + } + + values[4 + index * 2] = new Float4(location.X, _is2D ? location.Y : 0.0f, 0, 1.0f); + values[5 + index * 2] = asset; + _node.SetValues(values); + + // Auto-select + _node.SelectedAnimationIndex = index; + } + + /// + /// Sets the blend point asset. + /// + /// The index. + /// The asset. + /// True to use undo action. + public void SetAsset(int index, Guid asset, bool withUndo = true) + { + if (withUndo) + { + _node.SetValue(5 + index * 2, asset); + } + else + { + _node.Values[5 + index * 2] = asset; + _node.Surface.MarkAsEdited(); + } + + _node.UpdateUI(); + } /// /// Sets the blend point location. /// /// The index. /// The location. - public abstract void SetLocation(int index, Float2 location); + public void SetLocation(int index, Float2 location) + { + var dataA = (Float4)_node.Values[4 + index * 2]; + var ranges = (Float4)_node.Values[0]; + + dataA.X = Mathf.Clamp(location.X, ranges.X, ranges.Y); + if (_is2D) + dataA.Y = Mathf.Clamp(location.Y, ranges.Z, ranges.W); + + _node.Values[4 + index * 2] = dataA; + + _node.UpdateUI(); + } /// /// Gets the blend points area. @@ -249,10 +383,23 @@ namespace FlaxEditor.Surface.Archetypes public override void Update(float deltaTime) { // Synchronize blend points collection - GetData(out _rangeX, out _rangeY, _pointsAnims, _pointsLocations); - for (int i = 0; i < Animation.MultiBlend.MaxAnimationsCount; i++) + var data0 = (Float4)_node.Values[0]; + _rangeX = new Float2(data0.X, data0.Y); + _rangeY = _is2D ? new Float2(data0.Z, data0.W) : Float2.Zero; + var count = PointsCount; + while (_blendPoints.Count > count) { - if (_pointsAnims[i] != Guid.Empty) + _blendPoints[count].Dispose(); + _blendPoints.RemoveAt(count); + } + while (_blendPoints.Count < count) + _blendPoints.Add(null); + for (int i = 0; i < count; i++) + { + var animId = (Guid)_node.Values[5 + i * 2]; + var dataA = (Float4)_node.Values[4 + i * 2]; + var location = new Float2(Mathf.Clamp(dataA.X, _rangeX.X, _rangeX.Y), _is2D ? Mathf.Clamp(dataA.Y, _rangeY.X, _rangeY.Y) : 0.0f); + if (animId != Guid.Empty) { if (_blendPoints[i] == null) { @@ -264,7 +411,13 @@ namespace FlaxEditor.Surface.Archetypes } // Update blend point - _blendPoints[i].Location = BlendSpacePosToBlendPointPos(_pointsLocations[i]); + _blendPoints[i].Location = BlendSpacePosToBlendPointPos(location); + var asset = Editor.Instance.ContentDatabase.FindAsset(animId); + var tooltip = asset?.ShortName ?? string.Empty; + tooltip += "\nX: " + location.X; + if (_is2D) + tooltip += "\nY: " + location.Y; + _blendPoints[i].TooltipText = tooltip; } else { @@ -277,6 +430,26 @@ namespace FlaxEditor.Surface.Archetypes } } + // Debug current playback position + if (((AnimGraphSurface)_node.Surface).TryGetTraceEvent(_node, out var traceEvent)) + { + if (_is2D) + { + unsafe + { + // Unpack xy from 32-bits + Half2 packed = *(Half2*)&traceEvent.Value; + _debugPos = (Float2)packed; + } + } + else + _debugPos = new Float2(traceEvent.Value, 0.0f); + } + else + { + _debugPos = Float2.Minimum; + } + base.Update(deltaTime); } @@ -293,14 +466,90 @@ namespace FlaxEditor.Surface.Archetypes } } + /// + public override bool OnMouseDown(Float2 location, MouseButton button) + { + if (base.OnMouseDown(location, button)) + return true; + + Focus(); + return true; + } + + /// + public override bool OnMouseUp(Float2 location, MouseButton button) + { + if (base.OnMouseUp(location, button)) + return true; + + if (button == MouseButton.Right) + { + // Show context menu + var menu = new FlaxEditor.GUI.ContextMenu.ContextMenu(); + var b = menu.AddButton("Add point", OnAddPoint); + b.Tag = location; + b.Enabled = PointsCount < Animation.MultiBlend.MaxAnimationsCount; + if (GetChildAt(location) is BlendPoint blendPoint) + { + b = menu.AddButton("Remove point", OnRemovePoint); + b.Tag = blendPoint.Index; + b.TooltipText = blendPoint.TooltipText; + } + menu.Show(this, location); + } + + return true; + } + + private void OnAddPoint(FlaxEditor.GUI.ContextMenu.ContextMenuButton b) + { + AddPoint(BlendPointPosToBlendSpacePos((Float2)b.Tag)); + } + + private void OnRemovePoint(FlaxEditor.GUI.ContextMenu.ContextMenuButton b) + { + SetAsset((int)b.Tag, Guid.Empty); + } + + private void DrawAxis(bool vertical, Float2 start, Float2 end, ref Color gridColor, ref Color labelColor, Font labelFont, float value, bool isLast) + { + // Draw line + Render2D.DrawLine(start, end, gridColor); + + // Draw label + var labelWidth = 50.0f; + var labelHeight = 10.0f; + var labelMargin = 2.0f; + string label = Utils.RoundTo2DecimalPlaces(value).ToString(System.Globalization.CultureInfo.InvariantCulture); + var hAlign = TextAlignment.Near; + Rectangle labelRect; + if (vertical) + { + labelRect = new Rectangle(start.X + labelMargin * 2, start.Y, labelWidth, labelHeight); + if (isLast) + return; // Don't overlap with the first horizontal label + } + else + { + labelRect = new Rectangle(start.X + labelMargin, start.Y - labelHeight - labelMargin, labelWidth, labelHeight); + if (isLast) + { + labelRect.X = start.X - labelMargin - labelRect.Width; + hAlign = TextAlignment.Far; + } + } + Render2D.DrawText(labelFont, label, labelRect, labelColor, hAlign, TextAlignment.Center, TextWrapping.NoWrap, 1.0f, 0.7f); + } + /// public override void Draw() { - // Cache data var style = Style.Current; var rect = new Rectangle(Float2.Zero, Size); var containsFocus = ContainsFocus; GetPointsArea(out var pointsArea); + var data0 = (Float4)_node.Values[0]; + var rangeX = new Float2(data0.X, data0.Y); // Background Render2D.DrawRectangle(rect, IsMouseOver ? style.TextBoxBackgroundSelected : style.TextBoxBackground); @@ -309,19 +558,27 @@ namespace FlaxEditor.Surface.Archetypes // Grid int splits = 10; var gridColor = style.TextBoxBackgroundSelected * 1.1f; + var labelColor = style.ForegroundDisabled; + var labelFont = style.FontSmall; //var blendArea = BlendAreaRect; var blendArea = pointsArea; - for (int i = 0; i < splits; i++) + + for (int i = 0; i <= splits; i++) { - float x = blendArea.Left + blendArea.Width * i / splits; - Render2D.DrawLine(new Float2(x, 1), new Float2(x, rect.Height - 2), gridColor); + float alpha = (float)i / splits; + float x = blendArea.Left + blendArea.Width * alpha; + float value = Mathf.Lerp(rangeX.X, rangeX.Y, alpha); + DrawAxis(false, new Float2(x, rect.Height - 2), new Float2(x, 1), ref gridColor, ref labelColor, labelFont, value, i == splits); } if (_is2D) { - for (int i = 0; i < splits; i++) + var rangeY = new Float2(data0.Z, data0.W); + for (int i = 0; i <= splits; i++) { - float y = blendArea.Top + blendArea.Height * i / splits; - Render2D.DrawLine(new Float2(1, y), new Float2(rect.Width - 2, y), gridColor); + float alpha = (float)i / splits; + float y = blendArea.Top + blendArea.Height * alpha; + float value = Mathf.Lerp(rangeY.X, rangeY.Y, alpha); + DrawAxis(true, new Float2(1, y), new Float2(rect.Width - 2, y), ref gridColor, ref labelColor, labelFont, value, i == splits); } } else @@ -330,11 +587,24 @@ namespace FlaxEditor.Surface.Archetypes Render2D.DrawLine(new Float2(1, y), new Float2(rect.Width - 2, y), gridColor); } - // Base base.Draw(); + // Draw debug position + if (_debugPos.X > float.MinValue) + { + // Draw dot with outline + var icon = Editor.Instance.Icons.VisjectBoxOpen32; + var size = BlendPoint.DefaultSize; + var debugPos = BlendSpacePosToBlendPointPos(_debugPos); + var debugRect = new Rectangle(debugPos + new Float2(size * -0.5f) + size * 0.5f, new Float2(size)); + var outline = Color.Black; // Shadow + Render2D.DrawSprite(icon, debugRect.MakeExpanded(2.0f), outline); + Render2D.DrawSprite(icon, debugRect, style.ProgressNormal); + } + // Frame - Render2D.DrawRectangle(new Rectangle(1, 1, rect.Width - 2, rect.Height - 2), containsFocus ? style.ProgressNormal : style.BackgroundSelected); + var frameColor = containsFocus ? style.BackgroundSelected : (IsMouseOver ? style.ForegroundGrey : style.ForegroundDisabled); + Render2D.DrawRectangle(new Rectangle(1, 1, rect.Width - 2, rect.Height - 2), frameColor); } } @@ -346,6 +616,14 @@ namespace FlaxEditor.Surface.Archetypes /// public abstract class MultiBlend : SurfaceNode { + private Button _addButton; + private Button _removeButton; + + /// + /// The blend space editor. + /// + protected BlendPointsEditor _editor; + /// /// The selected animation label. /// @@ -379,7 +657,7 @@ namespace FlaxEditor.Surface.Archetypes /// /// The maximum animations amount to blend per node. /// - public const int MaxAnimationsCount = 14; + public const int MaxAnimationsCount = 255; /// /// Gets or sets the index of the selected animation. @@ -387,7 +665,12 @@ namespace FlaxEditor.Surface.Archetypes public int SelectedAnimationIndex { get => _selectedAnimation.SelectedIndex; - set => _selectedAnimation.SelectedIndex = value; + set + { + OnSelectedAnimationPopupShowing(_selectedAnimation); + _selectedAnimation.SelectedIndex = value; + UpdateUI(); + } } /// @@ -402,20 +685,14 @@ namespace FlaxEditor.Surface.Archetypes Text = "Selected Animation:", Parent = this }; - _selectedAnimation = new ComboBox(_selectedAnimationLabel.X, 4 * layoutOffsetY, _selectedAnimationLabel.Width) { + TooltipText = "Select blend point to view and edit it", Parent = this }; - _selectedAnimation.PopupShowing += OnSelectedAnimationPopupShowing; _selectedAnimation.SelectedIndexChanged += OnSelectedAnimationChanged; - var items = new List(MaxAnimationsCount); - while (items.Count < MaxAnimationsCount) - items.Add(string.Empty); - _selectedAnimation.Items = items; - _animationPicker = new AssetPicker(new ScriptType(typeof(FlaxEngine.Animation)), new Float2(_selectedAnimation.Left, _selectedAnimation.Bottom + 4)) { Parent = this @@ -428,20 +705,36 @@ namespace FlaxEditor.Surface.Archetypes Text = "Speed:", Parent = this }; - _animationSpeed = new FloatValueBox(1.0f, _animationSpeedLabel.Right + 4, _animationSpeedLabel.Y, _selectedAnimation.Right - _animationSpeedLabel.Right - 4) { SlideSpeed = 0.01f, Parent = this }; _animationSpeed.ValueChanged += OnAnimationSpeedValueChanged; + + var buttonsSize = 12; + _addButton = new Button(_selectedAnimation.Right - buttonsSize, _selectedAnimation.Bottom + 4, buttonsSize, buttonsSize) + { + Text = "+", + TooltipText = "Add a new blend point", + Parent = this + }; + _addButton.Clicked += OnAddButtonClicked; + _removeButton = new Button(_addButton.Left - buttonsSize - 4, _addButton.Y, buttonsSize, buttonsSize) + { + Text = "-", + TooltipText = "Remove selected blend point", + Parent = this + }; + _removeButton.Clicked += OnRemoveButtonClicked; } private void OnSelectedAnimationPopupShowing(ComboBox comboBox) { var items = comboBox.Items; items.Clear(); - for (var i = 0; i < MaxAnimationsCount; i++) + var count = _editor.PointsCount; + for (var i = 0; i < count; i++) { var animId = (Guid)Values[5 + i * 2]; var path = string.Empty; @@ -484,6 +777,16 @@ namespace FlaxEditor.Surface.Archetypes } } + private void OnAddButtonClicked() + { + _editor.AddPoint(); + } + + private void OnRemoveButtonClicked() + { + _editor.SetAsset(SelectedAnimationIndex, Guid.Empty); + } + /// /// Updates the editor UI. /// @@ -491,7 +794,7 @@ namespace FlaxEditor.Surface.Archetypes /// if set to true is selection valid. /// The packed data 0. /// The packed data 1. - protected virtual void UpdateUI(int selectedIndex, bool isValid, ref Float4 data0, ref Guid data1) + public virtual void UpdateUI(int selectedIndex, bool isValid, ref Float4 data0, ref Guid data1) { if (isValid) { @@ -511,19 +814,21 @@ namespace FlaxEditor.Surface.Archetypes _animationPicker.Enabled = isValid; _animationSpeedLabel.Enabled = isValid; _animationSpeed.Enabled = isValid; + _addButton.Enabled = _editor.PointsCount < MaxAnimationsCount; + _removeButton.Enabled = isValid && data1 != Guid.Empty; } /// /// Updates the editor UI. /// - protected void UpdateUI() + public void UpdateUI() { if (_isUpdatingUI) return; _isUpdatingUI = true; var selectedIndex = _selectedAnimation.SelectedIndex; - var isValid = selectedIndex != -1; + var isValid = selectedIndex >= 0 && selectedIndex < _editor.PointsCount; Float4 data0; Guid data1; if (isValid) @@ -549,6 +854,16 @@ namespace FlaxEditor.Surface.Archetypes UpdateUI(); } + /// + public override void OnSpawned(SurfaceNodeActions action) + { + base.OnSpawned(action); + + // Select the first animation to make setup easier + OnSelectedAnimationPopupShowing(_selectedAnimation); + _selectedAnimation.SelectedIndex = 0; + } + /// public override void OnValuesChanged() { @@ -566,67 +881,6 @@ namespace FlaxEditor.Surface.Archetypes { private readonly Label _animationXLabel; private readonly FloatValueBox _animationX; - private readonly Editor _editor; - - /// - /// The Multi Blend 1D blend space editor. - /// - /// - protected class Editor : BlendPointsEditor - { - private MultiBlend1D _node; - - /// - /// Initializes a new instance of the class. - /// - /// The parent Visject Node node. - /// The X location. - /// The Y location. - /// The width. - /// The height. - public Editor(MultiBlend1D node, float x, float y, float width, float height) - : base(false, x, y, width, height) - { - _node = node; - } - - /// - public override void GetData(out Float2 rangeX, out Float2 rangeY, Guid[] pointsAnims, Float2[] pointsLocations) - { - var data0 = (Float4)_node.Values[0]; - rangeX = new Float2(data0.X, data0.Y); - rangeY = Float2.Zero; - for (int i = 0; i < MaxAnimationsCount; i++) - { - var dataA = (Float4)_node.Values[4 + i * 2]; - var dataB = (Guid)_node.Values[5 + i * 2]; - - pointsAnims[i] = dataB; - pointsLocations[i] = new Float2(Mathf.Clamp(dataA.X, rangeX.X, rangeX.Y), 0.0f); - } - } - - /// - public override int SelectedIndex - { - get => _node.SelectedAnimationIndex; - set => _node.SelectedAnimationIndex = value; - } - - /// - public override void SetLocation(int index, Float2 location) - { - var dataA = (Float4)_node.Values[4 + index * 2]; - var ranges = (Float4)_node.Values[0]; - - dataA.X = Mathf.Clamp(location.X, ranges.X, ranges.Y); - - _node.Values[4 + index * 2] = dataA; - _node.Surface.MarkAsEdited(); - - _node.UpdateUI(); - } - } /// public MultiBlend1D(uint id, VisjectSurfaceContext context, NodeArchetype nodeArch, GroupArchetype groupArch) @@ -646,11 +900,8 @@ namespace FlaxEditor.Surface.Archetypes }; _animationX.ValueChanged += OnAnimationXChanged; - _editor = new Editor(this, - FlaxEditor.Surface.Constants.NodeMarginX, - _animationX.Bottom + 4.0f, - Width - FlaxEditor.Surface.Constants.NodeMarginX * 2.0f, - 120.0f); + var size = Width - FlaxEditor.Surface.Constants.NodeMarginX * 2.0f; + _editor = new BlendPointsEditor(this, false, FlaxEditor.Surface.Constants.NodeMarginX, _animationX.Bottom + 4.0f, size, 120.0f); _editor.Parent = this; } @@ -670,7 +921,7 @@ namespace FlaxEditor.Surface.Archetypes } /// - protected override void UpdateUI(int selectedIndex, bool isValid, ref Float4 data0, ref Guid data1) + public override void UpdateUI(int selectedIndex, bool isValid, ref Float4 data0, ref Guid data1) { base.UpdateUI(selectedIndex, isValid, ref data0, ref data1); @@ -700,68 +951,6 @@ namespace FlaxEditor.Surface.Archetypes private readonly FloatValueBox _animationX; private readonly Label _animationYLabel; private readonly FloatValueBox _animationY; - private readonly Editor _editor; - - /// - /// The Multi Blend 2D blend space editor. - /// - /// - protected class Editor : BlendPointsEditor - { - private MultiBlend2D _node; - - /// - /// Initializes a new instance of the class. - /// - /// The parent Visject Node node. - /// The X location. - /// The Y location. - /// The width. - /// The height. - public Editor(MultiBlend2D node, float x, float y, float width, float height) - : base(true, x, y, width, height) - { - _node = node; - } - - /// - public override void GetData(out Float2 rangeX, out Float2 rangeY, Guid[] pointsAnims, Float2[] pointsLocations) - { - var data0 = (Float4)_node.Values[0]; - rangeX = new Float2(data0.X, data0.Y); - rangeY = new Float2(data0.Z, data0.W); - for (int i = 0; i < MaxAnimationsCount; i++) - { - var dataA = (Float4)_node.Values[4 + i * 2]; - var dataB = (Guid)_node.Values[5 + i * 2]; - - pointsAnims[i] = dataB; - pointsLocations[i] = new Float2(Mathf.Clamp(dataA.X, rangeX.X, rangeX.Y), Mathf.Clamp(dataA.Y, rangeY.X, rangeY.Y)); - } - } - - /// - public override int SelectedIndex - { - get => _node.SelectedAnimationIndex; - set => _node.SelectedAnimationIndex = value; - } - - /// - public override void SetLocation(int index, Float2 location) - { - var dataA = (Float4)_node.Values[4 + index * 2]; - var ranges = (Float4)_node.Values[0]; - - dataA.X = Mathf.Clamp(location.X, ranges.X, ranges.Y); - dataA.Y = Mathf.Clamp(location.Y, ranges.Z, ranges.W); - - _node.Values[4 + index * 2] = dataA; - _node.Surface.MarkAsEdited(); - - _node.UpdateUI(); - } - } /// public MultiBlend2D(uint id, VisjectSurfaceContext context, NodeArchetype nodeArch, GroupArchetype groupArch) @@ -795,11 +984,8 @@ namespace FlaxEditor.Surface.Archetypes }; _animationY.ValueChanged += OnAnimationYChanged; - _editor = new Editor(this, - FlaxEditor.Surface.Constants.NodeMarginX, - _animationY.Bottom + 4.0f, - Width - FlaxEditor.Surface.Constants.NodeMarginX * 2.0f, - 120.0f); + var size = Width - FlaxEditor.Surface.Constants.NodeMarginX * 2.0f; + _editor = new BlendPointsEditor(this, true, FlaxEditor.Surface.Constants.NodeMarginX, _animationY.Bottom + 4.0f, size, size); _editor.Parent = this; } @@ -834,7 +1020,7 @@ namespace FlaxEditor.Surface.Archetypes } /// - protected override void UpdateUI(int selectedIndex, bool isValid, ref Float4 data0, ref Guid data1) + public override void UpdateUI(int selectedIndex, bool isValid, ref Float4 data0, ref Guid data1) { base.UpdateUI(selectedIndex, isValid, ref data0, ref data1); diff --git a/Source/Editor/Surface/Archetypes/Animation.cs b/Source/Editor/Surface/Archetypes/Animation.cs index 98f238391..1d897c395 100644 --- a/Source/Editor/Surface/Archetypes/Animation.cs +++ b/Source/Editor/Surface/Archetypes/Animation.cs @@ -621,7 +621,7 @@ namespace FlaxEditor.Surface.Archetypes Create = (id, context, arch, groupArch) => new MultiBlend1D(id, context, arch, groupArch), Title = "Multi Blend 1D", Description = "Animation blending in 1D", - Flags = NodeFlags.AnimGraph, + Flags = NodeFlags.AnimGraph | NodeFlags.VariableValuesSize, Size = new Float2(420, 300), DefaultValues = new object[] { @@ -633,19 +633,6 @@ namespace FlaxEditor.Surface.Archetypes // Per blend sample data new Float4(0, 0, 0, 1.0f), Guid.Empty, - new Float4(0, 0, 0, 1.0f), Guid.Empty, - new Float4(0, 0, 0, 1.0f), Guid.Empty, - new Float4(0, 0, 0, 1.0f), Guid.Empty, - new Float4(0, 0, 0, 1.0f), Guid.Empty, - new Float4(0, 0, 0, 1.0f), Guid.Empty, - new Float4(0, 0, 0, 1.0f), Guid.Empty, - new Float4(0, 0, 0, 1.0f), Guid.Empty, - new Float4(0, 0, 0, 1.0f), Guid.Empty, - new Float4(0, 0, 0, 1.0f), Guid.Empty, - new Float4(0, 0, 0, 1.0f), Guid.Empty, - new Float4(0, 0, 0, 1.0f), Guid.Empty, - new Float4(0, 0, 0, 1.0f), Guid.Empty, - new Float4(0, 0, 0, 1.0f), Guid.Empty, }, Elements = new[] { @@ -658,10 +645,10 @@ namespace FlaxEditor.Surface.Archetypes NodeElementArchetype.Factory.Input(2, "Start Position", true, typeof(float), 3, 3), // Axis X - NodeElementArchetype.Factory.Input(4, "X", true, typeof(float), 4), - NodeElementArchetype.Factory.Text(30, 4 * Surface.Constants.LayoutOffsetY, "(min: max: )"), - NodeElementArchetype.Factory.Float(60, 4 * Surface.Constants.LayoutOffsetY, 0, 0), - NodeElementArchetype.Factory.Float(145, 4 * Surface.Constants.LayoutOffsetY, 0, 1), + NodeElementArchetype.Factory.Input(3, "X", true, typeof(float), 4), + NodeElementArchetype.Factory.Text(30, 3 * Surface.Constants.LayoutOffsetY + 2, "(min: max: )"), + NodeElementArchetype.Factory.Float(60, 3 * Surface.Constants.LayoutOffsetY + 2, 0, 0), + NodeElementArchetype.Factory.Float(145, 3 * Surface.Constants.LayoutOffsetY + 2, 0, 1), } }, new NodeArchetype @@ -670,8 +657,8 @@ namespace FlaxEditor.Surface.Archetypes Create = (id, context, arch, groupArch) => new MultiBlend2D(id, context, arch, groupArch), Title = "Multi Blend 2D", Description = "Animation blending in 2D", - Flags = NodeFlags.AnimGraph, - Size = new Float2(420, 320), + Flags = NodeFlags.AnimGraph | NodeFlags.VariableValuesSize, + Size = new Float2(420, 620), DefaultValues = new object[] { // Node data @@ -682,19 +669,6 @@ namespace FlaxEditor.Surface.Archetypes // Per blend sample data new Float4(0, 0, 0, 1.0f), Guid.Empty, - new Float4(0, 0, 0, 1.0f), Guid.Empty, - new Float4(0, 0, 0, 1.0f), Guid.Empty, - new Float4(0, 0, 0, 1.0f), Guid.Empty, - new Float4(0, 0, 0, 1.0f), Guid.Empty, - new Float4(0, 0, 0, 1.0f), Guid.Empty, - new Float4(0, 0, 0, 1.0f), Guid.Empty, - new Float4(0, 0, 0, 1.0f), Guid.Empty, - new Float4(0, 0, 0, 1.0f), Guid.Empty, - new Float4(0, 0, 0, 1.0f), Guid.Empty, - new Float4(0, 0, 0, 1.0f), Guid.Empty, - new Float4(0, 0, 0, 1.0f), Guid.Empty, - new Float4(0, 0, 0, 1.0f), Guid.Empty, - new Float4(0, 0, 0, 1.0f), Guid.Empty, }, Elements = new[] { @@ -707,16 +681,16 @@ namespace FlaxEditor.Surface.Archetypes NodeElementArchetype.Factory.Input(2, "Start Position", true, typeof(float), 3, 3), // Axis X - NodeElementArchetype.Factory.Input(4, "X", true, typeof(float), 4), - NodeElementArchetype.Factory.Text(30, 4 * Surface.Constants.LayoutOffsetY, "(min: max: )"), - NodeElementArchetype.Factory.Float(60, 4 * Surface.Constants.LayoutOffsetY, 0, 0), - NodeElementArchetype.Factory.Float(145, 4 * Surface.Constants.LayoutOffsetY, 0, 1), + NodeElementArchetype.Factory.Input(3, "X", true, typeof(float), 4), + NodeElementArchetype.Factory.Text(30, 3 * Surface.Constants.LayoutOffsetY + 2, "(min: max: )"), + NodeElementArchetype.Factory.Float(60, 3 * Surface.Constants.LayoutOffsetY + 2, 0, 0), + NodeElementArchetype.Factory.Float(145, 3 * Surface.Constants.LayoutOffsetY + 2, 0, 1), // Axis Y - NodeElementArchetype.Factory.Input(5, "Y", true, typeof(float), 5), - NodeElementArchetype.Factory.Text(30, 5 * Surface.Constants.LayoutOffsetY, "(min: max: )"), - NodeElementArchetype.Factory.Float(60, 5 * Surface.Constants.LayoutOffsetY, 0, 2), - NodeElementArchetype.Factory.Float(145, 5 * Surface.Constants.LayoutOffsetY, 0, 3), + NodeElementArchetype.Factory.Input(4, "Y", true, typeof(float), 5), + NodeElementArchetype.Factory.Text(30, 4 * Surface.Constants.LayoutOffsetY + 2, "(min: max: )"), + NodeElementArchetype.Factory.Float(60, 4 * Surface.Constants.LayoutOffsetY + 2, 0, 2), + NodeElementArchetype.Factory.Float(145, 4 * Surface.Constants.LayoutOffsetY + 2, 0, 3), } }, new NodeArchetype diff --git a/Source/Editor/Surface/Archetypes/BehaviorTree.cs b/Source/Editor/Surface/Archetypes/BehaviorTree.cs index 1fa4f8402..c4bb13c5e 100644 --- a/Source/Editor/Surface/Archetypes/BehaviorTree.cs +++ b/Source/Editor/Surface/Archetypes/BehaviorTree.cs @@ -104,6 +104,16 @@ namespace FlaxEditor.Surface.Archetypes } } + private void OnTypeDisposing(ScriptType type) + { + if (_type == type && !IsDisposing) + { + // Turn into missing script + _type = ScriptType.Null; + Instance = null; + } + } + public override void OnLoaded(SurfaceNodeActions action) { base.OnLoaded(action); @@ -113,6 +123,7 @@ namespace FlaxEditor.Surface.Archetypes _type = TypeUtils.GetType(typeName); if (_type != null) { + _type.TrackLifetime(OnTypeDisposing); TooltipText = Editor.Instance.CodeDocs.GetTooltip(_type); try { diff --git a/Source/Editor/Surface/NodeFlags.cs b/Source/Editor/Surface/NodeFlags.cs index 2c02489ee..d3b469772 100644 --- a/Source/Editor/Surface/NodeFlags.cs +++ b/Source/Editor/Surface/NodeFlags.cs @@ -73,6 +73,11 @@ namespace FlaxEditor.Surface /// BehaviorTreeGraph = 1024, + /// + /// Node can have different amount of items in values array. + /// + VariableValuesSize = 2048, + /// /// Node can be used in the all visual graphs. /// diff --git a/Source/Editor/Surface/SurfaceNode.cs b/Source/Editor/Surface/SurfaceNode.cs index fdb323de9..0eb739d72 100644 --- a/Source/Editor/Surface/SurfaceNode.cs +++ b/Source/Editor/Surface/SurfaceNode.cs @@ -951,15 +951,20 @@ namespace FlaxEditor.Surface { if (_isDuringValuesEditing || !Surface.CanEdit) return; - - if (values == null || Values == null || values.Length != Values.Length) + if (values == null || Values == null) + throw new ArgumentException(); + bool resize = values.Length != Values.Length; + if (resize && (Archetype.Flags & NodeFlags.VariableValuesSize) == 0) throw new ArgumentException(); _isDuringValuesEditing = true; var before = Surface.Undo != null ? (object[])Values.Clone() : null; - Array.Copy(values, Values, values.Length); + if (resize) + Values = (object[])values.Clone(); + else + Array.Copy(values, Values, values.Length); OnValuesChanged(); Surface.MarkAsEdited(graphEdited); @@ -1057,6 +1062,20 @@ namespace FlaxEditor.Surface } } + /// + public override bool OnMouseDown(Float2 location, MouseButton button) + { + if (base.OnMouseDown(location, button)) + return true; + + if (button == MouseButton.Left && (Archetype.Flags & NodeFlags.NoCloseButton) == 0 && _closeButtonRect.Contains(ref location)) + return true; + if (button == MouseButton.Right) + return true; + + return false; + } + /// public override bool OnMouseUp(Float2 location, MouseButton button) { @@ -1064,13 +1083,10 @@ namespace FlaxEditor.Surface return true; // Close - if (button == MouseButton.Left && (Archetype.Flags & NodeFlags.NoCloseButton) == 0) + if (button == MouseButton.Left && (Archetype.Flags & NodeFlags.NoCloseButton) == 0 && _closeButtonRect.Contains(ref location)) { - if (_closeButtonRect.Contains(ref location)) - { - Surface.Delete(this); - return true; - } + Surface.Delete(this); + return true; } // Secondary Context Menu diff --git a/Source/Editor/Surface/Undo/EditNodeValuesAction.cs b/Source/Editor/Surface/Undo/EditNodeValuesAction.cs index 24dd686f2..8425fd65f 100644 --- a/Source/Editor/Surface/Undo/EditNodeValuesAction.cs +++ b/Source/Editor/Surface/Undo/EditNodeValuesAction.cs @@ -14,6 +14,7 @@ namespace FlaxEditor.Surface.Undo private ContextHandle _context; private readonly uint _nodeId; private readonly bool _graphEdited; + private readonly bool _resize; private object[] _before; private object[] _after; @@ -23,7 +24,8 @@ namespace FlaxEditor.Surface.Undo throw new ArgumentNullException(nameof(before)); if (node?.Values == null) throw new ArgumentNullException(nameof(node)); - if (before.Length != node.Values.Length) + _resize = before.Length != node.Values.Length; + if (_resize && (node.Archetype.Flags & NodeFlags.VariableValuesSize) == 0) throw new ArgumentException(nameof(before)); _surface = node.Surface; @@ -48,7 +50,10 @@ namespace FlaxEditor.Surface.Undo throw new Exception("Missing node."); node.SetIsDuringValuesEditing(true); - Array.Copy(_after, node.Values, _after.Length); + if (_resize) + node.Values = (object[])_after.Clone(); + else + Array.Copy(_after, node.Values, _after.Length); node.OnValuesChanged(); context.MarkAsModified(_graphEdited); node.SetIsDuringValuesEditing(false); @@ -65,7 +70,10 @@ namespace FlaxEditor.Surface.Undo throw new Exception("Missing node."); node.SetIsDuringValuesEditing(true); - Array.Copy(_before, node.Values, _before.Length); + if (_resize) + node.Values = (object[])_before.Clone(); + else + Array.Copy(_before, node.Values, _before.Length); node.OnValuesChanged(); context.MarkAsModified(_graphEdited); node.SetIsDuringValuesEditing(false); diff --git a/Source/Editor/Surface/VisjectSurfaceContext.Serialization.cs b/Source/Editor/Surface/VisjectSurfaceContext.Serialization.cs index 02aaa4c6d..09147efac 100644 --- a/Source/Editor/Surface/VisjectSurfaceContext.Serialization.cs +++ b/Source/Editor/Surface/VisjectSurfaceContext.Serialization.cs @@ -630,6 +630,15 @@ namespace FlaxEditor.Surface stream.ReadCommonValue(ref node.Values[j]); } } + else if ((node.Archetype.Flags & NodeFlags.VariableValuesSize) != 0) + { + node.Values = new object[valuesCnt]; + for (int j = firstValueReadIdx; j < valuesCnt; j++) + { + // ReSharper disable once PossibleNullReferenceException + stream.ReadCommonValue(ref node.Values[j]); + } + } else { Editor.LogWarning(string.Format("Invalid node values. Loaded: {0}, expected: {1}. Type: {2}, {3}", valuesCnt, nodeValuesCnt, node.Archetype.Title, node.Archetype.TypeID)); @@ -795,6 +804,12 @@ namespace FlaxEditor.Surface for (int j = firstValueReadIdx; j < valuesCnt; j++) node.Values[j] = stream.ReadVariant(); } + else if ((node.Archetype.Flags & NodeFlags.VariableValuesSize) != 0) + { + node.Values = new object[valuesCnt]; + for (int j = firstValueReadIdx; j < valuesCnt; j++) + node.Values[j] = stream.ReadVariant(); + } else { Editor.LogWarning(string.Format("Invalid node values. Loaded: {0}, expected: {1}. Type: {2}, {3}", valuesCnt, nodeValuesCnt, node.Archetype.Title, node.Archetype.TypeID)); diff --git a/Source/Editor/Utilities/DuplicateScenes.cs b/Source/Editor/Utilities/DuplicateScenes.cs index 20979940e..7902633b2 100644 --- a/Source/Editor/Utilities/DuplicateScenes.cs +++ b/Source/Editor/Utilities/DuplicateScenes.cs @@ -120,9 +120,9 @@ namespace FlaxEditor.Utilities /// /// Deletes the creates scenes for the simulation. /// - public void DeletedScenes() + public void UnloadScenes() { - Profiler.BeginEvent("DuplicateScenes.DeletedScenes"); + Profiler.BeginEvent("DuplicateScenes.UnloadScenes"); Editor.Log("Restoring scene data"); // TODO: here we can keep changes for actors marked to keep their state after simulation @@ -134,6 +134,8 @@ namespace FlaxEditor.Utilities throw new Exception("Failed to unload scenes."); } FlaxEngine.Scripting.FlushRemovedObjects(); + Editor.WipeOutLeftoverSceneObjects(); + Profiler.EndEvent(); } diff --git a/Source/Editor/Utilities/Utils.cs b/Source/Editor/Utilities/Utils.cs index 1b76de47e..fbbbd71bf 100644 --- a/Source/Editor/Utilities/Utils.cs +++ b/Source/Editor/Utilities/Utils.cs @@ -210,12 +210,17 @@ namespace FlaxEditor.Utilities /// The duplicated value. internal static object CloneValue(object value) { + // For object references just clone it + if (value is FlaxEngine.Object) + return value; + // For objects (eg. arrays) we need to clone them to prevent editing default/reference value within editor if (value != null && (!value.GetType().IsValueType || !value.GetType().IsClass)) { var json = JsonSerializer.Serialize(value); value = JsonSerializer.Deserialize(json, value.GetType()); } + return value; } diff --git a/Source/Editor/Viewport/Previews/PrefabPreview.cs b/Source/Editor/Viewport/Previews/PrefabPreview.cs index 16ec8f132..1673c1723 100644 --- a/Source/Editor/Viewport/Previews/PrefabPreview.cs +++ b/Source/Editor/Viewport/Previews/PrefabPreview.cs @@ -115,7 +115,12 @@ namespace FlaxEditor.Viewport.Previews _hasUILinked = true; } else if (_uiControlLinked != null) + { + if (_uiControlLinked.Control != null && + _uiControlLinked.Control.Parent == null) + _uiControlLinked.Control.Parent = _uiParentLink; _hasUILinked = true; + } } private void LinkCanvas(Actor actor) diff --git a/Source/Engine/Animations/Animations.cpp b/Source/Engine/Animations/Animations.cpp index 1366e18cf..21235daa5 100644 --- a/Source/Engine/Animations/Animations.cpp +++ b/Source/Engine/Animations/Animations.cpp @@ -35,7 +35,7 @@ public: namespace { - FORCE_INLINE bool CanUpdateModel(AnimatedModel* animatedModel) + FORCE_INLINE bool CanUpdateModel(const AnimatedModel* animatedModel) { auto skinnedModel = animatedModel->SkinnedModel.Get(); auto animGraph = animatedModel->AnimationGraph.Get(); diff --git a/Source/Engine/Animations/Graph/AnimGraph.Base.cpp b/Source/Engine/Animations/Graph/AnimGraph.Base.cpp index 7cbc3367d..204026724 100644 --- a/Source/Engine/Animations/Graph/AnimGraph.Base.cpp +++ b/Source/Engine/Animations/Graph/AnimGraph.Base.cpp @@ -117,11 +117,11 @@ void InstanceDataBucketInit(AnimGraphInstanceData::Bucket& bucket) bucket.InstanceData.Init = true; } -bool SortMultiBlend1D(const byte& a, const byte& b, AnimGraphNode* n) +bool SortMultiBlend1D(const ANIM_GRAPH_MULTI_BLEND_INDEX& a, const ANIM_GRAPH_MULTI_BLEND_INDEX& b, AnimGraphNode* n) { // Sort items by X location from the lowest to the highest - const auto aX = a == ANIM_GRAPH_MULTI_BLEND_MAX_ANIMS ? MAX_float : n->Values[4 + a * 2].AsFloat4().X; - const auto bX = b == ANIM_GRAPH_MULTI_BLEND_MAX_ANIMS ? MAX_float : n->Values[4 + b * 2].AsFloat4().X; + const auto aX = a == ANIM_GRAPH_MULTI_BLEND_INVALID ? MAX_float : n->Values[4 + a * 2].AsFloat4().X; + const auto bX = b == ANIM_GRAPH_MULTI_BLEND_INVALID ? MAX_float : n->Values[4 + b * 2].AsFloat4().X; return aX < bX; } @@ -133,8 +133,6 @@ bool SortMultiBlend1D(const byte& a, const byte& b, AnimGraphNode* n) bool AnimGraphBase::onNodeLoaded(Node* n) { ((AnimGraphNode*)n)->Graph = _graph; - - // Check if this node needs a state container switch (n->GroupID) { // Tools @@ -163,53 +161,51 @@ bool AnimGraphBase::onNodeLoaded(Node* n) // Animation case 2: ADD_BUCKET(AnimationBucketInit); + n->Assets.Resize(1); n->Assets[0] = (Asset*)Content::LoadAsync((Guid)n->Values[0]); break; // Blend with Mask case 11: + n->Assets.Resize(1); n->Assets[0] = (Asset*)Content::LoadAsync((Guid)n->Values[1]); break; // Multi Blend 1D case 12: - { ADD_BUCKET(MultiBlendBucketInit); + n->Data.MultiBlend1D.Count = (ANIM_GRAPH_MULTI_BLEND_INDEX)((n->Values.Count() - 4) / 2); // 4 node values + 2 per blend point n->Data.MultiBlend1D.Length = -1; - const Float4 range = n->Values[0].AsFloat4(); - for (int32 i = 0; i < ANIM_GRAPH_MULTI_BLEND_MAX_ANIMS; i++) + n->Data.MultiBlend1D.IndicesSorted = (ANIM_GRAPH_MULTI_BLEND_INDEX*)Allocator::Allocate(sizeof(ANIM_GRAPH_MULTI_BLEND_INDEX) * n->Data.MultiBlend1D.Count); + n->Assets.Resize(n->Data.MultiBlend1D.Count); + for (int32 i = 0; i < n->Data.MultiBlend1D.Count; i++) { n->Assets[i] = Content::LoadAsync((Guid)n->Values[i * 2 + 5]); - n->Data.MultiBlend1D.IndicesSorted[i] = n->Assets[i] ? i : ANIM_GRAPH_MULTI_BLEND_MAX_ANIMS; + n->Data.MultiBlend1D.IndicesSorted[i] = (ANIM_GRAPH_MULTI_BLEND_INDEX)(n->Assets[i] ? i : ANIM_GRAPH_MULTI_BLEND_INVALID); } - Sorting::SortArray(n->Data.MultiBlend1D.IndicesSorted, ANIM_GRAPH_MULTI_BLEND_MAX_ANIMS, &SortMultiBlend1D, n); + Sorting::SortArray(n->Data.MultiBlend1D.IndicesSorted, n->Data.MultiBlend1D.Count, &SortMultiBlend1D, n); break; - } // Multi Blend 2D case 13: { ADD_BUCKET(MultiBlendBucketInit); + n->Data.MultiBlend1D.Count = (ANIM_GRAPH_MULTI_BLEND_INDEX)((n->Values.Count() - 4) / 2); // 4 node values + 2 per blend point n->Data.MultiBlend2D.Length = -1; // Get blend points locations - Array> vertices; - byte vertexIndexToAnimIndex[ANIM_GRAPH_MULTI_BLEND_MAX_ANIMS]; - const Float4 range = n->Values[0].AsFloat4(); - for (int32 i = 0; i < ANIM_GRAPH_MULTI_BLEND_MAX_ANIMS; i++) + Array vertices; + Array vertexToAnim; + n->Assets.Resize(n->Data.MultiBlend1D.Count); + for (int32 i = 0; i < n->Data.MultiBlend1D.Count; i++) { - n->Assets[i] = (Asset*)Content::LoadAsync((Guid)n->Values[i * 2 + 5]); + n->Assets[i] = Content::LoadAsync((Guid)n->Values[i * 2 + 5]); if (n->Assets[i]) { - const int32 vertexIndex = vertices.Count(); - vertexIndexToAnimIndex[vertexIndex] = i; vertices.Add(Float2(n->Values[i * 2 + 4].AsFloat4())); - } - else - { - vertexIndexToAnimIndex[i] = -1; + vertexToAnim.Add((ANIM_GRAPH_MULTI_BLEND_INDEX)i); } } // Triangulate - Array> triangles; + Array triangles; Delaunay2D::Triangulate(vertices, triangles); if (triangles.Count() == 0) { @@ -227,15 +223,14 @@ bool AnimGraphBase::onNodeLoaded(Node* n) } // Store triangles vertices indices (map the back to the anim node slots) - for (int32 i = 0; i < triangles.Count(); i++) + n->Data.MultiBlend2D.TrianglesCount = triangles.Count(); + n->Data.MultiBlend2D.Triangles = (ANIM_GRAPH_MULTI_BLEND_INDEX*)Allocator::Allocate(triangles.Count() * 3 - sizeof(ANIM_GRAPH_MULTI_BLEND_INDEX)); + for (int32 i = 0, t = 0; i < triangles.Count(); i++) { - n->Data.MultiBlend2D.TrianglesP0[i] = vertexIndexToAnimIndex[triangles[i].Indices[0]]; - n->Data.MultiBlend2D.TrianglesP1[i] = vertexIndexToAnimIndex[triangles[i].Indices[1]]; - n->Data.MultiBlend2D.TrianglesP2[i] = vertexIndexToAnimIndex[triangles[i].Indices[2]]; + n->Data.MultiBlend2D.Triangles[t++] = vertexToAnim[triangles[i].Indices[0]]; + n->Data.MultiBlend2D.Triangles[t++] = vertexToAnim[triangles[i].Indices[1]]; + n->Data.MultiBlend2D.Triangles[t++] = vertexToAnim[triangles[i].Indices[2]]; } - if (triangles.Count() < ANIM_GRAPH_MULTI_BLEND_2D_MAX_TRIS) - n->Data.MultiBlend2D.TrianglesP0[triangles.Count()] = ANIM_GRAPH_MULTI_BLEND_MAX_ANIMS; - break; } // Blend Pose @@ -307,6 +302,7 @@ bool AnimGraphBase::onNodeLoaded(Node* n) data.Graph = nullptr; break; } + n->Assets.Resize(1); n->Assets[0] = function; // Load the graph @@ -384,6 +380,7 @@ bool AnimGraphBase::onNodeLoaded(Node* n) void AnimGraphBase::LoadStateTransitions(AnimGraphNode::StateBaseData& data, Value& transitionsData) { int32 validTransitions = 0; + data.Transitions = nullptr; if (transitionsData.Type == VariantType::Blob && transitionsData.AsBlob.Length) { MemoryReadStream stream((byte*)transitionsData.AsBlob.Data, transitionsData.AsBlob.Length); @@ -398,8 +395,11 @@ void AnimGraphBase::LoadStateTransitions(AnimGraphNode::StateBaseData& data, Val int32 transitionsCount; stream.ReadInt32(&transitionsCount); + if (transitionsCount == 0) + return; StateTransitions.EnsureCapacity(StateTransitions.Count() + transitionsCount); + data.Transitions = (uint16*)Allocator::Allocate(transitionsCount * sizeof(uint16)); AnimGraphStateTransition transition; for (int32 i = 0; i < transitionsCount; i++) @@ -434,7 +434,6 @@ void AnimGraphBase::LoadStateTransitions(AnimGraphNode::StateBaseData& data, Val // Skip disabled transitions continue; } - if (ruleSize != 0) { transition.RuleGraph = LoadSubGraph(ruleBytes, ruleSize, TEXT("Rule")); @@ -444,25 +443,19 @@ void AnimGraphBase::LoadStateTransitions(AnimGraphNode::StateBaseData& data, Val continue; } } - if (transition.Destination == nullptr) { LOG(Warning, "Missing target node for the state machine transition."); continue; } - if (validTransitions == ANIM_GRAPH_MAX_STATE_TRANSITIONS) - { - LOG(Warning, "State uses too many transitions."); - continue; - } - data.Transitions[validTransitions++] = (uint16)StateTransitions.Count(); StateTransitions.Add(transition); } - } - if (validTransitions != ANIM_GRAPH_MAX_STATE_TRANSITIONS) + + // Last entry is invalid to indicate end data.Transitions[validTransitions] = AnimGraphNode::StateData::InvalidTransitionIndex; + } // Release data to don't use that memory transitionsData = AnimGraphExecutor::Value::Null; diff --git a/Source/Engine/Animations/Graph/AnimGraph.cpp b/Source/Engine/Animations/Graph/AnimGraph.cpp index defd2b03d..18013b169 100644 --- a/Source/Engine/Animations/Graph/AnimGraph.cpp +++ b/Source/Engine/Animations/Graph/AnimGraph.cpp @@ -102,6 +102,34 @@ AnimGraphInstanceData::OutgoingEvent AnimGraphInstanceData::ActiveEvent::End(Ani return out; } +AnimGraphNode::~AnimGraphNode() +{ + // Free allocated memory + switch (GroupID) + { + // Animation + case 9: + switch (TypeID) + { + // Multi Blend 1D + case 12: + Allocator::Free(Data.MultiBlend1D.IndicesSorted); + break; + // Multi Blend 2D + case 13: + Allocator::Free(Data.MultiBlend2D.Triangles); + break; + // State + case 20: + // Any State + case 34: + Allocator::Free(Data.State.Transitions); + break; + } + break; + } +} + AnimGraphImpulse* AnimGraphNode::GetNodes(AnimGraphExecutor* executor) { auto& context = *AnimGraphExecutor::Context.Get(); diff --git a/Source/Engine/Animations/Graph/AnimGraph.h b/Source/Engine/Animations/Graph/AnimGraph.h index ebee7e901..ff8d9b977 100644 --- a/Source/Engine/Animations/Graph/AnimGraph.h +++ b/Source/Engine/Animations/Graph/AnimGraph.h @@ -13,9 +13,8 @@ #define ANIM_GRAPH_PARAM_BASE_MODEL_ID Guid(1000, 0, 0, 0) #define ANIM_GRAPH_IS_VALID_PTR(value) (value.Type.Type == VariantType::Pointer && value.AsPointer != nullptr) -#define ANIM_GRAPH_MULTI_BLEND_MAX_ANIMS 14 -#define ANIM_GRAPH_MULTI_BLEND_2D_MAX_TRIS 32 -#define ANIM_GRAPH_MAX_STATE_TRANSITIONS 64 +#define ANIM_GRAPH_MULTI_BLEND_INDEX byte +#define ANIM_GRAPH_MULTI_BLEND_INVALID 0xff #define ANIM_GRAPH_MAX_CALL_STACK 100 #define ANIM_GRAPH_MAX_EVENTS 64 @@ -427,61 +426,31 @@ struct AnimGraphTransitionData float Length; }; -class AnimGraphBox : public VisjectGraphBox -{ -public: - AnimGraphBox() - { - } - - AnimGraphBox(AnimGraphNode* parent, byte id, const VariantType::Types type) - : VisjectGraphBox(parent, id, type) - { - } - - AnimGraphBox(AnimGraphNode* parent, byte id, const VariantType& type) - : VisjectGraphBox(parent, id, type) - { - } -}; +typedef VisjectGraphBox AnimGraphBox; class AnimGraphNode : public VisjectGraphNode { public: struct MultiBlend1DData { - /// - /// The computed length of the mixes animations. Shared for all blend points to provide more stabilization during looped playback. - /// + // Amount of blend points. + ANIM_GRAPH_MULTI_BLEND_INDEX Count; + // The computed length of the mixes animations. Shared for all blend points to provide more stabilization during looped playback. float Length; - - /// - /// The indices of the animations to blend. Sorted from the lowest X to the highest X. Contains only valid used animations. Unused items are using index ANIM_GRAPH_MULTI_BLEND_MAX_ANIMS which is invalid. - /// - byte IndicesSorted[ANIM_GRAPH_MULTI_BLEND_MAX_ANIMS]; + // The indices of the animations to blend. Sorted from the lowest X to the highest X. Contains only valid used animations. Unused items are using index ANIM_GRAPH_MULTI_BLEND_INVALID which is invalid. + ANIM_GRAPH_MULTI_BLEND_INDEX* IndicesSorted; }; struct MultiBlend2DData { - /// - /// The computed length of the mixes animations. Shared for all blend points to provide more stabilization during looped playback. - /// + // Amount of blend points. + ANIM_GRAPH_MULTI_BLEND_INDEX Count; + // The computed length of the mixes animations. Shared for all blend points to provide more stabilization during looped playback. float Length; - - /// - /// Cached triangles vertices (vertex 0). Contains list of indices for triangles to use for blending. - /// - byte TrianglesP0[ANIM_GRAPH_MULTI_BLEND_2D_MAX_TRIS]; - - /// - /// Cached triangles vertices (vertex 1). Contains list of indices for triangles to use for blending. - /// - byte TrianglesP1[ANIM_GRAPH_MULTI_BLEND_2D_MAX_TRIS]; - - /// - /// Cached triangles vertices (vertex 2). Contains list of indices for triangles to use for blending. - /// - byte TrianglesP2[ANIM_GRAPH_MULTI_BLEND_2D_MAX_TRIS]; + // Amount of triangles. + int32 TrianglesCount; + // Cached triangles vertices (3 bytes per triangle). Contains list of indices for triangles to use for blending. + ANIM_GRAPH_MULTI_BLEND_INDEX* Triangles; }; struct StateMachineData @@ -494,15 +463,11 @@ public: struct StateBaseData { - /// - /// The invalid transition valid used in Transitions to indicate invalid transition linkage. - /// + // The invalid transition valid used in Transitions to indicate invalid transition linkage. const static uint16 InvalidTransitionIndex = MAX_uint16; - /// - /// The outgoing transitions from this state to the other states. Each array item contains index of the transition data from the state node graph transitions cache. Value InvalidTransitionIndex is used for last transition to indicate the transitions amount. - /// - uint16 Transitions[ANIM_GRAPH_MAX_STATE_TRANSITIONS]; + // The outgoing transitions from this state to the other states. Each array item contains index of the transition data from the state node graph transitions cache. Value InvalidTransitionIndex is used for last transition to indicate the transitions amount. + uint16* Transitions; }; struct StateData : StateBaseData @@ -598,12 +563,14 @@ public: { } + ~AnimGraphNode(); + public: /// /// Gets the per-node node transformations cache (cached). /// /// The Graph execution context. - /// The modes data. + /// Nodes data. AnimGraphImpulse* GetNodes(AnimGraphExecutor* executor); }; @@ -800,7 +767,7 @@ struct AnimGraphContext bool StackOverFlow; Array> CallStack; Array> GraphStack; - Array > NodePath; + Array> NodePath; Dictionary Functions; ChunkedArray PoseCache; int32 PoseCacheSize; diff --git a/Source/Engine/Animations/Graph/AnimGroup.Animation.cpp b/Source/Engine/Animations/Graph/AnimGroup.Animation.cpp index 21eb44265..0c1b0a373 100644 --- a/Source/Engine/Animations/Graph/AnimGroup.Animation.cpp +++ b/Source/Engine/Animations/Graph/AnimGroup.Animation.cpp @@ -247,6 +247,7 @@ void AnimGraphExecutor::ProcessAnimation(AnimGraphImpulse* nodes, AnimGraphNode* { // Per-channel bit to indicate which channels were used by nested usedNodesThis.Resize(nodes->Nodes.Count()); + usedNodesThis.SetAll(false); usedNodes = &usedNodesThis; } @@ -286,11 +287,11 @@ void AnimGraphExecutor::ProcessAnimation(AnimGraphImpulse* nodes, AnimGraphNode* SkinnedModel::SkeletonMapping sourceMapping; if (retarget) sourceMapping = _graph.BaseModel->GetSkeletonMapping(mapping.SourceSkeleton); - for (int32 i = 0; i < nodes->Nodes.Count(); i++) + for (int32 nodeIndex = 0; nodeIndex < nodes->Nodes.Count(); nodeIndex++) { - const int32 nodeToChannel = mapping.NodesMapping[i]; - Transform& dstNode = nodes->Nodes[i]; - Transform srcNode = emptyNodes->Nodes[i]; + const int32 nodeToChannel = mapping.NodesMapping[nodeIndex]; + Transform& dstNode = nodes->Nodes[nodeIndex]; + Transform srcNode = emptyNodes->Nodes[nodeIndex]; if (nodeToChannel != -1) { // Calculate the animated node transformation @@ -299,14 +300,14 @@ void AnimGraphExecutor::ProcessAnimation(AnimGraphImpulse* nodes, AnimGraphNode* // Optionally retarget animation into the skeleton used by the Anim Graph if (retarget) { - RetargetSkeletonNode(mapping.SourceSkeleton->Skeleton, mapping.TargetSkeleton->Skeleton, sourceMapping, srcNode, i); + RetargetSkeletonNode(mapping.SourceSkeleton->Skeleton, mapping.TargetSkeleton->Skeleton, sourceMapping, srcNode, nodeIndex); } // Mark node as used if (usedNodes) - usedNodes->Set(i, true); + usedNodes->Set(nodeIndex, true); } - else if (usedNodes && usedNodes != &usedNodesThis) + else if (usedNodes && (usedNodes != &usedNodesThis || usedNodes->Get(nodeIndex))) { // Skip for nested animations so other one or top-level anim will update remaining nodes continue; @@ -585,7 +586,7 @@ AnimGraphStateTransition* AnimGraphExecutor::UpdateStateTransitions(AnimGraphCon AnimGraphStateTransition* AnimGraphExecutor::UpdateStateTransitions(AnimGraphContext& context, const AnimGraphNode::StateMachineData& stateMachineData, const AnimGraphNode::StateBaseData& stateData, AnimGraphNode* state, AnimGraphNode* ignoreState) { int32 transitionIndex = 0; - while (transitionIndex < ANIM_GRAPH_MAX_STATE_TRANSITIONS && stateData.Transitions[transitionIndex] != AnimGraphNode::StateData::InvalidTransitionIndex) + while (stateData.Transitions && stateData.Transitions[transitionIndex] != AnimGraphNode::StateData::InvalidTransitionIndex) { const uint16 idx = stateData.Transitions[transitionIndex]; ASSERT(idx < stateMachineData.Graph->StateTransitions.Count()); @@ -673,19 +674,20 @@ void ComputeMultiBlendLength(float& length, AnimGraphNode* node) // TODO: lock graph or graph asset here? make it thread safe length = 0.0f; - for (int32 i = 0; i < ARRAY_COUNT(node->Assets); i++) + for (int32 i = 0; i < node->Assets.Count(); i++) { - if (node->Assets[i]) + auto& asset = node->Assets[i]; + if (asset) { // TODO: maybe don't update if not all anims are loaded? just skip the node with the bind pose? - if (node->Assets[i]->WaitForLoaded()) + if (asset->WaitForLoaded()) { - node->Assets[i] = nullptr; + asset = nullptr; LOG(Warning, "Failed to load one of the animations."); } else { - const auto anim = node->Assets[i].As(); + const auto anim = asset.As(); const auto aData = node->Values[4 + i * 2].AsFloat4(); length = Math::Max(length, anim->GetLength() * Math::Abs(aData.W)); } @@ -1268,13 +1270,20 @@ void AnimGraphExecutor::ProcessGroupAnimation(Box* boxBase, Node* nodeBase, Valu auto& data = node->Data.MultiBlend1D; // Check if not valid animation binded - if (data.IndicesSorted[0] == ANIM_GRAPH_MULTI_BLEND_MAX_ANIMS) + if (data.Count == 0) break; // Get axis X float x = (float)tryGetValue(node->GetBox(4), Value::Zero); x = Math::Clamp(x, range.X, range.Y); + // Add to trace + if (context.Data->EnableTracing) + { + auto& trace = context.AddTraceEvent(node); + trace.Value = x; + } + // Check if need to evaluate multi blend length if (data.Length < 0) ComputeMultiBlendLength(data.Length, node); @@ -1292,7 +1301,7 @@ void AnimGraphExecutor::ProcessGroupAnimation(Box* boxBase, Node* nodeBase, Valu ANIM_GRAPH_PROFILE_EVENT("Multi Blend 1D"); // Find 2 animations to blend (line) - for (int32 i = 0; i < ANIM_GRAPH_MULTI_BLEND_MAX_ANIMS - 1; i++) + for (int32 i = 0; i < data.Count - 1; i++) { const auto a = data.IndicesSorted[i]; const auto b = data.IndicesSorted[i + 1]; @@ -1302,14 +1311,14 @@ void AnimGraphExecutor::ProcessGroupAnimation(Box* boxBase, Node* nodeBase, Valu auto aData = node->Values[4 + a * 2].AsFloat4(); // Check single A case or the last valid animation - if (x <= aData.X + ANIM_GRAPH_BLEND_THRESHOLD || b == ANIM_GRAPH_MULTI_BLEND_MAX_ANIMS) + if (x <= aData.X + ANIM_GRAPH_BLEND_THRESHOLD || b == ANIM_GRAPH_MULTI_BLEND_INVALID) { value = SampleAnimation(node, loop, data.Length, startTimePos, bucket.TimePosition, newTimePos, aAnim, aData.W); break; } // Get B animation data - ASSERT(b != ANIM_GRAPH_MULTI_BLEND_MAX_ANIMS); + ASSERT(b != ANIM_GRAPH_MULTI_BLEND_INVALID); const auto bAnim = node->Assets[b].As(); auto bData = node->Values[4 + b * 2].AsFloat4(); @@ -1357,7 +1366,7 @@ void AnimGraphExecutor::ProcessGroupAnimation(Box* boxBase, Node* nodeBase, Valu auto& data = node->Data.MultiBlend2D; // Check if not valid animation binded - if (data.TrianglesP0[0] == ANIM_GRAPH_MULTI_BLEND_MAX_ANIMS) + if (data.TrianglesCount == 0) break; // Get axis X @@ -1368,6 +1377,14 @@ void AnimGraphExecutor::ProcessGroupAnimation(Box* boxBase, Node* nodeBase, Valu float y = (float)tryGetValue(node->GetBox(5), Value::Zero); y = Math::Clamp(y, range.Z, range.W); + // Add to trace + if (context.Data->EnableTracing) + { + auto& trace = context.AddTraceEvent(node); + const Half2 packed(x, y); // Pack xy into 32-bits + *(uint32*)&trace.Value = *(uint32*)&packed; + } + // Check if need to evaluate multi blend length if (data.Length < 0) ComputeMultiBlendLength(data.Length, node); @@ -1390,20 +1407,20 @@ void AnimGraphExecutor::ProcessGroupAnimation(Box* boxBase, Node* nodeBase, Valu Float2 bestPoint; float bestWeight = 0.0f; byte bestAnims[2]; - for (int32 i = 0; i < ANIM_GRAPH_MULTI_BLEND_2D_MAX_TRIS && data.TrianglesP0[i] != ANIM_GRAPH_MULTI_BLEND_MAX_ANIMS; i++) + for (int32 i = 0, t = 0; i < data.TrianglesCount; i++) { // Get A animation data - const auto a = data.TrianglesP0[i]; + const auto a = data.Triangles[t++]; const auto aAnim = node->Assets[a].As(); const auto aData = node->Values[4 + a * 2].AsFloat4(); // Get B animation data - const auto b = data.TrianglesP1[i]; + const auto b = data.Triangles[t++]; const auto bAnim = node->Assets[b].As(); const auto bData = node->Values[4 + b * 2].AsFloat4(); // Get C animation data - const auto c = data.TrianglesP2[i]; + const auto c = data.Triangles[t++]; const auto cAnim = node->Assets[c].As(); const auto cData = node->Values[4 + c * 2].AsFloat4(); diff --git a/Source/Engine/Animations/InverseKinematics.cpp b/Source/Engine/Animations/InverseKinematics.cpp index 06523be81..c5db48320 100644 --- a/Source/Engine/Animations/InverseKinematics.cpp +++ b/Source/Engine/Animations/InverseKinematics.cpp @@ -10,7 +10,6 @@ void InverseKinematics::SolveAimIK(const Transform& node, const Vector3& target, Quaternion::FindBetween(fromNode, toTarget, outNodeCorrection); } - void InverseKinematics::SolveTwoBoneIK(Transform& rootTransform, Transform& midJointTransform, Transform& endEffectorTransform, const Vector3& targetPosition, const Vector3& poleVector, bool allowStretching, float maxStretchScale) { // Calculate limb segment lengths @@ -82,17 +81,20 @@ void InverseKinematics::SolveTwoBoneIK(Transform& rootTransform, Transform& midJ Vector3 newEndEffectorPos = targetPosition; Vector3 newMidJointPos = midJointPos; - if (toTargetLength >= totalLimbLength) { + 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) { + if (slightBendDirection.LengthSquared() < ZeroTolerance * ZeroTolerance) + { slightBendDirection = Vector3::Up; } - else { + else + { slightBendDirection.Normalize(); } @@ -140,7 +142,6 @@ void InverseKinematics::SolveTwoBoneIK(Transform& rootTransform, Transform& midJ rootTransform.Orientation = newRootJointOrientation; } - // Update mid joint orientation to point Y-axis towards the end effector and Z-axis perpendicular to the IK plane { // Vector from mid joint to end effector (local Y-axis direction after rotation) @@ -150,7 +151,6 @@ void InverseKinematics::SolveTwoBoneIK(Transform& rootTransform, Transform& midJ 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(); @@ -163,7 +163,6 @@ void InverseKinematics::SolveTwoBoneIK(Transform& rootTransform, Transform& midJ // 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 diff --git a/Source/Engine/Animations/SceneAnimations/SceneAnimation.cpp b/Source/Engine/Animations/SceneAnimations/SceneAnimation.cpp index 5d75b676c..b3e7a6e27 100644 --- a/Source/Engine/Animations/SceneAnimations/SceneAnimation.cpp +++ b/Source/Engine/Animations/SceneAnimations/SceneAnimation.cpp @@ -35,7 +35,7 @@ const BytesContainer& SceneAnimation::LoadTimeline() #if USE_EDITOR -bool SceneAnimation::SaveTimeline(BytesContainer& data) +bool SceneAnimation::SaveTimeline(const BytesContainer& data) { // Wait for asset to be loaded or don't if last load failed (eg. by shader source compilation error) if (LastLoadFailed()) diff --git a/Source/Engine/Animations/SceneAnimations/SceneAnimation.h b/Source/Engine/Animations/SceneAnimations/SceneAnimation.h index f6beeb416..e08f0c38c 100644 --- a/Source/Engine/Animations/SceneAnimations/SceneAnimation.h +++ b/Source/Engine/Animations/SceneAnimations/SceneAnimation.h @@ -454,10 +454,10 @@ public: /// /// Saves the serialized timeline data to the asset. /// - /// The cannot be used by virtual assets. + /// It cannot be used by virtual assets. /// The timeline data container. /// true failed to save data; otherwise, false. - API_FUNCTION() bool SaveTimeline(BytesContainer& data); + API_FUNCTION() bool SaveTimeline(const BytesContainer& data); #endif diff --git a/Source/Engine/Content/Asset.cpp b/Source/Engine/Content/Asset.cpp index 30771899f..ddff7fc8a 100644 --- a/Source/Engine/Content/Asset.cpp +++ b/Source/Engine/Content/Asset.cpp @@ -587,8 +587,8 @@ 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 || Platform::AtomicRead(&_loadingTask) == 0) + // It may fail when task is cancelled and new one was created later (don't crash but just end with an error) return true; Locker.Lock(); @@ -601,7 +601,6 @@ bool Asset::onLoad(LoadAssetTask* task) } const bool isLoaded = result == LoadResult::Ok; const bool failed = !isLoaded; - LoadState state = LoadState::Loaded; Platform::AtomicStore(&_loadState, (int64)(isLoaded ? LoadState::Loaded : LoadState::LoadFailed)); if (failed) { diff --git a/Source/Engine/Content/Asset.h b/Source/Engine/Content/Asset.h index c4b89935d..d1256b7c7 100644 --- a/Source/Engine/Content/Asset.h +++ b/Source/Engine/Content/Asset.h @@ -103,7 +103,7 @@ public: public: /// - /// Gets the path to the asset storage file. In Editor it reflects the actual file, in cooked Game, it fakes the Editor path to be informative for developers. + /// Gets the path to the asset storage file. In Editor, it reflects the actual file, in cooked Game, it fakes the Editor path to be informative for developers. /// API_PROPERTY() virtual const String& GetPath() const = 0; diff --git a/Source/Engine/Content/Assets/Material.cpp b/Source/Engine/Content/Assets/Material.cpp index 9a3f7bace..ad373d660 100644 --- a/Source/Engine/Content/Assets/Material.cpp +++ b/Source/Engine/Content/Assets/Material.cpp @@ -550,7 +550,7 @@ BytesContainer Material::LoadSurface(bool createDefaultIfMissing) #if USE_EDITOR -bool Material::SaveSurface(BytesContainer& data, const MaterialInfo& info) +bool Material::SaveSurface(const BytesContainer& data, const MaterialInfo& info) { // Wait for asset to be loaded or don't if last load failed (eg. by shader source compilation error) if (LastLoadFailed()) diff --git a/Source/Engine/Content/Assets/Material.h b/Source/Engine/Content/Assets/Material.h index 569c86d93..e8f398e3d 100644 --- a/Source/Engine/Content/Assets/Material.h +++ b/Source/Engine/Content/Assets/Material.h @@ -32,7 +32,7 @@ public: /// The surface graph data. /// The material info structure. /// True if cannot save it, otherwise false. - API_FUNCTION() bool SaveSurface(BytesContainer& data, const MaterialInfo& info); + API_FUNCTION() bool SaveSurface(const BytesContainer& data, const MaterialInfo& info); #endif diff --git a/Source/Engine/Content/BinaryAsset.cpp b/Source/Engine/Content/BinaryAsset.cpp index 7735d8b28..692fc9329 100644 --- a/Source/Engine/Content/BinaryAsset.cpp +++ b/Source/Engine/Content/BinaryAsset.cpp @@ -507,8 +507,7 @@ public: /// /// The asset. InitAssetTask(BinaryAsset* asset) - : ContentLoadTask(Type::Custom) - , _asset(asset) + : _asset(asset) , _dataLock(asset->Storage->Lock()) { } @@ -527,8 +526,6 @@ protected: AssetReference ref = _asset.Get(); if (ref == nullptr) return Result::MissingReferences; - - // Prepare auto storage = ref->Storage; auto factory = (BinaryAssetFactoryBase*)Content::GetAssetFactory(ref->GetTypeName()); ASSERT(factory); @@ -548,7 +545,6 @@ protected: _dataLock.Release(); _asset = nullptr; - // Base ContentLoadTask::OnEnd(); } }; diff --git a/Source/Engine/Content/Content.cpp b/Source/Engine/Content/Content.cpp index 39fae81db..fa4517911 100644 --- a/Source/Engine/Content/Content.cpp +++ b/Source/Engine/Content/Content.cpp @@ -403,7 +403,7 @@ ContentStats Content::GetStats() return stats; } -Asset* Content::LoadAsyncInternal(const StringView& internalPath, MClass* type) +Asset* Content::LoadAsyncInternal(const StringView& internalPath, const MClass* type) { CHECK_RETURN(type, nullptr); const auto scriptingType = Scripting::FindScriptingType(type->GetFullName()); @@ -445,7 +445,7 @@ FLAXENGINE_API Asset* LoadAsset(const Guid& id, const ScriptingTypeHandle& type) return Content::LoadAsync(id, type); } -Asset* Content::LoadAsync(const StringView& path, MClass* type) +Asset* Content::LoadAsync(const StringView& path, const MClass* type) { CHECK_RETURN(type, nullptr); const auto scriptingType = Scripting::FindScriptingType(type->GetFullName()); @@ -832,7 +832,7 @@ void Content::UnloadAsset(Asset* asset) asset->DeleteObject(); } -Asset* Content::CreateVirtualAsset(MClass* type) +Asset* Content::CreateVirtualAsset(const MClass* type) { CHECK_RETURN(type, nullptr); const auto scriptingType = Scripting::FindScriptingType(type->GetFullName()); diff --git a/Source/Engine/Content/Content.h b/Source/Engine/Content/Content.h index 56363dd43..5697c17b0 100644 --- a/Source/Engine/Content/Content.h +++ b/Source/Engine/Content/Content.h @@ -164,7 +164,7 @@ public: /// The path of the asset (absolute or relative to the current workspace directory). /// The asset type. If loaded object has different type (excluding types derived from the given) the loading fails. /// Loaded asset or null if cannot - API_FUNCTION(Attributes="HideInEditor") static Asset* LoadAsync(const StringView& path, MClass* type); + API_FUNCTION(Attributes="HideInEditor") static Asset* LoadAsync(const StringView& path, const MClass* type); /// /// Loads asset and holds it until it won't be referenced by any object. Returns null if asset is missing. Actual asset data loading is performed on a other thread in async. @@ -192,7 +192,7 @@ public: /// The path of the asset relative to the engine internal content (excluding the extension). /// The asset type. If loaded object has different type (excluding types derived from the given) the loading fails. /// The loaded asset or null if failed. - API_FUNCTION(Attributes="HideInEditor") static Asset* LoadAsyncInternal(const StringView& internalPath, MClass* type); + API_FUNCTION(Attributes="HideInEditor") static Asset* LoadAsyncInternal(const StringView& internalPath, const MClass* type); /// /// Loads internal engine asset and holds it until it won't be referenced by any object. Returns null if asset is missing. Actual asset data loading is performed on a other thread in async. @@ -342,7 +342,7 @@ public: /// /// The asset type klass. /// Created asset or null if failed. - API_FUNCTION() static Asset* CreateVirtualAsset(API_PARAM(Attributes="TypeReference(typeof(Asset))") MClass* type); + API_FUNCTION() static Asset* CreateVirtualAsset(API_PARAM(Attributes="TypeReference(typeof(Asset))") const MClass* type); /// /// Creates temporary and virtual asset of the given type. diff --git a/Source/Engine/Content/Loading/ContentLoadTask.h b/Source/Engine/Content/Loading/ContentLoadTask.h index d8d44b50a..2e2608612 100644 --- a/Source/Engine/Content/Loading/ContentLoadTask.h +++ b/Source/Engine/Content/Loading/ContentLoadTask.h @@ -15,52 +15,11 @@ class ContentLoadTask : public Task friend LoadingThread; public: - /// - /// Describes work type - /// - DECLARE_ENUM_3(Type, Custom, LoadAsset, LoadAssetData); - /// /// Describes work result value /// DECLARE_ENUM_5(Result, Ok, AssetLoadError, MissingReferences, LoadDataError, TaskFailed); -private: - /// - /// Task type - /// - Type _type; - -protected: - /// - /// Initializes a new instance of the class. - /// - /// The task type. - ContentLoadTask(const Type type) - : _type(type) - { - } - -public: - /// - /// Gets a task type. - /// - FORCE_INLINE Type GetType() const - { - return _type; - } - -public: - /// - /// Checks if async task is loading given asset resource - /// - /// Target asset to check - /// True if is loading that asset, otherwise false - bool IsLoading(Asset* asset) const - { - return _type == Type::LoadAsset && HasReference((Object*)asset); - } - protected: virtual Result run() = 0; diff --git a/Source/Engine/Content/Loading/ContentLoadingManager.cpp b/Source/Engine/Content/Loading/ContentLoadingManager.cpp index 68d06b3d2..d0efbd197 100644 --- a/Source/Engine/Content/Loading/ContentLoadingManager.cpp +++ b/Source/Engine/Content/Loading/ContentLoadingManager.cpp @@ -4,6 +4,7 @@ #include "ContentLoadTask.h" #include "Engine/Core/Log.h" #include "Engine/Core/Math/Math.h" +#include "Engine/Core/Collections/Array.h" #include "Engine/Platform/CPUInfo.h" #include "Engine/Platform/Thread.h" #include "Engine/Platform/ConditionVariable.h" @@ -212,7 +213,7 @@ void ContentLoadingManagerService::Dispose() String ContentLoadTask::ToString() const { - return String::Format(TEXT("Content Load Task {0} ({1})"), ToString(GetType()), (int32)GetState()); + return String::Format(TEXT("Content Load Task ({})"), (int32)GetState()); } void ContentLoadTask::Enqueue() diff --git a/Source/Engine/Content/Loading/Tasks/LoadAssetDataTask.h b/Source/Engine/Content/Loading/Tasks/LoadAssetDataTask.h index 60ebba6cd..3fbd34835 100644 --- a/Source/Engine/Content/Loading/Tasks/LoadAssetDataTask.h +++ b/Source/Engine/Content/Loading/Tasks/LoadAssetDataTask.h @@ -15,28 +15,24 @@ class LoadAssetDataTask : public ContentLoadTask { private: - WeakAssetReference _asset; // Don't keep ref to the asset (so it can be unloaded if none using it, task will fail then) AssetChunksFlag _chunks; FlaxStorage::LockData _dataLock; public: - /// /// Initializes a new instance of the class. /// /// The asset to load. /// The chunks to load. LoadAssetDataTask(BinaryAsset* asset, AssetChunksFlag chunks) - : ContentLoadTask(Type::LoadAssetData) - , _asset(asset) + : _asset(asset) , _chunks(chunks) , _dataLock(asset->Storage->Lock()) { } public: - // [ContentLoadTask] bool HasReference(Object* obj) const override { @@ -44,7 +40,6 @@ public: } protected: - // [ContentLoadTask] Result run() override { diff --git a/Source/Engine/Content/Loading/Tasks/LoadAssetTask.h b/Source/Engine/Content/Loading/Tasks/LoadAssetTask.h index f71f2b4a6..24bd66119 100644 --- a/Source/Engine/Content/Loading/Tasks/LoadAssetTask.h +++ b/Source/Engine/Content/Loading/Tasks/LoadAssetTask.h @@ -15,38 +15,35 @@ class LoadAssetTask : public ContentLoadTask { public: - /// /// Initializes a new instance of the class. /// /// The asset to load. LoadAssetTask(Asset* asset) - : ContentLoadTask(Type::LoadAsset) - , Asset(asset) + : Asset(asset) { } ~LoadAssetTask() { - if (Asset) + auto asset = Asset.Get(); + if (asset) { - Asset->Locker.Lock(); - if (Platform::AtomicRead(&Asset->_loadingTask) == (intptr)this) + asset->Locker.Lock(); + if (Platform::AtomicRead(&asset->_loadingTask) == (intptr)this) { - Platform::AtomicStore(&Asset->_loadState, (int64)Asset::LoadState::LoadFailed); - Platform::AtomicStore(&Asset->_loadingTask, 0); + 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->Locker.Unlock(); + asset->Locker.Unlock(); } } public: - WeakAssetReference Asset; public: - // [ContentLoadTask] bool HasReference(Object* obj) const override { @@ -54,7 +51,6 @@ public: } protected: - // [ContentLoadTask] Result run() override { @@ -68,32 +64,36 @@ protected: // Call loading if (ref->onLoad(this)) return Result::AssetLoadError; - return Result::Ok; } + void OnFail() override { - if (Asset) + auto asset = Asset.Get(); + if (asset) { - Asset->Locker.Lock(); - if (Platform::AtomicRead(&Asset->_loadingTask) == (intptr)this) - Platform::AtomicStore(&Asset->_loadingTask, 0); - Asset->Locker.Unlock(); Asset = nullptr; + asset->Locker.Lock(); + if (Platform::AtomicRead(&asset->_loadingTask) == (intptr)this) + Platform::AtomicStore(&asset->_loadingTask, 0); + asset->Locker.Unlock(); } // Base ContentLoadTask::OnFail(); } + void OnEnd() override { - if (Asset) + auto asset = Asset.Get(); + if (asset) { - Asset->Locker.Lock(); - if (Platform::AtomicRead(&Asset->_loadingTask) == (intptr)this) - Platform::AtomicStore(&Asset->_loadingTask, 0); - Asset->Locker.Unlock(); Asset = nullptr; + asset->Locker.Lock(); + if (Platform::AtomicRead(&asset->_loadingTask) == (intptr)this) + Platform::AtomicStore(&asset->_loadingTask, 0); + asset->Locker.Unlock(); + asset = nullptr; } // Base diff --git a/Source/Engine/Content/Storage/FlaxStorage.h b/Source/Engine/Content/Storage/FlaxStorage.h index cc04e24a6..3b982c32e 100644 --- a/Source/Engine/Content/Storage/FlaxStorage.h +++ b/Source/Engine/Content/Storage/FlaxStorage.h @@ -440,7 +440,7 @@ public: /// In silent mode don't reload opened storage container that is using target file. /// Custom options. /// True if cannot create package, otherwise false - FORCE_INLINE static bool Create(const StringView& path, AssetInitData& data, bool silentMode = false, const CustomData* customData = nullptr) + FORCE_INLINE static bool Create(const StringView& path, const AssetInitData& data, bool silentMode = false, const CustomData* customData = nullptr) { return Create(path, &data, 1, silentMode, customData); } diff --git a/Source/Engine/ContentImporters/ImportModel.cpp b/Source/Engine/ContentImporters/ImportModel.cpp index 75a25b5fa..6f0a9c626 100644 --- a/Source/Engine/ContentImporters/ImportModel.cpp +++ b/Source/Engine/ContentImporters/ImportModel.cpp @@ -467,7 +467,7 @@ CreateAssetResult ImportModel::Import(CreateAssetContext& context) writer.StartObject(); eKey.Value.Instance->Serialize(writer, nullptr); writer.EndObject(); - cloneKey.Value.JsonData.Set(buffer.GetString(), buffer.GetSize()); + cloneKey.Value.JsonData.Set(buffer.GetString(), (int32)buffer.GetSize()); } } } diff --git a/Source/Engine/Core/Math/Float2.cs b/Source/Engine/Core/Math/Float2.cs index ef76cf7cb..9c3909214 100644 --- a/Source/Engine/Core/Math/Float2.cs +++ b/Source/Engine/Core/Math/Float2.cs @@ -1281,8 +1281,8 @@ namespace FlaxEngine /// The position snapped to the grid. public static Float2 SnapToGrid(Float2 pos, Float2 gridSize) { - pos.X = Mathf.Ceil((pos.X - (gridSize.X * 0.5f)) / gridSize.Y) * gridSize.X; - pos.Y = Mathf.Ceil((pos.Y - (gridSize.Y * 0.5f)) / gridSize.X) * gridSize.Y; + pos.X = Mathf.Ceil((pos.X - (gridSize.X * 0.5f)) / gridSize.X) * gridSize.X; + pos.Y = Mathf.Ceil((pos.Y - (gridSize.Y * 0.5f)) / gridSize.Y) * gridSize.Y; return pos; } diff --git a/Source/Engine/Core/Math/Matrix.cpp b/Source/Engine/Core/Math/Matrix.cpp index 5fc2321e1..5d7bab6b6 100644 --- a/Source/Engine/Core/Math/Matrix.cpp +++ b/Source/Engine/Core/Math/Matrix.cpp @@ -774,7 +774,7 @@ void Matrix::Transformation(const Float3& scalingCenter, const Quaternion& scali result = Translation(-scalingCenter) * Transpose(sr) * Scaling(scaling) * sr * Translation(scalingCenter) * Translation(-rotationCenter) * RotationQuaternion(rotation) * Translation(rotationCenter) * Translation(translation); } -void Matrix::Transformation2D(Float2& scalingCenter, float scalingRotation, const Float2& scaling, const Float2& rotationCenter, float rotation, const Float2& translation, Matrix& result) +void Matrix::Transformation2D(const Float2& scalingCenter, float scalingRotation, const Float2& scaling, const Float2& rotationCenter, float rotation, const Float2& translation, Matrix& result) { result = Translation((Float3)-scalingCenter) * RotationZ(-scalingRotation) * Scaling((Float3)scaling) * RotationZ(scalingRotation) * Translation((Float3)scalingCenter) * Translation((Float3)-rotationCenter) * RotationZ(rotation) * Translation((Float3)rotationCenter) * Translation((Float3)translation); result.M33 = 1.0f; diff --git a/Source/Engine/Core/Math/Matrix.h b/Source/Engine/Core/Math/Matrix.h index 03a34ecc8..b7f5ef8cf 100644 --- a/Source/Engine/Core/Math/Matrix.h +++ b/Source/Engine/Core/Math/Matrix.h @@ -941,7 +941,7 @@ public: // @param rotation The rotation of the transformation. // @param translation The translation factor of the transformation. // @param result When the method completes, contains the created transformation matrix. - static void Transformation2D(Float2& scalingCenter, float scalingRotation, const Float2& scaling, const Float2& rotationCenter, float rotation, const Float2& translation, Matrix& result); + static void Transformation2D(const Float2& scalingCenter, float scalingRotation, const Float2& scaling, const Float2& rotationCenter, float rotation, const Float2& translation, Matrix& result); // Creates a world matrix with the specified parameters. // @param position Position of the object. This value is used in translation operations. diff --git a/Source/Engine/Core/Math/Rectangle.cpp b/Source/Engine/Core/Math/Rectangle.cpp index 53d56678b..b4d832288 100644 --- a/Source/Engine/Core/Math/Rectangle.cpp +++ b/Source/Engine/Core/Math/Rectangle.cpp @@ -99,7 +99,7 @@ Rectangle Rectangle::FromPoints(const Float2& p1, const Float2& p2) return Rectangle(upperLeft, Math::Max(rightBottom - upperLeft, Float2::Zero)); } -Rectangle Rectangle::FromPoints(Float2* points, int32 pointsCount) +Rectangle Rectangle::FromPoints(const Float2* points, int32 pointsCount) { ASSERT(pointsCount > 0); Float2 upperLeft = points[0]; diff --git a/Source/Engine/Core/Math/Rectangle.h b/Source/Engine/Core/Math/Rectangle.h index 496b8bb8a..6a41fbe0f 100644 --- a/Source/Engine/Core/Math/Rectangle.h +++ b/Source/Engine/Core/Math/Rectangle.h @@ -287,7 +287,7 @@ public: // @returns Rectangle that contains both p1 and p2 static Rectangle FromPoints(const Float2& p1, const Float2& p2); - static Rectangle FromPoints(Float2* points, int32 pointsCount); + static Rectangle FromPoints(const Float2* points, int32 pointsCount); }; template<> diff --git a/Source/Engine/Core/Types/DataContainer.h b/Source/Engine/Core/Types/DataContainer.h index 500041d1c..6ad6f75c9 100644 --- a/Source/Engine/Core/Types/DataContainer.h +++ b/Source/Engine/Core/Types/DataContainer.h @@ -320,7 +320,7 @@ public: /// /// The data. /// The length. - void Append(T* data, int32 length) + void Append(const T* data, int32 length) { if (length <= 0) return; diff --git a/Source/Engine/Core/Types/Variant.cpp b/Source/Engine/Core/Types/Variant.cpp index f30662f86..7061567f7 100644 --- a/Source/Engine/Core/Types/Variant.cpp +++ b/Source/Engine/Core/Types/Variant.cpp @@ -112,7 +112,7 @@ VariantType::VariantType(Types type, const StringAnsiView& typeName) } } -VariantType::VariantType(Types type, MClass* klass) +VariantType::VariantType(Types type, const MClass* klass) { Type = type; TypeName = nullptr; diff --git a/Source/Engine/Core/Types/Variant.h b/Source/Engine/Core/Types/Variant.h index 80205cd36..fa7f748fe 100644 --- a/Source/Engine/Core/Types/Variant.h +++ b/Source/Engine/Core/Types/Variant.h @@ -105,7 +105,7 @@ public: explicit VariantType(Types type, const StringView& typeName); explicit VariantType(Types type, const StringAnsiView& typeName); - explicit VariantType(Types type, MClass* klass); + explicit VariantType(Types type, const MClass* klass); explicit VariantType(const StringAnsiView& typeName); VariantType(const VariantType& other); VariantType(VariantType&& other) noexcept; diff --git a/Source/Engine/Engine/Engine.cpp b/Source/Engine/Engine/Engine.cpp index 27b35a0b7..0a500eaf4 100644 --- a/Source/Engine/Engine/Engine.cpp +++ b/Source/Engine/Engine/Engine.cpp @@ -60,6 +60,7 @@ namespace EngineImpl DateTime Engine::StartupTime; bool Engine::HasFocus = false; +uint64 Engine::UpdateCount = 0; uint64 Engine::FrameCount = 0; Action Engine::FixedUpdate; Action Engine::Update; @@ -156,7 +157,7 @@ int32 Engine::Main(const Char* cmdLine) #endif Log::Logger::WriteFloor(); LOG_FLUSH(); - Time::OnBeforeRun(); + Time::Synchronize(); EngineImpl::IsReady = true; // Main engine loop @@ -191,8 +192,11 @@ int32 Engine::Main(const Char* cmdLine) OnUnpause(); } + // Use the same time for all ticks to improve synchronization + const double time = Platform::GetTimeSeconds(); + // Update game logic - if (Time::OnBeginUpdate()) + if (Time::OnBeginUpdate(time)) { OnUpdate(); OnLateUpdate(); @@ -200,7 +204,7 @@ int32 Engine::Main(const Char* cmdLine) } // Start physics simulation - if (Time::OnBeginPhysics()) + if (Time::OnBeginPhysics(time)) { OnFixedUpdate(); OnLateFixedUpdate(); @@ -208,7 +212,7 @@ int32 Engine::Main(const Char* cmdLine) } // Draw frame - if (Time::OnBeginDraw()) + if (Time::OnBeginDraw(time)) { OnDraw(); Time::OnEndDraw(); @@ -296,6 +300,8 @@ void Engine::OnUpdate() { PROFILE_CPU_NAMED("Update"); + UpdateCount++; + // Update application (will gather data and other platform related events) { PROFILE_CPU_NAMED("Platform.Tick"); @@ -465,7 +471,7 @@ void Engine::OnUnpause() LOG(Info, "App unpaused"); Unpause(); - Time::OnBeforeRun(); + Time::Synchronize(); } void Engine::OnExit() diff --git a/Source/Engine/Engine/Engine.h b/Source/Engine/Engine/Engine.h index f74fcab4b..15285a07e 100644 --- a/Source/Engine/Engine/Engine.h +++ b/Source/Engine/Engine/Engine.h @@ -28,7 +28,12 @@ public: API_FIELD(ReadOnly) static bool HasFocus; /// - /// Gets the current frame count since the start of the game. + /// Gets the current update counter since the start of the game. + /// + API_FIELD(ReadOnly) static uint64 UpdateCount; + + /// + /// Gets the current frame (drawing) count since the start of the game. /// API_FIELD(ReadOnly) static uint64 FrameCount; diff --git a/Source/Engine/Engine/Time.cpp b/Source/Engine/Engine/Time.cpp index 6df5275f5..43ac4f9b5 100644 --- a/Source/Engine/Engine/Time.cpp +++ b/Source/Engine/Engine/Time.cpp @@ -54,7 +54,7 @@ void TimeSettings::Apply() ::MaxUpdateDeltaTime = MaxUpdateDeltaTime; } -void Time::TickData::OnBeforeRun(float targetFps, double currentTime) +void Time::TickData::Synchronize(float targetFps, double currentTime) { Time = UnscaledTime = TimeSpan::Zero(); DeltaTime = UnscaledDeltaTime = targetFps > ZeroTolerance ? TimeSpan::FromSeconds(1.0f / targetFps) : TimeSpan::Zero(); @@ -72,10 +72,9 @@ void Time::TickData::OnReset(float targetFps, double currentTime) LastEnd = currentTime; } -bool Time::TickData::OnTickBegin(float targetFps, float maxDeltaTime) +bool Time::TickData::OnTickBegin(double time, float targetFps, float maxDeltaTime) { // Check if can perform a tick - const double time = Platform::GetTimeSeconds(); double deltaTime; if (FixedDeltaTimeEnable) { @@ -126,10 +125,9 @@ void Time::TickData::Advance(double time, double deltaTime) TicksCount++; } -bool Time::FixedStepTickData::OnTickBegin(float targetFps, float maxDeltaTime) +bool Time::FixedStepTickData::OnTickBegin(double time, float targetFps, float maxDeltaTime) { // Check if can perform a tick - double time = Platform::GetTimeSeconds(); double deltaTime, minDeltaTime; if (FixedDeltaTimeEnable) { @@ -240,18 +238,18 @@ void Time::SetFixedDeltaTime(bool enable, float value) FixedDeltaTimeValue = value; } -void Time::OnBeforeRun() +void Time::Synchronize() { // Initialize tick data (based on a time settings) const double time = Platform::GetTimeSeconds(); - Update.OnBeforeRun(UpdateFPS, time); - Physics.OnBeforeRun(PhysicsFPS, time); - Draw.OnBeforeRun(DrawFPS, time); + Update.Synchronize(UpdateFPS, time); + Physics.Synchronize(PhysicsFPS, time); + Draw.Synchronize(DrawFPS, time); } -bool Time::OnBeginUpdate() +bool Time::OnBeginUpdate(double time) { - if (Update.OnTickBegin(UpdateFPS, MaxUpdateDeltaTime)) + if (Update.OnTickBegin(time, UpdateFPS, MaxUpdateDeltaTime)) { Current = &Update; return true; @@ -259,9 +257,9 @@ bool Time::OnBeginUpdate() return false; } -bool Time::OnBeginPhysics() +bool Time::OnBeginPhysics(double time) { - if (Physics.OnTickBegin(PhysicsFPS, _physicsMaxDeltaTime)) + if (Physics.OnTickBegin(time, PhysicsFPS, _physicsMaxDeltaTime)) { Current = &Physics; return true; @@ -269,9 +267,9 @@ bool Time::OnBeginPhysics() return false; } -bool Time::OnBeginDraw() +bool Time::OnBeginDraw(double time) { - if (Draw.OnTickBegin(DrawFPS, 1.0f)) + if (Draw.OnTickBegin(time, DrawFPS, 1.0f)) { Current = &Draw; return true; diff --git a/Source/Engine/Engine/Time.h b/Source/Engine/Engine/Time.h index 06e0d1cc5..ff81fb93e 100644 --- a/Source/Engine/Engine/Time.h +++ b/Source/Engine/Engine/Time.h @@ -12,12 +12,12 @@ /// API_CLASS(Static) class FLAXENGINE_API Time { -DECLARE_SCRIPTING_TYPE_NO_SPAWN(Time); + DECLARE_SCRIPTING_TYPE_NO_SPAWN(Time); friend class Engine; friend class TimeService; friend class PhysicsSettings; -public: +public: /// /// Engine subsystem updating data. /// Used to invoke game logic updates, physics updates and rendering with possibly different frequencies. @@ -25,7 +25,6 @@ public: class FLAXENGINE_API TickData { public: - virtual ~TickData() = default; /// @@ -75,14 +74,12 @@ public: TimeSpan UnscaledTime; public: - - virtual void OnBeforeRun(float targetFps, double currentTime); + virtual void Synchronize(float targetFps, double currentTime); virtual void OnReset(float targetFps, double currentTime); - virtual bool OnTickBegin(float targetFps, float maxDeltaTime); + virtual bool OnTickBegin(double time, float targetFps, float maxDeltaTime); virtual void OnTickEnd(); protected: - void Advance(double time, double deltaTime); }; @@ -92,25 +89,21 @@ public: class FixedStepTickData : public TickData { public: - /// /// The last few ticks delta times. Used to check if can use fixed steps or whenever is running slowly so should use normal stepping. /// SamplesBuffer Samples; public: - // [TickData] - bool OnTickBegin(float targetFps, float maxDeltaTime) override; + bool OnTickBegin(double time, float targetFps, float maxDeltaTime) override; }; private: - static bool _gamePaused; static float _physicsMaxDeltaTime; public: - /// /// The time at which the game started (UTC local). /// @@ -140,7 +133,6 @@ public: API_FIELD() static float TimeScale; public: - /// /// The game logic updating data. /// @@ -162,7 +154,6 @@ public: static TickData* Current; public: - /// /// Gets the current tick data (safety so returns Update tick data if no active). /// @@ -225,15 +216,17 @@ public: /// The fixed draw/update rate for the time. API_FUNCTION() static void SetFixedDeltaTime(bool enable, float value); -private: + /// + /// Synchronizes update, fixed update and draw. Resets any pending deltas for fresh ticking in sync. + /// + API_FUNCTION() static void Synchronize(); +private: // Methods used by the Engine class - static void OnBeforeRun(); - - static bool OnBeginUpdate(); - static bool OnBeginPhysics(); - static bool OnBeginDraw(); + static bool OnBeginUpdate(double time); + static bool OnBeginPhysics(double time); + static bool OnBeginDraw(double time); static void OnEndUpdate(); static void OnEndPhysics(); diff --git a/Source/Engine/Foliage/Foliage.cpp b/Source/Engine/Foliage/Foliage.cpp index 31039ce03..39451207d 100644 --- a/Source/Engine/Foliage/Foliage.cpp +++ b/Source/Engine/Foliage/Foliage.cpp @@ -115,7 +115,7 @@ void Foliage::DrawInstance(RenderContext& renderContext, FoliageInstance& instan e = &result[key]; ASSERT_LOW_LAYER(key.Mat); e->DrawCall.Material = key.Mat; - e->DrawCall.Surface.Lightmap = EnumHasAnyFlags(_staticFlags, StaticFlags::Lightmap) ? _scene->LightmapsData.GetReadyLightmap(key.Lightmap) : nullptr; + e->DrawCall.Surface.Lightmap = EnumHasAnyFlags(_staticFlags, StaticFlags::Lightmap) && _scene ? _scene->LightmapsData.GetReadyLightmap(key.Lightmap) : nullptr; } // Add instance to the draw batch @@ -1172,7 +1172,7 @@ void Foliage::Draw(RenderContext& renderContext) draw.ForcedLOD = -1; draw.SortOrder = 0; draw.VertexColors = nullptr; - draw.Lightmap = _scene->LightmapsData.GetReadyLightmap(instance.Lightmap.TextureIndex); + draw.Lightmap = _scene ? _scene->LightmapsData.GetReadyLightmap(instance.Lightmap.TextureIndex) : nullptr; draw.LightmapUVs = &instance.Lightmap.UVsArea; draw.Buffer = &type.Entries; draw.World = &world; diff --git a/Source/Engine/Graphics/GPUBufferDescription.h b/Source/Engine/Graphics/GPUBufferDescription.h index e93541d7a..d4049ea5b 100644 --- a/Source/Engine/Graphics/GPUBufferDescription.h +++ b/Source/Engine/Graphics/GPUBufferDescription.h @@ -182,7 +182,7 @@ public: /// The elements count. /// The data. /// The buffer description. - static GPUBufferDescription Vertex(int32 elementStride, int32 elementsCount, void* data) + static GPUBufferDescription Vertex(int32 elementStride, int32 elementsCount, const void* data) { return Buffer(elementsCount * elementStride, GPUBufferFlags::VertexBuffer, PixelFormat::Unknown, data, elementStride, GPUResourceUsage::Default); } @@ -217,7 +217,7 @@ public: /// The elements count. /// The data. /// The buffer description. - static GPUBufferDescription Index(int32 elementStride, int32 elementsCount, void* data) + static GPUBufferDescription Index(int32 elementStride, int32 elementsCount, const void* data) { const auto format = elementStride == 4 ? PixelFormat::R32_UInt : PixelFormat::R16_UInt; return Buffer(elementsCount * elementStride, GPUBufferFlags::IndexBuffer, format, data, elementStride, GPUResourceUsage::Default); diff --git a/Source/Engine/Graphics/Materials/MaterialShader.h b/Source/Engine/Graphics/Materials/MaterialShader.h index 4233b4700..2d3c2836b 100644 --- a/Source/Engine/Graphics/Materials/MaterialShader.h +++ b/Source/Engine/Graphics/Materials/MaterialShader.h @@ -10,7 +10,7 @@ /// /// Current materials shader version. /// -#define MATERIAL_GRAPH_VERSION 164 +#define MATERIAL_GRAPH_VERSION 165 class Material; class GPUShader; diff --git a/Source/Engine/Graphics/Models/Mesh.cpp b/Source/Engine/Graphics/Models/Mesh.cpp index 8aa090325..567dbeba6 100644 --- a/Source/Engine/Graphics/Models/Mesh.cpp +++ b/Source/Engine/Graphics/Models/Mesh.cpp @@ -24,7 +24,7 @@ namespace { template - bool UpdateMesh(Mesh* mesh, uint32 vertexCount, uint32 triangleCount, Float3* vertices, IndexType* triangles, Float3* normals, Float3* tangents, Float2* uvs, Color32* colors) + bool UpdateMesh(Mesh* mesh, uint32 vertexCount, uint32 triangleCount, const Float3* vertices, const IndexType* triangles, const Float3* normals, const Float3* tangents, const Float2* uvs, const Color32* colors) { auto model = mesh->GetModel(); CHECK_RETURN(model && model->IsVirtual(), true); @@ -63,40 +63,39 @@ namespace const auto t = Float1010102(Float3::UnitX); for (uint32 i = 0; i < vertexCount; i++) { - vb1[i].Normal = n; - vb1[i].Tangent = t; + vb1.Get()[i].Normal = n; + vb1.Get()[i].Tangent = t; } } if (uvs) { for (uint32 i = 0; i < vertexCount; i++) - vb1[i].TexCoord = Half2(uvs[i]); + vb1.Get()[i].TexCoord = Half2(uvs[i]); } else { auto v = Half2::Zero; for (uint32 i = 0; i < vertexCount; i++) - vb1[i].TexCoord = v; + vb1.Get()[i].TexCoord = v; } { auto v = Half2::Zero; for (uint32 i = 0; i < vertexCount; i++) - vb1[i].LightmapUVs = v; + vb1.Get()[i].LightmapUVs = v; } if (colors) { vb2.Resize(vertexCount); for (uint32 i = 0; i < vertexCount; i++) - vb2[i].Color = colors[i]; + vb2.Get()[i].Color = colors[i]; } return mesh->UpdateMesh(vertexCount, triangleCount, (VB0ElementType*)vertices, vb1.Get(), vb2.HasItems() ? vb2.Get() : nullptr, triangles); } #if !COMPILE_WITHOUT_CSHARP - template - bool UpdateMesh(Mesh* mesh, uint32 vertexCount, uint32 triangleCount, MArray* verticesObj, MArray* trianglesObj, MArray* normalsObj, MArray* tangentsObj, MArray* uvObj, MArray* colorsObj) + bool UpdateMesh(Mesh* mesh, uint32 vertexCount, uint32 triangleCount, const MArray* verticesObj, const MArray* trianglesObj, const MArray* normalsObj, const MArray* tangentsObj, const MArray* uvObj, const MArray* colorsObj) { ASSERT((uint32)MCore::Array::GetLength(verticesObj) >= vertexCount); ASSERT((uint32)MCore::Array::GetLength(trianglesObj) / 3 >= triangleCount); @@ -110,7 +109,7 @@ namespace } template - bool UpdateTriangles(Mesh* mesh, int32 triangleCount, MArray* trianglesObj) + bool UpdateTriangles(Mesh* mesh, int32 triangleCount, const MArray* trianglesObj) { const auto model = mesh->GetModel(); ASSERT(model && model->IsVirtual() && trianglesObj); @@ -121,7 +120,6 @@ namespace return mesh->UpdateTriangles(triangleCount, ib); } - #endif } @@ -130,7 +128,7 @@ bool Mesh::HasVertexColors() const return _vertexBuffers[2] != nullptr && _vertexBuffers[2]->IsAllocated(); } -bool Mesh::UpdateMesh(uint32 vertexCount, uint32 triangleCount, VB0ElementType* vb0, VB1ElementType* vb1, VB2ElementType* vb2, void* ib, bool use16BitIndices) +bool Mesh::UpdateMesh(uint32 vertexCount, uint32 triangleCount, const VB0ElementType* vb0, const VB1ElementType* vb1, const VB2ElementType* vb2, const void* ib, bool use16BitIndices) { auto model = (Model*)_model; @@ -145,7 +143,7 @@ bool Mesh::UpdateMesh(uint32 vertexCount, uint32 triangleCount, VB0ElementType* // Calculate mesh bounds BoundingBox bounds; - BoundingBox::FromPoints((Float3*)vb0, vertexCount, bounds); + BoundingBox::FromPoints((const Float3*)vb0, vertexCount, bounds); SetBounds(bounds); // Send event (actors using this model can update bounds, etc.) @@ -155,17 +153,17 @@ bool Mesh::UpdateMesh(uint32 vertexCount, uint32 triangleCount, VB0ElementType* return failed; } -bool Mesh::UpdateMesh(uint32 vertexCount, uint32 triangleCount, Float3* vertices, uint16* triangles, Float3* normals, Float3* tangents, Float2* uvs, Color32* colors) +bool Mesh::UpdateMesh(uint32 vertexCount, uint32 triangleCount, const Float3* vertices, const uint16* triangles, const Float3* normals, const Float3* tangents, const Float2* uvs, const Color32* colors) { return ::UpdateMesh(this, vertexCount, triangleCount, vertices, triangles, normals, tangents, uvs, colors); } -bool Mesh::UpdateMesh(uint32 vertexCount, uint32 triangleCount, Float3* vertices, uint32* triangles, Float3* normals, Float3* tangents, Float2* uvs, Color32* colors) +bool Mesh::UpdateMesh(uint32 vertexCount, uint32 triangleCount, const Float3* vertices, const uint32* triangles, const Float3* normals, const Float3* tangents, const Float2* uvs, const Color32* colors) { return ::UpdateMesh(this, vertexCount, triangleCount, vertices, triangles, normals, tangents, uvs, colors); } -bool Mesh::UpdateTriangles(uint32 triangleCount, void* ib, bool use16BitIndices) +bool Mesh::UpdateTriangles(uint32 triangleCount, const void* ib, bool use16BitIndices) { // Cache data uint32 indicesCount = triangleCount * 3; @@ -217,7 +215,7 @@ Mesh::~Mesh() SAFE_DELETE_GPU_RESOURCE(_indexBuffer); } -bool Mesh::Load(uint32 vertices, uint32 triangles, void* vb0, void* vb1, void* vb2, void* ib, bool use16BitIndexBuffer) +bool Mesh::Load(uint32 vertices, uint32 triangles, const void* vb0, const void* vb1, const void* vb2, const void* ib, bool use16BitIndexBuffer) { // Cache data uint32 indicesCount = triangles * 3; @@ -697,22 +695,22 @@ ScriptingObject* Mesh::GetParentModel() #if !COMPILE_WITHOUT_CSHARP -bool Mesh::UpdateMeshUInt(int32 vertexCount, int32 triangleCount, MArray* verticesObj, MArray* trianglesObj, MArray* normalsObj, MArray* tangentsObj, MArray* uvObj, MArray* colorsObj) +bool Mesh::UpdateMeshUInt(int32 vertexCount, int32 triangleCount, const MArray* verticesObj, const MArray* trianglesObj, const MArray* normalsObj, const MArray* tangentsObj, const MArray* uvObj, const MArray* colorsObj) { return ::UpdateMesh(this, (uint32)vertexCount, (uint32)triangleCount, verticesObj, trianglesObj, normalsObj, tangentsObj, uvObj, colorsObj); } -bool Mesh::UpdateMeshUShort(int32 vertexCount, int32 triangleCount, MArray* verticesObj, MArray* trianglesObj, MArray* normalsObj, MArray* tangentsObj, MArray* uvObj, MArray* colorsObj) +bool Mesh::UpdateMeshUShort(int32 vertexCount, int32 triangleCount, const MArray* verticesObj, const MArray* trianglesObj, const MArray* normalsObj, const MArray* tangentsObj, const MArray* uvObj, const MArray* colorsObj) { return ::UpdateMesh(this, (uint32)vertexCount, (uint32)triangleCount, verticesObj, trianglesObj, normalsObj, tangentsObj, uvObj, colorsObj); } -bool Mesh::UpdateTrianglesUInt(int32 triangleCount, MArray* trianglesObj) +bool Mesh::UpdateTrianglesUInt(int32 triangleCount, const MArray* trianglesObj) { return ::UpdateTriangles(this, triangleCount, trianglesObj); } -bool Mesh::UpdateTrianglesUShort(int32 triangleCount, MArray* trianglesObj) +bool Mesh::UpdateTrianglesUShort(int32 triangleCount, const MArray* trianglesObj) { return ::UpdateTriangles(this, triangleCount, trianglesObj); } diff --git a/Source/Engine/Graphics/Models/Mesh.h b/Source/Engine/Graphics/Models/Mesh.h index 4885ce3cf..9b229fc5b 100644 --- a/Source/Engine/Graphics/Models/Mesh.h +++ b/Source/Engine/Graphics/Models/Mesh.h @@ -112,7 +112,7 @@ public: /// The third vertex buffer data. /// The index buffer in clockwise order. /// True if failed, otherwise false. - FORCE_INLINE bool UpdateMesh(uint32 vertexCount, uint32 triangleCount, VB0ElementType* vb0, VB1ElementType* vb1, VB2ElementType* vb2, uint32* ib) + FORCE_INLINE bool UpdateMesh(uint32 vertexCount, uint32 triangleCount, const VB0ElementType* vb0, const VB1ElementType* vb1, const VB2ElementType* vb2, const uint32* ib) { return UpdateMesh(vertexCount, triangleCount, vb0, vb1, vb2, ib, false); } @@ -127,7 +127,7 @@ public: /// The third vertex buffer data. /// The index buffer in clockwise order. /// True if failed, otherwise false. - FORCE_INLINE bool UpdateMesh(uint32 vertexCount, uint32 triangleCount, VB0ElementType* vb0, VB1ElementType* vb1, VB2ElementType* vb2, uint16* ib) + FORCE_INLINE bool UpdateMesh(uint32 vertexCount, uint32 triangleCount, const VB0ElementType* vb0, const VB1ElementType* vb1, const VB2ElementType* vb2, const uint16* ib) { return UpdateMesh(vertexCount, triangleCount, vb0, vb1, vb2, ib, true); } @@ -145,7 +145,7 @@ public: /// The index buffer in clockwise order. /// True if index buffer uses 16-bit index buffer, otherwise 32-bit. /// True if failed, otherwise false. - bool UpdateMesh(uint32 vertexCount, uint32 triangleCount, VB0ElementType* vb0, VB1ElementType* vb1, VB2ElementType* vb2, void* ib, bool use16BitIndices); + bool UpdateMesh(uint32 vertexCount, uint32 triangleCount, const VB0ElementType* vb0, const VB1ElementType* vb1, const VB2ElementType* vb2, const void* ib, bool use16BitIndices); /// /// Updates the model mesh (used by the virtual models created with Init rather than Load). @@ -161,7 +161,7 @@ public: /// The texture coordinates (per vertex). /// The vertex colors (per vertex). /// True if failed, otherwise false. - bool UpdateMesh(uint32 vertexCount, uint32 triangleCount, Float3* vertices, uint16* triangles, Float3* normals = nullptr, Float3* tangents = nullptr, Float2* uvs = nullptr, Color32* colors = nullptr); + bool UpdateMesh(uint32 vertexCount, uint32 triangleCount, const Float3* vertices, const uint16* triangles, const Float3* normals = nullptr, const Float3* tangents = nullptr, const Float2* uvs = nullptr, const Color32* colors = nullptr); /// /// Updates the model mesh (used by the virtual models created with Init rather than Load). @@ -177,7 +177,7 @@ public: /// The texture coordinates (per vertex). /// The vertex colors (per vertex). /// True if failed, otherwise false. - bool UpdateMesh(uint32 vertexCount, uint32 triangleCount, Float3* vertices, uint32* triangles, Float3* normals = nullptr, Float3* tangents = nullptr, Float2* uvs = nullptr, Color32* colors = nullptr); + bool UpdateMesh(uint32 vertexCount, uint32 triangleCount, const Float3* vertices, const uint32* triangles, const Float3* normals = nullptr, const Float3* tangents = nullptr, const Float2* uvs = nullptr, const Color32* colors = nullptr); public: /// @@ -186,7 +186,7 @@ public: /// The amount of triangles in the index buffer. /// The index buffer. /// True if failed, otherwise false. - FORCE_INLINE bool UpdateTriangles(uint32 triangleCount, uint32* ib) + FORCE_INLINE bool UpdateTriangles(uint32 triangleCount, const uint32* ib) { return UpdateTriangles(triangleCount, ib, false); } @@ -197,7 +197,7 @@ public: /// The amount of triangles in the index buffer. /// The index buffer. /// True if failed, otherwise false. - FORCE_INLINE bool UpdateTriangles(uint32 triangleCount, uint16* ib) + FORCE_INLINE bool UpdateTriangles(uint32 triangleCount, const uint16* ib) { return UpdateTriangles(triangleCount, ib, true); } @@ -209,7 +209,7 @@ public: /// The index buffer. /// True if index buffer uses 16-bit index buffer, otherwise 32-bit. /// True if failed, otherwise false. - bool UpdateTriangles(uint32 triangleCount, void* ib, bool use16BitIndices); + bool UpdateTriangles(uint32 triangleCount, const void* ib, bool use16BitIndices); public: /// @@ -235,7 +235,7 @@ public: /// Index buffer data /// True if use 16 bit indices for the index buffer (true: uint16, false: uint32). /// True if cannot load data, otherwise false. - bool Load(uint32 vertices, uint32 triangles, void* vb0, void* vb1, void* vb2, void* ib, bool use16BitIndexBuffer); + bool Load(uint32 vertices, uint32 triangles, const void* vb0, const void* vb1, const void* vb2, const void* ib, bool use16BitIndexBuffer); /// /// Unloads the mesh data (vertex buffers and cache). The opposite to Load. @@ -315,10 +315,10 @@ private: // Internal bindings API_FUNCTION(NoProxy) ScriptingObject* GetParentModel(); #if !COMPILE_WITHOUT_CSHARP - API_FUNCTION(NoProxy) bool UpdateMeshUInt(int32 vertexCount, int32 triangleCount, MArray* verticesObj, MArray* trianglesObj, MArray* normalsObj, MArray* tangentsObj, MArray* uvObj, MArray* colorsObj); - API_FUNCTION(NoProxy) bool UpdateMeshUShort(int32 vertexCount, int32 triangleCount, MArray* verticesObj, MArray* trianglesObj, MArray* normalsObj, MArray* tangentsObj, MArray* uvObj, MArray* colorsObj); - API_FUNCTION(NoProxy) bool UpdateTrianglesUInt(int32 triangleCount, MArray* trianglesObj); - API_FUNCTION(NoProxy) bool UpdateTrianglesUShort(int32 triangleCount, MArray* trianglesObj); + API_FUNCTION(NoProxy) bool UpdateMeshUInt(int32 vertexCount, int32 triangleCount, const MArray* verticesObj, const MArray* trianglesObj, const MArray* normalsObj, const MArray* tangentsObj, const MArray* uvObj, const MArray* colorsObj); + API_FUNCTION(NoProxy) bool UpdateMeshUShort(int32 vertexCount, int32 triangleCount, const MArray* verticesObj, const MArray* trianglesObj, const MArray* normalsObj, const MArray* tangentsObj, const MArray* uvObj, const MArray* colorsObj); + API_FUNCTION(NoProxy) bool UpdateTrianglesUInt(int32 triangleCount, const MArray* trianglesObj); + API_FUNCTION(NoProxy) bool UpdateTrianglesUShort(int32 triangleCount, const MArray* trianglesObj); API_FUNCTION(NoProxy) MArray* DownloadBuffer(bool forceGpu, MTypeObject* resultType, int32 typeI); #endif }; diff --git a/Source/Engine/Graphics/Models/ModelData.cpp b/Source/Engine/Graphics/Models/ModelData.cpp index dc9527b1d..b42288f7f 100644 --- a/Source/Engine/Graphics/Models/ModelData.cpp +++ b/Source/Engine/Graphics/Models/ModelData.cpp @@ -508,6 +508,31 @@ void MeshData::TransformBuffer(const Matrix& matrix) Matrix::Invert(matrix, inverseTransposeMatrix); Matrix::Transpose(inverseTransposeMatrix, inverseTransposeMatrix); + // Transform blend shapes + for (auto& blendShape : BlendShapes) + { + const auto vv = blendShape.Vertices.Get(); + for (int32 i = 0; i < blendShape.Vertices.Count(); i++) + { + auto& v = vv[i]; + + Float3 p = Positions[v.VertexIndex]; + Float3 vp = p + v.PositionDelta; + Float3::Transform(vp, matrix, vp); + Float3::Transform(p, matrix, p); + v.PositionDelta = vp - p; + + Float3 n = Normals[v.VertexIndex]; + Float3 vn = n + v.NormalDelta; + vn.Normalize(); + Float3::TransformNormal(vn, inverseTransposeMatrix, vn); + vn.Normalize(); + Float3::TransformNormal(n, inverseTransposeMatrix, n); + n.Normalize(); + v.NormalDelta = vn - n; + } + } + // Transform positions const auto pp = Positions.Get(); for (int32 i = 0; i < Positions.Count(); i++) @@ -531,18 +556,6 @@ void MeshData::TransformBuffer(const Matrix& matrix) Float3::TransformNormal(t, inverseTransposeMatrix, t); t.Normalize(); } - - // Transform blend shapes - for (auto& blendShape : BlendShapes) - { - for (int32 i = 0; i < blendShape.Vertices.Count(); i++) - { - auto& v = blendShape.Vertices[i]; - Float3::Transform(v.PositionDelta, matrix, v.PositionDelta); - Float3::TransformNormal(v.NormalDelta, inverseTransposeMatrix, v.NormalDelta); - v.NormalDelta.Normalize(); - } - } } void MeshData::NormalizeBlendWeights() @@ -550,9 +563,10 @@ void MeshData::NormalizeBlendWeights() ASSERT(Positions.Count() == BlendWeights.Count()); for (int32 i = 0; i < Positions.Count(); i++) { - const float sum = BlendWeights[i].SumValues(); + Float4& weights = BlendWeights.Get()[i]; + const float sum = weights.SumValues(); const float invSum = sum > ZeroTolerance ? 1.0f / sum : 0.0f; - BlendWeights[i] *= invSum; + weights *= invSum; } } diff --git a/Source/Engine/Graphics/Models/SkinnedMesh.cpp b/Source/Engine/Graphics/Models/SkinnedMesh.cpp index 8ee9599b0..14542ef91 100644 --- a/Source/Engine/Graphics/Models/SkinnedMesh.cpp +++ b/Source/Engine/Graphics/Models/SkinnedMesh.cpp @@ -105,7 +105,7 @@ SkinnedMesh::~SkinnedMesh() SAFE_DELETE_GPU_RESOURCE(_indexBuffer); } -bool SkinnedMesh::Load(uint32 vertices, uint32 triangles, void* vb0, void* ib, bool use16BitIndexBuffer) +bool SkinnedMesh::Load(uint32 vertices, uint32 triangles, const void* vb0, const void* ib, bool use16BitIndexBuffer) { // Cache data uint32 indicesCount = triangles * 3; @@ -159,7 +159,7 @@ void SkinnedMesh::Unload() _use16BitIndexBuffer = false; } -bool SkinnedMesh::UpdateMesh(uint32 vertexCount, uint32 triangleCount, VB0SkinnedElementType* vb, void* ib, bool use16BitIndices) +bool SkinnedMesh::UpdateMesh(uint32 vertexCount, uint32 triangleCount, const VB0SkinnedElementType* vb, const void* ib, bool use16BitIndices) { auto model = (SkinnedModel*)_model; @@ -169,7 +169,7 @@ bool SkinnedMesh::UpdateMesh(uint32 vertexCount, uint32 triangleCount, VB0Skinne { // Calculate mesh bounds BoundingBox bounds; - BoundingBox::FromPoints((Float3*)vb, vertexCount, bounds); + BoundingBox::FromPoints((const Float3*)vb, vertexCount, bounds); SetBounds(bounds); // Send event (actors using this model can update bounds, etc.) @@ -429,7 +429,7 @@ ScriptingObject* SkinnedMesh::GetParentModel() #if !COMPILE_WITHOUT_CSHARP template -bool UpdateMesh(SkinnedMesh* mesh, MArray* verticesObj, MArray* trianglesObj, MArray* blendIndicesObj, MArray* blendWeightsObj, MArray* normalsObj, MArray* tangentsObj, MArray* uvObj) +bool UpdateMesh(SkinnedMesh* mesh, const MArray* verticesObj, const MArray* trianglesObj, const MArray* blendIndicesObj, const MArray* blendWeightsObj, const MArray* normalsObj, const MArray* tangentsObj, const MArray* uvObj) { auto model = mesh->GetSkinnedModel(); ASSERT(model && model->IsVirtual() && verticesObj && trianglesObj && blendIndicesObj && blendWeightsObj); @@ -505,12 +505,12 @@ bool UpdateMesh(SkinnedMesh* mesh, MArray* verticesObj, MArray* trianglesObj, MA return mesh->UpdateMesh(vertexCount, triangleCount, vb.Get(), ib); } -bool SkinnedMesh::UpdateMeshUInt(MArray* verticesObj, MArray* trianglesObj, MArray* blendIndicesObj, MArray* blendWeightsObj, MArray* normalsObj, MArray* tangentsObj, MArray* uvObj) +bool SkinnedMesh::UpdateMeshUInt(const MArray* verticesObj, const MArray* trianglesObj, const MArray* blendIndicesObj, const MArray* blendWeightsObj, const MArray* normalsObj, const MArray* tangentsObj, const MArray* uvObj) { return ::UpdateMesh(this, verticesObj, trianglesObj, blendIndicesObj, blendWeightsObj, normalsObj, tangentsObj, uvObj); } -bool SkinnedMesh::UpdateMeshUShort(MArray* verticesObj, MArray* trianglesObj, MArray* blendIndicesObj, MArray* blendWeightsObj, MArray* normalsObj, MArray* tangentsObj, MArray* uvObj) +bool SkinnedMesh::UpdateMeshUShort(const MArray* verticesObj, const MArray* trianglesObj, const MArray* blendIndicesObj, const MArray* blendWeightsObj, const MArray* normalsObj, const MArray* tangentsObj, const MArray* uvObj) { return ::UpdateMesh(this, verticesObj, trianglesObj, blendIndicesObj, blendWeightsObj, normalsObj, tangentsObj, uvObj); } diff --git a/Source/Engine/Graphics/Models/SkinnedMesh.h b/Source/Engine/Graphics/Models/SkinnedMesh.h index d6ab54f16..963511671 100644 --- a/Source/Engine/Graphics/Models/SkinnedMesh.h +++ b/Source/Engine/Graphics/Models/SkinnedMesh.h @@ -76,7 +76,7 @@ public: /// Index buffer data /// True if use 16 bit indices for the index buffer (true: uint16, false: uint32). /// True if cannot load data, otherwise false. - bool Load(uint32 vertices, uint32 triangles, void* vb0, void* ib, bool use16BitIndexBuffer); + bool Load(uint32 vertices, uint32 triangles, const void* vb0, const void* ib, bool use16BitIndexBuffer); /// /// Unloads the mesh data (vertex buffers and cache). The opposite to Load. @@ -92,7 +92,7 @@ public: /// The vertex buffer data. /// The index buffer in clockwise order. /// True if failed, otherwise false. - FORCE_INLINE bool UpdateMesh(uint32 vertexCount, uint32 triangleCount, VB0SkinnedElementType* vb, int32* ib) + FORCE_INLINE bool UpdateMesh(uint32 vertexCount, uint32 triangleCount, const VB0SkinnedElementType* vb, const int32* ib) { return UpdateMesh(vertexCount, triangleCount, vb, ib, false); } @@ -105,7 +105,7 @@ public: /// The vertex buffer data. /// The index buffer in clockwise order. /// True if failed, otherwise false. - FORCE_INLINE bool UpdateMesh(uint32 vertexCount, uint32 triangleCount, VB0SkinnedElementType* vb, uint32* ib) + FORCE_INLINE bool UpdateMesh(uint32 vertexCount, uint32 triangleCount, const VB0SkinnedElementType* vb, const uint32* ib) { return UpdateMesh(vertexCount, triangleCount, vb, ib, false); } @@ -118,7 +118,7 @@ public: /// The vertex buffer data. /// The index buffer, clockwise order. /// True if failed, otherwise false. - FORCE_INLINE bool UpdateMesh(uint32 vertexCount, uint32 triangleCount, VB0SkinnedElementType* vb, uint16* ib) + FORCE_INLINE bool UpdateMesh(uint32 vertexCount, uint32 triangleCount, const VB0SkinnedElementType* vb, const uint16* ib) { return UpdateMesh(vertexCount, triangleCount, vb, ib, true); } @@ -132,7 +132,7 @@ public: /// The index buffer in clockwise order. /// True if index buffer uses 16-bit index buffer, otherwise 32-bit. /// True if failed, otherwise false. - bool UpdateMesh(uint32 vertexCount, uint32 triangleCount, VB0SkinnedElementType* vb, void* ib, bool use16BitIndices); + bool UpdateMesh(uint32 vertexCount, uint32 triangleCount, const VB0SkinnedElementType* vb, const void* ib, bool use16BitIndices); public: /// @@ -188,8 +188,8 @@ private: // Internal bindings API_FUNCTION(NoProxy) ScriptingObject* GetParentModel(); #if !COMPILE_WITHOUT_CSHARP - API_FUNCTION(NoProxy) bool UpdateMeshUInt(MArray* verticesObj, MArray* trianglesObj, MArray* blendIndicesObj, MArray* blendWeightsObj, MArray* normalsObj, MArray* tangentsObj, MArray* uvObj); - API_FUNCTION(NoProxy) bool UpdateMeshUShort(MArray* verticesObj, MArray* trianglesObj, MArray* blendIndicesObj, MArray* blendWeightsObj, MArray* normalsObj, MArray* tangentsObj, MArray* uvObj); + API_FUNCTION(NoProxy) bool UpdateMeshUInt(const MArray* verticesObj, const MArray* trianglesObj, const MArray* blendIndicesObj, const MArray* blendWeightsObj, const MArray* normalsObj, const MArray* tangentsObj, const MArray* uvObj); + API_FUNCTION(NoProxy) bool UpdateMeshUShort(const MArray* verticesObj, const MArray* trianglesObj, const MArray* blendIndicesObj, const MArray* blendWeightsObj, const MArray* normalsObj, const MArray* tangentsObj, const MArray* uvObj); API_FUNCTION(NoProxy) MArray* DownloadBuffer(bool forceGpu, MTypeObject* resultType, int32 typeI); #endif }; diff --git a/Source/Engine/GraphicsDevice/Vulkan/GPUTextureVulkan.cpp b/Source/Engine/GraphicsDevice/Vulkan/GPUTextureVulkan.cpp index cbd6d210f..3ef9aa9c3 100644 --- a/Source/Engine/GraphicsDevice/Vulkan/GPUTextureVulkan.cpp +++ b/Source/Engine/GraphicsDevice/Vulkan/GPUTextureVulkan.cpp @@ -131,14 +131,14 @@ void GPUTextureViewVulkan::Release() { Device->OnImageViewDestroy(ViewFramebuffer); Device->DeferredDeletionQueue.EnqueueResource(DeferredDeletionQueueVulkan::Type::ImageView, ViewFramebuffer); - ViewFramebuffer = VK_NULL_HANDLE; } + ViewFramebuffer = VK_NULL_HANDLE; if (ViewSRV != View && ViewSRV != VK_NULL_HANDLE) { Device->OnImageViewDestroy(ViewSRV); Device->DeferredDeletionQueue.EnqueueResource(DeferredDeletionQueueVulkan::Type::ImageView, ViewSRV); - ViewSRV = VK_NULL_HANDLE; } + ViewSRV = VK_NULL_HANDLE; Device->OnImageViewDestroy(View); Device->DeferredDeletionQueue.EnqueueResource(DeferredDeletionQueueVulkan::Type::ImageView, View); diff --git a/Source/Engine/Level/Actors/AnimatedModel.cpp b/Source/Engine/Level/Actors/AnimatedModel.cpp index 6f8137bc6..17b9acc11 100644 --- a/Source/Engine/Level/Actors/AnimatedModel.cpp +++ b/Source/Engine/Level/Actors/AnimatedModel.cpp @@ -55,10 +55,10 @@ void AnimatedModel::UpdateAnimation() || !IsActiveInHierarchy() || SkinnedModel == nullptr || !SkinnedModel->IsLoaded() - || _lastUpdateFrame == Engine::FrameCount + || _lastUpdateFrame == Engine::UpdateCount || _masterPose) return; - _lastUpdateFrame = Engine::FrameCount; + _lastUpdateFrame = Engine::UpdateCount; if (AnimationGraph && AnimationGraph->IsLoaded() && AnimationGraph->Graph.IsReady()) { @@ -113,7 +113,7 @@ void AnimatedModel::PreInitSkinningData() for (int32 boneIndex = 0; boneIndex < bonesCount; boneIndex++) { auto& bone = skeleton.Bones[boneIndex]; - identityMatrices[boneIndex] = bone.OffsetMatrix * GraphInstance.NodesPose[bone.NodeIndex]; + identityMatrices.Get()[boneIndex] = bone.OffsetMatrix * GraphInstance.NodesPose[bone.NodeIndex]; } _skinningData.SetData(identityMatrices.Get(), true); @@ -640,7 +640,7 @@ void AnimatedModel::RunBlendShapeDeformer(const MeshBase* mesh, MeshDeformationD ASSERT_LOW_LAYER(blendShapeVertex.VertexIndex < vertexCount); VB0SkinnedElementType& vertex = *(data + blendShapeVertex.VertexIndex); vertex.Position = vertex.Position + blendShapeVertex.PositionDelta * q.Second; - Float3 normal = (vertex.Normal.ToFloat3() * 2.0f - 1.0f) + blendShapeVertex.NormalDelta; + Float3 normal = (vertex.Normal.ToFloat3() * 2.0f - 1.0f) + blendShapeVertex.NormalDelta * q.Second; vertex.Normal = normal * 0.5f + 0.5f; } } diff --git a/Source/Engine/Level/Actors/StaticModel.cpp b/Source/Engine/Level/Actors/StaticModel.cpp index d85088a69..dd77692b3 100644 --- a/Source/Engine/Level/Actors/StaticModel.cpp +++ b/Source/Engine/Level/Actors/StaticModel.cpp @@ -346,7 +346,7 @@ void StaticModel::Draw(RenderContext& renderContext) draw.World = &world; draw.DrawState = &_drawState; draw.Deformation = _deformation; - draw.Lightmap = _scene->LightmapsData.GetReadyLightmap(Lightmap.TextureIndex); + draw.Lightmap = _scene ? _scene->LightmapsData.GetReadyLightmap(Lightmap.TextureIndex) : nullptr; draw.LightmapUVs = &Lightmap.UVsArea; draw.Flags = _staticFlags; draw.DrawModes = DrawModes; @@ -380,7 +380,7 @@ void StaticModel::Draw(RenderContextBatch& renderContextBatch) draw.World = &world; draw.DrawState = &_drawState; draw.Deformation = _deformation; - draw.Lightmap = _scene->LightmapsData.GetReadyLightmap(Lightmap.TextureIndex); + draw.Lightmap = _scene ? _scene->LightmapsData.GetReadyLightmap(Lightmap.TextureIndex) : nullptr; draw.LightmapUVs = &Lightmap.UVsArea; draw.Flags = _staticFlags; draw.DrawModes = DrawModes; diff --git a/Source/Engine/Navigation/NavMeshBuilder.cpp b/Source/Engine/Navigation/NavMeshBuilder.cpp index eedd71158..5bc5141fa 100644 --- a/Source/Engine/Navigation/NavMeshBuilder.cpp +++ b/Source/Engine/Navigation/NavMeshBuilder.cpp @@ -628,6 +628,7 @@ bool GenerateTile(NavMesh* navMesh, NavMeshRuntime* runtime, int32 x, int32 y, B LOG(Warning, "Could not build Detour navmesh."); return true; } + ASSERT_LOW_LAYER(navDataSize > 4 && *(uint32*)navData == DT_NAVMESH_MAGIC); // Sanity check for Detour header { PROFILE_CPU_NAMED("Navigation.CreateTile"); diff --git a/Source/Engine/Navigation/NavMeshData.cpp b/Source/Engine/Navigation/NavMeshData.cpp index 16227ce98..c6faccc1e 100644 --- a/Source/Engine/Navigation/NavMeshData.cpp +++ b/Source/Engine/Navigation/NavMeshData.cpp @@ -17,7 +17,7 @@ void NavMeshData::Save(WriteStream& stream) // Write tiles for (int32 tileIndex = 0; tileIndex < Tiles.Count(); tileIndex++) { - auto& tile = Tiles[tileIndex]; + auto& tile = Tiles.Get()[tileIndex]; // Write tile header NavMeshTileDataHeader tileHeader; @@ -41,7 +41,6 @@ void NavMeshData::Save(WriteStream& stream) bool NavMeshData::Load(BytesContainer& data, bool copyData) { - // No data if (data.Length() < sizeof(NavMeshDataHeader)) { LOG(Warning, "No valid navmesh data."); @@ -50,7 +49,7 @@ bool NavMeshData::Load(BytesContainer& data, bool copyData) MemoryReadStream stream(data.Get(), data.Length()); // Read header - const auto header = stream.Move(1); + const auto header = stream.Move(); if (header->Version != 1) { LOG(Warning, "Invalid valid navmesh data version {0}.", header->Version); @@ -67,10 +66,10 @@ bool NavMeshData::Load(BytesContainer& data, bool copyData) // Read tiles for (int32 tileIndex = 0; tileIndex < Tiles.Count(); tileIndex++) { - auto& tile = Tiles[tileIndex]; + auto& tile = Tiles.Get()[tileIndex]; // Read tile header - const auto tileHeader = stream.Move(1); + const auto tileHeader = stream.Move(); if (tileHeader->DataSize <= 0) { LOG(Warning, "Invalid navmesh tile data."); @@ -83,13 +82,9 @@ bool NavMeshData::Load(BytesContainer& data, bool copyData) // Read tile data const auto tileData = stream.Move(tileHeader->DataSize); if (copyData) - { tile.Data.Copy(tileData, tileHeader->DataSize); - } else - { tile.Data.Link(tileData, tileHeader->DataSize); - } } return false; diff --git a/Source/Engine/Navigation/NavMeshRuntime.cpp b/Source/Engine/Navigation/NavMeshRuntime.cpp index 0e6ae0a0e..c4576aa53 100644 --- a/Source/Engine/Navigation/NavMeshRuntime.cpp +++ b/Source/Engine/Navigation/NavMeshRuntime.cpp @@ -60,17 +60,12 @@ bool NavMeshRuntime::FindDistanceToWall(const Vector3& startPosition, NavMeshHit Float3::Transform(startPosition, Properties.Rotation, startPositionNavMesh); dtPolyRef startPoly = 0; - query->findNearestPoly(&startPositionNavMesh.X, &extent.X, &filter, &startPoly, nullptr); - if (!startPoly) - { + if (!dtStatusSucceed(query->findNearestPoly(&startPositionNavMesh.X, &extent.X, &filter, &startPoly, nullptr))) return false; - } Float3 hitPosition, hitNormal; if (!dtStatusSucceed(query->findDistanceToWall(startPoly, &startPositionNavMesh.X, maxDistance, &filter, &hitInfo.Distance, &hitPosition.X, &hitNormal.X))) - { return false; - } Quaternion invRotation; Quaternion::Invert(Properties.Rotation, invRotation); @@ -98,17 +93,11 @@ bool NavMeshRuntime::FindPath(const Vector3& startPosition, const Vector3& endPo Float3::Transform(endPosition, Properties.Rotation, endPositionNavMesh); dtPolyRef startPoly = 0; - query->findNearestPoly(&startPositionNavMesh.X, &extent.X, &filter, &startPoly, nullptr); - if (!startPoly) - { + if (!dtStatusSucceed(query->findNearestPoly(&startPositionNavMesh.X, &extent.X, &filter, &startPoly, nullptr))) return false; - } dtPolyRef endPoly = 0; - query->findNearestPoly(&endPositionNavMesh.X, &extent.X, &filter, &endPoly, nullptr); - if (!endPoly) - { + if (!dtStatusSucceed(query->findNearestPoly(&endPositionNavMesh.X, &extent.X, &filter, &endPoly, nullptr))) return false; - } dtPolyRef path[NAV_MESH_PATH_MAX_SIZE]; int32 pathSize; @@ -166,30 +155,19 @@ bool NavMeshRuntime::TestPath(const Vector3& startPosition, const Vector3& endPo Float3::Transform(endPosition, Properties.Rotation, endPositionNavMesh); dtPolyRef startPoly = 0; - query->findNearestPoly(&startPositionNavMesh.X, &extent.X, &filter, &startPoly, nullptr); - if (!startPoly) - { + if (!dtStatusSucceed(query->findNearestPoly(&startPositionNavMesh.X, &extent.X, &filter, &startPoly, nullptr))) return false; - } dtPolyRef endPoly = 0; - query->findNearestPoly(&endPositionNavMesh.X, &extent.X, &filter, &endPoly, nullptr); - if (!endPoly) - { + if (!dtStatusSucceed(query->findNearestPoly(&endPositionNavMesh.X, &extent.X, &filter, &endPoly, nullptr))) return false; - } dtPolyRef path[NAV_MESH_PATH_MAX_SIZE]; int32 pathSize; const auto findPathStatus = query->findPath(startPoly, endPoly, &startPositionNavMesh.X, &endPositionNavMesh.X, &filter, path, &pathSize, NAV_MESH_PATH_MAX_SIZE); if (dtStatusFailed(findPathStatus)) - { return false; - } - if (dtStatusDetail(findPathStatus, DT_PARTIAL_RESULT)) - { return false; - } return true; } @@ -210,11 +188,8 @@ bool NavMeshRuntime::FindClosestPoint(const Vector3& point, Vector3& result) con dtPolyRef startPoly = 0; Float3 nearestPt; - query->findNearestPoly(&pointNavMesh.X, &extent.X, &filter, &startPoly, &nearestPt.X); - if (!startPoly) - { + if (!dtStatusSucceed(query->findNearestPoly(&pointNavMesh.X, &extent.X, &filter, &startPoly, &nearestPt.X))) return false; - } Quaternion invRotation; Quaternion::Invert(Properties.Rotation, invRotation); @@ -235,11 +210,8 @@ bool NavMeshRuntime::FindRandomPoint(Vector3& result) const dtPolyRef randomPoly = 0; Float3 randomPt; - query->findRandomPoint(&filter, Random::Rand, &randomPoly, &randomPt.X); - if (!randomPoly) - { + if (!dtStatusSucceed(query->findRandomPoint(&filter, Random::Rand, &randomPoly, &randomPt.X))) return false; - } Quaternion invRotation; Quaternion::Invert(Properties.Rotation, invRotation); @@ -257,25 +229,19 @@ bool NavMeshRuntime::FindRandomPointAroundCircle(const Vector3& center, float ra dtQueryFilter filter; InitFilter(filter); - Float3 extent = Properties.DefaultQueryExtent; + Float3 extent(radius); Float3 centerNavMesh; Float3::Transform(center, Properties.Rotation, centerNavMesh); dtPolyRef centerPoly = 0; - query->findNearestPoly(¢erNavMesh.X, &extent.X, &filter, ¢erPoly, nullptr); - if (!centerPoly) - { + if (!dtStatusSucceed(query->findNearestPoly(¢erNavMesh.X, &extent.X, &filter, ¢erPoly, nullptr))) return false; - } dtPolyRef randomPoly = 0; Float3 randomPt; - query->findRandomPointAroundCircle(centerPoly, ¢erNavMesh.X, radius, &filter, Random::Rand, &randomPoly, &randomPt.X); - if (!randomPoly) - { + if (!dtStatusSucceed(query->findRandomPointAroundCircle(centerPoly, ¢erNavMesh.X, radius, &filter, Random::Rand, &randomPoly, &randomPt.X))) return false; - } Quaternion invRotation; Quaternion::Invert(Properties.Rotation, invRotation); @@ -300,11 +266,8 @@ bool NavMeshRuntime::RayCast(const Vector3& startPosition, const Vector3& endPos Float3::Transform(endPosition, Properties.Rotation, endPositionNavMesh); dtPolyRef startPoly = 0; - query->findNearestPoly(&startPositionNavMesh.X, &extent.X, &filter, &startPoly, nullptr); - if (!startPoly) - { + if (!dtStatusSucceed(query->findNearestPoly(&startPositionNavMesh.X, &extent.X, &filter, &startPoly, nullptr))) return false; - } dtRaycastHit hit; hit.path = nullptr; @@ -361,14 +324,6 @@ void NavMeshRuntime::EnsureCapacity(int32 tilesToAddCount) // Ensure to have size assigned ASSERT(_tileSize != 0); - // Allocate navmesh and initialize the default query - if (!_navMesh) - _navMesh = dtAllocNavMesh(); - if (dtStatusFailed(_navMeshQuery->init(_navMesh, MAX_NODES))) - { - LOG(Error, "Failed to initialize navmesh {0}.", Properties.Name); - } - // Prepare parameters dtNavMeshParams params; params.orig[0] = 0.0f; @@ -381,11 +336,17 @@ void NavMeshRuntime::EnsureCapacity(int32 tilesToAddCount) params.maxPolys = 1 << (22 - tilesBits); // Initialize nav mesh + if (!_navMesh) + _navMesh = dtAllocNavMesh(); if (dtStatusFailed(_navMesh->init(¶ms))) { - LOG(Error, "Navmesh {0} init failed.", Properties.Name); + LOG(Error, "Navmesh {0} init failed", Properties.Name); return; } + if (dtStatusFailed(_navMeshQuery->init(_navMesh, MAX_NODES))) + { + LOG(Error, "Navmesh query {0} init failed", Properties.Name); + } // Prepare tiles container _tiles.EnsureCapacity(newCapacity); @@ -405,7 +366,7 @@ void NavMeshRuntime::EnsureCapacity(int32 tilesToAddCount) const auto result = _navMesh->addTile(data, dataSize, flags, 0, nullptr); if (dtStatusFailed(result)) { - LOG(Warning, "Could not add tile to navmesh {0} (error: {1}).", Properties.Name, result & ~DT_FAILURE); + LOG(Warning, "Could not add tile ({2}x{3}, layer {4}) to navmesh {0} (error: {1})", Properties.Name, result & ~DT_FAILURE, tile.X, tile.Y, tile.Layer); } } } @@ -490,13 +451,11 @@ void NavMeshRuntime::RemoveTile(int32 x, int32 y, int32 layer) const auto tileRef = _navMesh->getTileRefAt(x, y, layer); if (tileRef == 0) - { return; - } if (dtStatusFailed(_navMesh->removeTile(tileRef, nullptr, nullptr))) { - LOG(Warning, "Failed to remove tile from navmesh {0}.", Properties.Name); + LOG(Warning, "Failed to remove tile ({1}x{2}, layer {3}) from navmesh {0}", Properties.Name, x, y, layer); } for (int32 i = 0; i < _tiles.Count(); i++) @@ -532,7 +491,7 @@ void NavMeshRuntime::RemoveTiles(bool (*prediction)(const NavMeshRuntime* navMes { if (dtStatusFailed(_navMesh->removeTile(tileRef, nullptr, nullptr))) { - LOG(Warning, "Failed to remove tile from navmesh {0}.", Properties.Name); + LOG(Warning, "Failed to remove tile ({1}x{2}, layer {3}) from navmesh {0}", Properties.Name, tile.X, tile.Y, tile.Layer); } } @@ -668,7 +627,7 @@ void NavMeshRuntime::AddTileInternal(NavMesh* navMesh, NavMeshTileData& tileData // Remove any existing tile at that location if (dtStatusFailed(_navMesh->removeTile(tileRef, nullptr, nullptr))) { - LOG(Warning, "Failed to remove tile from navmesh {0}.", Properties.Name); + LOG(Warning, "Failed to remove tile from navmesh {0}", Properties.Name); } // Reuse tile data container @@ -712,6 +671,6 @@ void NavMeshRuntime::AddTileInternal(NavMesh* navMesh, NavMeshTileData& tileData const auto result = _navMesh->addTile(data, dataSize, flags, 0, nullptr); if (dtStatusFailed(result)) { - LOG(Warning, "Could not add tile to navmesh {0} (error: {1}).", Properties.Name, result & ~DT_FAILURE); + LOG(Warning, "Could not add tile ({2}x{3}, layer {4}) to navmesh {0} (error: {1})", Properties.Name, result & ~DT_FAILURE, tileData.PosX, tileData.PosY, tileData.Layer); } } diff --git a/Source/Engine/Navigation/NavMeshRuntime.h b/Source/Engine/Navigation/NavMeshRuntime.h index 9b090b97c..40ea0959e 100644 --- a/Source/Engine/Navigation/NavMeshRuntime.h +++ b/Source/Engine/Navigation/NavMeshRuntime.h @@ -160,7 +160,7 @@ public: /// The source point. /// The result position on the navmesh (valid only if method returns true). /// True if found valid location on the navmesh, otherwise false. - API_FUNCTION() bool ProjectPoint(const Vector3& point, API_PARAM(Out) Vector3& result) const + API_FUNCTION() DEPRECATED bool ProjectPoint(const Vector3& point, API_PARAM(Out) Vector3& result) const { return FindClosestPoint(point, result); } diff --git a/Source/Engine/Networking/NetworkStream.cs b/Source/Engine/Networking/NetworkStream.cs index 02fb5eab8..bf8b9b269 100644 --- a/Source/Engine/Networking/NetworkStream.cs +++ b/Source/Engine/Networking/NetworkStream.cs @@ -1,4 +1,4 @@ -// Copyright (c) 2012-2024 Wojciech Figat. All rights reserved. +// Copyright (c) 2012-2024 Wojciech Figat. All rights reserved. using System; using System.Text; @@ -441,5 +441,23 @@ namespace FlaxEngine.Networking ReadBytes((byte*)&value, sizeof(bool)); return value; } + + /// + /// Writes the object data to the stream. Object has to be allocated. + /// + /// The serializable object. + public void Write(INetworkSerializable obj) + { + obj.Serialize(this); + } + + /// + /// Reads the object data from the stream. Object has to be allocated. + /// + /// The serializable object. + public void Read(INetworkSerializable obj) + { + obj.Deserialize(this); + } } } diff --git a/Source/Engine/Particles/Graph/GPU/ParticleEmitterGraph.GPU.h b/Source/Engine/Particles/Graph/GPU/ParticleEmitterGraph.GPU.h index 7874e4d82..25d9c853f 100644 --- a/Source/Engine/Particles/Graph/GPU/ParticleEmitterGraph.GPU.h +++ b/Source/Engine/Particles/Graph/GPU/ParticleEmitterGraph.GPU.h @@ -26,7 +26,7 @@ public: /// /// The asset references. Linked resources such as Animation assets are referenced in graph data as ID. We need to keep valid refs to them at runtime to keep data in memory. /// - AssetReference Assets[14]; + Array> Assets; }; /// diff --git a/Source/Engine/Particles/Graph/ParticleEmitterGraph.h b/Source/Engine/Particles/Graph/ParticleEmitterGraph.h index 77dd81d0f..07630a4b4 100644 --- a/Source/Engine/Particles/Graph/ParticleEmitterGraph.h +++ b/Source/Engine/Particles/Graph/ParticleEmitterGraph.h @@ -177,6 +177,7 @@ public: // Get Gameplay Global case GRAPH_NODE_MAKE_TYPE(7, 16): { + node->Assets.Resize(1); node->Assets[0] = Content::LoadAsync((Guid)node->Values[0]); break; } @@ -296,6 +297,7 @@ public: break; // Particle Emitter Function case GRAPH_NODE_MAKE_TYPE(14, 300): + node->Assets.Resize(1); InitParticleEmitterFunctionCall((Guid)node->Values[0], node->Assets[0], node->UsesParticleData, Layout); break; // Particle Index @@ -447,6 +449,7 @@ public: // Sprite Rendering case GRAPH_NODE_MAKE_TYPE(15, 400): { + node->Assets.Resize(1); node->Assets[0] = Content::LoadAsync((Guid)node->Values[2]); USE_ATTRIBUTE(Position, Float3, 0); USE_ATTRIBUTE(Rotation, Float3, 1); @@ -484,6 +487,7 @@ public: // Model Rendering case GRAPH_NODE_MAKE_TYPE(15, 403): { + node->Assets.Resize(2); node->Assets[0] = Content::LoadAsync((Guid)node->Values[2]); node->Assets[1] = Content::LoadAsync((Guid)node->Values[3]); USE_ATTRIBUTE(Position, Float3, 0); @@ -494,6 +498,7 @@ public: // Ribbon Rendering case GRAPH_NODE_MAKE_TYPE(15, 404): { + node->Assets.Resize(1); node->Assets[0] = Content::LoadAsync((Guid)node->Values[2]); USE_ATTRIBUTE(Position, Float3, 0); // TODO: add support for custom sorting key - not only by age @@ -503,6 +508,7 @@ public: // Volumetric Fog Rendering case GRAPH_NODE_MAKE_TYPE(15, 405): { + node->Assets.Resize(1); node->Assets[0] = Content::LoadAsync((Guid)node->Values[2]); USE_ATTRIBUTE(Position, Float3, 0); USE_ATTRIBUTE(Radius, Float, 1); diff --git a/Source/Engine/Particles/ParticleEffect.cpp b/Source/Engine/Particles/ParticleEffect.cpp index a5fd4cd02..6c584d513 100644 --- a/Source/Engine/Particles/ParticleEffect.cpp +++ b/Source/Engine/Particles/ParticleEffect.cpp @@ -270,11 +270,11 @@ void ParticleEffect::UpdateSimulation(bool singleFrame) if (!IsActiveInHierarchy() || ParticleSystem == nullptr || !ParticleSystem->IsLoaded() - || _lastUpdateFrame == Engine::FrameCount) + || _lastUpdateFrame == Engine::UpdateCount) return; // Request update - _lastUpdateFrame = Engine::FrameCount; + _lastUpdateFrame = Engine::UpdateCount; _lastMinDstSqr = MAX_Real; if (singleFrame) Instance.LastUpdateTime = (UseTimeScale ? Time::Update.Time : Time::Update.UnscaledTime).GetTotalSeconds(); @@ -371,7 +371,7 @@ void ParticleEffect::Sync() SceneRenderTask* ParticleEffect::GetRenderTask() const { - const uint64 minFrame = Engine::FrameCount - 2; + const uint64 minFrame = Engine::UpdateCount - 2; // Custom task const auto customViewRenderTask = CustomViewRenderTask.Get(); diff --git a/Source/Engine/Render2D/Render2D.cpp b/Source/Engine/Render2D/Render2D.cpp index 1c650585f..31136b455 100644 --- a/Source/Engine/Render2D/Render2D.cpp +++ b/Source/Engine/Render2D/Render2D.cpp @@ -988,7 +988,7 @@ void DrawBatch(int32 startIndex, int32 count) MaterialBase::BindParameters bindParams(Context, *(RenderContext*)nullptr); Render2D::CustomData customData; customData.ViewProjection = ViewProjection; - customData.ViewSize = Float2(d.AsMaterial.Width, d.AsMaterial.Height); + customData.ViewSize = Float2::One; bindParams.CustomData = &customData; material->Bind(bindParams); diff --git a/Source/Engine/Renderer/EyeAdaptationPass.cpp b/Source/Engine/Renderer/EyeAdaptationPass.cpp index e44189454..9b664278c 100644 --- a/Source/Engine/Renderer/EyeAdaptationPass.cpp +++ b/Source/Engine/Renderer/EyeAdaptationPass.cpp @@ -42,7 +42,7 @@ void EyeAdaptationPass::Render(RenderContext& renderContext, GPUTexture* colorBu bool dropHistory = renderContext.Buffers->LastEyeAdaptationTime < ZeroTolerance || renderContext.Task->IsCameraCut; const float time = Time::Draw.UnscaledTime.GetTotalSeconds(); //const float frameDelta = Time::ElapsedGameTime.GetTotalSeconds(); - const float frameDelta = time - renderContext.Buffers->LastEyeAdaptationTime; + const float frameDelta = Math::Clamp(time - renderContext.Buffers->LastEyeAdaptationTime, 0.0f, 1.0f); renderContext.Buffers->LastEyeAdaptationTime = 0.0f; if ((view.Flags & ViewFlags::EyeAdaptation) == ViewFlags::None || settings.Mode == EyeAdaptationMode::None || checkIfSkipPass()) return; diff --git a/Source/Engine/Scripting/Scripting.cpp b/Source/Engine/Scripting/Scripting.cpp index d6f2524c7..35b90a9ad 100644 --- a/Source/Engine/Scripting/Scripting.cpp +++ b/Source/Engine/Scripting/Scripting.cpp @@ -109,6 +109,45 @@ namespace #if USE_EDITOR bool LastBinariesLoadTriggeredCompilation = false; #endif + + void ReleaseObjects(bool gameOnly) + { + // Flush objects already enqueued objects to delete + ObjectsRemovalService::Flush(); + + // Give GC a try to cleanup old user objects and the other mess + MCore::GC::Collect(); + MCore::GC::WaitForPendingFinalizers(); + + // Destroy objects from game assemblies (eg. not released objects that might crash if persist in memory after reload) + const auto flaxModule = GetBinaryModuleFlaxEngine(); + _objectsLocker.Lock(); + for (auto i = _objectsDictionary.Begin(); i.IsNotEnd(); ++i) + { + auto obj = i->Value; + if (gameOnly && obj->GetTypeHandle().Module == flaxModule) + continue; + +#if USE_OBJECTS_DISPOSE_CRASHES_DEBUGGING + LOG(Info, "[OnScriptingDispose] obj = 0x{0:x}, {1}", (uint64)obj.Ptr, String(obj.TypeName)); +#endif + obj->OnScriptingDispose(); + } + _objectsLocker.Unlock(); + + // Release assets sourced from game assemblies + Array assets = Content::GetAssets(); + for (auto asset : assets) + { + if (asset->GetTypeHandle().Module == flaxModule) + { + continue; + } + + asset->DeleteObject(); + } + ObjectsRemovalService::Flush(); + } } Delegate Scripting::BinaryModuleLoaded; @@ -566,36 +605,8 @@ void Scripting::Release() // Fire event ScriptsUnload(); - // Cleanup - ObjectsRemovalService::Flush(); - - // Cleanup some managed objects - MCore::GC::Collect(); - MCore::GC::WaitForPendingFinalizers(); - // Release managed objects instances for persistent objects (assets etc.) - _objectsLocker.Lock(); - { - for (auto i = _objectsDictionary.Begin(); i.IsNotEnd(); ++i) - { - auto obj = i->Value; -#if USE_OBJECTS_DISPOSE_CRASHES_DEBUGGING - LOG(Info, "[OnScriptingDispose] obj = 0x{0:x}, {1}", (uint64)obj.Ptr, String(obj.TypeName)); -#endif - obj->OnScriptingDispose(); - } - } - _objectsLocker.Unlock(); - - // Release assets sourced from game assemblies - const auto flaxModule = GetBinaryModuleFlaxEngine(); - for (auto asset : Content::GetAssets()) - { - if (asset->GetTypeHandle().Module == flaxModule) - continue; - - asset->DeleteObjectNow(); - } + ReleaseObjects(false); auto* flaxEngineModule = (NativeBinaryModule*)GetBinaryModuleFlaxEngine(); onEngineUnloading(flaxEngineModule->Assembly); @@ -673,39 +684,8 @@ void Scripting::Reload(bool canTriggerSceneReload) LOG(Info, "Start user scripts reload"); ScriptsReloading(); - // Flush cache (some objects may be deleted after reload start event) - ObjectsRemovalService::Flush(); - - // Give GC a try to cleanup old user objects and the other mess - MCore::GC::Collect(); - MCore::GC::WaitForPendingFinalizers(); - // Destroy objects from game assemblies (eg. not released objects that might crash if persist in memory after reload) - const auto flaxModule = GetBinaryModuleFlaxEngine(); - _objectsLocker.Lock(); - { - for (auto i = _objectsDictionary.Begin(); i.IsNotEnd(); ++i) - { - auto obj = i->Value; - if (obj->GetTypeHandle().Module == flaxModule) - continue; - -#if USE_OBJECTS_DISPOSE_CRASHES_DEBUGGING - LOG(Info, "[OnScriptingDispose] obj = 0x{0:x}, {1}", (uint64)obj.Ptr, String(obj.TypeName)); -#endif - obj->OnScriptingDispose(); - } - } - _objectsLocker.Unlock(); - - // Release assets sourced from game assemblies - for (auto asset : Content::GetAssets()) - { - if (asset->GetTypeHandle().Module == flaxModule) - continue; - - asset->DeleteObjectNow(); - } + ReleaseObjects(true); // Unload all game modules LOG(Info, "Unloading game binary modules"); @@ -741,6 +721,15 @@ void Scripting::Reload(bool canTriggerSceneReload) #endif +Array Scripting::GetObjects() +{ + Array objects; + _objectsLocker.Lock(); + _objectsDictionary.GetValues(objects); + _objectsLocker.Unlock(); + return objects; +} + MClass* Scripting::FindClass(const StringAnsiView& fullname) { if (fullname.IsEmpty()) @@ -860,7 +849,7 @@ void ScriptingObjectReferenceBase::OnDeleted(ScriptingObject* obj) } } -ScriptingObject* Scripting::FindObject(Guid id, MClass* type) +ScriptingObject* Scripting::FindObject(Guid id, const MClass* type) { if (!id.IsValid()) return nullptr; @@ -914,7 +903,7 @@ ScriptingObject* Scripting::FindObject(Guid id, MClass* type) return nullptr; } -ScriptingObject* Scripting::TryFindObject(Guid id, MClass* type) +ScriptingObject* Scripting::TryFindObject(Guid id, const MClass* type) { if (!id.IsValid()) return nullptr; @@ -950,7 +939,7 @@ ScriptingObject* Scripting::TryFindObject(Guid id, MClass* type) return result; } -ScriptingObject* Scripting::TryFindObject(MClass* type) +ScriptingObject* Scripting::TryFindObject(const MClass* type) { if (type == nullptr) return nullptr; @@ -1020,7 +1009,7 @@ bool Scripting::IsEveryAssemblyLoaded() return true; } -bool Scripting::IsTypeFromGameScripts(MClass* type) +bool Scripting::IsTypeFromGameScripts(const MClass* type) { const auto binaryModule = ManagedBinaryModule::GetModule(type ? type->GetAssembly() : nullptr); return binaryModule && binaryModule != GetBinaryModuleCorlib() && binaryModule != GetBinaryModuleFlaxEngine(); diff --git a/Source/Engine/Scripting/Scripting.h b/Source/Engine/Scripting/Scripting.h index 41b0cbf4d..dad5a02fb 100644 --- a/Source/Engine/Scripting/Scripting.h +++ b/Source/Engine/Scripting/Scripting.h @@ -79,6 +79,13 @@ public: public: + /// + /// Gets all registered scripting objects. + /// + /// Use with caution due to potentially large memory allocation. + /// The collection of the objects. + static Array GetObjects(); + /// /// Finds the class with given fully qualified name within whole assembly. /// @@ -133,14 +140,14 @@ public: /// The object unique identifier. /// The type of the object to find (optional). /// The found object or null if missing. - static ScriptingObject* FindObject(Guid id, MClass* type = nullptr); + static ScriptingObject* FindObject(Guid id, const MClass* type = nullptr); /// /// Tries to find the object by the given class. /// /// The type of the object to find. /// The found object or null if missing. - static ScriptingObject* TryFindObject(MClass* type); + static ScriptingObject* TryFindObject(const MClass* type); /// /// Tries to find the object by the given identifier. @@ -159,7 +166,7 @@ public: /// The object unique identifier. /// The type of the object to find (optional). /// The found object or null if missing. - static ScriptingObject* TryFindObject(Guid id, MClass* type = nullptr); + static ScriptingObject* TryFindObject(Guid id, const MClass* type = nullptr); /// /// Finds the object by the given managed instance handle. Searches only registered scene objects. @@ -189,7 +196,7 @@ public: /// /// Returns true if given type is from one of the game scripts assemblies. /// - static bool IsTypeFromGameScripts(MClass* type); + static bool IsTypeFromGameScripts(const MClass* type); static void ProcessBuildInfoPath(String& path, const String& projectFolderPath); diff --git a/Source/Engine/Terrain/TerrainPatch.cpp b/Source/Engine/Terrain/TerrainPatch.cpp index d2c4eaa94..b20b5e9a1 100644 --- a/Source/Engine/Terrain/TerrainPatch.cpp +++ b/Source/Engine/Terrain/TerrainPatch.cpp @@ -1808,6 +1808,9 @@ bool TerrainPatch::UpdateHeightData(TerrainDataUpdateInfo& info, const Int2& mod } #endif + // Mark as modified (need to save texture data during scene saving) + _wasHeightModified = true; + if (!wasHeightChanged) return false; @@ -1820,9 +1823,6 @@ bool TerrainPatch::UpdateHeightData(TerrainDataUpdateInfo& info, const Int2& mod #endif _collisionVertices.Resize(0); - // Mark as modified (need to save texture data during scene saving) - _wasHeightModified = true; - // Note: if terrain is using virtual storage then it won't be updated, we could synchronize that data... // TODO: disable heightmap dynamic streaming - data on a GPU was modified and we don't want to override it with the old data stored in the asset container diff --git a/Source/Engine/Threading/Task.h b/Source/Engine/Threading/Task.h index d2d77a2e9..c741da73d 100644 --- a/Source/Engine/Threading/Task.h +++ b/Source/Engine/Threading/Task.h @@ -7,7 +7,6 @@ #include "Engine/Core/NonCopyable.h" #include "Engine/Core/Enums.h" #include "Engine/Core/Types/TimeSpan.h" -#include "Engine/Core/Collections/Array.h" #include "Engine/Platform/Platform.h" /// @@ -188,8 +187,8 @@ public: /// The tasks list to wait for. /// The maximum amount of milliseconds to wait for the task to finish it's job. Timeout smaller/equal 0 will result in infinite waiting. /// True if any task failed or has been canceled or has timeout, otherwise false. - template - static bool WaitAll(Array& tasks, double timeoutMilliseconds = -1) + template + static bool WaitAll(Array& tasks, double timeoutMilliseconds = -1) { for (int32 i = 0; i < tasks.Count(); i++) { @@ -300,27 +299,22 @@ public: /// /// Cancels all the tasks from the list and clears it. /// - template - static void CancelAll(Array& tasks) + template + static void CancelAll(Array& tasks) { for (int32 i = 0; i < tasks.Count(); i++) - { tasks[i]->Cancel(); - } tasks.Clear(); } protected: /// - /// Executes this task. - /// It should be called by the task consumer (thread pool or other executor of this task type). - /// It calls run() and handles result). + /// Executes this task. It should be called by the task consumer (thread pool or other executor of this task type). It calls run() and handles result). /// void Execute(); /// - /// Runs the task specified operations - /// Does not handles any task related logic, only performs the actual job. + /// Runs the task specified operations. It does not handle any task related logic, but only performs the actual job. /// /// The task execution result. Returns true if failed, otherwise false. virtual bool Run() = 0; diff --git a/Source/Engine/Threading/ThreadPool.cpp b/Source/Engine/Threading/ThreadPool.cpp index 0ffb7c12d..ee72d92ee 100644 --- a/Source/Engine/Threading/ThreadPool.cpp +++ b/Source/Engine/Threading/ThreadPool.cpp @@ -8,6 +8,7 @@ #include "Engine/Core/Log.h" #include "Engine/Core/Math/Math.h" #include "Engine/Core/Types/String.h" +#include "Engine/Core/Collections/Array.h" #include "Engine/Engine/Globals.h" #include "Engine/Engine/EngineService.h" #include "Engine/Platform/ConditionVariable.h" diff --git a/Source/Engine/Tools/ModelTool/ModelTool.OpenFBX.cpp b/Source/Engine/Tools/ModelTool/ModelTool.OpenFBX.cpp index 5bf9ef7d8..cf124f977 100644 --- a/Source/Engine/Tools/ModelTool/ModelTool.OpenFBX.cpp +++ b/Source/Engine/Tools/ModelTool/ModelTool.OpenFBX.cpp @@ -720,23 +720,23 @@ bool ProcessMesh(ModelData& result, OpenFbxImporterData& data, const ofbx::Mesh* { int vtxIndex = clusterIndices[j] - firstVertexOffset; float vtxWeight = (float)clusterWeights[j]; - if (vtxWeight <= 0 || vtxIndex < 0 || vtxIndex >= vertexCount) continue; - - auto& indices = mesh.BlendIndices[vtxIndex]; - auto& weights = mesh.BlendWeights[vtxIndex]; + Int4& indices = mesh.BlendIndices.Get()[vtxIndex]; + Float4& weights = mesh.BlendWeights.Get()[vtxIndex]; for (int32 k = 0; k < 4; k++) { if (vtxWeight >= weights.Raw[k]) { + // Move lower weights by one down for (int32 l = 2; l >= k; l--) { indices.Raw[l + 1] = indices.Raw[l]; weights.Raw[l + 1] = weights.Raw[l]; } + // Set bone influence indices.Raw[k] = boneIndex; weights.Raw[k] = vtxWeight; break; @@ -786,11 +786,13 @@ bool ProcessMesh(ModelData& result, OpenFbxImporterData& data, const ofbx::Mesh* auto shapeNormals = shape->getNormals(); for (int32 i = 0; i < blendShapeData.Vertices.Count(); i++) { - /*auto delta = ToFloat3(shapeNormals[i + firstVertexOffset]) - mesh.Normals[i]; - auto length = delta.Length(); - if (length > ZeroTolerance) - delta /= length;*/ - auto delta = Float3::Zero; // TODO: blend shape normals deltas fix when importing from fbx + auto delta = ToFloat3(shapeNormals[i + firstVertexOffset]); + if (data.ConvertRH) + { + // Mirror normals along the Z axis + delta.Z *= -1.0f; + } + delta = delta - mesh.Normals.Get()[i]; blendShapeData.Vertices.Get()[i].NormalDelta = delta; } } @@ -800,7 +802,7 @@ bool ProcessMesh(ModelData& result, OpenFbxImporterData& data, const ofbx::Mesh* { // Mirror positions along the Z axis for (int32 i = 0; i < vertexCount; i++) - mesh.Positions[i].Z *= -1.0f; + mesh.Positions.Get()[i].Z *= -1.0f; for (auto& blendShapeData : mesh.BlendShapes) { for (auto& v : blendShapeData.Vertices) @@ -815,7 +817,7 @@ bool ProcessMesh(ModelData& result, OpenFbxImporterData& data, const ofbx::Mesh* { // Invert the order for (int32 i = 0; i < mesh.Indices.Count(); i += 3) - Swap(mesh.Indices[i], mesh.Indices[i + 2]); + Swap(mesh.Indices.Get()[i], mesh.Indices.Get()[i + 2]); } if ((data.Options.CalculateTangents || !tangents) && mesh.UVs.HasItems()) diff --git a/Source/Engine/Tools/ModelTool/ModelTool.cpp b/Source/Engine/Tools/ModelTool/ModelTool.cpp index fc2298993..bfbd9421b 100644 --- a/Source/Engine/Tools/ModelTool/ModelTool.cpp +++ b/Source/Engine/Tools/ModelTool/ModelTool.cpp @@ -589,6 +589,9 @@ bool ModelTool::ImportData(const String& path, ModelData& data, Options& options { for (auto& n : mesh->Normals) n *= -1; + for (auto& shape : mesh->BlendShapes) + for (auto& v : shape.Vertices) + v.NormalDelta *= -1; } } } diff --git a/Source/Engine/Visject/GraphNode.h b/Source/Engine/Visject/GraphNode.h index 628bc6d57..f4eb9cc29 100644 --- a/Source/Engine/Visject/GraphNode.h +++ b/Source/Engine/Visject/GraphNode.h @@ -11,8 +11,6 @@ class GraphNode; #define GRAPH_NODE_MAKE_TYPE(groupID, typeID) (uint32)((groupID) << 16 | (typeID)) -#define GRAPH_NODE_MAX_VALUES 32 - /// /// Represents single box of the graph node /// @@ -114,14 +112,13 @@ public: uint16 TypeID; uint16 GroupID; }; - uint32 Type; }; /// /// List of all node values. Array size and value types are constant over time. Only value data can change. /// - Array> Values; + Array> Values; /// /// Node boxes cache. Array index matches the box ID (for fast O(1) lookups). diff --git a/Source/Engine/Visject/VisjectGraph.h b/Source/Engine/Visject/VisjectGraph.h index 90ba44ed5..3a2e6439d 100644 --- a/Source/Engine/Visject/VisjectGraph.h +++ b/Source/Engine/Visject/VisjectGraph.h @@ -6,13 +6,12 @@ #include "Engine/Core/Math/Vector2.h" #include "Engine/Core/Math/Vector3.h" #include "Engine/Core/Math/Vector4.h" +#include "Engine/Core/Collections/Array.h" #include "Engine/Content/Asset.h" #include "Engine/Content/AssetReference.h" #include "Engine/Content/AssetsContainer.h" #include "Engine/Animations/Curve.h" -#define VISJECT_GRAPH_NODE_MAX_ASSETS 14 - template class VisjectGraphNode; @@ -94,7 +93,7 @@ public: /// /// The asset references. Linked resources such as Animation assets are referenced in graph data as ID. We need to keep valid refs to them at runtime to keep data in memory. /// - AssetReference Assets[VISJECT_GRAPH_NODE_MAX_ASSETS]; + Array> Assets; }; /// @@ -188,11 +187,10 @@ public: #undef SETUP_CURVE // Get Gameplay Global case 16: - { + n->Assets.Resize(1); n->Assets[0] = ::LoadAsset((Guid)n->Values[0], Asset::TypeInitializer); break; } - } } // Base diff --git a/Source/Platforms/DotNet/Newtonsoft.Json.dll b/Source/Platforms/DotNet/Newtonsoft.Json.dll index b56719858..52dc74149 100644 --- a/Source/Platforms/DotNet/Newtonsoft.Json.dll +++ b/Source/Platforms/DotNet/Newtonsoft.Json.dll @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:2189e00df470e0fa7e3294a90a2faf286af51bd25f224605faf53cc42a7ff381 +oid sha256:5a3db675abf8c06f537cba70efa992723a93749e5771e24b8c911a90ccaea5a5 size 604672 diff --git a/Source/Platforms/DotNet/Newtonsoft.Json.pdb b/Source/Platforms/DotNet/Newtonsoft.Json.pdb index 97d99bb47..01e0fd1ce 100644 --- a/Source/Platforms/DotNet/Newtonsoft.Json.pdb +++ b/Source/Platforms/DotNet/Newtonsoft.Json.pdb @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:cd30824b5d1593411aa7c47816afb37f63ba4ec4f82c5777324d0411c853a912 -size 239516 +oid sha256:dbb3be761269e1f66da40e85bf426841bbdaf6c9e5e8df1a5771e82d96aaf999 +size 239564 diff --git a/Source/ThirdParty/recastnavigation/DetourNavMeshQuery.cpp b/Source/ThirdParty/recastnavigation/DetourNavMeshQuery.cpp index 7faefde81..5c7b6d797 100644 --- a/Source/ThirdParty/recastnavigation/DetourNavMeshQuery.cpp +++ b/Source/ThirdParty/recastnavigation/DetourNavMeshQuery.cpp @@ -487,14 +487,37 @@ dtStatus dtNavMeshQuery::findRandomPointAroundCircle(dtPolyRef startRef, const f int checksLimit = 100; do { + // Loop until finds a point on a random poly that is in the radius const float s = frand(); const float t = frand(); dtRandomPointInConvexPoly(verts, randomPoly->vertCount, areas, s, t, pt); } while (dtDistancePtPtSqr2D(centerPos, pt) > radiusSqr && checksLimit-- > 0); if (checksLimit <= 0) - return DT_FAILURE; - + { + // Check nearby polygons + float halfExtents[3] = { maxRadius, maxRadius, maxRadius }; + dtPolyRef polys[32]; + int polyCount; + queryPolygons(centerPos, halfExtents, filter, polys, &polyCount, 32); + for (int i = 0; i < polyCount && checksLimit <= 0; i++) + { + checksLimit = 100; + randomPolyRef = polys[i]; + m_nav->getTileAndPolyByRefUnsafe(randomPolyRef, &randomTile, &randomPoly); + do + { + // Loop until finds a point on a random poly that is in the radius + const float s = frand(); + const float t = frand(); + dtRandomPointInConvexPoly(verts, randomPoly->vertCount, areas, s, t, pt); + } + while (dtDistancePtPtSqr2D(centerPos, pt) > radiusSqr && checksLimit-- > 0); + } + if (checksLimit <= 0) + return DT_FAILURE; + } + closestPointOnPoly(randomPolyRef, pt, pt, NULL); dtVcopy(randomPt, pt);