Refactor Multi Blend nodes to support up to 255 blend points
This commit is contained in:
@@ -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<BlendPoint> _blendPoints = new List<BlendPoint>();
|
||||
|
||||
/// <summary>
|
||||
/// Represents single blend point.
|
||||
@@ -44,6 +42,11 @@ namespace FlaxEditor.Surface.Archetypes
|
||||
/// </summary>
|
||||
public const float DefaultSize = 8.0f;
|
||||
|
||||
/// <summary>
|
||||
/// Blend point index.
|
||||
/// </summary>
|
||||
public int Index => _index;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="BlendPoint"/> class.
|
||||
/// </summary>
|
||||
@@ -190,6 +193,11 @@ namespace FlaxEditor.Surface.Archetypes
|
||||
/// </summary>
|
||||
public bool Is2D => _is2D;
|
||||
|
||||
/// <summary>
|
||||
/// Blend points count.
|
||||
/// </summary>
|
||||
public int PointsCount => (_node.Values.Length - 4) / 2; // 4 node values + 2 per blend point
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="BlendPointsEditor"/> class.
|
||||
/// </summary>
|
||||
@@ -206,28 +214,6 @@ namespace FlaxEditor.Surface.Archetypes
|
||||
_is2D = is2D;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the blend space data.
|
||||
/// </summary>
|
||||
/// <param name="rangeX">The space range for X axis (X-width, Y-height).</param>
|
||||
/// <param name="rangeY">The space range for Y axis (X-width, Y-height).</param>
|
||||
/// <param name="pointsAnims">The points anims (input array to fill of size equal 14).</param>
|
||||
/// <param name="pointsLocations">The points locations (input array to fill of size equal 14).</param>
|
||||
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
|
||||
/// <seealso cref="FlaxEditor.Surface.SurfaceNode" />
|
||||
public abstract class MultiBlend : SurfaceNode
|
||||
{
|
||||
/// <summary>
|
||||
/// The blend space editor.
|
||||
/// </summary>
|
||||
protected BlendPointsEditor _editor;
|
||||
|
||||
/// <summary>
|
||||
/// The selected animation label.
|
||||
/// </summary>
|
||||
@@ -638,7 +645,7 @@ namespace FlaxEditor.Surface.Archetypes
|
||||
/// <summary>
|
||||
/// The maximum animations amount to blend per node.
|
||||
/// </summary>
|
||||
public const int MaxAnimationsCount = 14;
|
||||
public const int MaxAnimationsCount = 255;
|
||||
|
||||
/// <summary>
|
||||
/// 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<string>(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();
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void OnSpawned(SurfaceNodeActions action)
|
||||
{
|
||||
base.OnSpawned(action);
|
||||
|
||||
// Select the first animation to make setup easier
|
||||
OnSelectedAnimationPopupShowing(_selectedAnimation);
|
||||
_selectedAnimation.SelectedIndex = 0;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void OnValuesChanged()
|
||||
{
|
||||
@@ -825,7 +835,6 @@ namespace FlaxEditor.Surface.Archetypes
|
||||
{
|
||||
private readonly Label _animationXLabel;
|
||||
private readonly FloatValueBox _animationX;
|
||||
private readonly BlendPointsEditor _editor;
|
||||
|
||||
/// <inheritdoc />
|
||||
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;
|
||||
|
||||
/// <inheritdoc />
|
||||
public MultiBlend2D(uint id, VisjectSurfaceContext context, NodeArchetype nodeArch, GroupArchetype groupArch)
|
||||
|
||||
@@ -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[]
|
||||
{
|
||||
|
||||
@@ -73,6 +73,11 @@ namespace FlaxEditor.Surface
|
||||
/// </summary>
|
||||
BehaviorTreeGraph = 1024,
|
||||
|
||||
/// <summary>
|
||||
/// Node can have different amount of items in values array.
|
||||
/// </summary>
|
||||
VariableValuesSize = 2048,
|
||||
|
||||
/// <summary>
|
||||
/// Node can be used in the all visual graphs.
|
||||
/// </summary>
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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));
|
||||
|
||||
@@ -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<Animation>((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<Float2, FixedAllocation<ANIM_GRAPH_MULTI_BLEND_MAX_ANIMS + 3>> 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<Float2> vertices;
|
||||
Array<ANIM_GRAPH_MULTI_BLEND_INDEX> vertexToAnim;
|
||||
n->Assets.Resize(n->Data.MultiBlend1D.Count);
|
||||
for (int32 i = 0; i < n->Data.MultiBlend1D.Count; i++)
|
||||
{
|
||||
n->Assets[i] = (Asset*)Content::LoadAsync<Animation>((Guid)n->Values[i * 2 + 5]);
|
||||
n->Assets[i] = Content::LoadAsync<Animation>((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<Delaunay2D::Triangle, FixedAllocation<ANIM_GRAPH_MULTI_BLEND_2D_MAX_TRIS>> triangles;
|
||||
Array<Delaunay2D::Triangle> 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
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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<AnimGraphBox>
|
||||
public:
|
||||
struct MultiBlend1DData
|
||||
{
|
||||
/// <summary>
|
||||
/// The computed length of the mixes animations. Shared for all blend points to provide more stabilization during looped playback.
|
||||
/// </summary>
|
||||
// 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;
|
||||
|
||||
/// <summary>
|
||||
/// 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.
|
||||
/// </summary>
|
||||
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
|
||||
{
|
||||
/// <summary>
|
||||
/// The computed length of the mixes animations. Shared for all blend points to provide more stabilization during looped playback.
|
||||
/// </summary>
|
||||
// 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;
|
||||
|
||||
/// <summary>
|
||||
/// Cached triangles vertices (vertex 0). Contains list of indices for triangles to use for blending.
|
||||
/// </summary>
|
||||
byte TrianglesP0[ANIM_GRAPH_MULTI_BLEND_2D_MAX_TRIS];
|
||||
|
||||
/// <summary>
|
||||
/// Cached triangles vertices (vertex 1). Contains list of indices for triangles to use for blending.
|
||||
/// </summary>
|
||||
byte TrianglesP1[ANIM_GRAPH_MULTI_BLEND_2D_MAX_TRIS];
|
||||
|
||||
/// <summary>
|
||||
/// Cached triangles vertices (vertex 2). Contains list of indices for triangles to use for blending.
|
||||
/// </summary>
|
||||
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:
|
||||
/// <summary>
|
||||
/// Gets the per-node node transformations cache (cached).
|
||||
@@ -784,7 +772,7 @@ struct AnimGraphContext
|
||||
bool StackOverFlow;
|
||||
Array<VisjectExecutor::Node*, FixedAllocation<ANIM_GRAPH_MAX_CALL_STACK>> CallStack;
|
||||
Array<VisjectExecutor::Graph*, FixedAllocation<32>> GraphStack;
|
||||
Array<uint32, FixedAllocation<ANIM_GRAPH_MAX_CALL_STACK> > NodePath;
|
||||
Array<uint32, FixedAllocation<ANIM_GRAPH_MAX_CALL_STACK>> NodePath;
|
||||
Dictionary<VisjectExecutor::Node*, VisjectExecutor::Graph*> Functions;
|
||||
ChunkedArray<AnimGraphImpulse, 256> PoseCache;
|
||||
int32 PoseCacheSize;
|
||||
|
||||
@@ -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<Animation>();
|
||||
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<Animation>();
|
||||
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<Animation>();
|
||||
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<Animation>();
|
||||
const auto cData = node->Values[4 + c * 2].AsFloat4();
|
||||
|
||||
|
||||
Reference in New Issue
Block a user