Netcode Bootstrap
This commit is contained in:
+328
@@ -0,0 +1,328 @@
|
||||
using System;
|
||||
using Rukhanka.Toolbox;
|
||||
using Unity.Collections;
|
||||
using Unity.Collections.LowLevel.Unsafe;
|
||||
using Unity.Entities;
|
||||
using Unity.Mathematics;
|
||||
|
||||
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
namespace Rukhanka
|
||||
{
|
||||
public static partial class ScriptedAnimator
|
||||
{
|
||||
/// <summary>
|
||||
/// Clear animation to process component buffer, to clear current animation state. Usually used once per-frame.
|
||||
/// </summary>
|
||||
/// <param name="atps">Animation to process component buffer of animated entity.</param>
|
||||
public static void ResetAnimationState(ref DynamicBuffer<AnimationToProcessComponent> atps)
|
||||
{
|
||||
atps.Clear();
|
||||
}
|
||||
|
||||
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
/// <summary>
|
||||
/// Instruct Rukhanka to play given animation at specified point of the time. You must call this function every
|
||||
/// frame with progressively advancing normalized time value.
|
||||
/// </summary>
|
||||
/// <param name="atps">Animation to process component buffer used to fill animations to. Use buffer from animated entity.</param>
|
||||
/// <param name="clip">Clip to play.</param>
|
||||
/// <param name="normalizedTime">Normalized play time (0 - beginning of the animation, 1 - end of the animation).</param>
|
||||
/// <param name="weight">Weight of the animation.</param>
|
||||
/// <param name="avatarMask">Optional avatar mask to use with state animations.</param>
|
||||
public static void PlayAnimation
|
||||
(
|
||||
ref DynamicBuffer<AnimationToProcessComponent> atps,
|
||||
BlobAssetReference<AnimationClipBlob> clip,
|
||||
float normalizedTime,
|
||||
float weight = 1,
|
||||
BlobAssetReference<AvatarMaskBlob> avatarMask = default
|
||||
)
|
||||
{
|
||||
var atp = new AnimationToProcessComponent()
|
||||
{
|
||||
animation = clip,
|
||||
time = normalizedTime,
|
||||
weight = weight,
|
||||
avatarMask = avatarMask,
|
||||
blendMode = AnimationBlendingMode.Override,
|
||||
layerIndex = 0,
|
||||
layerWeight = 1,
|
||||
motionId = (uint)atps.Length
|
||||
};
|
||||
atps.Add(atp);
|
||||
}
|
||||
|
||||
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
/// <summary>
|
||||
/// Instruct Rukhanka to blend given two animations at specified point of the time. You must call this function every
|
||||
/// frame with progressively advancing normalized time value.
|
||||
/// </summary>
|
||||
/// <param name="atps">Animation to process component buffer used to fill animations to. Use buffer from animated entity.</param>
|
||||
/// <param name="clip0">First clip of blending operation.</param>
|
||||
/// <param name="clip1">Second clip of blending operation.</param>
|
||||
/// <param name="normalizedTime">Normalized time (0 - beginning of the state, 1 - end of the state) of state to play.</param>
|
||||
/// <param name="blendFactor">Linear interpolation factor. Interpolate from clip0 to clip1 with range [0..1].</param>
|
||||
/// <param name="weight">Weight of the blending operation.</param>
|
||||
/// <param name="avatarMask">Optional avatar mask to use with state animations.</param>
|
||||
public static void BlendTwoAnimations
|
||||
(
|
||||
ref DynamicBuffer<AnimationToProcessComponent> atps,
|
||||
BlobAssetReference<AnimationClipBlob> clip0,
|
||||
BlobAssetReference<AnimationClipBlob> clip1,
|
||||
float normalizedTime,
|
||||
float blendFactor,
|
||||
float weight = 1,
|
||||
BlobAssetReference<AvatarMaskBlob> avatarMask = default
|
||||
)
|
||||
{
|
||||
var atp = new AnimationToProcessComponent()
|
||||
{
|
||||
animation = clip0,
|
||||
time = normalizedTime,
|
||||
weight = (1 - blendFactor) * weight,
|
||||
avatarMask = avatarMask,
|
||||
blendMode = AnimationBlendingMode.Override,
|
||||
layerIndex = 0,
|
||||
layerWeight = 1,
|
||||
motionId = (uint)atps.Length
|
||||
};
|
||||
atps.Add(atp);
|
||||
|
||||
atp.animation = clip1;
|
||||
atp.weight = blendFactor * weight;
|
||||
atp.motionId = (uint)atps.Length;
|
||||
atps.Add(atp);
|
||||
}
|
||||
|
||||
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
/// <summary>
|
||||
/// Instruct Rukhanka to play given blend tree at specified point of the time. You must call this function every
|
||||
/// frame with progressively advancing normalized time value
|
||||
/// </summary>
|
||||
/// <param name="atps">Animation to process component buffer used to fill animations to. Use buffer from animated entity.</param>
|
||||
/// <param name="blendTreeClips">Array of all animation clips that make up the blend tree.</param>
|
||||
/// <param name="blendTreeThresholds">1D blend tree coordinate positions of input clips. Size must match blendTreeClips length.</param>
|
||||
/// <param name="blendTreeParameterValue">Current blend tree coordinate position.</param>
|
||||
/// <param name="normalizedTime">Normalized time (0 - beginning of the state, 1 - end of the state) of state to play.</param>
|
||||
/// <param name="blendTreeWeight">Weight of the entire blend tree.</param>
|
||||
/// <param name="avatarMask">Optional avatar mask to use with state animations.</param>
|
||||
public static unsafe void PlayBlendTree1D
|
||||
(
|
||||
ref DynamicBuffer<AnimationToProcessComponent> atps,
|
||||
in NativeArray<BlobAssetReference<AnimationClipBlob>> blendTreeClips,
|
||||
in NativeArray<float> blendTreeThresholds,
|
||||
float blendTreeParameterValue,
|
||||
float normalizedTime,
|
||||
float blendTreeWeight = 1,
|
||||
BlobAssetReference<AvatarMaskBlob> avatarMask = default
|
||||
)
|
||||
{
|
||||
BurstAssert.IsTrue(blendTreeClips.Length == blendTreeThresholds.Length, "Blend tree clips count must match thresholds array length");
|
||||
var bttSpan = new ReadOnlySpan<float>(blendTreeThresholds.GetUnsafeReadOnlyPtr(), blendTreeThresholds.Length);
|
||||
|
||||
var motions = ComputeBlendTree1D(bttSpan, blendTreeParameterValue);
|
||||
|
||||
for (var i = 0; i < motions.Length; ++i)
|
||||
{
|
||||
var m = motions[i];
|
||||
var atp = new AnimationToProcessComponent()
|
||||
{
|
||||
animation = blendTreeClips[m.motionIndex],
|
||||
time = normalizedTime,
|
||||
weight = m.weight * blendTreeWeight,
|
||||
avatarMask = avatarMask,
|
||||
blendMode = AnimationBlendingMode.Override,
|
||||
layerIndex = 0,
|
||||
layerWeight = 1,
|
||||
motionId = (uint)atps.Length
|
||||
};
|
||||
|
||||
atps.Add(atp);
|
||||
}
|
||||
}
|
||||
|
||||
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
public struct BlendTree2DMotionElement
|
||||
{
|
||||
// Element 2D coordinates
|
||||
public float2 pos;
|
||||
// Motion index of given element
|
||||
public int motionIndex;
|
||||
}
|
||||
|
||||
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
/// <summary>
|
||||
/// Instruct Rukhanka to play given blend tree at specified point of the time. You must call this function every
|
||||
/// frame with progressively advancing normalized time value
|
||||
/// </summary>
|
||||
/// <param name="atps">Animation to process component buffer used to fill animations to. Use buffer from animated entity.</param>
|
||||
/// <param name="blendTreeClips">Array of all animation clips that make up the blend tree.</param>
|
||||
/// <param name="blendTreePositions">2D blend tree coordinate positions of input clips.</param>
|
||||
/// <param name="blendTreeParameterValue">2D blend coordinate position of current blend tree state.</param>
|
||||
/// <param name="normalizedTime">Normalized time (0 - beginning of the state, 1 - end of the state) of state to play.</param>
|
||||
/// <param name="blendTreeType">Blend tree type.</param>
|
||||
/// <param name="blendTreeWeight">Weight of the entire blend tree.</param>
|
||||
/// <param name="avatarMask">Optional avatar mask to use with state animations.</param>
|
||||
public static unsafe void PlayBlendTree2D
|
||||
(
|
||||
ref DynamicBuffer<AnimationToProcessComponent> atps,
|
||||
in NativeArray<BlobAssetReference<AnimationClipBlob>> blendTreeClips,
|
||||
in NativeArray<BlendTree2DMotionElement> blendTreePositions,
|
||||
float2 blendTreeParameterValue,
|
||||
float normalizedTime,
|
||||
MotionBlob.Type blendTreeType,
|
||||
float blendTreeWeight = 1,
|
||||
BlobAssetReference<AvatarMaskBlob> avatarMask = default
|
||||
)
|
||||
{
|
||||
BurstAssert.IsTrue(blendTreeClips.Length == blendTreePositions.Length, "Blend tree clips and positions array lengths must match.");
|
||||
|
||||
var bttSpan = new ReadOnlySpan<BlendTree2DMotionElement>(blendTreePositions.GetUnsafeReadOnlyPtr(), blendTreePositions.Length);
|
||||
|
||||
var motions = blendTreeType switch
|
||||
{
|
||||
MotionBlob.Type.BlendTree2DSimpleDirectional => ComputeBlendTree2DSimpleDirectional(bttSpan, blendTreeParameterValue),
|
||||
MotionBlob.Type.BlendTree2DFreeformCartesian => ComputeBlendTree2DFreeformCartesian(bttSpan, blendTreeParameterValue),
|
||||
MotionBlob.Type.BlendTree2DFreeformDirectional => ComputeBlendTree2DFreeformDirectional(bttSpan, blendTreeParameterValue),
|
||||
_ => default
|
||||
};
|
||||
|
||||
for (var i = 0; i < motions.Length; ++i)
|
||||
{
|
||||
var m = motions[i];
|
||||
var atp = new AnimationToProcessComponent()
|
||||
{
|
||||
animation = blendTreeClips[m.motionIndex],
|
||||
time = normalizedTime,
|
||||
weight = m.weight * blendTreeWeight,
|
||||
avatarMask = avatarMask,
|
||||
blendMode = AnimationBlendingMode.Override,
|
||||
layerIndex = 0,
|
||||
layerWeight = 1,
|
||||
motionId = (uint)atps.Length
|
||||
};
|
||||
|
||||
atps.Add(atp);
|
||||
}
|
||||
}
|
||||
|
||||
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
/// <summary>
|
||||
/// Get index of the state in layer with given name hash.
|
||||
/// </summary>
|
||||
/// <param name="cb">Controller layer blob</param>
|
||||
/// <param name="layerIndex">Later index state belongs to</param>
|
||||
/// <param name="stateHash">State name hash. Use "StateName".CalculateHash32() to obtain one.</param>
|
||||
/// <returns></returns>
|
||||
public static int GetStateIndexInControllerLayer(BlobAssetReference<ControllerBlob> cb, int layerIndex, uint stateHash)
|
||||
{
|
||||
ref var layerBlob = ref cb.Value.layers[layerIndex];
|
||||
for (var i = 0; i < layerBlob.states.Length; ++i)
|
||||
{
|
||||
ref var stateBlob = ref layerBlob.states[i];
|
||||
if (stateBlob.hash == stateHash)
|
||||
return i;
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
/// <summary>
|
||||
/// Instruct Rukhanka to play given animator state at specified point of the time. You must call this function every
|
||||
/// frame with progressively advancing normalized time value.
|
||||
/// </summary>
|
||||
/// <param name="atps">Animation to process component buffer used to fill animations from requested state. Use buffer from animated entity.</param>
|
||||
/// <param name="animatorControllerParameters">Animator runtime parameters buffer of given animator controller. Use buffer from animated entity.</param>
|
||||
/// <param name="controllerBlob">Animator controller blob asset. Blob asset can be obtained from AnimatorControllerLayerComponent of animated entity.</param>
|
||||
/// <param name="animationsBlob">Animations blob asset to play with given controller. Usually can be obtained from AnimatorControllerLayerComponent of animated entity.</param>
|
||||
/// <param name="blobDatabase">Blob database singleton to query requested animations from animations blob.</param>
|
||||
/// <param name="layerIndex">Layer index of controller to play.</param>
|
||||
/// <param name="stateIndex">State index of layer to play. Index can be obtained using ScriptedAnimator.GetStateIndexInControllerLayer function.</param>
|
||||
/// <param name="normalizedTime">Normalized time (0 - beginning of the state, 1 - end of the state) of state to play.</param>
|
||||
/// <param name="weight">Weight of current state.</param>
|
||||
/// <param name="avatarMask">Optional avatar mask to use with state animations.</param>
|
||||
public static void PlayAnimatorState
|
||||
(
|
||||
ref DynamicBuffer<AnimationToProcessComponent> atps,
|
||||
in NativeArray<AnimatorControllerParameterComponent> animatorControllerParameters,
|
||||
in BlobAssetReference<ControllerBlob> controllerBlob,
|
||||
in BlobAssetReference<ControllerAnimationsBlob> animationsBlob,
|
||||
in BlobDatabaseSingleton blobDatabase,
|
||||
int layerIndex,
|
||||
int stateIndex,
|
||||
float normalizedTime,
|
||||
float weight = 1,
|
||||
BlobAssetReference<AvatarMaskBlob> avatarMask = default
|
||||
)
|
||||
{
|
||||
BurstAssert.IsTrue(controllerBlob.IsCreated, "Controller blob is not valid");
|
||||
BurstAssert.IsTrue(animationsBlob.IsCreated, "Controller animations blob is not valid");
|
||||
|
||||
BurstAssert.IsTrue(controllerBlob.Value.layers.Length > layerIndex, "Layer index is out of range of controller layers array");
|
||||
if (controllerBlob.Value.layers.Length <= layerIndex || layerIndex < 0)
|
||||
return;
|
||||
|
||||
ref var lb = ref controllerBlob.Value.layers[layerIndex];
|
||||
|
||||
BurstAssert.IsTrue(lb.states.Length > stateIndex, "State index is out of range of controller layer states array");
|
||||
if (lb.states.Length <= stateIndex || stateIndex < 0)
|
||||
return;
|
||||
|
||||
ref var sb = ref lb.states[stateIndex];
|
||||
|
||||
PlayMotion
|
||||
(
|
||||
ref atps,
|
||||
ref sb.motion,
|
||||
animatorControllerParameters,
|
||||
animationsBlob,
|
||||
blobDatabase,
|
||||
normalizedTime,
|
||||
weight,
|
||||
avatarMask
|
||||
);
|
||||
}
|
||||
|
||||
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
/// <summary>
|
||||
/// Start cross fade (linear blend) of current controller layer state and any other layer state with specified transition properties.
|
||||
/// This function is full analog of Unity's Animator.CrossFade API.
|
||||
/// </summary>
|
||||
/// <remarks>This is "fire and forget" function. It needs to be called only once to start transition. Transition flow will continue authomatically.</remarks>
|
||||
/// <param name="animatorControllerLayer">Controller layer to perform transition.</param>
|
||||
/// <param name="stateIndex">State index to crossfade into.</param>
|
||||
/// <param name="normalizedTransitionDuration">Transition duration as a fraction of current state length. I.e. if current state length is 3 sec, and
|
||||
/// normalizedTransitionDuration is 0.5f, then transition duration will be 1.5 sec.</param>
|
||||
/// <param name="normalizedTimeOffset">Offset of start point of target state (defined by state index) in transition.</param>
|
||||
/// <param name="normalizedTransitionTime">Offset of transition start point.</param>
|
||||
/// <see href="https://docs.unity3d.com/ScriptReference/Animator.CrossFade.html">Unity Animator.CrossFade</see>
|
||||
public static void CrossFade
|
||||
(
|
||||
ref AnimatorControllerLayerComponent animatorControllerLayer,
|
||||
int stateIndex,
|
||||
float normalizedTransitionDuration,
|
||||
float normalizedTimeOffset = 0,
|
||||
float normalizedTransitionTime = 0
|
||||
)
|
||||
{
|
||||
var rt = new RuntimeAnimatorData.TransitionData()
|
||||
{
|
||||
id = 0xffffff,
|
||||
length = -normalizedTransitionDuration,
|
||||
normalizedDuration = normalizedTransitionTime
|
||||
};
|
||||
animatorControllerLayer.rtd.activeTransition = rt;
|
||||
animatorControllerLayer.rtd.dstState.id = stateIndex;
|
||||
animatorControllerLayer.rtd.dstState.normalizedDuration = normalizedTimeOffset;
|
||||
}
|
||||
}
|
||||
}
|
||||
+18
@@ -0,0 +1,18 @@
|
||||
fileFormatVersion: 2
|
||||
guid: fa5bacd742509fd4da91afcad6d0bf3d
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
AssetOrigin:
|
||||
serializedVersion: 1
|
||||
productId: 298480
|
||||
packageName: Rukhanka Animation System 2
|
||||
packageVersion: 2.9.0
|
||||
assetPath: Packages/com.rukhanka.animation/Rukhanka.Runtime/ScriptedAnimator/ScriptedAnimator.cs
|
||||
uploadId: 897522
|
||||
+496
@@ -0,0 +1,496 @@
|
||||
using System;
|
||||
using Rukhanka.Toolbox;
|
||||
using Unity.Collections;
|
||||
using Unity.Entities;
|
||||
using Unity.Mathematics;
|
||||
|
||||
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
namespace Rukhanka
|
||||
{
|
||||
public static partial class ScriptedAnimator
|
||||
{
|
||||
public struct MotionIndexAndWeight: IComparable<MotionIndexAndWeight>
|
||||
{
|
||||
public int motionIndex;
|
||||
public float weight;
|
||||
|
||||
public int CompareTo(MotionIndexAndWeight a)
|
||||
{
|
||||
if (weight < a.weight)
|
||||
return 1;
|
||||
if (weight > a.weight)
|
||||
return -1;
|
||||
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------------------------------------------------//
|
||||
|
||||
internal static NativeList<MotionIndexAndWeight> ComputeBlendTree1D(in ReadOnlySpan<float> blendTreeThresholds, float blendTreeParameter)
|
||||
{
|
||||
var i0 = 0;
|
||||
var i1 = 0;
|
||||
bool found = false;
|
||||
for (int i = 0; i < blendTreeThresholds.Length && !found; ++i)
|
||||
{
|
||||
var t = blendTreeThresholds[i];
|
||||
i0 = i1;
|
||||
i1 = i;
|
||||
if (t > blendTreeParameter)
|
||||
found = true;
|
||||
}
|
||||
if (!found)
|
||||
{
|
||||
i1 = blendTreeThresholds.Length - 1;
|
||||
i0 = i1 - 1;
|
||||
}
|
||||
|
||||
var motion0Threshold = blendTreeThresholds[i0];
|
||||
var motion1Threshold = blendTreeThresholds[i1];
|
||||
float f = math.saturate((blendTreeParameter - motion0Threshold) / (motion1Threshold - motion0Threshold));
|
||||
|
||||
var rv = new NativeList<MotionIndexAndWeight>(2, Allocator.Temp);
|
||||
rv.Add(new MotionIndexAndWeight { motionIndex = i0, weight = 1 - f });
|
||||
rv.Add(new MotionIndexAndWeight { motionIndex = i1, weight = f });
|
||||
return rv;
|
||||
}
|
||||
|
||||
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
static void HandleCentroidCase(ref NativeList<MotionIndexAndWeight> rv, float2 pt, in ReadOnlySpan<BlendTree2DMotionElement> blendTreePositions)
|
||||
{
|
||||
if (math.any(pt))
|
||||
return;
|
||||
|
||||
int i = 0;
|
||||
for (; i < blendTreePositions.Length && math.any(blendTreePositions[i].pos); ++i) { }
|
||||
|
||||
if (i < blendTreePositions.Length)
|
||||
{
|
||||
var miw = new MotionIndexAndWeight() { motionIndex = i, weight = 1 };
|
||||
rv.Add(miw);
|
||||
}
|
||||
else
|
||||
{
|
||||
var f = 1.0f / blendTreePositions.Length;
|
||||
for (int l = 0; l < blendTreePositions.Length; ++l)
|
||||
{
|
||||
var miw = new MotionIndexAndWeight() { motionIndex = l, weight = f };
|
||||
rv.Add(miw);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
// p0 = (0,0)
|
||||
static (float, float, float) CalculateBarycentric(float2 p1, float2 p2, float2 pt)
|
||||
{
|
||||
var np2 = new float2(0 - p2.y, p2.x - 0);
|
||||
var np1 = new float2(0 - p1.y, p1.x - 0);
|
||||
|
||||
var l1 = math.dot(pt, np2) / math.dot(p1, np2);
|
||||
var l2 = math.dot(pt, np1) / math.dot(p2, np1);
|
||||
var l0 = 1 - l1 - l2;
|
||||
return (l0, l1, l2);
|
||||
}
|
||||
|
||||
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
internal static NativeList<MotionIndexAndWeight> ComputeBlendTree2DSimpleDirectional(in ReadOnlySpan<BlendTree2DMotionElement> blendTreePositions, float2 blendTreeParameter)
|
||||
{
|
||||
var rv = new NativeList<MotionIndexAndWeight>(Allocator.Temp);
|
||||
|
||||
if (blendTreePositions.Length < 2)
|
||||
{
|
||||
if (blendTreePositions.Length == 1)
|
||||
rv.Add(new MotionIndexAndWeight() { weight = 1, motionIndex = 0 });
|
||||
return rv;
|
||||
}
|
||||
|
||||
HandleCentroidCase(ref rv, blendTreeParameter, blendTreePositions);
|
||||
if (rv.Length > 0)
|
||||
return rv;
|
||||
|
||||
var centerPtIndex = -1;
|
||||
// Loop over all directions and search for sector that contains requested point
|
||||
var dotProductsAndWeights = new NativeList<MotionIndexAndWeight>(blendTreePositions.Length, Allocator.Temp);
|
||||
for (int i = 0; i < blendTreePositions.Length; ++i)
|
||||
{
|
||||
var motionDir = blendTreePositions[i].pos;
|
||||
if (!math.any(motionDir))
|
||||
{
|
||||
centerPtIndex = i;
|
||||
continue;
|
||||
}
|
||||
var angle = math.atan2(motionDir.y, motionDir.x);
|
||||
var miw = new MotionIndexAndWeight() { motionIndex = blendTreePositions[i].motionIndex, weight = angle };
|
||||
dotProductsAndWeights.Add(miw);
|
||||
}
|
||||
|
||||
var ptAngle = math.atan2(blendTreeParameter.y, blendTreeParameter.x);
|
||||
|
||||
dotProductsAndWeights.Sort();
|
||||
|
||||
// Pick two closest points
|
||||
MotionIndexAndWeight d0 = default, d1 = default;
|
||||
var l = 0;
|
||||
for (; l < dotProductsAndWeights.Length; ++l)
|
||||
{
|
||||
var d = dotProductsAndWeights[l];
|
||||
if (d.weight < ptAngle)
|
||||
{
|
||||
var ld0 = l == 0 ? dotProductsAndWeights.Length - 1 : l - 1;
|
||||
d1 = d;
|
||||
d0 = dotProductsAndWeights[ld0];
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Handle last sector
|
||||
if (l == dotProductsAndWeights.Length)
|
||||
{
|
||||
d0 = dotProductsAndWeights[dotProductsAndWeights.Length - 1];
|
||||
d1 = dotProductsAndWeights[0];
|
||||
}
|
||||
|
||||
var p0 = blendTreePositions[d0.motionIndex].pos;
|
||||
var p1 = blendTreePositions[d1.motionIndex].pos;
|
||||
|
||||
// Barycentric coordinates for point pt in triangle <p0,p1,0>
|
||||
var (l0, l1, l2) = CalculateBarycentric(p0, p1, blendTreeParameter);
|
||||
|
||||
var m0Weight = l1;
|
||||
var m1Weight = l2;
|
||||
if (l0 < 0)
|
||||
{
|
||||
var sum = m0Weight + m1Weight;
|
||||
m0Weight /= sum;
|
||||
m1Weight /= sum;
|
||||
}
|
||||
|
||||
l0 = math.saturate(l0);
|
||||
|
||||
var evenlyDistributedMotionWeight = centerPtIndex < 0 ? 1.0f / blendTreePositions.Length * l0 : 0;
|
||||
|
||||
var miw0 = new MotionIndexAndWeight() { motionIndex = d0.motionIndex, weight = m0Weight + evenlyDistributedMotionWeight };
|
||||
rv.Add(miw0);
|
||||
|
||||
var miw1 = new MotionIndexAndWeight() { motionIndex = d1.motionIndex, weight = m1Weight + evenlyDistributedMotionWeight };
|
||||
rv.Add(miw1);
|
||||
|
||||
// Add other motions of blend tree
|
||||
if (evenlyDistributedMotionWeight > 0)
|
||||
{
|
||||
for (int i = 0; i < blendTreePositions.Length; ++i)
|
||||
{
|
||||
if (i != d0.motionIndex && i != d1.motionIndex)
|
||||
{
|
||||
var miw = new MotionIndexAndWeight() { motionIndex = blendTreePositions[i].motionIndex, weight = evenlyDistributedMotionWeight };
|
||||
rv.Add(miw);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Add centroid motion
|
||||
if (centerPtIndex >= 0)
|
||||
{
|
||||
var miw = new MotionIndexAndWeight() { motionIndex = centerPtIndex, weight = l0 };
|
||||
rv.Add(miw);
|
||||
}
|
||||
|
||||
dotProductsAndWeights.Dispose();
|
||||
|
||||
return rv;
|
||||
}
|
||||
|
||||
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
static unsafe NativeList<MotionIndexAndWeight> ComputeBlendTree2DFreeformCartesian(in ReadOnlySpan<BlendTree2DMotionElement> blendTreePositions, float2 blendTreeParameter)
|
||||
{
|
||||
var p = blendTreeParameter;
|
||||
Span<float> hpArr = stackalloc float[blendTreePositions.Length];
|
||||
|
||||
var hpSum = 0.0f;
|
||||
|
||||
// Calculate influence factors
|
||||
for (int i = 0; i < blendTreePositions.Length; ++i)
|
||||
{
|
||||
var pi = blendTreePositions[i].pos;
|
||||
var pip = p - pi;
|
||||
|
||||
var w = 1.0f;
|
||||
|
||||
for (int j = 0; j < blendTreePositions.Length && w > 0; ++j)
|
||||
{
|
||||
if (i == j) continue;
|
||||
var pj = blendTreePositions[j].pos;
|
||||
var pipj = pj - pi;
|
||||
var f = math.dot(pip, pipj) / math.lengthsq(pipj);
|
||||
var hj = math.max(1 - f, 0);
|
||||
w = math.min(hj, w);
|
||||
}
|
||||
hpSum += w;
|
||||
hpArr[i] = w;
|
||||
}
|
||||
|
||||
var rv = new NativeList<MotionIndexAndWeight>(blendTreePositions.Length, Allocator.Temp);
|
||||
// Calculate weight functions
|
||||
for (int i = 0; i < blendTreePositions.Length; ++i)
|
||||
{
|
||||
var w = hpArr[i] / hpSum;
|
||||
if (w > 0)
|
||||
{
|
||||
var miw = new MotionIndexAndWeight() { motionIndex = blendTreePositions[i].motionIndex , weight = w };
|
||||
rv.Add(miw);
|
||||
}
|
||||
}
|
||||
return rv;
|
||||
}
|
||||
|
||||
/////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
static float CalcAngle(float2 a, float2 b)
|
||||
{
|
||||
var cross = a.x * b.y - a.y * b.x;
|
||||
var dot = math.dot(a, b);
|
||||
var tanA = new float2(cross, dot);
|
||||
var rv = math.atan2(tanA.x, tanA.y);
|
||||
return rv;
|
||||
}
|
||||
|
||||
/////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
static float2 CalcAngleWeights(float2 i, float2 j, float2 s)
|
||||
{
|
||||
float2 rv = 0;
|
||||
if (!math. any(i))
|
||||
{
|
||||
rv.x = CalcAngle(j, s);
|
||||
rv.y = 0;
|
||||
}
|
||||
else if (!math.any(j))
|
||||
{
|
||||
rv.x = CalcAngle(i, s);
|
||||
rv.y = rv.x;
|
||||
}
|
||||
else
|
||||
{
|
||||
rv.x = CalcAngle(i, j);
|
||||
if (!math.any(s))
|
||||
rv.y = rv.x;
|
||||
else
|
||||
rv.y = CalcAngle(i, s);
|
||||
}
|
||||
return rv;
|
||||
}
|
||||
|
||||
/////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
static unsafe NativeList<MotionIndexAndWeight> ComputeBlendTree2DFreeformDirectional(in ReadOnlySpan<BlendTree2DMotionElement> blendTreePositions, float2 blendTreeParameter)
|
||||
{
|
||||
var p = blendTreeParameter;
|
||||
var lp = math.length(p);
|
||||
|
||||
Span<float> hpArr = stackalloc float[blendTreePositions.Length];
|
||||
|
||||
var hpSum = 0.0f;
|
||||
|
||||
// Calculate influence factors
|
||||
for (int i = 0; i < blendTreePositions.Length; ++i)
|
||||
{
|
||||
var pi = blendTreePositions[i].pos;
|
||||
var lpi = math.length(pi);
|
||||
|
||||
var w = 1.0f;
|
||||
|
||||
for (int j = 0; j < blendTreePositions.Length && w > 0; ++j)
|
||||
{
|
||||
if (i == j) continue;
|
||||
var pj = blendTreePositions[j].pos;
|
||||
var lpj = math.length(pj);
|
||||
|
||||
var pRcpMiddle = math.rcp((lpj + lpi) * 0.5f);
|
||||
var lpip = (lp - lpi) * pRcpMiddle;
|
||||
var lpipj = (lpj - lpi) * pRcpMiddle;
|
||||
var angleWeights = CalcAngleWeights(pi, pj, p);
|
||||
|
||||
var pip = new float2(lpip, angleWeights.y);
|
||||
var pipj = new float2(lpipj, angleWeights.x);
|
||||
|
||||
var f = math.dot(pip, pipj) / math.lengthsq(pipj);
|
||||
var hj = math.saturate(1 - f);
|
||||
w = math.min(hj, w);
|
||||
}
|
||||
hpSum += w;
|
||||
hpArr[i] = w;
|
||||
}
|
||||
|
||||
var rv = new NativeList<MotionIndexAndWeight>(blendTreePositions.Length, Allocator.Temp);
|
||||
// Calculate weight functions
|
||||
for (int i = 0; i < blendTreePositions.Length; ++i)
|
||||
{
|
||||
var w = hpArr[i] / hpSum;
|
||||
if (w > 0)
|
||||
{
|
||||
var miw = new MotionIndexAndWeight() { motionIndex = blendTreePositions[i].motionIndex, weight = w };
|
||||
rv.Add(miw);
|
||||
}
|
||||
}
|
||||
return rv;
|
||||
}
|
||||
|
||||
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
internal static NativeList<MotionIndexAndWeight> GetChildMotionsList
|
||||
(
|
||||
ref MotionBlob mb,
|
||||
in NativeArray<AnimatorControllerParameterComponent> runtimeParams
|
||||
)
|
||||
{
|
||||
NativeList<MotionIndexAndWeight> blendTreeMotionsAndWeights = default;
|
||||
|
||||
switch (mb.type)
|
||||
{
|
||||
// If no child motions, simply return
|
||||
case MotionBlob.Type.None:
|
||||
case MotionBlob.Type.AnimationClip:
|
||||
return blendTreeMotionsAndWeights;
|
||||
case MotionBlob.Type.BlendTreeDirect:
|
||||
blendTreeMotionsAndWeights = GetBlendTreeDirectCurrentMotions(ref mb, runtimeParams);
|
||||
break;
|
||||
case MotionBlob.Type.BlendTree1D:
|
||||
blendTreeMotionsAndWeights = GetBlendTree1DCurrentMotions(ref mb, runtimeParams);
|
||||
break;
|
||||
case MotionBlob.Type.BlendTree2DSimpleDirectional:
|
||||
case MotionBlob.Type.BlendTree2DFreeformCartesian:
|
||||
case MotionBlob.Type.BlendTree2DFreeformDirectional:
|
||||
blendTreeMotionsAndWeights = GetBlendTree2DCurrentMotions(ref mb, runtimeParams, mb.type);
|
||||
break;
|
||||
}
|
||||
|
||||
return blendTreeMotionsAndWeights;
|
||||
}
|
||||
|
||||
/////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
static NativeList<MotionIndexAndWeight> GetBlendTree1DCurrentMotions(ref MotionBlob mb, in NativeArray<AnimatorControllerParameterComponent> runtimeParams)
|
||||
{
|
||||
var blendTreeParameter = runtimeParams[mb.blendTree.blendParameterIndex];
|
||||
ref var motions = ref mb.blendTree.motions;
|
||||
|
||||
Span<float> bttSpan = stackalloc float[motions.Length];
|
||||
for (var i = 0; i < motions.Length; ++i)
|
||||
{
|
||||
bttSpan[i] = motions[i].threshold;
|
||||
}
|
||||
var rv = ComputeBlendTree1D(bttSpan, blendTreeParameter.FloatValue);
|
||||
|
||||
return rv;
|
||||
}
|
||||
|
||||
/////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
static NativeList<MotionIndexAndWeight> GetBlendTreeDirectCurrentMotions(ref MotionBlob mb, in NativeArray<AnimatorControllerParameterComponent> runtimeParams)
|
||||
{
|
||||
ref var motions = ref mb.blendTree.motions;
|
||||
var rv = new NativeList<MotionIndexAndWeight>(motions.Length, Allocator.Temp);
|
||||
|
||||
var weightSum = 0.0f;
|
||||
for (int i = 0; i < motions.Length; ++i)
|
||||
{
|
||||
ref var cm = ref motions[i];
|
||||
var w = cm.directBlendParameterIndex >= 0 ? runtimeParams[cm.directBlendParameterIndex].FloatValue : 0;
|
||||
if (w > 0)
|
||||
{
|
||||
var miw = new MotionIndexAndWeight() { motionIndex = i, weight = w };
|
||||
weightSum += miw.weight;
|
||||
rv.Add(miw);
|
||||
}
|
||||
}
|
||||
|
||||
if (mb.blendTree.normalizeBlendValues && weightSum > 1)
|
||||
{
|
||||
for (int i = 0; i < rv.Length; ++i)
|
||||
{
|
||||
var miw = rv[i];
|
||||
miw.weight = miw.weight / weightSum;
|
||||
rv[i] = miw;
|
||||
}
|
||||
}
|
||||
|
||||
return rv;
|
||||
}
|
||||
|
||||
/////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
static NativeList<MotionIndexAndWeight> GetBlendTree2DCurrentMotions(ref MotionBlob mb, in NativeArray<AnimatorControllerParameterComponent> runtimeParams, MotionBlob.Type btType)
|
||||
{
|
||||
var pX = runtimeParams[mb.blendTree.blendParameterIndex];
|
||||
var pY = runtimeParams[mb.blendTree.blendParameterYIndex];
|
||||
var pt = new float2(pX.FloatValue, pY.FloatValue);
|
||||
ref var motions = ref mb.blendTree.motions;
|
||||
|
||||
Span<BlendTree2DMotionElement> bttSpan = stackalloc BlendTree2DMotionElement[motions.Length];
|
||||
var validMotionCount = 0;
|
||||
for (var i = 0; i < motions.Length; ++i)
|
||||
{
|
||||
// Skip empty motions
|
||||
ref var m = ref motions[i];
|
||||
if (m.motion.type == MotionBlob.Type.None)
|
||||
continue;
|
||||
|
||||
var btme = new BlendTree2DMotionElement() { pos = m.position2D, motionIndex = i };
|
||||
bttSpan[validMotionCount++] = btme;
|
||||
}
|
||||
bttSpan = bttSpan.Slice(0, validMotionCount);
|
||||
|
||||
BurstAssert.IsTrue(btType != MotionBlob.Type.None && btType != MotionBlob.Type.AnimationClip, "Not a 2D blend tree type!");
|
||||
var rv = btType switch
|
||||
{
|
||||
MotionBlob.Type.BlendTree2DSimpleDirectional => ComputeBlendTree2DSimpleDirectional(bttSpan, pt),
|
||||
MotionBlob.Type.BlendTree2DFreeformCartesian => ComputeBlendTree2DFreeformCartesian(bttSpan, pt),
|
||||
MotionBlob.Type.BlendTree2DFreeformDirectional => ComputeBlendTree2DFreeformDirectional(bttSpan, pt),
|
||||
_ => default
|
||||
};
|
||||
return rv;
|
||||
}
|
||||
|
||||
/////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
static void PlayMotion
|
||||
(
|
||||
ref DynamicBuffer<AnimationToProcessComponent> atps,
|
||||
ref MotionBlob motionBlob,
|
||||
in NativeArray<AnimatorControllerParameterComponent> acpc,
|
||||
in BlobAssetReference<ControllerAnimationsBlob> animationsBlob,
|
||||
in BlobDatabaseSingleton blobDatabase,
|
||||
float normalizedTime,
|
||||
float weight,
|
||||
BlobAssetReference<AvatarMaskBlob> avatarMask
|
||||
)
|
||||
{
|
||||
switch (motionBlob.type)
|
||||
{
|
||||
case MotionBlob.Type.None:
|
||||
return;
|
||||
case MotionBlob.Type.AnimationClip:
|
||||
var animBlobHash = animationsBlob.Value.animations[motionBlob.animationIndex];
|
||||
var animBlob = blobDatabase.GetAnimationClipBlob(animBlobHash);
|
||||
PlayAnimation(ref atps, animBlob, normalizedTime, weight, avatarMask);
|
||||
return;
|
||||
}
|
||||
|
||||
var childMotions = GetChildMotionsList(ref motionBlob, acpc);
|
||||
for (var i = 0; i < childMotions.Length; ++i)
|
||||
{
|
||||
var cm = childMotions[i];
|
||||
ref var m = ref motionBlob.blendTree.motions[cm.motionIndex];
|
||||
PlayMotion(ref atps, ref m.motion, acpc, animationsBlob, blobDatabase, normalizedTime, weight * cm.weight, avatarMask);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
+18
@@ -0,0 +1,18 @@
|
||||
fileFormatVersion: 2
|
||||
guid: aa44f27a135ad4e4899aabb39661518b
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
AssetOrigin:
|
||||
serializedVersion: 1
|
||||
productId: 298480
|
||||
packageName: Rukhanka Animation System 2
|
||||
packageVersion: 2.9.0
|
||||
assetPath: Packages/com.rukhanka.animation/Rukhanka.Runtime/ScriptedAnimator/ScriptedAnimator_Internals.cs
|
||||
uploadId: 897522
|
||||
Reference in New Issue
Block a user