Add **Any State to Anim Graph state machines**
This commit is contained in:
@@ -46,6 +46,19 @@ namespace FlaxEditor.Surface
|
||||
},
|
||||
Size = new Float2(100, 0),
|
||||
},
|
||||
new NodeArchetype
|
||||
{
|
||||
TypeID = 34,
|
||||
Create = (id, context, arch, groupArch) => new Animation.StateMachineAny(id, context, arch, groupArch),
|
||||
Title = "Any",
|
||||
Description = "The generic animation states machine state with source transitions from any other state",
|
||||
Flags = NodeFlags.AnimGraph,
|
||||
Size = new Float2(100, 0),
|
||||
DefaultValues = new object[]
|
||||
{
|
||||
Utils.GetEmptyArray<byte>(),
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
@@ -528,12 +528,12 @@ namespace FlaxEditor.Surface.Archetypes
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Customized <see cref="SurfaceNode" /> for the state machine state node.
|
||||
/// Base class for state machine state node.
|
||||
/// </summary>
|
||||
/// <seealso cref="FlaxEditor.Surface.SurfaceNode" />
|
||||
/// <seealso cref="FlaxEditor.Surface.IConnectionInstigator" />
|
||||
/// <seealso cref="FlaxEditor.Surface.ISurfaceContext" />
|
||||
internal class StateMachineState : SurfaceNode, ISurfaceContext, IConnectionInstigator
|
||||
internal abstract class StateMachineStateBase : SurfaceNode, IConnectionInstigator
|
||||
{
|
||||
internal class AddRemoveTransitionAction : IUndoAction
|
||||
{
|
||||
@@ -595,11 +595,11 @@ namespace FlaxEditor.Surface.Archetypes
|
||||
{
|
||||
var context = _context.Get(_surface);
|
||||
|
||||
var src = context.FindNode(_srcStateId) as StateMachineState;
|
||||
var src = context.FindNode(_srcStateId) as StateMachineStateBase;
|
||||
if (src == null)
|
||||
throw new Exception("Missing source state.");
|
||||
|
||||
var dst = context.FindNode(_dstStateId) as StateMachineState;
|
||||
var dst = context.FindNode(_dstStateId) as StateMachineStateBase;
|
||||
if (dst == null)
|
||||
throw new Exception("Missing destination state.");
|
||||
|
||||
@@ -610,17 +610,17 @@ namespace FlaxEditor.Surface.Archetypes
|
||||
src.UpdateTransitions();
|
||||
src.UpdateTransitionsColors();
|
||||
|
||||
src.SaveData();
|
||||
src.SaveTransitions();
|
||||
}
|
||||
|
||||
private void Remove()
|
||||
{
|
||||
var context = _context.Get(_surface);
|
||||
|
||||
if (!(context.FindNode(_srcStateId) is StateMachineState src))
|
||||
if (!(context.FindNode(_srcStateId) is StateMachineStateBase src))
|
||||
throw new Exception("Missing source state.");
|
||||
|
||||
if (!(context.FindNode(_dstStateId) is StateMachineState dst))
|
||||
if (!(context.FindNode(_dstStateId) is StateMachineStateBase dst))
|
||||
throw new Exception("Missing destination state.");
|
||||
|
||||
var transition = src.Transitions.Find(x => x.DestinationState == dst);
|
||||
@@ -634,7 +634,7 @@ namespace FlaxEditor.Surface.Archetypes
|
||||
src.UpdateTransitions();
|
||||
src.UpdateTransitionsColors();
|
||||
|
||||
src.SaveData();
|
||||
src.SaveTransitions();
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
@@ -646,9 +646,9 @@ namespace FlaxEditor.Surface.Archetypes
|
||||
|
||||
private bool _isSavingData;
|
||||
private bool _isMouseDown;
|
||||
private Rectangle _textRect;
|
||||
private Rectangle _dragAreaRect;
|
||||
private Rectangle _renameButtonRect;
|
||||
protected Rectangle _textRect;
|
||||
protected Rectangle _dragAreaRect;
|
||||
protected Rectangle _renameButtonRect;
|
||||
private bool _cursorChanged = false;
|
||||
private bool _textRectHovered = false;
|
||||
|
||||
@@ -657,40 +657,20 @@ namespace FlaxEditor.Surface.Archetypes
|
||||
/// </summary>
|
||||
public readonly List<StateMachineTransition> Transitions = new List<StateMachineTransition>();
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the node title text.
|
||||
/// </summary>
|
||||
public string StateTitle
|
||||
{
|
||||
get => (string)Values[0];
|
||||
set
|
||||
{
|
||||
if (!string.Equals(value, (string)Values[0], StringComparison.Ordinal))
|
||||
{
|
||||
SetValue(0, value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the state data (transitions list with rules graph and other options).
|
||||
/// </summary>
|
||||
public byte[] StateData
|
||||
{
|
||||
get => (byte[])Values[2];
|
||||
set => Values[2] = value;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The transitions rectangle (in surface-space).
|
||||
/// </summary>
|
||||
public Rectangle TransitionsRectangle;
|
||||
public Rectangle TransitionsRectangle = Rectangle.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// State transitions data value index (from node values).
|
||||
/// </summary>
|
||||
public abstract int TransitionsDataIndex { get; }
|
||||
|
||||
/// <inheritdoc />
|
||||
public StateMachineState(uint id, VisjectSurfaceContext context, NodeArchetype nodeArch, GroupArchetype groupArch)
|
||||
protected StateMachineStateBase(uint id, VisjectSurfaceContext context, NodeArchetype nodeArch, GroupArchetype groupArch)
|
||||
: base(id, context, nodeArch, groupArch)
|
||||
{
|
||||
TransitionsRectangle = Rectangle.Empty;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -744,20 +724,7 @@ namespace FlaxEditor.Surface.Archetypes
|
||||
base.OnValuesChanged();
|
||||
|
||||
if (!_isSavingData)
|
||||
LoadData();
|
||||
UpdateTitle();
|
||||
}
|
||||
|
||||
private void UpdateTitle()
|
||||
{
|
||||
Title = StateTitle;
|
||||
var style = Style.Current;
|
||||
var titleSize = style.FontLarge.MeasureText(Title);
|
||||
var width = Mathf.Max(100, titleSize.X + 50);
|
||||
Resize(width, 0);
|
||||
titleSize.X += 8.0f;
|
||||
var padding = new Float2(8, 8);
|
||||
_dragAreaRect = new Rectangle(padding, Size - padding * 2);
|
||||
LoadTransitions();
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
@@ -765,11 +732,9 @@ namespace FlaxEditor.Surface.Archetypes
|
||||
{
|
||||
base.UpdateRectangles();
|
||||
|
||||
const float buttonMargin = FlaxEditor.Surface.Constants.NodeCloseButtonMargin;
|
||||
const float buttonSize = FlaxEditor.Surface.Constants.NodeCloseButtonSize;
|
||||
_renameButtonRect = new Rectangle(_closeButtonRect.Left - buttonSize - buttonMargin, buttonMargin, buttonSize, buttonSize);
|
||||
_textRect = new Rectangle(Float2.Zero, Size);
|
||||
_dragAreaRect = _headerRect;
|
||||
var padding = new Float2(8, 8);
|
||||
_dragAreaRect = new Rectangle(padding, _textRect.Size - padding * 2);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
@@ -777,8 +742,7 @@ namespace FlaxEditor.Surface.Archetypes
|
||||
{
|
||||
base.OnSurfaceLoaded();
|
||||
|
||||
UpdateTitle();
|
||||
LoadData();
|
||||
LoadTransitions();
|
||||
|
||||
// Register for surface mouse events to handle transition arrows interactions
|
||||
Surface.CustomMouseUp += OnSurfaceMouseUp;
|
||||
@@ -857,34 +821,14 @@ namespace FlaxEditor.Surface.Archetypes
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void OnSpawned()
|
||||
{
|
||||
base.OnSpawned();
|
||||
|
||||
// Ensure to have unique name
|
||||
var title = StateTitle;
|
||||
var value = title;
|
||||
int count = 1;
|
||||
while (!OnRenameValidate(null, value))
|
||||
{
|
||||
value = title + " " + count++;
|
||||
}
|
||||
Values[0] = value;
|
||||
Title = value;
|
||||
|
||||
// Let user pick a name
|
||||
StartRenaming();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Loads the state data from the node value (reads transitions and related information).
|
||||
/// </summary>
|
||||
public void LoadData()
|
||||
public void LoadTransitions()
|
||||
{
|
||||
ClearData();
|
||||
ClearTransitions();
|
||||
|
||||
var bytes = StateData;
|
||||
var bytes = (byte[])Values[TransitionsDataIndex];
|
||||
if (bytes == null || bytes.Length == 0)
|
||||
{
|
||||
// Empty state
|
||||
@@ -923,7 +867,7 @@ namespace FlaxEditor.Surface.Archetypes
|
||||
if (ruleSize != 0)
|
||||
rule = reader.ReadBytes(ruleSize);
|
||||
|
||||
var destination = Context.FindNode(data.Destination) as StateMachineState;
|
||||
var destination = Context.FindNode(data.Destination) as StateMachineStateBase;
|
||||
if (destination == null)
|
||||
{
|
||||
Editor.LogWarning("Missing state machine state destination node.");
|
||||
@@ -944,10 +888,10 @@ namespace FlaxEditor.Surface.Archetypes
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Saves the state data to the node value (writes transitions and related information).
|
||||
/// Saves the state transitions data to the node value.
|
||||
/// </summary>
|
||||
/// <param name="withUndo">True if save data via node parameter editing via undo or without undo action.</param>
|
||||
public void SaveData(bool withUndo = false)
|
||||
public void SaveTransitions(bool withUndo = false)
|
||||
{
|
||||
try
|
||||
{
|
||||
@@ -998,9 +942,9 @@ namespace FlaxEditor.Surface.Archetypes
|
||||
}
|
||||
|
||||
if (withUndo)
|
||||
SetValue(2, value);
|
||||
SetValue(TransitionsDataIndex, value);
|
||||
else
|
||||
Values[2] = value;
|
||||
Values[TransitionsDataIndex] = value;
|
||||
}
|
||||
finally
|
||||
{
|
||||
@@ -1009,29 +953,21 @@ namespace FlaxEditor.Surface.Archetypes
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Clears the state data (removes transitions and related information).
|
||||
/// Clears the state transitions.
|
||||
/// </summary>
|
||||
public void ClearData()
|
||||
public void ClearTransitions()
|
||||
{
|
||||
Transitions.Clear();
|
||||
TransitionsRectangle = Rectangle.Empty;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Opens the state editing UI.
|
||||
/// </summary>
|
||||
public void Edit()
|
||||
{
|
||||
Surface.OpenContext(this);
|
||||
}
|
||||
|
||||
private bool IsSoloAndEnabled(StateMachineTransition t)
|
||||
{
|
||||
return t.Solo && t.Enabled;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Updates the transitions order in the list vy using the <see cref="StateMachineTransition.Order"/> property.
|
||||
/// Updates the transitions order in the list by using the <see cref="StateMachineTransition.Order"/> property.
|
||||
/// </summary>
|
||||
public void UpdateTransitionsOrder()
|
||||
{
|
||||
@@ -1138,32 +1074,6 @@ namespace FlaxEditor.Surface.Archetypes
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Starts the state renaming by showing a rename popup to the user.
|
||||
/// </summary>
|
||||
public void StartRenaming()
|
||||
{
|
||||
Surface.Select(this);
|
||||
var dialog = RenamePopup.Show(this, _textRect, Title, false);
|
||||
dialog.Validate += OnRenameValidate;
|
||||
dialog.Renamed += OnRenamed;
|
||||
}
|
||||
|
||||
private bool OnRenameValidate(RenamePopup popup, string value)
|
||||
{
|
||||
return Context.Nodes.All(node =>
|
||||
{
|
||||
if (node != this && node is StateMachineState state)
|
||||
return state.StateTitle != value;
|
||||
return true;
|
||||
});
|
||||
}
|
||||
|
||||
private void OnRenamed(RenamePopup renamePopup)
|
||||
{
|
||||
StateTitle = renamePopup.Text;
|
||||
}
|
||||
|
||||
private void StartCreatingTransition()
|
||||
{
|
||||
Surface.ConnectingStart(this);
|
||||
@@ -1216,9 +1126,6 @@ namespace FlaxEditor.Surface.Archetypes
|
||||
|
||||
// Close button
|
||||
Render2D.DrawSprite(style.Cross, _closeButtonRect, _closeButtonRect.Contains(_mousePosition) ? style.Foreground : style.ForegroundGrey);
|
||||
|
||||
// Rename button
|
||||
Render2D.DrawSprite(style.Settings, _renameButtonRect, _renameButtonRect.Contains(_mousePosition) ? style.Foreground : style.ForegroundGrey);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
@@ -1236,8 +1143,7 @@ namespace FlaxEditor.Surface.Archetypes
|
||||
if (_renameButtonRect.Contains(ref location) || _closeButtonRect.Contains(ref location))
|
||||
return true;
|
||||
|
||||
Edit();
|
||||
return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
@@ -1269,17 +1175,7 @@ namespace FlaxEditor.Surface.Archetypes
|
||||
Surface.ConnectingEnd(this);
|
||||
}
|
||||
|
||||
if (base.OnMouseUp(location, button))
|
||||
return true;
|
||||
|
||||
// Rename
|
||||
if (_renameButtonRect.Contains(ref location))
|
||||
{
|
||||
StartRenaming();
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
return base.OnMouseUp(location, button);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
@@ -1305,7 +1201,7 @@ namespace FlaxEditor.Surface.Archetypes
|
||||
{
|
||||
_textRectHovered = false;
|
||||
}
|
||||
|
||||
|
||||
base.OnMouseMove(location);
|
||||
}
|
||||
|
||||
@@ -1341,7 +1237,7 @@ namespace FlaxEditor.Surface.Archetypes
|
||||
{
|
||||
Transitions.Clear();
|
||||
UpdateTransitions();
|
||||
SaveData(true);
|
||||
SaveTransitions(true);
|
||||
}
|
||||
|
||||
for (int i = 0; i < Surface.Nodes.Count; i++)
|
||||
@@ -1351,7 +1247,7 @@ namespace FlaxEditor.Surface.Archetypes
|
||||
// Break link
|
||||
entry.FirstState = null;
|
||||
}
|
||||
else if (Surface.Nodes[i] is StateMachineState state)
|
||||
else if (Surface.Nodes[i] is StateMachineStateBase state)
|
||||
{
|
||||
bool modified = false;
|
||||
for (int j = 0; j < state.Transitions.Count && state.Transitions.Count > 0; j++)
|
||||
@@ -1366,7 +1262,7 @@ namespace FlaxEditor.Surface.Archetypes
|
||||
if (modified)
|
||||
{
|
||||
state.UpdateTransitions();
|
||||
state.SaveData(true);
|
||||
state.SaveTransitions(true);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1375,56 +1271,11 @@ namespace FlaxEditor.Surface.Archetypes
|
||||
/// <inheritdoc />
|
||||
public override void OnDestroy()
|
||||
{
|
||||
Surface.RemoveContext(this);
|
||||
|
||||
ClearData();
|
||||
ClearTransitions();
|
||||
|
||||
base.OnDestroy();
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public Asset SurfaceAsset => null;
|
||||
|
||||
/// <inheritdoc />
|
||||
public string SurfaceName => StateTitle;
|
||||
|
||||
/// <inheritdoc />
|
||||
public byte[] SurfaceData
|
||||
{
|
||||
get => (byte[])Values[1];
|
||||
set => Values[1] = value;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public VisjectSurfaceContext ParentContext => Context;
|
||||
|
||||
/// <inheritdoc />
|
||||
public void OnContextCreated(VisjectSurfaceContext context)
|
||||
{
|
||||
context.Loaded += OnSurfaceLoaded;
|
||||
}
|
||||
|
||||
private void OnSurfaceLoaded(VisjectSurfaceContext context)
|
||||
{
|
||||
// Ensure that loaded surface has output node for state
|
||||
if (context.FindNode(9, 21) == null)
|
||||
{
|
||||
var wasEnabled = true;
|
||||
if (Surface.Undo != null)
|
||||
{
|
||||
wasEnabled = Surface.Undo.Enabled;
|
||||
Surface.Undo.Enabled = false;
|
||||
}
|
||||
|
||||
context.SpawnNode(9, 21, new Float2(100.0f));
|
||||
|
||||
if (Surface.Undo != null)
|
||||
{
|
||||
Surface.Undo.Enabled = wasEnabled;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void DrawConnections(ref Float2 mousePosition)
|
||||
{
|
||||
@@ -1448,7 +1299,7 @@ namespace FlaxEditor.Surface.Archetypes
|
||||
/// <inheritdoc />
|
||||
public bool AreConnected(IConnectionInstigator other)
|
||||
{
|
||||
if (other is StateMachineState otherState)
|
||||
if (other is StateMachineStateBase otherState)
|
||||
return Transitions.Any(x => x.DestinationState == otherState);
|
||||
return false;
|
||||
}
|
||||
@@ -1473,8 +1324,7 @@ namespace FlaxEditor.Surface.Archetypes
|
||||
/// <inheritdoc />
|
||||
public void Connect(IConnectionInstigator other)
|
||||
{
|
||||
var state = (StateMachineState)other;
|
||||
|
||||
var state = (StateMachineStateBase)other;
|
||||
var action = new AddRemoveTransitionAction(this, state);
|
||||
Surface?.AddBatchedUndoAction(action);
|
||||
action.Do();
|
||||
@@ -1482,15 +1332,241 @@ namespace FlaxEditor.Surface.Archetypes
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Customized <see cref="SurfaceNode" /> for the state machine state node.
|
||||
/// </summary>
|
||||
internal class StateMachineState : StateMachineStateBase, ISurfaceContext
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public StateMachineState(uint id, VisjectSurfaceContext context, NodeArchetype nodeArch, GroupArchetype groupArch)
|
||||
: base(id, context, nodeArch, groupArch)
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the node title text.
|
||||
/// </summary>
|
||||
public string StateTitle
|
||||
{
|
||||
get => (string)Values[0];
|
||||
set
|
||||
{
|
||||
if (!string.Equals(value, (string)Values[0], StringComparison.Ordinal))
|
||||
{
|
||||
SetValue(0, value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Opens the state editing UI.
|
||||
/// </summary>
|
||||
public void Edit()
|
||||
{
|
||||
Surface.OpenContext(this);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Starts the state renaming by showing a rename popup to the user.
|
||||
/// </summary>
|
||||
public void StartRenaming()
|
||||
{
|
||||
Surface.Select(this);
|
||||
var dialog = RenamePopup.Show(this, _textRect, Title, false);
|
||||
dialog.Validate += OnRenameValidate;
|
||||
dialog.Renamed += OnRenamed;
|
||||
}
|
||||
|
||||
private bool OnRenameValidate(RenamePopup popup, string value)
|
||||
{
|
||||
return Context.Nodes.All(node =>
|
||||
{
|
||||
if (node != this && node is StateMachineState state)
|
||||
return state.StateTitle != value;
|
||||
return true;
|
||||
});
|
||||
}
|
||||
|
||||
private void OnRenamed(RenamePopup renamePopup)
|
||||
{
|
||||
StateTitle = renamePopup.Text;
|
||||
}
|
||||
|
||||
private void UpdateTitle()
|
||||
{
|
||||
Title = StateTitle;
|
||||
var style = Style.Current;
|
||||
var titleSize = style.FontLarge.MeasureText(Title);
|
||||
var width = Mathf.Max(100, titleSize.X + 50);
|
||||
Resize(width, 0);
|
||||
titleSize.X += 8.0f;
|
||||
var padding = new Float2(8, 8);
|
||||
_dragAreaRect = new Rectangle(padding, Size - padding * 2);
|
||||
}
|
||||
|
||||
private void OnSurfaceLoaded(VisjectSurfaceContext context)
|
||||
{
|
||||
// Ensure that loaded surface has output node for state
|
||||
if (context.FindNode(9, 21) == null)
|
||||
{
|
||||
var wasEnabled = true;
|
||||
if (Surface.Undo != null)
|
||||
{
|
||||
wasEnabled = Surface.Undo.Enabled;
|
||||
Surface.Undo.Enabled = false;
|
||||
}
|
||||
|
||||
context.SpawnNode(9, 21, new Float2(100.0f));
|
||||
|
||||
if (Surface.Undo != null)
|
||||
{
|
||||
Surface.Undo.Enabled = wasEnabled;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override int TransitionsDataIndex => 2;
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void OnSpawned()
|
||||
{
|
||||
base.OnSpawned();
|
||||
|
||||
// Ensure to have unique name
|
||||
var title = StateTitle;
|
||||
var value = title;
|
||||
int count = 1;
|
||||
while (!OnRenameValidate(null, value))
|
||||
{
|
||||
value = title + " " + count++;
|
||||
}
|
||||
Values[0] = value;
|
||||
Title = value;
|
||||
|
||||
// Let user pick a name
|
||||
StartRenaming();
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void OnSurfaceLoaded()
|
||||
{
|
||||
base.OnSurfaceLoaded();
|
||||
|
||||
UpdateTitle();
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void OnValuesChanged()
|
||||
{
|
||||
base.OnValuesChanged();
|
||||
|
||||
UpdateTitle();
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void Draw()
|
||||
{
|
||||
base.Draw();
|
||||
|
||||
var style = Style.Current;
|
||||
|
||||
// Rename button
|
||||
Render2D.DrawSprite(style.Settings, _renameButtonRect, _renameButtonRect.Contains(_mousePosition) ? style.Foreground : style.ForegroundGrey);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override bool OnMouseUp(Float2 location, MouseButton button)
|
||||
{
|
||||
if (base.OnMouseUp(location, button))
|
||||
return true;
|
||||
|
||||
// Rename
|
||||
if (_renameButtonRect.Contains(ref location))
|
||||
{
|
||||
StartRenaming();
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override bool OnMouseDoubleClick(Float2 location, MouseButton button)
|
||||
{
|
||||
if (base.OnMouseDoubleClick(location, button))
|
||||
return true;
|
||||
|
||||
Edit();
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void OnDestroy()
|
||||
{
|
||||
Surface.RemoveContext(this);
|
||||
|
||||
base.OnDestroy();
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void UpdateRectangles()
|
||||
{
|
||||
base.UpdateRectangles();
|
||||
|
||||
const float buttonMargin = FlaxEditor.Surface.Constants.NodeCloseButtonMargin;
|
||||
const float buttonSize = FlaxEditor.Surface.Constants.NodeCloseButtonSize;
|
||||
_renameButtonRect = new Rectangle(_closeButtonRect.Left - buttonSize - buttonMargin, buttonMargin, buttonSize, buttonSize);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public Asset SurfaceAsset => null;
|
||||
|
||||
/// <inheritdoc />
|
||||
public string SurfaceName => StateTitle;
|
||||
|
||||
/// <inheritdoc />
|
||||
public byte[] SurfaceData
|
||||
{
|
||||
get => (byte[])Values[1];
|
||||
set => Values[1] = value;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public VisjectSurfaceContext ParentContext => Context;
|
||||
|
||||
/// <inheritdoc />
|
||||
public void OnContextCreated(VisjectSurfaceContext context)
|
||||
{
|
||||
context.Loaded += OnSurfaceLoaded;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Customized <see cref="SurfaceNode" /> for the state machine Any state node.
|
||||
/// </summary>
|
||||
internal class StateMachineAny : StateMachineStateBase
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public StateMachineAny(uint id, VisjectSurfaceContext context, NodeArchetype nodeArch, GroupArchetype groupArch)
|
||||
: base(id, context, nodeArch, groupArch)
|
||||
{
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override int TransitionsDataIndex => 0;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// State machine transition data container object.
|
||||
/// </summary>
|
||||
/// <seealso cref="StateMachineState"/>
|
||||
/// <seealso cref="StateMachineStateBase"/>
|
||||
/// <seealso cref="ISurfaceContext"/>
|
||||
internal class StateMachineTransition : ISurfaceContext
|
||||
{
|
||||
/// <summary>
|
||||
/// The packed data container for the transition data storage. Helps with serialization and versioning the data.
|
||||
/// Must match AnimGraphBase::LoadStateTransition in C++
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// It does not store GC objects references to make it more lightweight. Transition rule bytes data is stores in a separate way.
|
||||
@@ -1596,13 +1672,13 @@ namespace FlaxEditor.Surface.Archetypes
|
||||
/// The transition start state.
|
||||
/// </summary>
|
||||
[HideInEditor]
|
||||
public readonly StateMachineState SourceState;
|
||||
public readonly StateMachineStateBase SourceState;
|
||||
|
||||
/// <summary>
|
||||
/// The transition end state.
|
||||
/// </summary>
|
||||
[HideInEditor]
|
||||
public readonly StateMachineState DestinationState;
|
||||
public readonly StateMachineStateBase DestinationState;
|
||||
|
||||
/// <summary>
|
||||
/// If checked, the transition can be triggered, otherwise it will be ignored.
|
||||
@@ -1615,7 +1691,7 @@ namespace FlaxEditor.Surface.Archetypes
|
||||
{
|
||||
_data.SetFlag(Data.FlagTypes.Enabled, value);
|
||||
SourceState.UpdateTransitionsColors();
|
||||
SourceState.SaveData(true);
|
||||
SourceState.SaveTransitions(true);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1630,7 +1706,7 @@ namespace FlaxEditor.Surface.Archetypes
|
||||
{
|
||||
_data.SetFlag(Data.FlagTypes.Solo, value);
|
||||
SourceState.UpdateTransitionsColors();
|
||||
SourceState.SaveData(true);
|
||||
SourceState.SaveTransitions(true);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1644,7 +1720,7 @@ namespace FlaxEditor.Surface.Archetypes
|
||||
set
|
||||
{
|
||||
_data.SetFlag(Data.FlagTypes.UseDefaultRule, value);
|
||||
SourceState.SaveData(true);
|
||||
SourceState.SaveTransitions(true);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1660,7 +1736,7 @@ namespace FlaxEditor.Surface.Archetypes
|
||||
_data.Order = value;
|
||||
SourceState.UpdateTransitionsOrder();
|
||||
SourceState.UpdateTransitionsColors();
|
||||
SourceState.SaveData(true);
|
||||
SourceState.SaveTransitions(true);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1674,7 +1750,7 @@ namespace FlaxEditor.Surface.Archetypes
|
||||
set
|
||||
{
|
||||
_data.BlendDuration = value;
|
||||
SourceState.SaveData(true);
|
||||
SourceState.SaveTransitions(true);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1688,7 +1764,7 @@ namespace FlaxEditor.Surface.Archetypes
|
||||
set
|
||||
{
|
||||
_data.BlendMode = value;
|
||||
SourceState.SaveData(true);
|
||||
SourceState.SaveTransitions(true);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1702,7 +1778,7 @@ namespace FlaxEditor.Surface.Archetypes
|
||||
set
|
||||
{
|
||||
_ruleGraph = value ?? Utils.GetEmptyArray<byte>();
|
||||
SourceState.SaveData();
|
||||
SourceState.SaveTransitions();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1737,7 +1813,7 @@ namespace FlaxEditor.Surface.Archetypes
|
||||
/// <param name="destination">The destination.</param>
|
||||
/// <param name="data">The transition data container.</param>
|
||||
/// <param name="ruleGraph">The transition rule graph. Can be null.</param>
|
||||
public StateMachineTransition(StateMachineState source, StateMachineState destination, ref Data data, byte[] ruleGraph = null)
|
||||
public StateMachineTransition(StateMachineStateBase source, StateMachineStateBase destination, ref Data data, byte[] ruleGraph = null)
|
||||
{
|
||||
SourceState = source;
|
||||
DestinationState = destination;
|
||||
@@ -1759,7 +1835,7 @@ namespace FlaxEditor.Surface.Archetypes
|
||||
public Asset SurfaceAsset => null;
|
||||
|
||||
/// <inheritdoc />
|
||||
public string SurfaceName => string.Format("{0} to {1}", SourceState.StateTitle, DestinationState.StateTitle);
|
||||
public string SurfaceName => string.Format("{0} to {1}", SourceState.Title, DestinationState.Title);
|
||||
|
||||
/// <inheritdoc />
|
||||
[HideInEditor]
|
||||
@@ -1807,7 +1883,7 @@ namespace FlaxEditor.Surface.Archetypes
|
||||
/// </summary>
|
||||
public void Delete()
|
||||
{
|
||||
var action = new StateMachineState.AddRemoveTransitionAction(this);
|
||||
var action = new StateMachineStateBase.AddRemoveTransitionAction(this);
|
||||
SourceState.Surface?.AddBatchedUndoAction(action);
|
||||
action.Do();
|
||||
}
|
||||
|
||||
@@ -986,6 +986,19 @@ namespace FlaxEditor.Surface.Archetypes
|
||||
NodeElementArchetype.Factory.Input(0, "Init", true, typeof(Float4), 1),
|
||||
}
|
||||
},
|
||||
new NodeArchetype
|
||||
{
|
||||
TypeID = 34,
|
||||
Create = (id, context, arch, groupArch) => new StateMachineAny(id, context, arch, groupArch),
|
||||
Title = "Any",
|
||||
Description = "The generic animation states machine state with source transitions from any other state",
|
||||
Flags = NodeFlags.AnimGraph | NodeFlags.NoSpawnViaGUI | NodeFlags.NoSpawnViaPaste,
|
||||
Size = new Float2(100, 0),
|
||||
DefaultValues = new object[]
|
||||
{
|
||||
Utils.GetEmptyArray<byte>(),
|
||||
},
|
||||
},
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -180,8 +180,6 @@ bool AnimGraphBase::onNodeLoaded(Node* n)
|
||||
const Float4 range = n->Values[0].AsFloat4();
|
||||
for (int32 i = 0; i < ANIM_GRAPH_MULTI_BLEND_MAX_ANIMS; i++)
|
||||
{
|
||||
auto data0 = n->Values[i * 2 + 4].AsFloat4();
|
||||
data0.X = Math::Clamp(data0.X, range.X, range.Y);
|
||||
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;
|
||||
}
|
||||
@@ -200,9 +198,6 @@ bool AnimGraphBase::onNodeLoaded(Node* n)
|
||||
const Float4 range = n->Values[0].AsFloat4();
|
||||
for (int32 i = 0; i < ANIM_GRAPH_MULTI_BLEND_MAX_ANIMS; i++)
|
||||
{
|
||||
auto data0 = n->Values[i * 2 + 4].AsFloat4();
|
||||
data0.X = Math::Clamp(data0.X, range.X, range.Y);
|
||||
data0.Y = Math::Clamp(data0.Y, range.Z, range.W);
|
||||
n->Assets[i] = (Asset*)Content::LoadAsync<Animation>((Guid)n->Values[i * 2 + 5]);
|
||||
if (n->Assets[i])
|
||||
{
|
||||
@@ -283,91 +278,11 @@ bool AnimGraphBase::onNodeLoaded(Node* n)
|
||||
Value& surfaceData = n->Values[1];
|
||||
data.Graph = LoadSubGraph(surfaceData.AsBlob.Data, surfaceData.AsBlob.Length, (const Char*)name.AsBlob.Data);
|
||||
|
||||
// Initialize transitions
|
||||
Value& transitionsData = n->Values[2];
|
||||
int32 validTransitions = 0;
|
||||
if (transitionsData.Type == VariantType::Blob && transitionsData.AsBlob.Length)
|
||||
{
|
||||
MemoryReadStream stream((byte*)transitionsData.AsBlob.Data, transitionsData.AsBlob.Length);
|
||||
|
||||
int32 version;
|
||||
stream.ReadInt32(&version);
|
||||
if (version != 1)
|
||||
{
|
||||
LOG(Warning, "Invalid version of the Anim Graph state transitions data.");
|
||||
return true;
|
||||
}
|
||||
|
||||
int32 transitionsCount;
|
||||
stream.ReadInt32(&transitionsCount);
|
||||
|
||||
StateTransitions.EnsureCapacity(StateTransitions.Count() + transitionsCount);
|
||||
|
||||
AnimGraphStateTransition transition;
|
||||
for (int32 i = 0; i < transitionsCount; i++)
|
||||
{
|
||||
struct Data
|
||||
{
|
||||
int32 Destination;
|
||||
int32 Flags;
|
||||
int32 Order;
|
||||
float BlendDuration;
|
||||
int32 BlendMode;
|
||||
int32 Unused0;
|
||||
int32 Unused1;
|
||||
int32 Unused2;
|
||||
};
|
||||
Data transitionData;
|
||||
stream.ReadBytes(&transitionData, sizeof(transitionData));
|
||||
|
||||
transition.Flags = (AnimGraphStateTransition::FlagTypes)transitionData.Flags;
|
||||
transition.BlendDuration = transitionData.BlendDuration;
|
||||
transition.BlendMode = (AlphaBlendMode)transitionData.BlendMode;
|
||||
transition.Destination = GetNode(transitionData.Destination);
|
||||
transition.RuleGraph = nullptr;
|
||||
|
||||
int32 ruleSize;
|
||||
stream.ReadInt32(&ruleSize);
|
||||
const auto ruleBytes = (byte*)stream.Move(ruleSize);
|
||||
|
||||
if (static_cast<int32>(transition.Flags & AnimGraphStateTransition::FlagTypes::Enabled) == 0)
|
||||
{
|
||||
// Skip disabled transitions
|
||||
continue;
|
||||
}
|
||||
|
||||
if (ruleSize != 0)
|
||||
{
|
||||
transition.RuleGraph = LoadSubGraph(ruleBytes, ruleSize, TEXT("Rule"));
|
||||
if (transition.RuleGraph && transition.RuleGraph->GetRootNode() == nullptr)
|
||||
{
|
||||
LOG(Warning, "Missing root node for the state machine transition rule graph.");
|
||||
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)
|
||||
data.Transitions[validTransitions] = AnimGraphNode::StateData::InvalidTransitionIndex;
|
||||
|
||||
// Release data to don't use that memory
|
||||
surfaceData = Value::Null;
|
||||
transitionsData = Value::Null;
|
||||
|
||||
// Initialize transitions
|
||||
LoadStateTransitions(data, n->Values[2]);
|
||||
|
||||
break;
|
||||
}
|
||||
@@ -443,6 +358,10 @@ bool AnimGraphBase::onNodeLoaded(Node* n)
|
||||
case 33:
|
||||
ADD_BUCKET(InstanceDataBucketInit);
|
||||
break;
|
||||
// Any State
|
||||
case 34:
|
||||
LoadStateTransitions(n->Data.AnyState, n->Values[0]);
|
||||
break;
|
||||
}
|
||||
break;
|
||||
// Custom
|
||||
@@ -465,4 +384,91 @@ bool AnimGraphBase::onNodeLoaded(Node* n)
|
||||
return VisjectGraph::onNodeLoaded(n);
|
||||
}
|
||||
|
||||
void AnimGraphBase::LoadStateTransitions(AnimGraphNode::StateBaseData& data, Value& transitionsData)
|
||||
{
|
||||
int32 validTransitions = 0;
|
||||
if (transitionsData.Type == VariantType::Blob && transitionsData.AsBlob.Length)
|
||||
{
|
||||
MemoryReadStream stream((byte*)transitionsData.AsBlob.Data, transitionsData.AsBlob.Length);
|
||||
|
||||
int32 version;
|
||||
stream.ReadInt32(&version);
|
||||
if (version != 1)
|
||||
{
|
||||
LOG(Warning, "Invalid version of the Anim Graph state transitions data.");
|
||||
return;
|
||||
}
|
||||
|
||||
int32 transitionsCount;
|
||||
stream.ReadInt32(&transitionsCount);
|
||||
|
||||
StateTransitions.EnsureCapacity(StateTransitions.Count() + transitionsCount);
|
||||
|
||||
AnimGraphStateTransition transition;
|
||||
for (int32 i = 0; i < transitionsCount; i++)
|
||||
{
|
||||
// Must match StateMachineTransition.Data in C#
|
||||
struct Data
|
||||
{
|
||||
int32 Destination;
|
||||
int32 Flags;
|
||||
int32 Order;
|
||||
float BlendDuration;
|
||||
int32 BlendMode;
|
||||
int32 Unused0;
|
||||
int32 Unused1;
|
||||
int32 Unused2;
|
||||
};
|
||||
Data transitionData;
|
||||
stream.ReadBytes(&transitionData, sizeof(transitionData));
|
||||
|
||||
transition.Flags = (AnimGraphStateTransition::FlagTypes)transitionData.Flags;
|
||||
transition.BlendDuration = transitionData.BlendDuration;
|
||||
transition.BlendMode = (AlphaBlendMode)transitionData.BlendMode;
|
||||
transition.Destination = GetNode(transitionData.Destination);
|
||||
transition.RuleGraph = nullptr;
|
||||
|
||||
int32 ruleSize;
|
||||
stream.ReadInt32(&ruleSize);
|
||||
const auto ruleBytes = (byte*)stream.Move(ruleSize);
|
||||
|
||||
if (static_cast<int32>(transition.Flags & AnimGraphStateTransition::FlagTypes::Enabled) == 0)
|
||||
{
|
||||
// Skip disabled transitions
|
||||
continue;
|
||||
}
|
||||
|
||||
if (ruleSize != 0)
|
||||
{
|
||||
transition.RuleGraph = LoadSubGraph(ruleBytes, ruleSize, TEXT("Rule"));
|
||||
if (transition.RuleGraph && transition.RuleGraph->GetRootNode() == nullptr)
|
||||
{
|
||||
LOG(Warning, "Missing root node for the state machine transition rule graph.");
|
||||
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)
|
||||
data.Transitions[validTransitions] = AnimGraphNode::StateData::InvalidTransitionIndex;
|
||||
|
||||
// Release data to don't use that memory
|
||||
transitionsData = AnimGraphExecutor::Value::Null;
|
||||
}
|
||||
|
||||
#undef ADD_BUCKET
|
||||
|
||||
@@ -495,24 +495,31 @@ public:
|
||||
AnimSubGraph* Graph;
|
||||
};
|
||||
|
||||
struct StateData
|
||||
struct StateBaseData
|
||||
{
|
||||
/// <summary>
|
||||
/// The invalid transition valid used in Transitions to indicate invalid transition linkage.
|
||||
/// </summary>
|
||||
const static uint16 InvalidTransitionIndex = MAX_uint16;
|
||||
|
||||
/// <summary>
|
||||
/// The graph of the state. Contains the state animation evaluation graph. Its root node is the state output node with an input box for the state blend pose sampling.
|
||||
/// </summary>
|
||||
AnimSubGraph* Graph;
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// 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.
|
||||
/// </summary>
|
||||
uint16 Transitions[ANIM_GRAPH_MAX_STATE_TRANSITIONS];
|
||||
};
|
||||
|
||||
struct StateData : StateBaseData
|
||||
{
|
||||
/// <summary>
|
||||
/// The graph of the state. Contains the state animation evaluation graph. Its root node is the state output node with an input box for the state blend pose sampling.
|
||||
/// </summary>
|
||||
AnimSubGraph* Graph;
|
||||
};
|
||||
|
||||
struct AnyStateData : StateBaseData
|
||||
{
|
||||
};
|
||||
|
||||
struct CustomData
|
||||
{
|
||||
/// <summary>
|
||||
@@ -564,6 +571,7 @@ public:
|
||||
MultiBlend2DData MultiBlend2D;
|
||||
StateMachineData StateMachine;
|
||||
StateData State;
|
||||
AnyStateData AnyState;
|
||||
CustomData Custom;
|
||||
CurveData Curve;
|
||||
AnimationGraphFunctionData AnimationGraphFunction;
|
||||
@@ -681,6 +689,9 @@ public:
|
||||
protected:
|
||||
// [Graph]
|
||||
bool onNodeLoaded(Node* n) override;
|
||||
|
||||
private:
|
||||
void LoadStateTransitions(AnimGraphNode::StateBaseData& data, Value& transitionsData);
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
@@ -895,4 +906,5 @@ private:
|
||||
Variant SampleAnimationsWithBlend(AnimGraphNode* node, bool loop, float length, float startTimePos, float prevTimePos, float& newTimePos, Animation* animA, Animation* animB, Animation* animC, float speedA, float speedB, float speedC, float alphaA, float alphaB, float alphaC);
|
||||
Variant Blend(AnimGraphNode* node, const Value& poseA, const Value& poseB, float alpha, AlphaBlendMode alphaMode);
|
||||
Variant SampleState(AnimGraphNode* state);
|
||||
void UpdateStateTransitions(AnimGraphContext& context, const AnimGraphNode::StateMachineData& stateMachineData, AnimGraphInstanceData::StateMachineBucket& stateMachineBucket, const AnimGraphNode::StateBaseData& stateData);
|
||||
};
|
||||
|
||||
@@ -11,7 +11,7 @@
|
||||
|
||||
namespace
|
||||
{
|
||||
void BlendAdditiveWeightedRotation(Quaternion& base, Quaternion& additive, float weight)
|
||||
FORCE_INLINE void BlendAdditiveWeightedRotation(Quaternion& base, Quaternion& additive, float weight)
|
||||
{
|
||||
// Pick a shortest path between rotation to fix blending artifacts
|
||||
additive *= weight;
|
||||
@@ -463,6 +463,79 @@ Variant AnimGraphExecutor::SampleState(AnimGraphNode* state)
|
||||
return result;
|
||||
}
|
||||
|
||||
void AnimGraphExecutor::UpdateStateTransitions(AnimGraphContext& context, const AnimGraphNode::StateMachineData& stateMachineData, AnimGraphInstanceData::StateMachineBucket& stateMachineBucket, const AnimGraphNode::StateBaseData& stateData)
|
||||
{
|
||||
int32 transitionIndex = 0;
|
||||
while (transitionIndex < ANIM_GRAPH_MAX_STATE_TRANSITIONS && stateData.Transitions[transitionIndex] != AnimGraphNode::StateData::InvalidTransitionIndex)
|
||||
{
|
||||
const uint16 idx = stateData.Transitions[transitionIndex];
|
||||
ASSERT(idx < stateMachineData.Graph->StateTransitions.Count());
|
||||
auto& transition = stateMachineData.Graph->StateTransitions[idx];
|
||||
if (transition.Destination == stateMachineBucket.CurrentState)
|
||||
{
|
||||
// Ignore transition to the current state
|
||||
transitionIndex++;
|
||||
continue;
|
||||
}
|
||||
const bool useDefaultRule = EnumHasAnyFlags(transition.Flags, AnimGraphStateTransition::FlagTypes::UseDefaultRule);
|
||||
if (transition.RuleGraph && !useDefaultRule)
|
||||
{
|
||||
// Execute transition rule
|
||||
auto rootNode = transition.RuleGraph->GetRootNode();
|
||||
ASSERT(rootNode);
|
||||
if (!(bool)eatBox((Node*)rootNode, &rootNode->Boxes[0]))
|
||||
{
|
||||
transitionIndex++;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
// Evaluate source state transition data (position, length, etc.)
|
||||
const Value sourceStatePtr = SampleState(stateMachineBucket.CurrentState);
|
||||
auto& transitionData = context.TransitionData; // Note: this could support nested transitions but who uses state machine inside transition rule?
|
||||
if (ANIM_GRAPH_IS_VALID_PTR(sourceStatePtr))
|
||||
{
|
||||
// Use source state as data provider
|
||||
const auto sourceState = (AnimGraphImpulse*)sourceStatePtr.AsPointer;
|
||||
auto sourceLength = Math::Max(sourceState->Length, 0.0f);
|
||||
transitionData.Position = Math::Clamp(sourceState->Position, 0.0f, sourceLength);
|
||||
transitionData.Length = sourceLength;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Reset
|
||||
transitionData.Position = 0;
|
||||
transitionData.Length = ZeroTolerance;
|
||||
}
|
||||
|
||||
// Check if can trigger the transition
|
||||
bool canEnter = false;
|
||||
if (useDefaultRule)
|
||||
{
|
||||
// Start transition when the current state animation is about to end (split blend duration evenly into two states)
|
||||
const auto transitionDurationHalf = transition.BlendDuration * 0.5f + ZeroTolerance;
|
||||
const auto endPos = transitionData.Length - transitionDurationHalf;
|
||||
canEnter = transitionData.Position >= endPos;
|
||||
}
|
||||
else if (transition.RuleGraph)
|
||||
canEnter = true;
|
||||
if (canEnter)
|
||||
{
|
||||
// Start transition
|
||||
stateMachineBucket.ActiveTransition = &transition;
|
||||
stateMachineBucket.TransitionPosition = 0.0f;
|
||||
break;
|
||||
}
|
||||
|
||||
// Skip after Solo transition
|
||||
// TODO: don't load transitions after first enabled Solo transition and remove this check here
|
||||
if (EnumHasAnyFlags(transition.Flags, AnimGraphStateTransition::FlagTypes::Solo))
|
||||
break;
|
||||
|
||||
transitionIndex++;
|
||||
}
|
||||
}
|
||||
|
||||
void ComputeMultiBlendLength(float& length, AnimGraphNode* node)
|
||||
{
|
||||
ANIM_GRAPH_PROFILE_EVENT("Setup Mutli Blend Length");
|
||||
@@ -1502,77 +1575,21 @@ void AnimGraphExecutor::ProcessGroupAnimation(Box* boxBase, Node* nodeBase, Valu
|
||||
}
|
||||
}
|
||||
|
||||
ASSERT(bucket.CurrentState && bucket.CurrentState->GroupID == 9 && bucket.CurrentState->TypeID == 20);
|
||||
ASSERT(bucket.CurrentState && bucket.CurrentState->Type == GRAPH_NODE_MAKE_TYPE(9, 20));
|
||||
|
||||
// Update transitions
|
||||
// Note: this logic assumes that all transitions are sorted by Order property and Enabled
|
||||
// Note: this logic assumes that all transitions are sorted by Order property and Enabled (by Editor when saving Anim Graph asset)
|
||||
while (!bucket.ActiveTransition && transitionsLeft-- > 0)
|
||||
{
|
||||
// Check if can change the current state
|
||||
const auto& stateData = bucket.CurrentState->Data.State;
|
||||
int32 transitionIndex = 0;
|
||||
while (stateData.Transitions[transitionIndex] != AnimGraphNode::StateData::InvalidTransitionIndex
|
||||
&& transitionIndex < ANIM_GRAPH_MAX_STATE_TRANSITIONS)
|
||||
// State transitions
|
||||
UpdateStateTransitions(context, data, bucket, bucket.CurrentState->Data.State);
|
||||
|
||||
// Any state transitions
|
||||
// TODO: cache Any state nodes inside State Machine to optimize the loop below
|
||||
for (const AnimGraphNode& anyStateNode : data.Graph->Nodes)
|
||||
{
|
||||
const uint16 idx = stateData.Transitions[transitionIndex];
|
||||
ASSERT(idx < data.Graph->StateTransitions.Count());
|
||||
auto& transition = data.Graph->StateTransitions[idx];
|
||||
const bool useDefaultRule = EnumHasAnyFlags(transition.Flags, AnimGraphStateTransition::FlagTypes::UseDefaultRule);
|
||||
if (transition.RuleGraph && !useDefaultRule)
|
||||
{
|
||||
// Execute transition rule
|
||||
auto rootNode = transition.RuleGraph->GetRootNode();
|
||||
ASSERT(rootNode);
|
||||
if (!(bool)eatBox((Node*)rootNode, &rootNode->Boxes[0]))
|
||||
{
|
||||
transitionIndex++;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
// Evaluate source state transition data (position, length, etc.)
|
||||
const Value sourceStatePtr = SampleState(bucket.CurrentState);
|
||||
auto& transitionData = context.TransitionData; // Note: this could support nested transitions but who uses state machine inside transition rule?
|
||||
if (ANIM_GRAPH_IS_VALID_PTR(sourceStatePtr))
|
||||
{
|
||||
// Use source state as data provider
|
||||
const auto sourceState = (AnimGraphImpulse*)sourceStatePtr.AsPointer;
|
||||
auto sourceLength = Math::Max(sourceState->Length, 0.0f);
|
||||
transitionData.Position = Math::Clamp(sourceState->Position, 0.0f, sourceLength);
|
||||
transitionData.Length = sourceLength;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Reset
|
||||
transitionData.Position = 0;
|
||||
transitionData.Length = ZeroTolerance;
|
||||
}
|
||||
|
||||
// Check if can trigger the transition
|
||||
bool canEnter = false;
|
||||
if (useDefaultRule)
|
||||
{
|
||||
// Start transition when the current state animation is about to end (split blend duration evenly into two states)
|
||||
const auto transitionDurationHalf = transition.BlendDuration * 0.5f + ZeroTolerance;
|
||||
const auto endPos = transitionData.Length - transitionDurationHalf;
|
||||
canEnter = transitionData.Position >= endPos;
|
||||
}
|
||||
else if (transition.RuleGraph)
|
||||
canEnter = true;
|
||||
if (canEnter)
|
||||
{
|
||||
// Start transition
|
||||
bucket.ActiveTransition = &transition;
|
||||
bucket.TransitionPosition = 0.0f;
|
||||
break;
|
||||
}
|
||||
|
||||
// Skip after Solo transition
|
||||
// TODO: don't load transitions after first enabled Solo transition and remove this check here
|
||||
if (EnumHasAnyFlags(transition.Flags, AnimGraphStateTransition::FlagTypes::Solo))
|
||||
break;
|
||||
|
||||
transitionIndex++;
|
||||
if (anyStateNode.Type == GRAPH_NODE_MAKE_TYPE(9, 34))
|
||||
UpdateStateTransitions(context, data, bucket, anyStateNode.Data.AnyState);
|
||||
}
|
||||
|
||||
// Check for instant transitions
|
||||
@@ -1608,13 +1625,10 @@ void AnimGraphExecutor::ProcessGroupAnimation(Box* boxBase, Node* nodeBase, Valu
|
||||
}
|
||||
// Entry
|
||||
case 19:
|
||||
{
|
||||
// Not used
|
||||
CRASH;
|
||||
break;
|
||||
}
|
||||
// State
|
||||
case 20:
|
||||
// Any State
|
||||
case 34:
|
||||
{
|
||||
// Not used
|
||||
CRASH;
|
||||
@@ -1622,8 +1636,6 @@ void AnimGraphExecutor::ProcessGroupAnimation(Box* boxBase, Node* nodeBase, Valu
|
||||
}
|
||||
// State Output
|
||||
case 21:
|
||||
value = box->HasConnection() ? eatBox(nodeBase, box->FirstConnection()) : Value::Null;
|
||||
break;
|
||||
// Rule Output
|
||||
case 22:
|
||||
value = box->HasConnection() ? eatBox(nodeBase, box->FirstConnection()) : Value::Null;
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
template<class BoxType>
|
||||
class GraphNode;
|
||||
|
||||
#define GRAPH_NODE_MAKE_TYPE(groupID, typeID) ((groupID) << 16 | (typeID))
|
||||
#define GRAPH_NODE_MAKE_TYPE(groupID, typeID) (uint32)((groupID) << 16 | (typeID))
|
||||
|
||||
#define GRAPH_NODE_MAX_VALUES 32
|
||||
|
||||
|
||||
Reference in New Issue
Block a user