chore: initial commit
This commit is contained in:
@@ -0,0 +1,128 @@
|
||||
// Animancer // https://kybernetik.com.au/animancer // Copyright 2018-2026 Kybernetik //
|
||||
|
||||
using System;
|
||||
using Unity.Collections;
|
||||
using UnityEngine;
|
||||
using UnityEngine.Animations;
|
||||
|
||||
namespace Animancer
|
||||
{
|
||||
/// <summary>
|
||||
/// A replacement for the default <see cref="AnimationLayerMixerPlayable"/> which uses custom
|
||||
/// <see cref="BoneWeights"/> for each individual bone instead of just using an <see cref="AvatarMask"/>
|
||||
/// to include or exclude them entirely.
|
||||
/// </summary>
|
||||
/// https://kybernetik.com.au/animancer/api/Animancer/WeightedMaskLayerList
|
||||
public class WeightedMaskLayerList : AnimancerLayerList, IDisposable
|
||||
{
|
||||
/************************************************************************************************************************/
|
||||
|
||||
/// <summary>The objects being masked.</summary>
|
||||
public readonly Transform[] Bones;
|
||||
|
||||
/// <summary>The job data.</summary>
|
||||
private readonly WeightedMaskMixerJob _Job;
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
/// <summary>The root motion weight of each layer.</summary>
|
||||
public NativeArray<float> RootMotionWeights
|
||||
=> _Job.rootMotionWeights;
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
/// <summary>The number of objects being masked (excluding the root bone).</summary>
|
||||
public int BoneCount
|
||||
=> Bones.Length - 1;
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
/// <summary>The blend weight of each of the <see cref="Bones"/>.</summary>
|
||||
public NativeArray<float> BoneWeights
|
||||
=> _Job.boneWeights;
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
/// <summary>Returns the index of the value corresponding to the 'bone' in the <see cref="BoneWeights"/> array.</summary>
|
||||
public int IndexOf(Transform bone)
|
||||
=> Array.IndexOf(Bones, bone) - 1;// Index - 1 since the root is ignored.
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
/// <summary>Creates a new <see cref="AnimancerGraph"/> and <see cref="WeightedMaskLayerList"/>.</summary>
|
||||
/// <remarks>
|
||||
/// This method can't be a constructor because it would need to
|
||||
/// assign itself to the graph before being fully constructed.
|
||||
/// </remarks>
|
||||
public static WeightedMaskLayerList Create(Animator animator, int layerCount)
|
||||
{
|
||||
var graph = new AnimancerGraph();
|
||||
var layers = new WeightedMaskLayerList(graph, animator, layerCount);
|
||||
graph.Layers = layers;
|
||||
return layers;
|
||||
}
|
||||
|
||||
/// <summary>Creates a new <see cref="WeightedMaskLayerList"/>.</summary>
|
||||
public WeightedMaskLayerList(AnimancerGraph graph, Animator animator, int layerCount)
|
||||
: base(graph, layerCount)
|
||||
{
|
||||
if (layerCount < 2)
|
||||
throw new ArgumentOutOfRangeException(
|
||||
nameof(layerCount),
|
||||
"Layer count must be at least 2 (Base + 1).");
|
||||
|
||||
graph.Layers = this;
|
||||
|
||||
Bones = animator.GetComponentsInChildren<Transform>(true);
|
||||
|
||||
var boneCount = BoneCount;
|
||||
|
||||
_Job = new WeightedMaskMixerJob()
|
||||
{
|
||||
boneTransforms = new(boneCount, Allocator.Persistent, NativeArrayOptions.UninitializedMemory),
|
||||
boneWeights = new(boneCount * (layerCount - 1), Allocator.Persistent, NativeArrayOptions.ClearMemory),
|
||||
layerCount = layerCount,
|
||||
rootMotionWeights = new(layerCount, Allocator.Persistent, NativeArrayOptions.UninitializedMemory),
|
||||
};
|
||||
|
||||
graph.Disposables.Add(this);
|
||||
|
||||
for (var i = 0; i < layerCount; i++)
|
||||
_Job.rootMotionWeights[i] = 1;
|
||||
|
||||
for (var i = 0; i < boneCount; i++)
|
||||
{
|
||||
_Job.boneTransforms[i] = animator.BindStreamTransform(Bones[i + 1]);
|
||||
_Job.boneWeights[i] = 1;
|
||||
}
|
||||
|
||||
var playable = AnimationScriptPlayable.Create(graph, _Job, Capacity);
|
||||
playable.SetProcessInputs(false);
|
||||
Playable = playable;
|
||||
}
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
/// <inheritdoc/>
|
||||
void IDisposable.Dispose()
|
||||
{
|
||||
_Job.rootMotionWeights.Dispose();
|
||||
_Job.boneTransforms.Dispose();
|
||||
_Job.boneWeights.Dispose();
|
||||
}
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override AnimancerLayer Add()
|
||||
{
|
||||
if (Count >= Capacity)
|
||||
throw new InvalidOperationException(
|
||||
$"{nameof(WeightedMaskLayerList)} doesn't support dynamically changing its layer count.");
|
||||
|
||||
return base.Add();
|
||||
}
|
||||
|
||||
/************************************************************************************************************************/
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 48650aff8ff11364a9c5044d1722636f
|
||||
timeCreated: 1515060256
|
||||
licenseType: Store
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,264 @@
|
||||
// Animancer // https://kybernetik.com.au/animancer // Copyright 2018-2026 Kybernetik //
|
||||
|
||||
using System;
|
||||
using Unity.Collections;
|
||||
using UnityEngine;
|
||||
|
||||
namespace Animancer
|
||||
{
|
||||
/// <summary>
|
||||
/// Replaces the default <see cref="AnimancerLayerMixerList"/>
|
||||
/// with a <see cref="WeightedMaskLayerList"/>.
|
||||
/// </summary>
|
||||
/// https://kybernetik.com.au/animancer/api/Animancer/WeightedMaskLayers
|
||||
[AddComponentMenu(Strings.MenuPrefix + "Weighted Mask Layers")]
|
||||
[AnimancerHelpUrl(typeof(WeightedMaskLayers))]
|
||||
[DefaultExecutionOrder(-10000)]// Awake before anything else initializes Animancer.
|
||||
public class WeightedMaskLayers : MonoBehaviour
|
||||
{
|
||||
/************************************************************************************************************************/
|
||||
|
||||
[SerializeField] private AnimancerComponent _Animancer;
|
||||
|
||||
/// <summary>[<see cref="SerializeField"/>] The component to apply the layers to.</summary>
|
||||
public AnimancerComponent Animancer
|
||||
=> _Animancer;
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
[SerializeField] private WeightedMaskLayersDefinition _Definition;
|
||||
|
||||
/// <summary>[<see cref="SerializeField"/>]
|
||||
/// The definition of transforms to control and weights to apply to them.
|
||||
/// </summary>
|
||||
public ref WeightedMaskLayersDefinition Definition
|
||||
=> ref _Definition;
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
[SerializeField] private int _LayerCount = 2;
|
||||
|
||||
/// <summary>[<see cref="SerializeField"/>] The number of layers (minimum 2).</summary>
|
||||
public ref int LayerCount
|
||||
=> ref _LayerCount;
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
/// <summary>The layer list created at runtime and assigned to <see cref="AnimancerGraph.Layers"/>.</summary>
|
||||
public WeightedMaskLayerList Layers { get; protected set; }
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
/// <summary>The index of each of the <see cref="WeightedMaskLayersDefinition.Transforms"/>.</summary>
|
||||
public int[] Indices { get; protected set; }
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
/// <summary>Finds the <see cref="Animancer"/> reference if it was missing.</summary>
|
||||
protected virtual void OnValidate()
|
||||
{
|
||||
gameObject.GetComponentInParentOrChildren(ref _Animancer);
|
||||
|
||||
if (LayerCount < 2)
|
||||
LayerCount = 2;
|
||||
}
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
/// <summary>Initializes the <see cref="Layers"/> and applies the default group weights.</summary>
|
||||
protected virtual void Awake()
|
||||
{
|
||||
if (Definition == null ||
|
||||
!Definition.IsValid)
|
||||
return;
|
||||
|
||||
if (_Animancer == null)
|
||||
TryGetComponent(out _Animancer);
|
||||
|
||||
Layers = WeightedMaskLayerList.Create(_Animancer.Animator, LayerCount);
|
||||
_Animancer.InitializeGraph(Layers.Graph);
|
||||
|
||||
Indices = Definition.CalculateIndices(Layers);
|
||||
|
||||
for (int i = 1; i < LayerCount; i++)// Start at 1.
|
||||
SetWeights(i, 0);
|
||||
}
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
/// <summary>Applies the weights of the specified group to the specified layer.</summary>
|
||||
public void SetWeights(int layerIndex, int groupIndex)
|
||||
{
|
||||
Definition.AssertGroupIndex(groupIndex);
|
||||
|
||||
var boneWeights = Layers.BoneWeights;
|
||||
var definitionWeights = Definition.Weights;
|
||||
|
||||
var layerIndexOffset = (layerIndex - 1) * Layers.BoneCount;
|
||||
var groupDefinitionStart = groupIndex * Indices.Length;
|
||||
|
||||
for (int i = 0; i < Indices.Length; i++)
|
||||
{
|
||||
var index = Indices[i];
|
||||
if (index < 0)
|
||||
continue;
|
||||
|
||||
var weight = definitionWeights[groupDefinitionStart + i];
|
||||
boneWeights[layerIndexOffset + index] = weight;
|
||||
}
|
||||
|
||||
var rootMotionWeights = Layers.RootMotionWeights;
|
||||
rootMotionWeights[layerIndex] = Definition.RootMotionWeights[groupIndex];
|
||||
}
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
private Fade _Fade;
|
||||
|
||||
/// <summary>Fades the weights towards the specified group.</summary>
|
||||
public void FadeWeights(
|
||||
int layerIndex,
|
||||
int groupIndex,
|
||||
float fadeDuration,
|
||||
Func<float, float> easing = null)
|
||||
{
|
||||
if (fadeDuration > 0)
|
||||
{
|
||||
_Fade ??= new();
|
||||
_Fade.Start(this, layerIndex, groupIndex, fadeDuration, easing);
|
||||
}
|
||||
else
|
||||
{
|
||||
SetWeights(layerIndex, groupIndex);
|
||||
}
|
||||
}
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
/// <summary>An <see cref="IUpdatable"/> which fades <see cref="WeightedMaskLayers"/> over time.</summary>
|
||||
/// https://kybernetik.com.au/animancer/api/Animancer/Fade
|
||||
public class Fade : Updatable
|
||||
{
|
||||
/************************************************************************************************************************/
|
||||
|
||||
private NativeArray<float> _CurrentWeights;
|
||||
private NativeArray<float> _CurrentRootMotionWeights;
|
||||
private float[] _OriginalWeights;
|
||||
private WeightedMaskLayers _Layers;
|
||||
private int _LayerIndex;
|
||||
private int _LayerIndexOffset;
|
||||
private int _TargetWeightIndex;
|
||||
private float _OriginalRootMotionWeight;
|
||||
private float _TargetRootMotionWeight;
|
||||
private Func<float, float> _Easing;
|
||||
|
||||
/// <summary>The amount of time that has passed since the start of this fade (in seconds).</summary>
|
||||
public float ElapsedTime;
|
||||
|
||||
/// <summary>The total amount of time this fade will take (in seconds).</summary>
|
||||
public float Duration;
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
/// <summary>Initializes this fade and registers it to receive updates.</summary>
|
||||
public void Start(
|
||||
WeightedMaskLayers layers,
|
||||
int layerIndex,
|
||||
int groupIndex,
|
||||
float duration,
|
||||
Func<float, float> easing = null)
|
||||
{
|
||||
layers.Definition.AssertGroupIndex(groupIndex);
|
||||
|
||||
_CurrentWeights = layers.Layers.BoneWeights;
|
||||
_CurrentRootMotionWeights = layers.Layers.RootMotionWeights;
|
||||
_OriginalRootMotionWeight = _CurrentRootMotionWeights[layerIndex];
|
||||
_TargetRootMotionWeight = layers.Definition.RootMotionWeights[groupIndex];
|
||||
_Easing = easing;
|
||||
_Layers = layers;
|
||||
_LayerIndex = layerIndex;
|
||||
_TargetWeightIndex = layers.Definition.IndexOf(groupIndex, 0);
|
||||
Duration = duration;
|
||||
|
||||
_LayerIndexOffset = (layerIndex - 1) * layers.Layers.BoneCount;
|
||||
|
||||
var indices = _Layers.Indices;
|
||||
AnimancerUtilities.SetLength(ref _OriginalWeights, indices.Length);
|
||||
for (int i = 0; i < indices.Length; i++)
|
||||
{
|
||||
var index = _LayerIndexOffset + indices[i];
|
||||
_OriginalWeights[i] = _CurrentWeights[index];
|
||||
}
|
||||
|
||||
ElapsedTime = 0;
|
||||
|
||||
layers.Layers.Graph.RequirePreUpdate(this);
|
||||
}
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override void Update()
|
||||
{
|
||||
ElapsedTime += AnimancerGraph.DeltaTime;
|
||||
if (ElapsedTime < Duration)
|
||||
{
|
||||
ApplyFade(ElapsedTime / Duration);
|
||||
}
|
||||
else
|
||||
{
|
||||
ApplyTargetWeights();
|
||||
|
||||
AnimancerGraph.Current.CancelPreUpdate(this);
|
||||
}
|
||||
}
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
/// <summary>Recalculates the weights by interpolating based on `t`.</summary>
|
||||
private void ApplyFade(float t)
|
||||
{
|
||||
if (_Easing != null)
|
||||
t = _Easing(t);
|
||||
|
||||
var targetWeights = _Layers.Definition.Weights;
|
||||
var indices = _Layers.Indices;
|
||||
var boneWeights = _CurrentWeights;
|
||||
|
||||
for (int i = 0; i < indices.Length; i++)
|
||||
{
|
||||
var index = _LayerIndexOffset + indices[i];
|
||||
var from = _OriginalWeights[i];
|
||||
var to = targetWeights[_TargetWeightIndex + i];
|
||||
boneWeights[index] = Mathf.LerpUnclamped(from, to, t);
|
||||
}
|
||||
|
||||
_CurrentRootMotionWeights[_LayerIndex] = Mathf.LerpUnclamped(
|
||||
_OriginalRootMotionWeight,
|
||||
_TargetRootMotionWeight,
|
||||
t);
|
||||
}
|
||||
|
||||
/// <summary>Recalculates the target weights.</summary>
|
||||
private void ApplyTargetWeights()
|
||||
{
|
||||
var targetWeights = _Layers.Definition.Weights;
|
||||
var indices = _Layers.Indices;
|
||||
var boneWeights = _CurrentWeights;
|
||||
|
||||
for (int i = 0; i < indices.Length; i++)
|
||||
{
|
||||
var index = _LayerIndexOffset + indices[i];
|
||||
var to = targetWeights[_TargetWeightIndex + i];
|
||||
boneWeights[index] = to;
|
||||
}
|
||||
|
||||
_CurrentRootMotionWeights[_LayerIndex] = _TargetRootMotionWeight;
|
||||
}
|
||||
|
||||
/************************************************************************************************************************/
|
||||
}
|
||||
|
||||
/************************************************************************************************************************/
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 005fb639ab942fa46adf4e9a009744d2
|
||||
timeCreated: 1515060256
|
||||
licenseType: Store
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,386 @@
|
||||
// Animancer // https://kybernetik.com.au/animancer // Copyright 2018-2026 Kybernetik //
|
||||
|
||||
using System;
|
||||
using System.Runtime.CompilerServices;
|
||||
using UnityEngine;
|
||||
|
||||
namespace Animancer
|
||||
{
|
||||
/// <summary>Serializable data which defines how to control a <see cref="WeightedMaskLayerList"/>.</summary>
|
||||
/// https://kybernetik.com.au/animancer/api/Animancer/WeightedMaskLayersDefinition
|
||||
[Serializable]
|
||||
public class WeightedMaskLayersDefinition :
|
||||
ICopyable<WeightedMaskLayersDefinition>,
|
||||
IEquatable<WeightedMaskLayersDefinition>
|
||||
#if UNITY_EDITOR
|
||||
, ISerializationCallbackReceiver
|
||||
#endif
|
||||
{
|
||||
/************************************************************************************************************************/
|
||||
|
||||
/// <summary>The name of the serialized backing field of <see cref="Transforms"/>.</summary>
|
||||
public const string
|
||||
TransformsField = nameof(_Transforms);
|
||||
|
||||
[SerializeField]
|
||||
private Transform[] _Transforms;
|
||||
|
||||
/// <summary><see cref="Transform"/>s being controlled by this definition.</summary>
|
||||
public ref Transform[] Transforms
|
||||
=> ref _Transforms;
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
/// <summary>The name of the serialized backing field of <see cref="Weights"/>.</summary>
|
||||
public const string
|
||||
WeightsField = nameof(_Weights);
|
||||
|
||||
[SerializeField]
|
||||
private float[] _Weights;
|
||||
|
||||
/// <summary>Groups of weights which will be applied to the <see cref="Transforms"/>.</summary>
|
||||
/// <remarks>
|
||||
/// This is a flattened 2D array containing groups of target weights corresponding to the transforms.
|
||||
/// With n transforms, indices 0 to n-1 are Group 0, n to n*2-1 are Group 1, etc.
|
||||
/// </remarks>
|
||||
public ref float[] Weights
|
||||
=> ref _Weights;
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
[SerializeField]
|
||||
private float[] _RootMotionWeights;
|
||||
|
||||
/// <summary>Each group has a multiplier for the Root Motion output of any layer the group is applied to.</summary>
|
||||
public ref float[] RootMotionWeights
|
||||
=> ref _RootMotionWeights;
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
/// <summary>The number of weight groups in this definition.</summary>
|
||||
public int GroupCount
|
||||
{
|
||||
get => _Transforms == null || _Transforms.Length == 0 || _Weights == null
|
||||
? 0
|
||||
: _Weights.Length / _Transforms.Length;
|
||||
set
|
||||
{
|
||||
if (_Transforms != null && value > 0)
|
||||
{
|
||||
Array.Resize(ref _Weights, _Transforms.Length * value);
|
||||
Array.Resize(ref _RootMotionWeights, value);
|
||||
}
|
||||
else
|
||||
{
|
||||
_Weights = Array.Empty<float>();
|
||||
_RootMotionWeights = Array.Empty<float>();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
/// <summary>[Assert-Conditional] Asserts that the `groupIndex` is valid.</summary>
|
||||
[System.Diagnostics.Conditional(Strings.Assertions)]
|
||||
public void AssertGroupIndex(int groupIndex)
|
||||
{
|
||||
if ((uint)groupIndex >= (uint)GroupCount)
|
||||
throw new ArgumentOutOfRangeException(
|
||||
nameof(groupIndex),
|
||||
groupIndex,
|
||||
$"Must be 0 <= {nameof(groupIndex)} < Group Count ({GroupCount})");
|
||||
}
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
/// <summary>Calculates the index of each of the <see cref="Transforms"/>.</summary>
|
||||
public int[] CalculateIndices(WeightedMaskLayerList layers)
|
||||
{
|
||||
var indices = new int[_Transforms.Length];
|
||||
|
||||
for (int i = 0; i < _Transforms.Length; i++)
|
||||
{
|
||||
indices[i] = layers.IndexOf(_Transforms[i]);
|
||||
#if UNITY_ASSERTIONS
|
||||
if (indices[i] < 0)
|
||||
Debug.LogWarning(
|
||||
$"Unable to find index of {_Transforms[i]} in {nameof(WeightedMaskLayerList)}",
|
||||
_Transforms[i]);
|
||||
#endif
|
||||
}
|
||||
|
||||
return indices;
|
||||
}
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
/// <summary>
|
||||
/// Adds the `transform` at the specified `index`
|
||||
/// along with any associated <see cref="_Weights"/>.
|
||||
/// </summary>
|
||||
public void AddTransform(Transform transform)
|
||||
{
|
||||
var index = _Transforms.Length;
|
||||
|
||||
AnimancerUtilities.InsertAt(ref _Transforms, index, transform);
|
||||
|
||||
if (_Transforms.Length == 1 && _Weights.IsNullOrEmpty())
|
||||
{
|
||||
_Weights = new float[1];
|
||||
return;
|
||||
}
|
||||
|
||||
while (index <= _Weights.Length)
|
||||
{
|
||||
AnimancerUtilities.InsertAt(ref _Weights, index, 0);
|
||||
|
||||
index += _Transforms.Length;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Removes the `index` from the <see cref="_Transforms"/>
|
||||
/// along with any associated <see cref="_Weights"/>.
|
||||
/// </summary>
|
||||
public void RemoveTransform(int index)
|
||||
{
|
||||
AnimancerUtilities.RemoveAt(ref _Transforms, index);
|
||||
|
||||
while (index < _Weights.Length)
|
||||
{
|
||||
AnimancerUtilities.RemoveAt(ref _Weights, index);
|
||||
|
||||
index += _Transforms.Length;
|
||||
}
|
||||
}
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
/// <summary>Calculates the index in the <see cref="Weights"/> corresponding to the specified values.</summary>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public int IndexOfGroup(int groupIndex)
|
||||
=> groupIndex * _Transforms.Length;
|
||||
|
||||
/// <summary>Calculates the index in the <see cref="Weights"/> corresponding to the specified values.</summary>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public int IndexOf(int groupIndex, int transformIndex)
|
||||
=> groupIndex * _Transforms.Length + transformIndex;
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
/// <summary>Gets the specified weight.</summary>
|
||||
/// <remarks>Returns <see cref="float.NaN"/> if the indices are outside the <see cref="Weights"/>.</remarks>
|
||||
public float GetWeight(int groupIndex, int transformIndex)
|
||||
{
|
||||
if (Weights == null)
|
||||
return float.NaN;
|
||||
|
||||
var index = IndexOf(groupIndex, transformIndex);
|
||||
return (uint)index < (uint)Weights.Length
|
||||
? Weights[index]
|
||||
: float.NaN;
|
||||
}
|
||||
|
||||
/// <summary>Sets the specified weight.</summary>
|
||||
/// <remarks>Returns false if the indices are outside the <see cref="Weights"/>.</remarks>
|
||||
public bool SetWeight(int groupIndex, int transformIndex, float value)
|
||||
{
|
||||
if (Weights == null)
|
||||
return false;
|
||||
|
||||
var index = IndexOf(groupIndex, transformIndex);
|
||||
if ((uint)index < (uint)Weights.Length)
|
||||
{
|
||||
Weights[index] = value;
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
/// <summary>Gets the specified RootMotion weight.</summary>
|
||||
/// <remarks>Returns <see cref="float.NaN"/> if the indices are outside the <see cref="RootMotionWeights"/>.</remarks>
|
||||
public float GetRmWeight(int groupIndex)
|
||||
{
|
||||
if (RootMotionWeights == null)
|
||||
return float.NaN;
|
||||
|
||||
return (uint)groupIndex < (uint)RootMotionWeights.Length
|
||||
? RootMotionWeights[groupIndex]
|
||||
: float.NaN;
|
||||
}
|
||||
|
||||
/// <summary>Sets the specified RootMotion weight.</summary>
|
||||
/// <remarks>Returns false if the indices are outside the <see cref="RootMotionWeights"/>.</remarks>
|
||||
public bool SetRmWeight(int groupIndex, float value)
|
||||
{
|
||||
if (RootMotionWeights == null)
|
||||
return false;
|
||||
|
||||
if ((uint)groupIndex < (uint)RootMotionWeights.Length)
|
||||
{
|
||||
RootMotionWeights[groupIndex] = value;
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
/// <inheritdoc/>
|
||||
public void CopyFrom(WeightedMaskLayersDefinition copyFrom, CloneContext context)
|
||||
{
|
||||
AnimancerUtilities.CopyExactArray(copyFrom._Transforms, ref _Transforms);
|
||||
AnimancerUtilities.CopyExactArray(copyFrom._Weights, ref _Weights);
|
||||
AnimancerUtilities.CopyExactArray(copyFrom._RootMotionWeights, ref _RootMotionWeights);
|
||||
}
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
/// <summary>Does this definition contain valid data?</summary>
|
||||
public bool IsValid
|
||||
=> !_Transforms.IsNullOrEmpty()
|
||||
&& _Weights != null && _Weights.Length >= _Transforms.Length
|
||||
&& _RootMotionWeights != null && _RootMotionWeights.Length == GroupCount;
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
/// <summary>Ensures that the data in this definition is value.</summary>
|
||||
public void Validate()
|
||||
{
|
||||
ValidateArraySizes();
|
||||
RemoveMissingAndDuplicate();
|
||||
}
|
||||
|
||||
/// <summary>Ensures that all the arrays have valid sizes.</summary>
|
||||
public void ValidateArraySizes()
|
||||
{
|
||||
if (_Transforms.IsNullOrEmpty())
|
||||
{
|
||||
_Transforms = Array.Empty<Transform>();
|
||||
_Weights = Array.Empty<float>();
|
||||
_RootMotionWeights = Array.Empty<float>();
|
||||
}
|
||||
|
||||
if (_Weights == null ||
|
||||
_Weights.Length < _Transforms.Length)
|
||||
{
|
||||
AnimancerUtilities.SetLength(ref _Weights, _Transforms.Length);
|
||||
}
|
||||
else
|
||||
{
|
||||
var expectedWeightCount = (int)Math.Ceiling(_Weights.Length / (double)_Transforms.Length);
|
||||
expectedWeightCount *= _Transforms.Length;
|
||||
AnimancerUtilities.SetLength(ref _Weights, expectedWeightCount);
|
||||
}
|
||||
|
||||
var rootMotionWeightCount = _RootMotionWeights == null
|
||||
? 0
|
||||
: _RootMotionWeights.Length;
|
||||
var groupCount = GroupCount;
|
||||
if (rootMotionWeightCount != groupCount)
|
||||
{
|
||||
AnimancerUtilities.SetLength(ref _RootMotionWeights, groupCount);
|
||||
for (int i = rootMotionWeightCount; i < groupCount; i++)
|
||||
_RootMotionWeights[i] = 1;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>Removes any missing or identical <see cref="_Transforms"/>.</summary>
|
||||
public bool RemoveMissingAndDuplicate()
|
||||
{
|
||||
var removedAny = false;
|
||||
|
||||
for (int i = 0; i < _Transforms.Length; i++)
|
||||
{
|
||||
var transform = _Transforms[i];
|
||||
if (transform == null)
|
||||
{
|
||||
RemoveTransform(i);
|
||||
removedAny = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
var nextIndex = i + 1;
|
||||
|
||||
RemoveDuplicates:
|
||||
|
||||
nextIndex = Array.IndexOf(_Transforms, transform, nextIndex);
|
||||
if (nextIndex > i)
|
||||
{
|
||||
RemoveTransform(nextIndex);
|
||||
removedAny = true;
|
||||
goto RemoveDuplicates;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return removedAny;
|
||||
}
|
||||
|
||||
/************************************************************************************************************************/
|
||||
#if UNITY_EDITOR
|
||||
/************************************************************************************************************************/
|
||||
|
||||
/// <inheritdoc/>
|
||||
void ISerializationCallbackReceiver.OnBeforeSerialize()
|
||||
=> Validate();
|
||||
|
||||
/// <inheritdoc/>
|
||||
void ISerializationCallbackReceiver.OnAfterDeserialize()
|
||||
=> Validate();
|
||||
|
||||
/************************************************************************************************************************/
|
||||
#endif
|
||||
/************************************************************************************************************************/
|
||||
|
||||
/// <summary>Returns a summary of this definition.</summary>
|
||||
public override string ToString()
|
||||
=> $"{nameof(WeightedMaskLayersDefinition)}(" +
|
||||
$"{nameof(Transforms)}={(Transforms != null ? Transforms.Length : 0)}, " +
|
||||
$"{nameof(Weights)}={(Weights != null ? Weights.Length : 0)}, " +
|
||||
$"{nameof(RootMotionWeights)}={(RootMotionWeights != null ? RootMotionWeights.Length : 0)})";
|
||||
|
||||
/************************************************************************************************************************/
|
||||
#region Equality
|
||||
/************************************************************************************************************************/
|
||||
|
||||
/// <summary>Are all fields in this object equal to the equivalent in `obj`?</summary>
|
||||
public override bool Equals(object obj)
|
||||
=> Equals(obj as WeightedMaskLayersDefinition);
|
||||
|
||||
/// <summary>Are all fields in this object equal to the equivalent fields in `other`?</summary>
|
||||
public bool Equals(WeightedMaskLayersDefinition other)
|
||||
=> other != null
|
||||
&& AnimancerUtilities.ContentsAreEqual(_Transforms, other._Transforms)
|
||||
&& AnimancerUtilities.ContentsAreEqual(_Weights, other._Weights)
|
||||
&& AnimancerUtilities.ContentsAreEqual(_RootMotionWeights, other._RootMotionWeights);
|
||||
|
||||
/// <summary>Are all fields in `a` equal to the equivalent fields in `b`?</summary>
|
||||
public static bool operator ==(WeightedMaskLayersDefinition a, WeightedMaskLayersDefinition b)
|
||||
=> a is null
|
||||
? b is null
|
||||
: a.Equals(b);
|
||||
|
||||
/// <summary>Are any fields in `a` not equal to the equivalent fields in `b`?</summary>
|
||||
public static bool operator !=(WeightedMaskLayersDefinition a, WeightedMaskLayersDefinition b)
|
||||
=> !(a == b);
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
/// <summary>Returns a hash code based on the values of this object's fields.</summary>
|
||||
public override int GetHashCode()
|
||||
=> AnimancerUtilities.Hash(-871379578,
|
||||
_Transforms.SafeGetHashCode(),
|
||||
_Weights.SafeGetHashCode(),
|
||||
_RootMotionWeights.SafeGetHashCode());
|
||||
|
||||
/************************************************************************************************************************/
|
||||
#endregion
|
||||
/************************************************************************************************************************/
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,13 @@
|
||||
fileFormatVersion: 2
|
||||
guid: c9269482d6edd6349972960b7bc4b82f
|
||||
timeCreated: 1515060256
|
||||
licenseType: Store
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,199 @@
|
||||
// Animancer // https://kybernetik.com.au/animancer // Copyright 2018-2026 Kybernetik //
|
||||
|
||||
using Unity.Collections;
|
||||
using UnityEngine;
|
||||
using UnityEngine.Animations;
|
||||
|
||||
#if UNITY_BURST
|
||||
using Unity.Burst;
|
||||
#endif
|
||||
|
||||
namespace Animancer
|
||||
{
|
||||
/// <summary>
|
||||
/// An <see cref="IAnimationJob"/> which mixes its inputs based on individual <see cref="boneWeights"/>.
|
||||
/// </summary>
|
||||
#if UNITY_BURST
|
||||
[BurstCompile(FloatPrecision.Low, FloatMode.Fast
|
||||
#if UNITY_BURST_1_6_0
|
||||
, OptimizeFor = OptimizeFor.Performance
|
||||
#endif
|
||||
)]
|
||||
#endif
|
||||
public struct WeightedMaskMixerJob : IAnimationJob
|
||||
{
|
||||
/************************************************************************************************************************/
|
||||
|
||||
/// <summary>The number of layers being mixed.</summary>
|
||||
public int layerCount;
|
||||
|
||||
/// <summary>The root motion weight of each layer.</summary>
|
||||
public NativeArray<float> rootMotionWeights;
|
||||
|
||||
/// <summary>The handles for each bone being mixed.</summary>
|
||||
/// <remarks>
|
||||
/// All animated bones must be included,
|
||||
/// even if their individual weight isn't modified.
|
||||
/// </remarks>
|
||||
public NativeArray<TransformStreamHandle> boneTransforms;
|
||||
|
||||
/// <summary>The blend weight of each bone.</summary>
|
||||
/// <remarks>
|
||||
/// This array corresponds to the <see cref="boneTransforms"/>,
|
||||
/// repeated for each layer after the first and excluding the base layer.
|
||||
/// For example, if there are 3 layers and 10 bones, then this array will have 20 elements
|
||||
/// with the first 10 being for Layer 1 and the next 10 being for Layer 2.
|
||||
/// </remarks>
|
||||
public NativeArray<float> boneWeights;
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
/// <inheritdoc/>
|
||||
readonly void IAnimationJob.ProcessRootMotion(AnimationStream output)
|
||||
{
|
||||
var input = output.GetInputStream(0);
|
||||
|
||||
var velocity = input.velocity;
|
||||
var angularVelocity = input.angularVelocity;
|
||||
|
||||
var hasRootMotionWeights = rootMotionWeights.IsCreated;
|
||||
if (hasRootMotionWeights)
|
||||
{
|
||||
var baseLayerWeight = rootMotionWeights[0];
|
||||
velocity *= baseLayerWeight;
|
||||
angularVelocity *= baseLayerWeight;
|
||||
}
|
||||
|
||||
for (int i = 1; i < layerCount; i++)// Start at 1.
|
||||
{
|
||||
input = output.GetInputStream(i);
|
||||
if (!input.isValid)
|
||||
continue;
|
||||
|
||||
var layerWeight = output.GetInputWeight(i);
|
||||
if (hasRootMotionWeights)
|
||||
layerWeight *= rootMotionWeights[i];
|
||||
velocity = Vector3.LerpUnclamped(
|
||||
velocity,
|
||||
input.velocity,
|
||||
layerWeight);
|
||||
angularVelocity = Vector3.LerpUnclamped(
|
||||
angularVelocity,
|
||||
input.angularVelocity,
|
||||
layerWeight);
|
||||
}
|
||||
|
||||
output.velocity = velocity;
|
||||
output.angularVelocity = angularVelocity;
|
||||
}
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
/// <inheritdoc/>
|
||||
readonly void IAnimationJob.ProcessAnimation(AnimationStream output)
|
||||
{
|
||||
if (layerCount == 2)
|
||||
{
|
||||
ProcessAnimation2Layers(output);
|
||||
return;
|
||||
}
|
||||
|
||||
// Blending more than 2 layers is less efficient because we need to use these temporary arrays.
|
||||
|
||||
var transformCount = boneTransforms.Length;
|
||||
var localPositions = new NativeArray<Vector3>(transformCount, Allocator.Temp, NativeArrayOptions.UninitializedMemory);
|
||||
var localRotations = new NativeArray<Quaternion>(transformCount, Allocator.Temp, NativeArrayOptions.UninitializedMemory);
|
||||
|
||||
var input = output.GetInputStream(0);
|
||||
|
||||
for (var i = 0; i < transformCount; i++)
|
||||
{
|
||||
var transform = boneTransforms[i];
|
||||
localPositions[i] = transform.GetLocalPosition(input);
|
||||
localRotations[i] = transform.GetLocalRotation(input);
|
||||
}
|
||||
|
||||
for (int iLayer = 1; iLayer < layerCount; iLayer++)// Start at 1.
|
||||
{
|
||||
input = output.GetInputStream(iLayer);
|
||||
if (!input.isValid)
|
||||
break;
|
||||
|
||||
var layerWeight = output.GetInputWeight(iLayer);
|
||||
if (layerWeight == 0)
|
||||
continue;
|
||||
|
||||
var weightOffset = (iLayer - 1) * transformCount;
|
||||
|
||||
for (var iTransform = 0; iTransform < transformCount; iTransform++)
|
||||
{
|
||||
var transform = boneTransforms[iTransform];
|
||||
var weight = layerWeight * boneWeights[weightOffset + iTransform];
|
||||
if (weight == 0)
|
||||
continue;
|
||||
|
||||
localPositions[iTransform] = Vector3.LerpUnclamped(
|
||||
localPositions[iTransform],
|
||||
transform.GetLocalPosition(input),
|
||||
weight);
|
||||
|
||||
localRotations[iTransform] = Quaternion.SlerpUnclamped(
|
||||
localRotations[iTransform],
|
||||
transform.GetLocalRotation(input),
|
||||
weight);
|
||||
}
|
||||
}
|
||||
|
||||
for (var i = 0; i < transformCount; i++)
|
||||
{
|
||||
var transform = boneTransforms[i];
|
||||
transform.SetLocalPosition(output, localPositions[i]);
|
||||
transform.SetLocalRotation(output, localRotations[i]);
|
||||
}
|
||||
|
||||
localPositions.Dispose();
|
||||
localRotations.Dispose();
|
||||
}
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
/// <summary>Blends the layers in an optimized way when there are only 2.</summary>
|
||||
private readonly void ProcessAnimation2Layers(AnimationStream output)
|
||||
{
|
||||
var input0 = output.GetInputStream(0);
|
||||
var input1 = output.GetInputStream(1);
|
||||
|
||||
if (input1.isValid)
|
||||
{
|
||||
var layerWeight = output.GetInputWeight(1);
|
||||
var transformCount = boneTransforms.Length;
|
||||
for (var i = 0; i < transformCount; i++)
|
||||
{
|
||||
var transform = boneTransforms[i];
|
||||
var weight = layerWeight * boneWeights[i];
|
||||
|
||||
var position0 = transform.GetLocalPosition(input0);
|
||||
var position1 = transform.GetLocalPosition(input1);
|
||||
transform.SetLocalPosition(output, Vector3.LerpUnclamped(position0, position1, weight));
|
||||
|
||||
var rotation0 = transform.GetLocalRotation(input0);
|
||||
var rotation1 = transform.GetLocalRotation(input1);
|
||||
transform.SetLocalRotation(output, Quaternion.SlerpUnclamped(rotation0, rotation1, weight));
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
var transformCount = boneTransforms.Length;
|
||||
for (var i = 0; i < transformCount; i++)
|
||||
{
|
||||
var transform = boneTransforms[i];
|
||||
transform.SetLocalPosition(output, transform.GetLocalPosition(input0));
|
||||
transform.SetLocalRotation(output, transform.GetLocalRotation(input0));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/************************************************************************************************************************/
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,13 @@
|
||||
fileFormatVersion: 2
|
||||
guid: bf71afa335d45144c8e1513206b7c473
|
||||
timeCreated: 1515060256
|
||||
licenseType: Store
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
Reference in New Issue
Block a user