chore: initial commit

This commit is contained in:
2026-05-08 11:04:00 +08:00
commit f55d2a57c3
6278 changed files with 866081 additions and 0 deletions

View File

@@ -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();
}
/************************************************************************************************************************/
}
}

View File

@@ -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:

View File

@@ -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;
}
/************************************************************************************************************************/
}
/************************************************************************************************************************/
}
}

View File

@@ -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:

View File

@@ -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
/************************************************************************************************************************/
}
}

View File

@@ -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:

View File

@@ -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));
}
}
}
/************************************************************************************************************************/
}
}

View File

@@ -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: