init
This commit is contained in:
@@ -0,0 +1,99 @@
|
||||
// Animancer // https://kybernetik.com.au/animancer // Copyright 2018-2024 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>
|
||||
/// <remarks>
|
||||
/// This system currently only supports 2 layers (Base + 1). Adding support for more would require additional
|
||||
/// <see cref="BoneWeights"/> for each additional layer and modifications to <see cref="WeightedMaskMixerJob"/>
|
||||
/// to iterate through each layer instead of just using the first two.
|
||||
/// </remarks>
|
||||
/// 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 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)
|
||||
{
|
||||
var graph = new AnimancerGraph();
|
||||
var layers = new WeightedMaskLayerList(graph, animator);
|
||||
graph.Layers = layers;
|
||||
return layers;
|
||||
}
|
||||
|
||||
/// <summary>Creates a new <see cref="WeightedMaskLayerList"/>.</summary>
|
||||
public WeightedMaskLayerList(AnimancerGraph graph, Animator animator)
|
||||
: base(graph)
|
||||
{
|
||||
graph.Layers = this;
|
||||
|
||||
Bones = animator.GetComponentsInChildren<Transform>();
|
||||
|
||||
var boneCount = Bones.Length - 1;// Ignore the root bone.
|
||||
|
||||
_Job = new WeightedMaskMixerJob()
|
||||
{
|
||||
boneTransforms = new(boneCount, Allocator.Persistent, NativeArrayOptions.UninitializedMemory),
|
||||
boneWeights = new(boneCount, Allocator.Persistent, NativeArrayOptions.UninitializedMemory),
|
||||
};
|
||||
|
||||
graph.Disposables.Add(this);
|
||||
|
||||
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.boneTransforms.Dispose();
|
||||
_Job.boneWeights.Dispose();
|
||||
}
|
||||
|
||||
/************************************************************************************************************************/
|
||||
}
|
||||
}
|
||||
|
||||
+13
@@ -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,225 @@
|
||||
// Animancer // https://kybernetik.com.au/animancer // Copyright 2018-2024 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;
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
/// <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);
|
||||
}
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
/// <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);
|
||||
_Animancer.InitializePlayable(Layers.Graph);
|
||||
|
||||
Indices = Definition.CalculateIndices(Layers);
|
||||
|
||||
SetWeights(0);
|
||||
}
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
/// <summary>Applies the weights of the specified group.</summary>
|
||||
public void SetWeights(int groupIndex)
|
||||
{
|
||||
Definition.AssertGroupIndex(groupIndex);
|
||||
|
||||
var boneWeights = Layers.BoneWeights;
|
||||
var definitionWeights = Definition.Weights;
|
||||
|
||||
var start = groupIndex * Indices.Length;
|
||||
|
||||
for (int i = 0; i < Indices.Length; i++)
|
||||
{
|
||||
var index = Indices[i];
|
||||
var weight = definitionWeights[start + i];
|
||||
boneWeights[index] = weight;
|
||||
}
|
||||
}
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
private Fade _Fade;
|
||||
|
||||
/// <summary>Fades the weights towards the specified group.</summary>
|
||||
public void FadeWeights(
|
||||
int groupIndex,
|
||||
float fadeDuration,
|
||||
Func<float, float> easing = null)
|
||||
{
|
||||
if (fadeDuration > 0)
|
||||
{
|
||||
_Fade ??= new();
|
||||
_Fade.Start(this, groupIndex, fadeDuration, easing);
|
||||
}
|
||||
else
|
||||
{
|
||||
SetWeights(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 float[] _OriginalWeights;
|
||||
private WeightedMaskLayers _Layers;
|
||||
private int _TargetWeightIndex;
|
||||
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 { get; set; }
|
||||
|
||||
/// <summary>The total amount of time this fade will take (in seconds).</summary>
|
||||
public float Duration { get; set; }
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
/// <summary>Initializes this fade and registers it to receive updates.</summary>
|
||||
public void Start(
|
||||
WeightedMaskLayers layers,
|
||||
int groupIndex,
|
||||
float duration,
|
||||
Func<float, float> easing = null)
|
||||
{
|
||||
layers.Definition.AssertGroupIndex(groupIndex);
|
||||
|
||||
_CurrentWeights = layers.Layers.BoneWeights;
|
||||
_Easing = easing;
|
||||
_Layers = layers;
|
||||
_TargetWeightIndex = layers.Definition.IndexOf(groupIndex, 0);
|
||||
Duration = duration;
|
||||
|
||||
var indices = _Layers.Indices;
|
||||
AnimancerUtilities.SetLength(ref _OriginalWeights, indices.Length);
|
||||
for (int i = 0; i < _OriginalWeights.Length; i++)
|
||||
{
|
||||
var index = 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 = indices[i];
|
||||
var from = _OriginalWeights[i];
|
||||
var to = targetWeights[_TargetWeightIndex + i];
|
||||
boneWeights[index] = Mathf.LerpUnclamped(from, to, 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 = indices[i];
|
||||
var to = targetWeights[_TargetWeightIndex + i];
|
||||
boneWeights[index] = to;
|
||||
}
|
||||
}
|
||||
|
||||
/************************************************************************************************************************/
|
||||
}
|
||||
|
||||
/************************************************************************************************************************/
|
||||
}
|
||||
}
|
||||
@@ -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:
|
||||
+322
@@ -0,0 +1,322 @@
|
||||
// Animancer // https://kybernetik.com.au/animancer // Copyright 2018-2024 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;
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
/// <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);
|
||||
else
|
||||
_Weights = 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;
|
||||
}
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
/// <inheritdoc/>
|
||||
public void CopyFrom(WeightedMaskLayersDefinition copyFrom, CloneContext context)
|
||||
{
|
||||
AnimancerUtilities.CopyExactArray(copyFrom._Transforms, ref _Transforms);
|
||||
AnimancerUtilities.CopyExactArray(copyFrom._Weights, ref _Weights);
|
||||
}
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
/// <summary>Does this definition contain valid data?</summary>
|
||||
public bool IsValid
|
||||
=> !_Transforms.IsNullOrEmpty()
|
||||
&& _Weights != null && _Weights.Length > _Transforms.Length;
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
/// <inheritdoc/>
|
||||
public bool OnValidate()
|
||||
=> ValidateArraySizes()
|
||||
|| RemoveMissingAndDuplicate();
|
||||
|
||||
/// <summary>Ensures that all the arrays have valid sizes.</summary>
|
||||
public bool ValidateArraySizes()
|
||||
{
|
||||
if (_Transforms.IsNullOrEmpty())
|
||||
{
|
||||
_Transforms = Array.Empty<Transform>();
|
||||
_Weights = Array.Empty<float>();
|
||||
return true;
|
||||
}
|
||||
|
||||
if (_Weights == null ||
|
||||
_Weights.Length < _Transforms.Length)
|
||||
{
|
||||
AnimancerUtilities.SetLength(ref _Weights, _Transforms.Length);
|
||||
return true;
|
||||
}
|
||||
|
||||
var expectedWeightCount = (int)Math.Ceiling(_Weights.Length / (double)_Transforms.Length);
|
||||
expectedWeightCount *= _Transforms.Length;
|
||||
return AnimancerUtilities.SetLength(ref _Weights, expectedWeightCount);
|
||||
}
|
||||
|
||||
/// <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()
|
||||
=> OnValidate();
|
||||
|
||||
/// <inheritdoc/>
|
||||
void ISerializationCallbackReceiver.OnAfterDeserialize()
|
||||
=> OnValidate();
|
||||
|
||||
/************************************************************************************************************************/
|
||||
#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)})";
|
||||
|
||||
/************************************************************************************************************************/
|
||||
#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);
|
||||
|
||||
/// <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());
|
||||
|
||||
/************************************************************************************************************************/
|
||||
#endregion
|
||||
/************************************************************************************************************************/
|
||||
}
|
||||
}
|
||||
|
||||
+13
@@ -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,87 @@
|
||||
// Animancer // https://kybernetik.com.au/animancer // Copyright 2018-2024 Kybernetik //
|
||||
|
||||
using Unity.Collections;
|
||||
using UnityEngine;
|
||||
using UnityEngine.Animations;
|
||||
|
||||
namespace Animancer
|
||||
{
|
||||
/// <summary>
|
||||
/// An <see cref="IAnimationJob"/> which mixes its inputs based on individual <see cref="boneWeights"/>.
|
||||
/// </summary>
|
||||
public struct WeightedMaskMixerJob : IAnimationJob
|
||||
{
|
||||
/************************************************************************************************************************/
|
||||
|
||||
/// <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. This array corresponds to the <see cref="boneTransforms"/>.</summary>
|
||||
public NativeArray<float> boneWeights;
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
/// <inheritdoc/>
|
||||
readonly void IAnimationJob.ProcessRootMotion(AnimationStream stream)
|
||||
{
|
||||
var stream0 = stream.GetInputStream(0);
|
||||
var stream1 = stream.GetInputStream(1);
|
||||
|
||||
if (stream1.isValid)
|
||||
{
|
||||
var layerWeight = stream.GetInputWeight(1);
|
||||
var velocity = Vector3.LerpUnclamped(stream0.velocity, stream1.velocity, layerWeight);
|
||||
var angularVelocity = Vector3.LerpUnclamped(stream0.angularVelocity, stream1.angularVelocity, layerWeight);
|
||||
stream.velocity = velocity;
|
||||
stream.angularVelocity = angularVelocity;
|
||||
}
|
||||
else
|
||||
{
|
||||
stream.velocity = stream0.velocity;
|
||||
stream.angularVelocity = stream0.angularVelocity;
|
||||
}
|
||||
}
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
/// <inheritdoc/>
|
||||
readonly void IAnimationJob.ProcessAnimation(AnimationStream stream)
|
||||
{
|
||||
var stream0 = stream.GetInputStream(0);
|
||||
var stream1 = stream.GetInputStream(1);
|
||||
|
||||
if (stream1.isValid)
|
||||
{
|
||||
var layerWeight = stream.GetInputWeight(1);
|
||||
var handleCount = boneTransforms.Length;
|
||||
for (var i = 0; i < handleCount; i++)
|
||||
{
|
||||
var handle = boneTransforms[i];
|
||||
var weight = layerWeight * boneWeights[i];
|
||||
|
||||
var position0 = handle.GetLocalPosition(stream0);
|
||||
var position1 = handle.GetLocalPosition(stream1);
|
||||
handle.SetLocalPosition(stream, Vector3.LerpUnclamped(position0, position1, weight));
|
||||
|
||||
var rotation0 = handle.GetLocalRotation(stream0);
|
||||
var rotation1 = handle.GetLocalRotation(stream1);
|
||||
handle.SetLocalRotation(stream, Quaternion.SlerpUnclamped(rotation0, rotation1, weight));
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
var handleCount = boneTransforms.Length;
|
||||
for (var i = 0; i < handleCount; i++)
|
||||
{
|
||||
var handle = boneTransforms[i];
|
||||
handle.SetLocalPosition(stream, handle.GetLocalPosition(stream0));
|
||||
handle.SetLocalRotation(stream, handle.GetLocalRotation(stream0));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/************************************************************************************************************************/
|
||||
}
|
||||
}
|
||||
|
||||
+13
@@ -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