2025-05-31 20:44:33 -07:00

303 lines
12 KiB
C#

using AssetRipper.Assets.Collections;
using AssetRipper.Assets.Generics;
using AssetRipper.SourceGenerated;
using AssetRipper.SourceGenerated.Classes.ClassID_1101;
using AssetRipper.SourceGenerated.Classes.ClassID_1102;
using AssetRipper.SourceGenerated.Classes.ClassID_1107;
using AssetRipper.SourceGenerated.Classes.ClassID_1109;
using AssetRipper.SourceGenerated.Classes.ClassID_114;
using AssetRipper.SourceGenerated.Classes.ClassID_206;
using AssetRipper.SourceGenerated.Classes.ClassID_207;
using AssetRipper.SourceGenerated.Classes.ClassID_91;
using AssetRipper.SourceGenerated.Enums;
using AssetRipper.SourceGenerated.Extensions;
using AssetRipper.SourceGenerated.Subclasses.AnimatorCondition;
using AssetRipper.SourceGenerated.Subclasses.BlendTreeConstant;
using AssetRipper.SourceGenerated.Subclasses.BlendTreeNodeConstant;
using AssetRipper.SourceGenerated.Subclasses.ChildMotion;
using AssetRipper.SourceGenerated.Subclasses.ConditionConstant;
using AssetRipper.SourceGenerated.Subclasses.LeafInfoConstant;
using AssetRipper.SourceGenerated.Subclasses.SelectorTransitionConstant;
using AssetRipper.SourceGenerated.Subclasses.StateConstant;
using AssetRipper.SourceGenerated.Subclasses.TransitionConstant;
namespace AssetRipper.Processing.AnimatorControllers
{
public static class VirtualAnimationFactory
{
// Example of default BlendTree Name:
// https://github.com/ds5678/Binoculars/blob/d6702ed3a1db39b1a2788956ff195b2590c3d08b/Unity/Assets/Models/binoculars_animator.controller#L106
private static Utf8String BlendTreeName { get; } = new Utf8String("Blend Tree");
private static Utf8String AnimatorStateName { get; } = new Utf8String("New State");
private static IMotion? CreateMotion(ProcessedAssetCollection virtualFile, IAnimatorController controller, IStateConstant stateConstant, int nodeIndex)
{
if (stateConstant.BlendTreeConstantArray.Count == 0)
{
return default; // null Motion
}
else
{
IBlendTreeNodeConstant node = stateConstant.GetBlendTree().NodeArray[nodeIndex].Data;
if (node.IsBlendTree())
{
return CreateBlendTree(virtualFile, controller, stateConstant, nodeIndex); // BlendTree Motion
}
else
{
int clipIndex = -1;
if (stateConstant.Has_LeafInfoArray())
{
for (int i = 0; i < stateConstant.LeafInfoArray.Count; i++)
{
LeafInfoConstant leafInfo = stateConstant.LeafInfoArray[i];
int index = leafInfo.IDArray.IndexOf(node.ClipID);
if (index >= 0)
{
clipIndex = (int)leafInfo.IndexOffset + index;
break;
}
}
}
else
{
clipIndex = unchecked((int)node.ClipID);
}
if (clipIndex == -1)
{
return default; // null Motion
}
else
{
return controller.AnimationClipsP[clipIndex] as IMotion; // AnimationClip Motion (inherited since Unity 4)
}
}
}
}
private static IBlendTree CreateBlendTree(ProcessedAssetCollection virtualFile, IAnimatorController controller, IStateConstant state, int nodeIndex)
{
IBlendTree blendTree = virtualFile.CreateBlendTree();
blendTree.HideFlagsE = HideFlags.HideInHierarchy;
IBlendTreeNodeConstant node = state.GetBlendTree().NodeArray[nodeIndex].Data;
blendTree.Name = BlendTreeName;
blendTree.Childs.Capacity = node.ChildIndices.Count;
for (int i = 0; i < node.ChildIndices.Count; i++)
{
AddAndInitializeNewChild(virtualFile, controller, state, blendTree, nodeIndex, i);
}
if (node.BlendEventID != uint.MaxValue)
{
blendTree.BlendParameter = controller.TOS[node.BlendEventID];
}
if (node.BlendEventYID != uint.MaxValue)
{
blendTree.BlendParameterY = controller.TOS[node.BlendEventYID];
}
blendTree.MinThreshold = node.GetMinThreshold();
blendTree.MaxThreshold = node.GetMaxThreshold();
blendTree.UseAutomaticThresholds = false;
blendTree.NormalizedBlendValues = node.BlendDirectData?.Data.NormalizedBlendValues ?? false;
if (blendTree.Has_BlendType_Int32())
{
blendTree.BlendType_Int32 = (int)node.BlendType;
}
else
{
blendTree.BlendType_UInt32 = node.BlendType;
}
return blendTree;
}
private static IChildMotion AddAndInitializeNewChild(ProcessedAssetCollection virtualFile, IAnimatorController controller, IStateConstant state, IBlendTree tree, int nodeIndex, int childIndex)
{
IChildMotion childMotion = tree.Childs.AddNew();
IBlendTreeConstant treeConstant = state.GetBlendTree();
IBlendTreeNodeConstant node = treeConstant.NodeArray[nodeIndex].Data;
int childNodeIndex = (int)node.ChildIndices[childIndex];
// https://github.com/AssetRipper/AssetRipper/issues/1566
// Strangely, some BlendTree nodes have the same index as the child node index.
// In the case of the above issue, both indices were 0.
IMotion? motion = nodeIndex != childNodeIndex
? CreateMotion(virtualFile, controller, state, childNodeIndex)
: null; // tree might be more accurate here since the indices are the same, but it doesn't make sense for a BlendTree to be a child of itself.
childMotion.Motion.SetAsset(tree.Collection, motion);
IBlendTreeNodeConstant childNode = treeConstant.NodeArray[childNodeIndex].Data;
if (childNode.IsBlendTree())
{
// BlendTree ChildMotions are not allowed to use TimeScale or Mirror
// https://github.com/Unity-Technologies/UnityCsReference/blob/4e215c07ca8e9a32a589043202fd919bdfc0a26d/Editor/Mono/Inspector/BlendTreeInspector.cs#L1469
// https://github.com/Unity-Technologies/UnityCsReference/blob/4e215c07ca8e9a32a589043202fd919bdfc0a26d/Editor/Mono/Inspector/BlendTreeInspector.cs#L1488
childMotion.TimeScale = 1;
childMotion.Mirror = false;
}
else
{
childMotion.TimeScale = 1 / childNode.Duration;
childMotion.Mirror = childNode.Mirror;
}
childMotion.CycleOffset = childNode.CycleOffset;
childMotion.Threshold = node.GetThreshold(childIndex);
childMotion.Position?.CopyValues(node.GetPosition(childIndex));
if (node.TryGetDirectBlendParameter(childIndex, out uint directID))
{
childMotion.DirectBlendParameter = controller.TOS[directID];
}
return childMotion;
}
/// <summary>
/// Create a fully solved Root AnimatorStateMachine for the corresponding index
/// </summary>
public static IAnimatorStateMachine CreateRootAnimatorStateMachine(ProcessedAssetCollection virtualFile, IAnimatorController controller, int stateMachineIndex)
{
AnimatorStateMachineContext stateMachineContext = new(virtualFile, controller, stateMachineIndex);
stateMachineContext.Process();
return stateMachineContext.RootStateMachine;
}
public static IAnimatorStateMachine CreateStateMachine(ProcessedAssetCollection virtualFile, IAnimatorController controller, int layerIndex, uint fullPathID = 0)
{
IAnimatorStateMachine stateMachine = virtualFile.CreateAnimatorStateMachine();
stateMachine.HideFlagsE = HideFlags.HideInHierarchy;
// can add StateMachineBehaviours now
if (stateMachine.Has_StateMachineBehaviours())
{
IMonoBehaviour?[] stateBehaviours = controller.GetStateBehaviours(layerIndex, fullPathID);
foreach (IMonoBehaviour? stateBehaviour in stateBehaviours)
{
if (stateBehaviour != null)
{
stateBehaviour.HideFlagsE = HideFlags.HideInHierarchy;
stateMachine.StateMachineBehavioursP.Add(stateBehaviour);
}
}
}
return stateMachine;
}
public static IAnimatorState CreateAnimatorState(ProcessedAssetCollection virtualFile, IAnimatorController controller, AssetDictionary<uint, Utf8String> tos, int layerIndex, IStateConstant stateConstant)
{
IAnimatorState animatorState = virtualFile.CreateAnimatorState();
animatorState.HideFlagsE = HideFlags.HideInHierarchy;
animatorState.Name = stateConstant.GetName(tos);
animatorState.Speed = stateConstant.Speed;
animatorState.CycleOffset = stateConstant.CycleOffset;
if (animatorState.Has_StateMachineBehaviours())
{
uint stateID = stateConstant.GetId();
IMonoBehaviour?[] stateBehaviours = controller.GetStateBehaviours(layerIndex, stateID);
animatorState.StateMachineBehavioursP.AddRange(stateBehaviours);
}
animatorState.IKOnFeet = stateConstant.IKOnFeet;
animatorState.WriteDefaultValues = stateConstant.GetWriteDefaultValues();
animatorState.Mirror = stateConstant.Mirror;
animatorState.SpeedParameterActive = stateConstant.SpeedParamID > 0;
animatorState.MirrorParameterActive = stateConstant.MirrorParamID > 0;
animatorState.CycleOffsetParameterActive = stateConstant.CycleOffsetParamID > 0;
animatorState.TimeParameterActive = stateConstant.TimeParamID > 0;
IMotion? motion = CreateMotion(virtualFile, controller, stateConstant, 0);
if (animatorState.Has_Motion())
{
animatorState.MotionP = motion;
}
else
{
animatorState.MotionsP.Add(motion);
}
animatorState.Tag = tos[stateConstant.TagID];
animatorState.SpeedParameter = tos[stateConstant.SpeedParamID];
animatorState.MirrorParameter = tos[stateConstant.MirrorParamID];
animatorState.CycleOffsetParameter = tos[stateConstant.CycleOffsetParamID];
animatorState.TimeParameter = tos[stateConstant.TimeParamID];
return animatorState;
}
public static IAnimatorState CreateDefaultAnimatorState(ProcessedAssetCollection virtualFile)
{
IAnimatorState animatorState = virtualFile.CreateAnimatorState();
animatorState.HideFlagsE = HideFlags.HideInHierarchy;
animatorState.Name = AnimatorStateName;
animatorState.Speed = 1;
animatorState.WriteDefaultValues = true;
return animatorState;
}
public static IAnimatorStateTransition CreateAnimatorStateTransition(ProcessedAssetCollection virtualFile, AssetDictionary<uint, Utf8String> TOS, ITransitionConstant Transition)
{
IAnimatorStateTransition animatorStateTransition = virtualFile.CreateAnimatorStateTransition();
animatorStateTransition.HideFlags = (uint)HideFlags.HideInHierarchy;
animatorStateTransition.Conditions.Capacity = Transition.ConditionConstantArray.Count;
for (int i = 0; i < Transition.ConditionConstantArray.Count; i++)
{
ConditionConstant conditionConstant = Transition.ConditionConstantArray[i].Data;
if (!animatorStateTransition.Has_ExitTime() || conditionConstant.ConditionMode != (int)AnimatorConditionMode.ExitTime)
{
IAnimatorCondition condition = animatorStateTransition.Conditions.AddNew();
condition.ConditionMode = (int)conditionConstant.ConditionModeE;
condition.ConditionEvent = TOS[conditionConstant.EventID];
condition.EventTreshold = conditionConstant.EventThreshold;
condition.ExitTime = conditionConstant.ExitTime;
}
}
animatorStateTransition.Name = TOS[Transition.UserID];
animatorStateTransition.Atomic = Transition.Atomic;
animatorStateTransition.TransitionDuration = Transition.TransitionDuration;
animatorStateTransition.TransitionOffset = Transition.TransitionOffset;
animatorStateTransition.ExitTime = Transition.GetExitTime();
animatorStateTransition.HasExitTime = Transition.GetHasExitTime();
animatorStateTransition.HasFixedDuration = Transition.GetHasFixedDuration();
animatorStateTransition.InterruptionSourceE = Transition.GetInterruptionSource();
animatorStateTransition.OrderedInterruption = Transition.OrderedInterruption;
animatorStateTransition.CanTransitionToSelf = Transition.CanTransitionToSelf;
return animatorStateTransition;
}
public static IAnimatorTransition CreateAnimatorTransition(ProcessedAssetCollection virtualFile, AssetDictionary<uint, Utf8String> TOS, SelectorTransitionConstant transition)
{
IAnimatorTransition animatorTransition = virtualFile.CreateAnimatorTransition();
animatorTransition.HideFlagsE = HideFlags.HideInHierarchy;
animatorTransition.Conditions.Capacity = transition.ConditionConstantArray.Count;
for (int i = 0; i < transition.ConditionConstantArray.Count; i++)
{
ConditionConstant conditionConstant = transition.ConditionConstantArray[i].Data;
if (conditionConstant.ConditionMode != (int)AnimatorConditionMode.ExitTime)
{
IAnimatorCondition condition = animatorTransition.Conditions.AddNew();
condition.ConditionMode = (int)conditionConstant.ConditionModeE;
condition.ConditionEvent = TOS[conditionConstant.EventID];
condition.EventTreshold = conditionConstant.EventThreshold;
}
}
return animatorTransition;
}
}
}