Netcode Bootstrap

This commit is contained in:
Luis Gonzalez
2026-05-31 14:27:52 -07:00
parent 99d8d2d2a9
commit 7fa77ce821
1813 changed files with 2921554 additions and 84 deletions
@@ -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
@@ -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
@@ -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
}
@@ -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
@@ -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
@@ -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
@@ -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