Netcode Bootstrap
This commit is contained in:
@@ -0,0 +1,795 @@
|
||||
#if UNITY_EDITOR
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using Unity.Collections;
|
||||
using Unity.Collections.LowLevel.Unsafe;
|
||||
using UnityEditor;
|
||||
using UnityEngine;
|
||||
using System.Linq;
|
||||
using Rukhanka.Hybrid.RTP;
|
||||
using Rukhanka.Toolbox;
|
||||
using Unity.Assertions;
|
||||
using FixedStringName = Unity.Collections.FixedString512Bytes;
|
||||
using Unity.Mathematics;
|
||||
using Unity.Burst;
|
||||
using Unity.Entities;
|
||||
using AnimationClip = UnityEngine.AnimationClip;
|
||||
using Hash128 = Unity.Entities.Hash128;
|
||||
|
||||
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
namespace Rukhanka.Hybrid
|
||||
{
|
||||
[BurstCompile]
|
||||
public partial class AnimationClipBaker
|
||||
{
|
||||
// To reduce huge memory consumption during baking of many animations, I will reuse temporarty buffers
|
||||
NativeList<KeyFrame> keyFramesList = new (Allocator.Temp);
|
||||
NativeList<Track> trackList = new (Allocator.Temp);
|
||||
NativeList<uint> trackGroupHashes = new (Allocator.Temp);
|
||||
NativeList<uint> trackGroupOffsets = new (Allocator.Temp);
|
||||
#if RUKHANKA_DEBUG_INFO
|
||||
NativeList<FixedString128Bytes> trackGroupNames = new (Allocator.Temp);
|
||||
#endif
|
||||
|
||||
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
struct ParsedCurveBinding
|
||||
{
|
||||
public BindingType bindingType;
|
||||
public uint channelIndex;
|
||||
public string boneName;
|
||||
public string channelName;
|
||||
|
||||
public bool IsValid() => boneName.Length > 0;
|
||||
}
|
||||
|
||||
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
(string, string) SplitPath(string path)
|
||||
{
|
||||
var arr = path.Split('/');
|
||||
Assert.IsTrue(arr.Length > 0);
|
||||
var rv = (arr.Last(), arr.Length > 1 ? arr[^2] : "");
|
||||
return rv;
|
||||
}
|
||||
|
||||
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
BindingType PickGenericBindingTypeByString(string bindingString) => bindingString switch
|
||||
{
|
||||
"m_LocalPosition" => BindingType.Translation,
|
||||
"m_LocalRotation" => BindingType.Quaternion,
|
||||
"localEulerAngles" => BindingType.EulerAngles,
|
||||
"localEulerAnglesRaw" => BindingType.EulerAngles,
|
||||
"m_LocalScale" => BindingType.Scale,
|
||||
_ => BindingType.Unknown
|
||||
};
|
||||
|
||||
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
uint ChannelIndexFromString(string c) => c switch
|
||||
{
|
||||
"x" => 0,
|
||||
"y" => 1,
|
||||
"z" => 2,
|
||||
"w" => 3,
|
||||
_ => 0
|
||||
};
|
||||
|
||||
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
string ConstructBoneClipName(string name, string path, Type animatedObjectType)
|
||||
{
|
||||
if (animatedObjectType == typeof(UnityEngine.Animator))
|
||||
return SpecialBones.AnimatorTypeName;
|
||||
|
||||
var rv = name;
|
||||
if (animatedObjectType != typeof(UnityEngine.SkinnedMeshRenderer))
|
||||
{
|
||||
// Empty name string is unnamed root bone
|
||||
if (name.Length == 0 && path.Length == 0)
|
||||
{
|
||||
rv = SpecialBones.UnnamedRootBoneName;
|
||||
}
|
||||
}
|
||||
return rv;
|
||||
}
|
||||
|
||||
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
int2 CopyKeyFrames(Keyframe[] keysArr, ref NativeList<KeyFrame> outKeyFrames)
|
||||
{
|
||||
var rv = new int2(outKeyFrames.Length, keysArr.Length);
|
||||
foreach (var k in keysArr)
|
||||
{
|
||||
var kf = new KeyFrame()
|
||||
{
|
||||
time = k.time,
|
||||
inTan = math.select(0, k.inTangent, math.isfinite(k.inTangent)),
|
||||
outTan = math.select(0, k.outTangent, math.isfinite(k.outTangent)),
|
||||
v = k.value
|
||||
};
|
||||
outKeyFrames.Add(kf);
|
||||
}
|
||||
return rv;
|
||||
}
|
||||
|
||||
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
Track CreateTrackData(int2 keyFrameRange, ParsedCurveBinding pb)
|
||||
{
|
||||
var rv = new Track();
|
||||
rv.keyFrameRange = keyFrameRange;
|
||||
#if RUKHANKA_DEBUG_INFO
|
||||
rv.name = $"{pb.boneName}.{pb.channelName}";
|
||||
#endif
|
||||
// For unknown binding types treat props as channel name hash
|
||||
if (pb.bindingType == BindingType.Unknown)
|
||||
{
|
||||
rv.props = Track.CalculateHash(pb.channelName);
|
||||
}
|
||||
else
|
||||
{
|
||||
rv.bindingType = pb.bindingType;
|
||||
rv.channelIndex = pb.channelIndex;
|
||||
}
|
||||
return rv;
|
||||
}
|
||||
|
||||
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
void InsertTrackIntoGroup(Track t, string trackName)
|
||||
{
|
||||
// Tracks with same path hash will go into same track group. This is needed to speedup access of bone data
|
||||
// (no need to query each attribute hash)
|
||||
|
||||
var nameHash = trackName.CalculateHash32();
|
||||
// Search for name in already existing groups
|
||||
var i = trackGroupHashes.IndexOf(nameHash);
|
||||
|
||||
if (i < 0)
|
||||
{
|
||||
// Create new track group and add track into it
|
||||
trackGroupHashes.Add(nameHash);
|
||||
trackGroupOffsets.Add((uint)trackList.Length);
|
||||
trackList.Add(t);
|
||||
#if RUKHANKA_DEBUG_INFO
|
||||
trackGroupNames.Add(trackName);
|
||||
#endif
|
||||
}
|
||||
else
|
||||
{
|
||||
// Insert track into existing group
|
||||
var startIndex = (int)trackGroupOffsets[i];
|
||||
trackList.InsertRange(startIndex, 1);
|
||||
trackList[startIndex] = t;
|
||||
|
||||
// Shift all other groups to the right
|
||||
for (var l = 0; l < trackGroupOffsets.Length; ++l)
|
||||
{
|
||||
ref var tgo = ref trackGroupOffsets.ElementAt(l);
|
||||
if (tgo > startIndex)
|
||||
tgo += 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
ParsedCurveBinding ParseGenericCurveBinding(EditorCurveBinding b)
|
||||
{
|
||||
var rv = new ParsedCurveBinding();
|
||||
|
||||
var t = b.propertyName.Split('.');
|
||||
var propName = t[0];
|
||||
var channel = t.Length > 1 ? t[1] : "";
|
||||
|
||||
rv.channelIndex = ChannelIndexFromString(channel);
|
||||
rv.bindingType = PickGenericBindingTypeByString(propName);
|
||||
rv.channelName = b.propertyName;
|
||||
var nameAndPath = SplitPath(b.path);
|
||||
rv.boneName = ConstructBoneClipName(nameAndPath.Item1, nameAndPath.Item2, b.type);
|
||||
|
||||
return rv;
|
||||
}
|
||||
|
||||
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
int GetHumanBoneIndexForHumanName(in HumanDescription hd, FixedStringName humanBoneName)
|
||||
{
|
||||
var humanBoneIndexInAvatar = Array.FindIndex(hd.human, x => x.humanName == humanBoneName);
|
||||
return humanBoneIndexInAvatar;
|
||||
}
|
||||
|
||||
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
ParsedCurveBinding ParseHumanoidCurveBinding(EditorCurveBinding b, Avatar avatar)
|
||||
{
|
||||
if (!humanoidMappingTable.TryGetValue(b.propertyName, out var rv))
|
||||
return ParseGenericCurveBinding(b);
|
||||
|
||||
var hd = avatar.humanDescription;
|
||||
var humanBoneIndexInAvatar = GetHumanBoneIndexForHumanName(hd, rv.boneName);
|
||||
if (humanBoneIndexInAvatar < 0)
|
||||
return rv;
|
||||
|
||||
if (rv.bindingType == BindingType.HumanMuscle)
|
||||
{
|
||||
var humanBoneDef = hd.human[humanBoneIndexInAvatar];
|
||||
rv.boneName = humanBoneDef.boneName;
|
||||
}
|
||||
|
||||
return rv;
|
||||
}
|
||||
|
||||
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
ParsedCurveBinding ParseCurveBinding(AnimationClip ac, EditorCurveBinding b, Avatar avatar)
|
||||
{
|
||||
var rv = ac.isHumanMotion ?
|
||||
ParseHumanoidCurveBinding(b, avatar) :
|
||||
ParseGenericCurveBinding(b);
|
||||
|
||||
return rv;
|
||||
}
|
||||
|
||||
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
KeyFrame AddKeyFrameFromFloatValue(float2 key, float v)
|
||||
{
|
||||
var kf = new KeyFrame()
|
||||
{
|
||||
time = key.x,
|
||||
inTan = key.y,
|
||||
outTan = key.y,
|
||||
v = v
|
||||
};
|
||||
return kf;
|
||||
}
|
||||
|
||||
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
[BurstCompile]
|
||||
void ComputeTangents(in Span<KeyFrame> keyFrames)
|
||||
{
|
||||
for (int i = 0; i < keyFrames.Length; ++i)
|
||||
{
|
||||
var p0 = i == 0 ? keyFrames[0] : keyFrames[i - 1];
|
||||
var p1 = keyFrames[i];
|
||||
var p2 = i == keyFrames.Length - 1 ? keyFrames[i] : keyFrames[i + 1];
|
||||
|
||||
var outV = math.normalizesafe(new float2(p2.time, p2.v) - new float2(p1.time, p1.v));
|
||||
var outTan = outV.x > 0.0001f ? outV.y / outV.x : 0;
|
||||
|
||||
var inV = math.normalizesafe(new float2(p1.time, p1.v) - new float2(p0.time, p0.v));
|
||||
var inTan = inV.x > 0.0001f ? inV.y / inV.x : 0;
|
||||
|
||||
var dt = math.abs(inTan) + math.abs(outTan);
|
||||
var f = dt > 0 ? math.abs(inTan) / dt : 0;
|
||||
|
||||
var avgTan = math.lerp(inTan, outTan, f);
|
||||
|
||||
var k = keyFrames[i];
|
||||
k.outTan = avgTan;
|
||||
k.inTan = avgTan;
|
||||
keyFrames[i] = k;
|
||||
}
|
||||
}
|
||||
|
||||
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
NativeList<float> CreateKeyframeTimes(float animationLength, float dt, float frameTime)
|
||||
{
|
||||
var numFrames = (int)math.ceil(animationLength / dt) + 1;
|
||||
var rv = new NativeList<float>(numFrames, Allocator.Temp);
|
||||
|
||||
if (frameTime >= 0)
|
||||
{
|
||||
rv.Add(frameTime);
|
||||
return rv;
|
||||
}
|
||||
|
||||
var curTime = 0.0f;
|
||||
for (var i = 0; i < numFrames; ++i)
|
||||
{
|
||||
rv.Add(curTime);
|
||||
curTime += dt;
|
||||
}
|
||||
|
||||
if (rv.Length > 0)
|
||||
rv[^1] = math.min(animationLength, rv[^1]);
|
||||
return rv;
|
||||
}
|
||||
|
||||
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
void ReadCurvesFromTransform(Transform tr, in NativeArray<Track> trackSpan, int keyIndex, float time)
|
||||
{
|
||||
quaternion q = tr.localRotation;
|
||||
float3 t = tr.localPosition;
|
||||
|
||||
Span<float> vArr = stackalloc float[7];
|
||||
vArr[0] = t.x;
|
||||
vArr[1] = t.y;
|
||||
vArr[2] = t.z;
|
||||
vArr[3] = q.value.x;
|
||||
vArr[4] = q.value.y;
|
||||
vArr[5] = q.value.z;
|
||||
vArr[6] = q.value.w;
|
||||
|
||||
for (int l = 0; l < vArr.Length; ++l)
|
||||
{
|
||||
var kfIndex = trackSpan[l].keyFrameRange.x + keyIndex;
|
||||
var kf = AddKeyFrameFromFloatValue(time, vArr[l]);
|
||||
keyFramesList[kfIndex] = kf;
|
||||
}
|
||||
}
|
||||
|
||||
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
unsafe void SampleUnityAnimation(AnimationClip ac, Animator anm, ValueTuple<Transform, uint>[] trs, bool applyRootMotion, float frameTime)
|
||||
{
|
||||
if (trs.Length == 0)
|
||||
return;
|
||||
|
||||
var sampleAnimationFrameTime = 1 / 60.0f;
|
||||
var keysList = CreateKeyframeTimes(ac.length, sampleAnimationFrameTime, frameTime);
|
||||
|
||||
var tracks = new []
|
||||
{
|
||||
new Track(BindingType.Translation, 0),
|
||||
new Track(BindingType.Translation, 1),
|
||||
new Track(BindingType.Translation, 2),
|
||||
new Track(BindingType.Quaternion, 0),
|
||||
new Track(BindingType.Quaternion, 1),
|
||||
new Track(BindingType.Quaternion, 2),
|
||||
new Track(BindingType.Quaternion, 3),
|
||||
};
|
||||
|
||||
var rac = anm.runtimeAnimatorController;
|
||||
var origPos = anm.transform.position;
|
||||
var origRot = anm.transform.rotation;
|
||||
var origRootMotion = anm.applyRootMotion;
|
||||
var prevAnmCulling = anm.cullingMode;
|
||||
|
||||
anm.runtimeAnimatorController = null;
|
||||
anm.cullingMode = AnimatorCullingMode.AlwaysAnimate;
|
||||
anm.applyRootMotion = true;
|
||||
anm.transform.position = Vector3.zero;
|
||||
anm.transform.rotation = quaternion.identity;
|
||||
|
||||
var newTracks = new NativeArray<Track>(tracks.Length * trs.Length, Allocator.Temp);
|
||||
for (int k = 0; k < newTracks.Length; ++k)
|
||||
{
|
||||
var nt = tracks[k % tracks.Length];
|
||||
nt.keyFrameRange = new int2(keyFramesList.Length, keysList.Length);
|
||||
#if RUKHANKA_DEBUG_INFO
|
||||
nt.name = $"{trs[k / tracks.Length].Item1.name}.{nt.bindingType}.{nt.channelIndex}";
|
||||
#endif
|
||||
keyFramesList.Resize(keyFramesList.Length + keysList.Length, NativeArrayOptions.ClearMemory);
|
||||
newTracks[k] = nt;
|
||||
}
|
||||
|
||||
for (int i = 0; i < keysList.Length; ++i)
|
||||
{
|
||||
var time = keysList[i];
|
||||
ac.SampleAnimation(anm.gameObject, time);
|
||||
|
||||
for (int l = 0; l < trs.Length; ++l)
|
||||
{
|
||||
var tr = trs[l].Item1;
|
||||
var trackSpan = newTracks.GetSubArray(l * tracks.Length, tracks.Length);
|
||||
ReadCurvesFromTransform(tr, trackSpan, i, time);
|
||||
}
|
||||
}
|
||||
|
||||
for (int l = 0; l < trs.Length; ++l)
|
||||
{
|
||||
var transformHash = trs[l].Item2;
|
||||
var idx = trackGroupHashes.IndexOf(transformHash);
|
||||
if (idx < 0)
|
||||
{
|
||||
trackGroupHashes.Add(transformHash);
|
||||
trackGroupOffsets.Add((uint)trackList.Length);
|
||||
#if RUKHANKA_DEBUG_INFO
|
||||
trackGroupNames.Add(trs[l].Item1.name);
|
||||
#endif
|
||||
}
|
||||
else
|
||||
{
|
||||
trackGroupOffsets[idx] = (uint)trackList.Length;
|
||||
}
|
||||
|
||||
trackList.AddRange(newTracks.GetSubArray(l * tracks.Length, tracks.Length));
|
||||
}
|
||||
|
||||
for (int m = 0; m < newTracks.Length; ++m)
|
||||
{
|
||||
var nt = newTracks[m];
|
||||
var trackKeyFrames = new Span<KeyFrame>(keyFramesList.GetUnsafePtr() + nt.keyFrameRange.x, nt.keyFrameRange.y);
|
||||
ComputeTangents(trackKeyFrames);
|
||||
}
|
||||
|
||||
anm.cullingMode = prevAnmCulling;
|
||||
anm.runtimeAnimatorController = rac;
|
||||
anm.transform.position = origPos;
|
||||
anm.transform.rotation = origRot;
|
||||
anm.applyRootMotion = origRootMotion;
|
||||
}
|
||||
|
||||
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
(Transform, uint) GetRootBoneTransform(Animator anm)
|
||||
{
|
||||
if (anm.avatar.isHuman)
|
||||
{
|
||||
var hipsTransform = anm.GetBoneTransform(HumanBodyBones.Hips);
|
||||
var hd = anm.avatar.humanDescription;
|
||||
var humanBoneIndexInDesc = GetHumanBoneIndexForHumanName(hd, "Hips");
|
||||
var rigHipsBoneName = new FixedStringName(hd.human[humanBoneIndexInDesc].boneName).CalculateHash32();
|
||||
return (hipsTransform, rigHipsBoneName);
|
||||
}
|
||||
|
||||
var rootBoneName = anm.avatar.GetRootMotionNodeName();
|
||||
var rootBoneNameHash = new FixedStringName(rootBoneName).CalculateHash32();
|
||||
var rootBoneTransform = Rukhanka.Toolbox.TransformUtils.FindChildRecursively(anm.transform, rootBoneName);
|
||||
return (rootBoneTransform, rootBoneNameHash);
|
||||
}
|
||||
|
||||
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
void SampleMissingCurves(AnimationClip ac, Animator anm, float frameTime)
|
||||
{
|
||||
var trs = new List<ValueTuple<Transform, uint>>();
|
||||
var entityRootTransform = anm.transform;
|
||||
var rootBoneTransformData = GetRootBoneTransform(anm);
|
||||
|
||||
if (anm.isHuman)
|
||||
trs.Add(rootBoneTransformData);
|
||||
|
||||
// Sample curves for non-rootmotion animations
|
||||
SampleUnityAnimation(ac, anm, trs.ToArray(), false, frameTime);
|
||||
|
||||
// Sample root motion curves
|
||||
trs.Clear();
|
||||
|
||||
var entityRootHash = SpecialBones.UnnamedRootBoneName.CalculateHash32();
|
||||
entityRootHash = AnimationProcessSystem.ComputeBoneAnimationJob.ModifyBoneHashForRootMotion(entityRootHash);
|
||||
trs.Add((entityRootTransform, entityRootHash));
|
||||
|
||||
// Modify bone hash to separate root motion tracks and ordinary tracks
|
||||
rootBoneTransformData.Item2 = AnimationProcessSystem.ComputeBoneAnimationJob.ModifyBoneHashForRootMotion(rootBoneTransformData.Item2);
|
||||
trs.Add(rootBoneTransformData);
|
||||
|
||||
SampleUnityAnimation(ac, anm, trs.ToArray(), true, frameTime);
|
||||
}
|
||||
|
||||
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
void BakeAnimationEvents(BlobBuilder bb, ref AnimationClipBlob acb, AnimationClip ac)
|
||||
{
|
||||
if (ac.events.Length == 0)
|
||||
return;
|
||||
|
||||
var eventsArr = bb.Allocate(ref acb.events, ac.events.Length);
|
||||
for (var i = 0; i < eventsArr.Length; ++i)
|
||||
{
|
||||
var ae = ac.events[i];
|
||||
ref var bakedEvent = ref eventsArr[i];
|
||||
#if RUKHANKA_DEBUG_INFO
|
||||
if (ae.functionName.Length > 0)
|
||||
bb.AllocateString(ref bakedEvent.name, ae.functionName);
|
||||
if (ae.stringParameter.Length > 0)
|
||||
bb.AllocateString(ref bakedEvent.stringParam, ae.stringParameter);
|
||||
#endif
|
||||
bakedEvent.nameHash = new FixedStringName(ae.functionName).CalculateHash32();
|
||||
bakedEvent.time = ae.time / ac.length;
|
||||
bakedEvent.floatParam = ae.floatParameter;
|
||||
bakedEvent.intParam = ae.intParameter;
|
||||
bakedEvent.stringParamHash = new FixedStringName(ae.stringParameter).CalculateHash32();
|
||||
}
|
||||
}
|
||||
|
||||
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
AnimationClip[] Deduplicate(AnimationClip[] animationClips)
|
||||
{
|
||||
var dedupList = new List<AnimationClip>();
|
||||
var dupSet = new NativeHashSet<ulong>(animationClips.Length, Allocator.Temp);
|
||||
|
||||
foreach (var a in animationClips)
|
||||
{
|
||||
if (a != null &&
|
||||
#if UNITY_6000_4_OR_NEWER
|
||||
!dupSet.Add(a.GetEntityId().GetRawData())
|
||||
#else
|
||||
!dupSet.Add((ulong)a.GetInstanceID())
|
||||
#endif
|
||||
)
|
||||
continue;
|
||||
|
||||
dedupList.Add(a);
|
||||
}
|
||||
return dedupList.ToArray();
|
||||
}
|
||||
|
||||
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
int BuildAnimationBakeList(IBaker baker, AnimationClip[] animationClips, Avatar avatar, out NativeArray<BlobAssetReference<AnimationClipBlob>> alreadyBakedList)
|
||||
{
|
||||
alreadyBakedList = new (animationClips.Length, Allocator.Temp);
|
||||
var rv = 0;
|
||||
for (var i = 0; i < animationClips.Length; ++i)
|
||||
{
|
||||
var ac = animationClips[i];
|
||||
if (ac == null)
|
||||
continue;
|
||||
|
||||
// Check for blob asset store first
|
||||
var animationHash = BakingUtils.ComputeAnimationHash(ac, avatar);
|
||||
var isAnimationExists = baker.TryGetBlobAssetReference<AnimationClipBlob>(animationHash, out var acb);
|
||||
if (!isAnimationExists)
|
||||
{
|
||||
// Try cached baked animation
|
||||
acb = BlobCache.LoadBakedAnimationFromCache(ac, avatar);
|
||||
if (acb == BlobAssetReference<AnimationClipBlob>.Null)
|
||||
{
|
||||
rv += 1;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Don't forget to add loaded animation to blob asset store
|
||||
baker.AddBlobAssetWithCustomHash(ref acb, animationHash);
|
||||
}
|
||||
}
|
||||
|
||||
alreadyBakedList[i] = acb;
|
||||
}
|
||||
|
||||
// Return count of animations need to perform full bake
|
||||
return rv;
|
||||
}
|
||||
|
||||
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
public NativeArray<BlobAssetReference<AnimationClipBlob>> BakeAnimations(IBaker baker, AnimationClip[] animationClips, Avatar avatar, GameObject animatedObjectRoot)
|
||||
{
|
||||
if (animationClips == null || animationClips.Length == 0)
|
||||
return default;
|
||||
|
||||
animationClips = Deduplicate(animationClips);
|
||||
|
||||
// Firstly create list of animations that need to be baked (not present in cache and in not in blob asset store)
|
||||
var numClipsToBake = BuildAnimationBakeList(baker, animationClips, avatar, out var alreadyBakedAnimations);
|
||||
|
||||
// If nothing to bake, just return already baked list
|
||||
if (numClipsToBake == 0)
|
||||
return alreadyBakedAnimations;
|
||||
|
||||
// Now bake animations that require full rebake
|
||||
// Need to make instance of object because when we will sample animations object placement can be modified.
|
||||
// Also prefabs will not update its transforms
|
||||
GameObject objectCopy = null;
|
||||
Animator animatorCopy = null;
|
||||
if (avatar != null)
|
||||
{
|
||||
objectCopy = GameObject.Instantiate(animatedObjectRoot);
|
||||
objectCopy.hideFlags = HideFlags.HideAndDontSave;
|
||||
animatorCopy = objectCopy.GetComponent<Animator>();
|
||||
if (animatorCopy == null)
|
||||
animatorCopy = objectCopy.AddComponent<Animator>();
|
||||
animatorCopy.avatar = avatar;
|
||||
}
|
||||
|
||||
for (var i = 0; i < animationClips.Length; ++i)
|
||||
{
|
||||
var clipBlob = alreadyBakedAnimations[i];
|
||||
var a = animationClips[i];
|
||||
if (clipBlob != BlobAssetReference<AnimationClipBlob>.Null)
|
||||
continue;
|
||||
|
||||
var animationHash = BakingUtils.ComputeAnimationHash(a, avatar);
|
||||
var isAnimationExists = baker.TryGetBlobAssetReference(animationHash, out clipBlob);
|
||||
if (!isAnimationExists)
|
||||
{
|
||||
clipBlob = CreateAnimationBlobAsset(a, animatorCopy, animationHash);
|
||||
baker.AddBlobAssetWithCustomHash(ref clipBlob, animationHash);
|
||||
}
|
||||
else
|
||||
{
|
||||
Debug.Log($"Animation '{a.name}' is duplicate!");
|
||||
}
|
||||
|
||||
alreadyBakedAnimations[i] = clipBlob;
|
||||
baker.DependsOn(a);
|
||||
}
|
||||
|
||||
if (objectCopy != null)
|
||||
GameObject.DestroyImmediate(objectCopy);
|
||||
|
||||
return alreadyBakedAnimations;
|
||||
}
|
||||
|
||||
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
public BlobAssetReference<AnimationClipBlob> CreateAnimationBlobAsset(AnimationClip ac, Animator animator, Hash128 animationHash)
|
||||
{
|
||||
var avatar = animator?.avatar;
|
||||
var acSettings = AnimationUtility.GetAnimationClipSettings(ac);
|
||||
|
||||
var bb = new BlobBuilder(Allocator.Temp);
|
||||
ref var rv = ref bb.ConstructRoot<AnimationClipBlob>();
|
||||
|
||||
#if RUKHANKA_DEBUG_INFO
|
||||
var startTimeMarker = Time.realtimeSinceStartupAsDouble;
|
||||
if (ac.name.Length > 0)
|
||||
bb.AllocateString(ref rv.name, ac.name);
|
||||
#endif
|
||||
|
||||
rv.length = ac.length;
|
||||
rv.looped = ac.isLooping;
|
||||
rv.hash = animationHash;
|
||||
rv.loopPoseBlend = acSettings.loopBlend;
|
||||
rv.cycleOffset = acSettings.cycleOffset;
|
||||
|
||||
BakeAnimationEvents(bb, ref rv, ac);
|
||||
BakeTrackSet(bb, ref rv.clipTracks, out var maxTrackKeyframeLength, -1, ac, animator, avatar);
|
||||
if (acSettings.additiveReferencePoseClip != null)
|
||||
BakeTrackSet(bb, ref rv.additiveReferencePoseFrame, out _, acSettings.additiveReferencePoseTime, acSettings.additiveReferencePoseClip, animator, avatar);
|
||||
|
||||
#if RUKHANKA_DEBUG_INFO
|
||||
var dt = Time.realtimeSinceStartupAsDouble - startTimeMarker;
|
||||
rv.bakingTime = (float)dt;
|
||||
#endif
|
||||
|
||||
rv.maxTrackKeyframeLength = maxTrackKeyframeLength;
|
||||
var bar = bb.CreateBlobAssetReference<AnimationClipBlob>(Allocator.Persistent);
|
||||
|
||||
// Save baked animation into cache
|
||||
BlobCache.SaveBakedAnimationToCache(ac, avatar, bar);
|
||||
|
||||
return bar;
|
||||
}
|
||||
|
||||
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
Keyframe[] GetKeyFramesArray(UnityEngine.AnimationCurve animationCurve, float frameTime)
|
||||
{
|
||||
if (frameTime < 0)
|
||||
return animationCurve.keys;
|
||||
|
||||
var oneFrameAnimation = new Keyframe[]
|
||||
{
|
||||
new (0, animationCurve.Evaluate(frameTime))
|
||||
};
|
||||
return oneFrameAnimation;
|
||||
}
|
||||
|
||||
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
void BakeTrackSet(BlobBuilder bb, ref TrackSet outTrackSet, out uint maxTrackKeyframeLength, float frameTime, AnimationClip ac, Animator animator, Avatar avatar)
|
||||
{
|
||||
var bindings = AnimationUtility.GetCurveBindings(ac);
|
||||
|
||||
keyFramesList.Clear();
|
||||
trackList.Clear();
|
||||
trackGroupHashes.Clear();
|
||||
trackGroupOffsets.Clear();
|
||||
#if RUKHANKA_DEBUG_INFO
|
||||
trackGroupNames.Clear();
|
||||
#endif
|
||||
|
||||
maxTrackKeyframeLength = 0u;
|
||||
foreach (var b in bindings)
|
||||
{
|
||||
var ec = AnimationUtility.GetEditorCurve(ac, b);
|
||||
var pb = ParseCurveBinding(ac, b, animator?.avatar);
|
||||
var inKeyframes = GetKeyFramesArray(ec, frameTime);
|
||||
var keyFramesRange = CopyKeyFrames(inKeyframes, ref keyFramesList);
|
||||
var trackData = CreateTrackData(keyFramesRange, pb);
|
||||
InsertTrackIntoGroup(trackData, pb.boneName);
|
||||
|
||||
maxTrackKeyframeLength = math.max(maxTrackKeyframeLength, (uint)keyFramesRange.y);
|
||||
}
|
||||
|
||||
if (avatar != null)
|
||||
{
|
||||
// Sample root and hips curves and from unity animations. Maybe sometime I will figure out all RootT/RootQ and body pose generation formulas and this step could be replaced with generation.
|
||||
SampleMissingCurves(ac, animator, frameTime);
|
||||
}
|
||||
|
||||
// Create special end empty track group because track group tracks count is calculated as trackGroup[i + 1].trackCount - trackGroup[i].trackCount
|
||||
trackGroupHashes.Add(0xffffffff);
|
||||
trackGroupOffsets.Add((uint)trackList.Length);
|
||||
#if RUKHANKA_DEBUG_INFO
|
||||
trackGroupNames.Add("RUKHANKA_TRAILING_TRACK");
|
||||
#endif
|
||||
|
||||
CreateTrackSetBlob(ref bb, ref outTrackSet, ac.name);
|
||||
}
|
||||
|
||||
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
unsafe void CreateTrackSetBlob(ref BlobBuilder bb, ref TrackSet outTrackSet, string animationName)
|
||||
{
|
||||
var keyFramesBlobArray = bb.Allocate(ref outTrackSet.keyframes, keyFramesList.Length);
|
||||
UnsafeUtility.MemCpy(keyFramesBlobArray.GetUnsafePtr(), keyFramesList.GetUnsafeReadOnlyPtr(), keyFramesList.Length * UnsafeUtility.SizeOf<KeyFrame>());
|
||||
|
||||
var tracksBlobArray = bb.Allocate(ref outTrackSet.tracks, trackList.Length);
|
||||
UnsafeUtility.MemCpy(tracksBlobArray.GetUnsafePtr(), trackList.GetUnsafeReadOnlyPtr(), trackList.Length * UnsafeUtility.SizeOf<Track>());
|
||||
|
||||
var trackGroupsBlobArray = bb.Allocate(ref outTrackSet.trackGroups, trackGroupOffsets.Length);
|
||||
UnsafeUtility.MemCpy(trackGroupsBlobArray.GetUnsafePtr(), trackGroupOffsets.GetUnsafeReadOnlyPtr(), trackGroupOffsets.Length * UnsafeUtility.SizeOf<uint>());
|
||||
|
||||
// Make a hash table from track group hashes
|
||||
var phtIsCreated = Perfect2HashTable.Build(trackGroupHashes.AsArray(), out var pht, out var seed);
|
||||
Assert.IsTrue(phtIsCreated, $"Cannot create track perfect hash table for animation {animationName}");
|
||||
|
||||
var phtBlobArray = bb.Allocate(ref outTrackSet.trackGroupPHT.pht, pht.Length);
|
||||
UnsafeUtility.MemCpy(phtBlobArray.GetUnsafePtr(), pht.GetUnsafeReadOnlyPtr(), pht.Length * UnsafeUtility.SizeOf<uint2>());
|
||||
|
||||
#if RUKHANKA_DEBUG_INFO
|
||||
var trackGroupDebugInfoArr = bb.Allocate(ref outTrackSet.trackGroupDebugInfo, trackGroupHashes.Length);
|
||||
for (var i = 0; i < trackGroupHashes.Length; ++i)
|
||||
{
|
||||
var trackGroupInfoBlob = new TrackGroupInfo()
|
||||
{
|
||||
hash = trackGroupHashes[i],
|
||||
name = trackGroupNames[i]
|
||||
};
|
||||
trackGroupDebugInfoArr[i] = trackGroupInfoBlob;
|
||||
}
|
||||
#endif
|
||||
|
||||
outTrackSet.trackGroupPHT.seed = seed;
|
||||
}
|
||||
|
||||
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
void MakeAdditiveReferencePoseFrame(ref NativeList<BoneClip> bc, AnimationClipSettings acs, Avatar avatar)
|
||||
{
|
||||
/*
|
||||
if (acs.additiveReferencePoseClip == null)
|
||||
return;
|
||||
|
||||
for (var k = 0; k < bc.Length; ++k)
|
||||
{
|
||||
var bcc = bc[k];
|
||||
for (var m = 0; m < bcc.animationCurves.Length; ++m)
|
||||
{
|
||||
ref var ac = ref bcc.animationCurves.ElementAt(m);
|
||||
}
|
||||
}
|
||||
|
||||
var clipPose = SampleAnimation(acs.additiveReferencePoseClip, avatar, acs.additiveReferencePoseTime);
|
||||
foreach (var c in clipPose)
|
||||
{
|
||||
for (var k = 0; k < bc.Length; ++k)
|
||||
{
|
||||
var bcc = bc[k];
|
||||
if (c.pb.boneName != bcc.name)
|
||||
continue;
|
||||
|
||||
for (var m = 0; m < bcc.animationCurves.Length; ++m)
|
||||
{
|
||||
ref var ac = ref bcc.animationCurves.ElementAt(m);
|
||||
|
||||
if (ac.bindingType == BindingType.Scale || (ac.bindingType == BindingType.Quaternion && ac.channelIndex == 3))
|
||||
ac.additiveReferenceValue = 1;
|
||||
|
||||
if (ac.bindingType == c.pb.bindingType && ac.channelIndex == c.pb.channelIndex)
|
||||
{
|
||||
ac.additiveReferenceValue = c.value;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
*/
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
+18
@@ -0,0 +1,18 @@
|
||||
fileFormatVersion: 2
|
||||
guid: aedde7e2d280f234882a7bb61985b6f7
|
||||
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.Hybrid/AnimationClip/AnimationClipBaker.cs
|
||||
uploadId: 897522
|
||||
+94
@@ -0,0 +1,94 @@
|
||||
using System;
|
||||
using Rukhanka.Toolbox;
|
||||
using Unity.Collections.LowLevel.Unsafe;
|
||||
using Unity.Entities;
|
||||
using FixedStringName = Unity.Collections.FixedString512Bytes;
|
||||
|
||||
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
namespace Rukhanka.Hybrid
|
||||
{
|
||||
// RTP - Ready to process
|
||||
namespace RTP
|
||||
{
|
||||
public struct AnimationClip: IDisposable
|
||||
{
|
||||
public FixedStringName name;
|
||||
public UnsafeList<BoneClip> bones;
|
||||
public UnsafeList<BoneClip> curves;
|
||||
public UnsafeList<AnimationEvent> events;
|
||||
public bool looped;
|
||||
public bool loopPoseBlend;
|
||||
public float cycleOffset;
|
||||
public float length;
|
||||
public float additiveReferencePoseTime;
|
||||
public bool hasRootMotionCurves;
|
||||
public Hash128 hash;
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
foreach (var a in bones) a.Dispose();
|
||||
foreach (var a in curves) a.Dispose();
|
||||
|
||||
bones.Dispose();
|
||||
curves.Dispose();
|
||||
events.Dispose();
|
||||
}
|
||||
}
|
||||
|
||||
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
public struct BoneClip: IEquatable<Hash128>, IDisposable
|
||||
{
|
||||
public FixedStringName name;
|
||||
public Hash128 nameHash;
|
||||
public bool isHumanMuscleClip;
|
||||
public UnsafeList<AnimationCurve> animationCurves;
|
||||
|
||||
public bool Equals(Hash128 o) => o == nameHash;
|
||||
|
||||
public void SetName(string n)
|
||||
{
|
||||
name = n;
|
||||
nameHash = name.CalculateHash128();
|
||||
}
|
||||
|
||||
public void DisposeCurves()
|
||||
{
|
||||
foreach (var a in animationCurves) a.Dispose();
|
||||
animationCurves.Clear();
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
DisposeCurves();
|
||||
animationCurves.Dispose();
|
||||
}
|
||||
}
|
||||
|
||||
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
public struct AnimationCurve: IDisposable
|
||||
{
|
||||
public BindingType bindingType;
|
||||
public short channelIndex; // 0, 1, 2, 3 -> x, y, z, w
|
||||
public UnsafeList<KeyFrame> keyFrames;
|
||||
|
||||
public void Dispose() => keyFrames.Dispose();
|
||||
}
|
||||
|
||||
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
public struct AnimationEvent
|
||||
{
|
||||
public FixedStringName name;
|
||||
public float time;
|
||||
public float floatParam;
|
||||
public int intParam;
|
||||
public FixedStringName stringParam;
|
||||
}
|
||||
|
||||
} // RTP
|
||||
}
|
||||
|
||||
|
||||
+18
@@ -0,0 +1,18 @@
|
||||
fileFormatVersion: 2
|
||||
guid: a14aafd10469a274986298f1cfa7e496
|
||||
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.Hybrid/AnimationClip/AnimationClipBakerData.cs
|
||||
uploadId: 897522
|
||||
+189
@@ -0,0 +1,189 @@
|
||||
#if UNITY_EDITOR
|
||||
|
||||
using System.Collections.Generic;
|
||||
|
||||
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
namespace Rukhanka.Hybrid
|
||||
{
|
||||
public partial class AnimationClipBaker
|
||||
{
|
||||
static Dictionary<string, ParsedCurveBinding> humanoidMappingTable;
|
||||
static Dictionary<string, string> humanoidMuscleNameFromCurveProperty;
|
||||
|
||||
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
static AnimationClipBaker()
|
||||
{
|
||||
humanoidMappingTable = new ()
|
||||
{
|
||||
// --- Head ---
|
||||
// Neck
|
||||
{ "Neck Nod Down-Up", new ParsedCurveBinding() { boneName = "Neck", channelIndex = 2, bindingType = BindingType.HumanMuscle }},
|
||||
{ "Neck Tilt Left-Right", new ParsedCurveBinding() { boneName = "Neck", channelIndex = 1, bindingType = BindingType.HumanMuscle }},
|
||||
{ "Neck Turn Left-Right", new ParsedCurveBinding() { boneName = "Neck", channelIndex = 0, bindingType = BindingType.HumanMuscle }},
|
||||
// Head
|
||||
{ "Head Nod Down-Up", new ParsedCurveBinding() { boneName = "Head", channelIndex = 2, bindingType = BindingType.HumanMuscle }},
|
||||
{ "Head Tilt Left-Right", new ParsedCurveBinding() { boneName = "Head", channelIndex = 1, bindingType = BindingType.HumanMuscle }},
|
||||
{ "Head Turn Left-Right", new ParsedCurveBinding() { boneName = "Head", channelIndex = 0, bindingType = BindingType.HumanMuscle }},
|
||||
// Left Eye
|
||||
{ "Left Eye Down-Up", new ParsedCurveBinding() { boneName = "LeftEye", channelIndex = 2, bindingType = BindingType.HumanMuscle }},
|
||||
{ "Left Eye In-Out", new ParsedCurveBinding() { boneName = "LeftEye", channelIndex = 1, bindingType = BindingType.HumanMuscle }},
|
||||
// Right Eye
|
||||
{ "Right Eye Down-Up", new ParsedCurveBinding() { boneName = "RightEye", channelIndex = 2, bindingType = BindingType.HumanMuscle }},
|
||||
{ "Right Eye In-Out", new ParsedCurveBinding() { boneName = "RightEye", channelIndex = 1, bindingType = BindingType.HumanMuscle }},
|
||||
// Jaw
|
||||
{ "Jaw Close", new ParsedCurveBinding() { boneName = "Jaw", channelIndex = 2, bindingType = BindingType.HumanMuscle }},
|
||||
{ "Jaw Left-Right", new ParsedCurveBinding() { boneName = "Jaw", channelIndex = 1, bindingType = BindingType.HumanMuscle }},
|
||||
|
||||
// --- Body ---
|
||||
// Spine
|
||||
{ "Spine Front-Back", new ParsedCurveBinding() { boneName = "Spine", channelIndex = 2, bindingType = BindingType.HumanMuscle }},
|
||||
{ "Spine Left-Right", new ParsedCurveBinding() { boneName = "Spine", channelIndex = 1, bindingType = BindingType.HumanMuscle }},
|
||||
{ "Spine Twist Left-Right", new ParsedCurveBinding() { boneName = "Spine", channelIndex = 0, bindingType = BindingType.HumanMuscle }},
|
||||
// Chest
|
||||
{ "Chest Front-Back", new ParsedCurveBinding() { boneName = "Chest", channelIndex = 2, bindingType = BindingType.HumanMuscle }},
|
||||
{ "Chest Left-Right", new ParsedCurveBinding() { boneName = "Chest", channelIndex = 1, bindingType = BindingType.HumanMuscle }},
|
||||
{ "Chest Twist Left-Right", new ParsedCurveBinding() { boneName = "Chest", channelIndex = 0, bindingType = BindingType.HumanMuscle }},
|
||||
// UpperChest
|
||||
{ "UpperChest Front-Back", new ParsedCurveBinding() { boneName = "UpperChest", channelIndex = 2, bindingType = BindingType.HumanMuscle }},
|
||||
{ "UpperChest Left-Right", new ParsedCurveBinding() { boneName = "UpperChest", channelIndex = 1, bindingType = BindingType.HumanMuscle }},
|
||||
{ "UpperChest Twist Left-Right", new ParsedCurveBinding() { boneName = "UpperChest", channelIndex = 0, bindingType = BindingType.HumanMuscle }},
|
||||
|
||||
// --- Left Arm ---
|
||||
// LeftShoulder
|
||||
{ "Left Shoulder Down-Up", new ParsedCurveBinding() { boneName = "LeftShoulder", channelIndex = 2, bindingType = BindingType.HumanMuscle }},
|
||||
{ "Left Shoulder Front-Back", new ParsedCurveBinding() { boneName = "LeftShoulder", channelIndex = 1, bindingType = BindingType.HumanMuscle }},
|
||||
// LeftUpperArm
|
||||
{ "Left Arm Down-Up", new ParsedCurveBinding() { boneName = "LeftUpperArm", channelIndex = 2, bindingType = BindingType.HumanMuscle }},
|
||||
{ "Left Arm Front-Back", new ParsedCurveBinding() { boneName = "LeftUpperArm", channelIndex = 1, bindingType = BindingType.HumanMuscle }},
|
||||
{ "Left Arm Twist In-Out", new ParsedCurveBinding() { boneName = "LeftUpperArm", channelIndex = 0, bindingType = BindingType.HumanMuscle }},
|
||||
// LeftLowerArm
|
||||
{ "Left Forearm Stretch", new ParsedCurveBinding() { boneName = "LeftLowerArm", channelIndex = 2, bindingType = BindingType.HumanMuscle }},
|
||||
{ "Left Forearm Twist In-Out", new ParsedCurveBinding() { boneName = "LeftLowerArm", channelIndex = 0, bindingType = BindingType.HumanMuscle }},
|
||||
// LeftHand
|
||||
{ "Left Hand Down-Up", new ParsedCurveBinding() { boneName = "LeftHand", channelIndex = 2, bindingType = BindingType.HumanMuscle }},
|
||||
{ "Left Hand In-Out", new ParsedCurveBinding() { boneName = "LeftHand", channelIndex = 1, bindingType = BindingType.HumanMuscle }},
|
||||
|
||||
// --- Left Hand ---
|
||||
// Thumb 1
|
||||
{ "LeftHand.Thumb.1 Stretched", new ParsedCurveBinding() { boneName = "Left Thumb Proximal", channelIndex = 2, bindingType = BindingType.HumanMuscle }},
|
||||
{ "LeftHand.Thumb.Spread", new ParsedCurveBinding() { boneName = "Left Thumb Proximal", channelIndex = 1, bindingType = BindingType.HumanMuscle }},
|
||||
// Thumb 2
|
||||
{ "LeftHand.Thumb.2 Stretched", new ParsedCurveBinding() { boneName = "Left Thumb Intermediate", channelIndex = 2, bindingType = BindingType.HumanMuscle }},
|
||||
// Thumb 3
|
||||
{ "LeftHand.Thumb.3 Stretched", new ParsedCurveBinding() { boneName = "Left Thumb Distal", channelIndex = 2, bindingType = BindingType.HumanMuscle }},
|
||||
// Index 1
|
||||
{ "LeftHand.Index.1 Stretched", new ParsedCurveBinding() { boneName = "Left Index Proximal", channelIndex = 2, bindingType = BindingType.HumanMuscle }},
|
||||
{ "LeftHand.Index.Spread", new ParsedCurveBinding() { boneName = "Left Index Proximal", channelIndex = 1, bindingType = BindingType.HumanMuscle }},
|
||||
// Index 2
|
||||
{ "LeftHand.Index.2 Stretched", new ParsedCurveBinding() { boneName = "Left Index Intermediate", channelIndex = 2, bindingType = BindingType.HumanMuscle }},
|
||||
// Index 3
|
||||
{ "LeftHand.Index.3 Stretched", new ParsedCurveBinding() { boneName = "Left Index Distal", channelIndex = 2, bindingType = BindingType.HumanMuscle }},
|
||||
// Middle 1
|
||||
{ "LeftHand.Middle.1 Stretched", new ParsedCurveBinding() { boneName = "Left Middle Proximal", channelIndex = 2, bindingType = BindingType.HumanMuscle }},
|
||||
{ "LeftHand.Middle.Spread", new ParsedCurveBinding() { boneName = "Left Middle Proximal", channelIndex = 1, bindingType = BindingType.HumanMuscle }},
|
||||
// Middle 2
|
||||
{ "LeftHand.Middle.2 Stretched", new ParsedCurveBinding() { boneName = "Left Middle Intermediate", channelIndex = 2, bindingType = BindingType.HumanMuscle }},
|
||||
// Middle 3
|
||||
{ "LeftHand.Middle.3 Stretched", new ParsedCurveBinding() { boneName = "Left Middle Distal", channelIndex = 2, bindingType = BindingType.HumanMuscle }},
|
||||
// Ring 1
|
||||
{ "LeftHand.Ring.1 Stretched", new ParsedCurveBinding() { boneName = "Left Ring Proximal", channelIndex = 2, bindingType = BindingType.HumanMuscle }},
|
||||
{ "LeftHand.Ring.Spread", new ParsedCurveBinding() { boneName = "Left Ring Proximal", channelIndex = 1, bindingType = BindingType.HumanMuscle }},
|
||||
// Ring 2
|
||||
{ "LeftHand.Ring.2 Stretched", new ParsedCurveBinding() { boneName = "Left Ring Intermediate", channelIndex = 2, bindingType = BindingType.HumanMuscle }},
|
||||
// Ring 3
|
||||
{ "LeftHand.Ring.3 Stretched", new ParsedCurveBinding() { boneName = "Left Ring Distal", channelIndex = 2, bindingType = BindingType.HumanMuscle }},
|
||||
// Little 1
|
||||
{ "LeftHand.Little.1 Stretched", new ParsedCurveBinding() { boneName = "Left Little Proximal", channelIndex = 2, bindingType = BindingType.HumanMuscle }},
|
||||
{ "LeftHand.Little.Spread", new ParsedCurveBinding() { boneName = "Left Little Proximal", channelIndex = 1, bindingType = BindingType.HumanMuscle }},
|
||||
// Little 2
|
||||
{ "LeftHand.Little.2 Stretched", new ParsedCurveBinding() { boneName = "Left Little Intermediate", channelIndex = 2, bindingType = BindingType.HumanMuscle }},
|
||||
// Little 3
|
||||
{ "LeftHand.Little.3 Stretched", new ParsedCurveBinding() { boneName = "Left Little Distal", channelIndex = 2, bindingType = BindingType.HumanMuscle }},
|
||||
|
||||
// --- Right Arm ---
|
||||
// RightShoulder
|
||||
{ "Right Shoulder Down-Up", new ParsedCurveBinding() { boneName = "RightShoulder", channelIndex = 2, bindingType = BindingType.HumanMuscle }},
|
||||
{ "Right Shoulder Front-Back", new ParsedCurveBinding() { boneName = "RightShoulder", channelIndex = 1, bindingType = BindingType.HumanMuscle }},
|
||||
// RightUpperArm
|
||||
{ "Right Arm Down-Up", new ParsedCurveBinding() { boneName = "RightUpperArm", channelIndex = 2, bindingType = BindingType.HumanMuscle }},
|
||||
{ "Right Arm Front-Back", new ParsedCurveBinding() { boneName = "RightUpperArm", channelIndex = 1, bindingType = BindingType.HumanMuscle }},
|
||||
{ "Right Arm Twist In-Out", new ParsedCurveBinding() { boneName = "RightUpperArm", channelIndex = 0, bindingType = BindingType.HumanMuscle }},
|
||||
// RightLowerArm
|
||||
{ "Right Forearm Stretch", new ParsedCurveBinding() { boneName = "RightLowerArm", channelIndex = 2, bindingType = BindingType.HumanMuscle }},
|
||||
{ "Right Forearm Twist In-Out", new ParsedCurveBinding() { boneName = "RightLowerArm", channelIndex = 0, bindingType = BindingType.HumanMuscle }},
|
||||
// RightHand
|
||||
{ "Right Hand Down-Up", new ParsedCurveBinding() { boneName = "RightHand", channelIndex = 2, bindingType = BindingType.HumanMuscle }},
|
||||
{ "Right Hand In-Out", new ParsedCurveBinding() { boneName = "RightHand", channelIndex = 1, bindingType = BindingType.HumanMuscle }},
|
||||
|
||||
// --- Right Hand ---
|
||||
// Thumb 1
|
||||
{ "RightHand.Thumb.1 Stretched", new ParsedCurveBinding() { boneName = "Right Thumb Proximal", channelIndex = 2, bindingType = BindingType.HumanMuscle }},
|
||||
{ "RightHand.Thumb.Spread", new ParsedCurveBinding() { boneName = "Right Thumb Proximal", channelIndex = 1, bindingType = BindingType.HumanMuscle }},
|
||||
// Thumb 2
|
||||
{ "RightHand.Thumb.2 Stretched", new ParsedCurveBinding() { boneName = "Right Thumb Intermediate", channelIndex = 2, bindingType = BindingType.HumanMuscle }},
|
||||
// Thumb 3
|
||||
{ "RightHand.Thumb.3 Stretched", new ParsedCurveBinding() { boneName = "Right Thumb Distal", channelIndex = 2, bindingType = BindingType.HumanMuscle }},
|
||||
// Index 1
|
||||
{ "RightHand.Index.1 Stretched", new ParsedCurveBinding() { boneName = "Right Index Proximal", channelIndex = 2, bindingType = BindingType.HumanMuscle }},
|
||||
{ "RightHand.Index.Spread", new ParsedCurveBinding() { boneName = "Right Index Proximal", channelIndex = 1, bindingType = BindingType.HumanMuscle }},
|
||||
// Index 2
|
||||
{ "RightHand.Index.2 Stretched", new ParsedCurveBinding() { boneName = "Right Index Intermediate", channelIndex = 2, bindingType = BindingType.HumanMuscle }},
|
||||
// Index 3
|
||||
{ "RightHand.Index.3 Stretched", new ParsedCurveBinding() { boneName = "Right Index Distal", channelIndex = 2, bindingType = BindingType.HumanMuscle }},
|
||||
// Middle 1
|
||||
{ "RightHand.Middle.1 Stretched", new ParsedCurveBinding() { boneName = "Right Middle Proximal", channelIndex = 2, bindingType = BindingType.HumanMuscle }},
|
||||
{ "RightHand.Middle.Spread", new ParsedCurveBinding() { boneName = "Right Middle Proximal", channelIndex = 1, bindingType = BindingType.HumanMuscle }},
|
||||
// Middle 2
|
||||
{ "RightHand.Middle.2 Stretched", new ParsedCurveBinding() { boneName = "Right Middle Intermediate", channelIndex = 2, bindingType = BindingType.HumanMuscle }},
|
||||
// Middle 3
|
||||
{ "RightHand.Middle.3 Stretched", new ParsedCurveBinding() { boneName = "Right Middle Distal", channelIndex = 2, bindingType = BindingType.HumanMuscle }},
|
||||
// Ring 1
|
||||
{ "RightHand.Ring.1 Stretched", new ParsedCurveBinding() { boneName = "Right Ring Proximal", channelIndex = 2, bindingType = BindingType.HumanMuscle }},
|
||||
{ "RightHand.Ring.Spread", new ParsedCurveBinding() { boneName = "Right Ring Proximal", channelIndex = 1, bindingType = BindingType.HumanMuscle }},
|
||||
// Ring 2
|
||||
{ "RightHand.Ring.2 Stretched", new ParsedCurveBinding() { boneName = "Right Ring Intermediate", channelIndex = 2, bindingType = BindingType.HumanMuscle }},
|
||||
// Ring 3
|
||||
{ "RightHand.Ring.3 Stretched", new ParsedCurveBinding() { boneName = "Right Ring Distal", channelIndex = 2, bindingType = BindingType.HumanMuscle }},
|
||||
// Little 1
|
||||
{ "RightHand.Little.1 Stretched", new ParsedCurveBinding() { boneName = "Right Little Proximal", channelIndex = 2, bindingType = BindingType.HumanMuscle }},
|
||||
{ "RightHand.Little.Spread", new ParsedCurveBinding() { boneName = "Right Little Proximal", channelIndex = 1, bindingType = BindingType.HumanMuscle }},
|
||||
// Little 2
|
||||
{ "RightHand.Little.2 Stretched", new ParsedCurveBinding() { boneName = "Right Little Intermediate", channelIndex = 2, bindingType = BindingType.HumanMuscle }},
|
||||
// Little 3
|
||||
{ "RightHand.Little.3 Stretched", new ParsedCurveBinding() { boneName = "Right Little Distal", channelIndex = 2, bindingType = BindingType.HumanMuscle }},
|
||||
|
||||
// --- Left Leg ---
|
||||
// LeftUpperLeg
|
||||
{ "Left Upper Leg Front-Back", new ParsedCurveBinding() { boneName = "LeftUpperLeg", channelIndex = 2, bindingType = BindingType.HumanMuscle }},
|
||||
{ "Left Upper Leg In-Out", new ParsedCurveBinding() { boneName = "LeftUpperLeg", channelIndex = 1, bindingType = BindingType.HumanMuscle }},
|
||||
{ "Left Upper Leg Twist In-Out", new ParsedCurveBinding() { boneName = "LeftUpperLeg", channelIndex = 0, bindingType = BindingType.HumanMuscle }},
|
||||
// LeftLowerLeg
|
||||
{ "Left Lower Leg Stretch", new ParsedCurveBinding() { boneName = "LeftLowerLeg", channelIndex = 2, bindingType = BindingType.HumanMuscle }},
|
||||
{ "Left Lower Leg Twist In-Out", new ParsedCurveBinding() { boneName = "LeftLowerLeg", channelIndex = 0, bindingType = BindingType.HumanMuscle }},
|
||||
// LeftFoot
|
||||
{ "Left Foot Up-Down", new ParsedCurveBinding() { boneName = "LeftFoot", channelIndex = 2, bindingType = BindingType.HumanMuscle }},
|
||||
{ "Left Foot Twist In-Out", new ParsedCurveBinding() { boneName = "LeftFoot", channelIndex = 1, bindingType = BindingType.HumanMuscle }},
|
||||
// LeftToes
|
||||
{ "Left Toes Up-Down", new ParsedCurveBinding() { boneName = "LeftToes", channelIndex = 2, bindingType = BindingType.HumanMuscle }},
|
||||
|
||||
// --- Right Leg ---
|
||||
// RightUpperLeg
|
||||
{ "Right Upper Leg Front-Back", new ParsedCurveBinding() { boneName = "RightUpperLeg", channelIndex = 2, bindingType = BindingType.HumanMuscle }},
|
||||
{ "Right Upper Leg In-Out", new ParsedCurveBinding() { boneName = "RightUpperLeg", channelIndex = 1, bindingType = BindingType.HumanMuscle }},
|
||||
{ "Right Upper Leg Twist In-Out", new ParsedCurveBinding() { boneName = "RightUpperLeg", channelIndex = 0, bindingType = BindingType.HumanMuscle }},
|
||||
// RightLowerLeg
|
||||
{ "Right Lower Leg Stretch", new ParsedCurveBinding() { boneName = "RightLowerLeg", channelIndex = 2, bindingType = BindingType.HumanMuscle }},
|
||||
{ "Right Lower Leg Twist In-Out", new ParsedCurveBinding() { boneName = "RightLowerLeg", channelIndex = 0, bindingType = BindingType.HumanMuscle }},
|
||||
// RightFoot
|
||||
{ "Right Foot Up-Down", new ParsedCurveBinding() { boneName = "RightFoot", channelIndex = 2, bindingType = BindingType.HumanMuscle }},
|
||||
{ "Right Foot Twist In-Out", new ParsedCurveBinding() { boneName = "RightFoot", channelIndex = 1, bindingType = BindingType.HumanMuscle }},
|
||||
// RightToes
|
||||
{ "Right Toes Up-Down", new ParsedCurveBinding() { boneName = "RightToes", channelIndex = 2, bindingType = BindingType.HumanMuscle }},
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
+18
@@ -0,0 +1,18 @@
|
||||
fileFormatVersion: 2
|
||||
guid: a0bc7b37fdee74b4e8e6e52530590fd9
|
||||
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.Hybrid/AnimationClip/AnimationClipHumanoidMapTable.cs
|
||||
uploadId: 897522
|
||||
@@ -0,0 +1,39 @@
|
||||
#if UNITY_EDITOR
|
||||
|
||||
using System.Collections.Generic;
|
||||
using Unity.Collections;
|
||||
using UnityEditor;
|
||||
using UnityEngine;
|
||||
|
||||
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
namespace Rukhanka.Hybrid
|
||||
{
|
||||
public partial class AnimationClipBaker
|
||||
{
|
||||
struct SampledCurve
|
||||
{
|
||||
public ParsedCurveBinding pb;
|
||||
public float value;
|
||||
}
|
||||
|
||||
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
List<SampledCurve> SampleAnimation(AnimationClip ac, Avatar avatar, float time)
|
||||
{
|
||||
var rv = new List<SampledCurve>();
|
||||
var bindings = AnimationUtility.GetCurveBindings(ac);
|
||||
foreach (var cb in bindings)
|
||||
{
|
||||
var ec = AnimationUtility.GetEditorCurve(ac, cb);
|
||||
var v = new SampledCurve();
|
||||
v.pb = ParseCurveBinding(ac, cb, avatar);
|
||||
v.value = ec.Evaluate(time);
|
||||
rv.Add(v);
|
||||
}
|
||||
return rv;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
+18
@@ -0,0 +1,18 @@
|
||||
fileFormatVersion: 2
|
||||
guid: b7296fa1c2965e841a8a623e67ae1ed9
|
||||
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.Hybrid/AnimationClip/AnimationSampler.cs
|
||||
uploadId: 897522
|
||||
Reference in New Issue
Block a user