From 7653fba3814855ac24911654aa578155b97ba87a Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Fri, 19 Apr 2024 16:30:34 +0200 Subject: [PATCH] Refactor Multi Blend nodes to support up to `255` blend points --- .../Archetypes/Animation.MultiBlend.cs | 104 ++++++++++-------- Source/Editor/Surface/Archetypes/Animation.cs | 30 +---- Source/Editor/Surface/NodeFlags.cs | 5 + Source/Editor/Surface/SurfaceNode.cs | 11 +- .../Surface/Undo/EditNodeValuesAction.cs | 14 ++- .../VisjectSurfaceContext.Serialization.cs | 15 +++ .../Animations/Graph/AnimGraph.Base.cpp | 53 ++++----- Source/Engine/Animations/Graph/AnimGraph.cpp | 22 ++++ Source/Engine/Animations/Graph/AnimGraph.h | 46 +++----- .../Animations/Graph/AnimGroup.Animation.cpp | 18 +-- 10 files changed, 168 insertions(+), 150 deletions(-) diff --git a/Source/Editor/Surface/Archetypes/Animation.MultiBlend.cs b/Source/Editor/Surface/Archetypes/Animation.MultiBlend.cs index 174f867fc..a3095a6bf 100644 --- a/Source/Editor/Surface/Archetypes/Animation.MultiBlend.cs +++ b/Source/Editor/Surface/Archetypes/Animation.MultiBlend.cs @@ -23,9 +23,7 @@ namespace FlaxEditor.Surface.Archetypes private readonly bool _is2D; private Float2 _rangeX, _rangeY; private Float2 _debugPos = Float2.Minimum; - 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 readonly List _blendPoints = new List(); /// /// Represents single blend point. @@ -44,6 +42,11 @@ namespace FlaxEditor.Surface.Archetypes /// public const float DefaultSize = 8.0f; + /// + /// Blend point index. + /// + public int Index => _index; + /// /// Initializes a new instance of the class. /// @@ -190,6 +193,11 @@ 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. /// @@ -206,28 +214,6 @@ namespace FlaxEditor.Surface.Archetypes _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 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 = _is2D ? new Float2(data0.Z, data0.W) : Float2.Zero; - for (int i = 0; i < Animation.MultiBlend.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), _is2D ? Mathf.Clamp(dataA.Y, rangeY.X, rangeY.Y) : 0.0f); - } - } - private void AddAsset(Float2 location) { // Reuse existing animation @@ -259,16 +245,22 @@ namespace FlaxEditor.Surface.Archetypes public void AddAsset(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 < Animation.MultiBlend.MaxAnimationsCount; index++) + for (; index < count; index++) { var dataB = (Guid)_node.Values[5 + index * 2]; if (dataB == Guid.Empty) break; } - if (index == Animation.MultiBlend.MaxAnimationsCount) - return; // TODO: unlimited amount of blend points + 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; @@ -382,11 +374,22 @@ 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) { - var animId = _pointsAnims[i]; - var location = _pointsLocations[i]; + _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) @@ -394,7 +397,6 @@ namespace FlaxEditor.Surface.Archetypes // Create missing blend point _blendPoints[i] = new BlendPoint(this, i) { - Tag = i, Parent = this, }; } @@ -477,10 +479,11 @@ namespace FlaxEditor.Surface.Archetypes 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.Tag; + b.Tag = blendPoint.Index; b.TooltipText = blendPoint.TooltipText; } menu.Show(this, location); @@ -538,8 +541,6 @@ namespace FlaxEditor.Surface.Archetypes GetPointsArea(out var pointsArea); var data0 = (Float4)_node.Values[0]; var rangeX = new Float2(data0.X, data0.Y); - var rangeY = _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); // Background Render2D.DrawRectangle(rect, IsMouseOver ? style.TextBoxBackgroundSelected : style.TextBoxBackground); @@ -562,6 +563,7 @@ namespace FlaxEditor.Surface.Archetypes } if (_is2D) { + var rangeY = new Float2(data0.Z, data0.W); for (int i = 0; i <= splits; i++) { float alpha = (float)i / splits; @@ -605,6 +607,11 @@ namespace FlaxEditor.Surface.Archetypes /// public abstract class MultiBlend : SurfaceNode { + /// + /// The blend space editor. + /// + protected BlendPointsEditor _editor; + /// /// The selected animation label. /// @@ -638,7 +645,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. @@ -661,20 +668,13 @@ namespace FlaxEditor.Surface.Archetypes Text = "Selected Animation:", Parent = this }; - _selectedAnimation = new ComboBox(_selectedAnimationLabel.X, 4 * layoutOffsetY, _selectedAnimationLabel.Width) { 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 @@ -687,7 +687,6 @@ 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, @@ -700,7 +699,8 @@ namespace FlaxEditor.Surface.Archetypes { 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; @@ -808,6 +808,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() { @@ -825,7 +835,6 @@ namespace FlaxEditor.Surface.Archetypes { private readonly Label _animationXLabel; private readonly FloatValueBox _animationX; - private readonly BlendPointsEditor _editor; /// public MultiBlend1D(uint id, VisjectSurfaceContext context, NodeArchetype nodeArch, GroupArchetype groupArch) @@ -896,7 +905,6 @@ namespace FlaxEditor.Surface.Archetypes private readonly FloatValueBox _animationX; private readonly Label _animationYLabel; private readonly FloatValueBox _animationY; - private readonly BlendPointsEditor _editor; /// public MultiBlend2D(uint id, VisjectSurfaceContext context, NodeArchetype nodeArch, GroupArchetype groupArch) diff --git a/Source/Editor/Surface/Archetypes/Animation.cs b/Source/Editor/Surface/Archetypes/Animation.cs index f00bd670b..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[] { @@ -670,7 +657,7 @@ 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, + Flags = NodeFlags.AnimGraph | NodeFlags.VariableValuesSize, Size = new Float2(420, 620), DefaultValues = new object[] { @@ -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[] { 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..ac5680d8c 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); 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/Engine/Animations/Graph/AnimGraph.Base.cpp b/Source/Engine/Animations/Graph/AnimGraph.Base.cpp index ea887b056..55241fc70 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 @@ -173,45 +171,41 @@ bool AnimGraphBase::onNodeLoaded(Node* n) 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; - n->Assets.Resize(ANIM_GRAPH_MULTI_BLEND_MAX_ANIMS); - 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]; - n->Assets.Resize(ANIM_GRAPH_MULTI_BLEND_MAX_ANIMS); - 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) { @@ -229,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 diff --git a/Source/Engine/Animations/Graph/AnimGraph.cpp b/Source/Engine/Animations/Graph/AnimGraph.cpp index defd2b03d..321f10604 100644 --- a/Source/Engine/Animations/Graph/AnimGraph.cpp +++ b/Source/Engine/Animations/Graph/AnimGraph.cpp @@ -102,6 +102,28 @@ 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; + } + 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 ef72c2d72..f34b7900f 100644 --- a/Source/Engine/Animations/Graph/AnimGraph.h +++ b/Source/Engine/Animations/Graph/AnimGraph.h @@ -13,8 +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_MULTI_BLEND_INDEX byte +#define ANIM_GRAPH_MULTI_BLEND_INVALID 0xff #define ANIM_GRAPH_MAX_STATE_TRANSITIONS 64 #define ANIM_GRAPH_MAX_CALL_STACK 100 #define ANIM_GRAPH_MAX_EVENTS 64 @@ -434,38 +434,24 @@ 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 @@ -582,6 +568,8 @@ public: { } + ~AnimGraphNode(); + public: /// /// Gets the per-node node transformations cache (cached). @@ -784,7 +772,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 fbb61c4a2..c85d83603 100644 --- a/Source/Engine/Animations/Graph/AnimGroup.Animation.cpp +++ b/Source/Engine/Animations/Graph/AnimGroup.Animation.cpp @@ -1269,7 +1269,7 @@ 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 @@ -1300,7 +1300,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]; @@ -1310,14 +1310,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(); @@ -1365,7 +1365,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 @@ -1406,20 +1406,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();