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,71 @@
using System.Reflection;
using UnityEngine;
/////////////////////////////////////////////////////////////////////////////////
namespace Rukhanka.Hybrid
{
public static class AvatarExtensions
{
static readonly MethodInfo getPreRotationFn = typeof(Avatar).GetMethod("GetPreRotation", BindingFlags.NonPublic | BindingFlags.Instance);
static readonly MethodInfo getPostRotationFn = typeof(Avatar).GetMethod("GetPostRotation", BindingFlags.NonPublic | BindingFlags.Instance);
static readonly MethodInfo getLimitSignFn = typeof(Avatar).GetMethod("GetLimitSign", BindingFlags.NonPublic | BindingFlags.Instance);
static readonly MethodInfo getZYPostQFn = typeof(Avatar).GetMethod("GetZYPostQ", BindingFlags.NonPublic | BindingFlags.Instance);
static readonly MethodInfo getZYRollFn = typeof(Avatar).GetMethod("GetZYRoll", BindingFlags.NonPublic | BindingFlags.Instance);
static readonly MethodInfo getAxisLengthFn = typeof(Avatar).GetMethod("GetAxisLength", BindingFlags.NonPublic | BindingFlags.Instance);
/////////////////////////////////////////////////////////////////////////////////
public static Quaternion GetZYPostQ(this Avatar a, int humanId, Quaternion parentQ, Quaternion q)
{
return (Quaternion)getZYPostQFn.Invoke(a, new object[] {humanId, parentQ, q});
}
/////////////////////////////////////////////////////////////////////////////////
public static Quaternion GetZYRoll(this Avatar a, int humanId, Vector3 uvw)
{
return (Quaternion)getZYRollFn.Invoke(a, new object[] {humanId, uvw});
}
/////////////////////////////////////////////////////////////////////////////////
public static float GetAxisLength(this Avatar a, int humanId)
{
return (float)getAxisLengthFn.Invoke(a, new object[] {humanId});
}
/////////////////////////////////////////////////////////////////////////////////
public static Quaternion GetPreRotation(this Avatar a, int humanId)
{
return (Quaternion)getPreRotationFn.Invoke(a, new object[] {humanId});
}
/////////////////////////////////////////////////////////////////////////////////
public static Quaternion GetPostRotation(this Avatar a, int humanId)
{
return (Quaternion)getPostRotationFn.Invoke(a, new object[] {humanId});
}
/////////////////////////////////////////////////////////////////////////////////
public static Vector3 GetLimitSign(this Avatar a, int humanId)
{
return (Vector3)getLimitSignFn.Invoke(a, new object[] {humanId});
}
/////////////////////////////////////////////////////////////////////////////////
public static string GetRootMotionNodeName(this Avatar a)
{
if (a == null) return "";
var fi = typeof(HumanDescription).GetField("m_RootMotionBoneName", BindingFlags.NonPublic | BindingFlags.Instance);
return fi == null ? "" : (string)fi.GetValue(a.humanDescription);
}
}
}
@@ -0,0 +1,18 @@
fileFormatVersion: 2
guid: 32c3a7f3e2e8b3047b37fc8615a558b9
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/Rig/AvatarExtensions.cs
uploadId: 897522
@@ -0,0 +1,31 @@
using Unity.Collections;
using Unity.Entities;
using Unity.Entities.Hybrid.Baking;
/////////////////////////////////////////////////////////////////////////////////
namespace Rukhanka.Hybrid
{
[WorldSystemFilter(WorldSystemFilterFlags.BakingSystem)]
[UpdateInGroup(typeof(BakingSystemGroup))]
[UpdateBefore(typeof(BakingOnlyEntityAuthoringBakingSystem))]
partial class FlatHierarchyStripBoneEntitiesBakingSystem : SystemBase
{
protected override void OnUpdate()
{
var ecb = new EntityCommandBuffer(Allocator.Temp);
foreach (var boneEntitiesToRemove in SystemAPI.Query<DynamicBuffer<BoneEntitiesToRemove>>().WithOptions(EntityQueryOptions.IncludePrefab))
{
for (int i = 0; i < boneEntitiesToRemove.Length; ++i)
{
var e = boneEntitiesToRemove[i].boneEntity;
if (EntityManager.Exists(e))
ecb.AddComponent<BakingOnlyEntity>(e);
}
}
ecb.Playback(EntityManager);
}
}
}
@@ -0,0 +1,18 @@
fileFormatVersion: 2
guid: bb83eb059c52e0f4db966da19bc1668b
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/Rig/FlatHierarchyStripBoneEntitiesBakingSystem.cs
uploadId: 897522
@@ -0,0 +1,12 @@
using UnityEngine;
////////////////////////////////////////////////////////////////////////////////////////
namespace Rukhanka.Hybrid
{
public class HumanBodyPartOverrideAuthoring: MonoBehaviour
{
public AvatarMaskBodyPart humanBodyPart;
}
}
@@ -0,0 +1,18 @@
fileFormatVersion: 2
guid: 0e4b4300f9d9ae3429077a8035ffb6fd
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/Rig/HumanBodyPartOverrideAuthoring.cs
uploadId: 897522
@@ -0,0 +1,87 @@
using Unity.Mathematics;
using UnityEngine;
using UnityEngine.Assertions;
using Hash128 = Unity.Entities.Hash128;
////////////////////////////////////////////////////////////////////////////////////////
namespace Rukhanka.Hybrid
{
[HelpURL("https://docs.rukhanka.com/getting_started#rig-definition")]
[Icon(iconPath)]
public class RigDefinitionAuthoring: MonoBehaviour
{
public const string iconPath = "Packages/com.rukhanka.animation/Rukhanka.Editor/Editor Default Resources/Icons/Icon@64.png";
public enum BoneEntityStrippingMode
{
None,
Automatic,
Manual
}
public enum RigConfigSource
{
FromAnimator,
UserDefined
}
public enum AnimationEngine
{
CPU,
GPU
}
public enum RootMotionMode
{
Normal,
DisableBuiltinMovement
}
public RigConfigSource rigConfigSource;
public Avatar avatar;
public AvatarMask avatarOptimizationMask;
public bool applyRootMotion;
public bool animationCulling;
[Tooltip("<color=Cyan><b>None</b></color> - keep all skeleton bone entities.\n<color=Cyan><b>Automatic</b></color> - automatically strip unreferenced bone entities.\n<color=Cyan><b>Manual</b></color> - included and stripped bone entities will be taken from specified avatar mask. This mode will make 'flat' bone hierarchy.")]
public BoneEntityStrippingMode boneEntityStrippingMode;
[Tooltip("<color=Cyan><b>Normal</b></color> - Compute root motion movement from animations and apply it to the animated entity pose.\n<color=Cyan><b>Disable Builtin Movement</b></color> - Compute root motion movement but don't apply it to the entity pose. 'RootMotionVelocityComponent' can be used to get movement velocity.")]
public RootMotionMode rootMotionMode;
public AvatarMask boneStrippingMask;
public bool hasAnimationEvents;
public bool hasAnimatorControllerEvents;
public AnimationEngine animationEngine;
////////////////////////////////////////////////////////////////////////////////////////
public Avatar GetAvatar()
{
var rv = avatar;
if (rigConfigSource == RigConfigSource.FromAnimator)
{
var anm = GetComponent<Animator>();
if (anm)
rv = anm.avatar;
}
return rv;
}
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
public Hash128 CalculateRigHash()
{
#if UNITY_EDITOR
var a = GetAvatar();
Object obj = a == null ? gameObject : a;
var h = BakingUtils.GetAssetID(obj);
var rv = new Hash128(h.x, h.y, 0, 0);
Assert.IsTrue(!math.all(rv.Value == default), $"Rig hash error '{name}'");
#else
var rv = new Hash128();
#endif
return rv;
}
}
}
@@ -0,0 +1,18 @@
fileFormatVersion: 2
guid: 2fb757bd46edfe349a52b4ea886d67e7
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/Rig/RigDefinitionAuthoring.cs
uploadId: 897522
@@ -0,0 +1,600 @@
using Unity.Collections;
using Unity.Entities;
using UnityEngine;
using FixedStringName = Unity.Collections.FixedString512Bytes;
using System.Reflection;
using System.Collections.Generic;
using System;
using Unity.Assertions;
using Unity.Mathematics;
using Rukhanka.Toolbox;
using Hash128 = Unity.Entities.Hash128;
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
namespace Rukhanka.Hybrid
{
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
[TemporaryBakingType]
internal struct BoneEntitiesToRemove : IBufferElementData
{
public Entity boneEntity;
}
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
[TemporaryBakingType]
internal struct BoneEntityRef: IBufferElementData
{
public Entity boneEntity;
public int rigBoneIndex;
}
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
internal class InternalSkeletonBone
{
public Transform boneTransform;
public string name;
public string parentName;
public Vector3 position;
public Quaternion rotation;
public Vector3 scale;
}
//=================================================================================================================//
public partial class RigDefinitionBaker: Baker<RigDefinitionAuthoring>
{
static FieldInfo parentBoneNameField;
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
static RigDefinitionBaker()
{
parentBoneNameField = typeof(SkeletonBone).GetField("parentName", BindingFlags.NonPublic | BindingFlags.Instance);
}
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
public override void Bake(RigDefinitionAuthoring a)
{
var e = GetEntity(TransformUsageFlags.Dynamic);
var animator = GetComponent<Animator>();
if (a.rigConfigSource == RigDefinitionAuthoring.RigConfigSource.FromAnimator)
{
Assert.IsNotNull(animator, "Rig is configured to use parameters from Unity.Animator, but no one found. Please switch to manual configuration mode, or attach Animator to the authoring GameObject.");
a.avatar = animator.avatar;
a.animationCulling = animator.cullingMode != AnimatorCullingMode.AlwaysAnimate;
a.applyRootMotion = animator.applyRootMotion;
}
DependsOn(a.avatar);
AddBuffer<AnimationToProcessComponent>(e);
AddComponent<GPURigFrameOffsetsComponent>(e);
CreateRigDefinitionFromRigAuthoring(e, a);
}
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
InternalSkeletonBone CreateSkeletonBoneFromTransform(Transform t, string parentName)
{
var bone = new InternalSkeletonBone();
bone.boneTransform = t;
bone.name = t.name;
bone.position = t.localPosition;
bone.rotation = t.localRotation;
bone.scale = t.localScale;
bone.parentName = parentName;
return bone;
}
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
void TransformHierarchyWalk(Transform parent, List<InternalSkeletonBone> sb)
{
for (int i = 0; i < parent.childCount; ++i)
{
var c = parent.GetChild(i);
var ct = c.transform;
var bone = CreateSkeletonBoneFromTransform(ct, parent.name);
sb.Add(bone);
TransformHierarchyWalk(ct, sb);
}
}
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
List<InternalSkeletonBone> CreateAvatarFromObjectHierarchy(GameObject root)
{
// Manually fill all bone transforms
var sb = new List<InternalSkeletonBone>();
var rootBone = CreateSkeletonBoneFromTransform(root.transform, "");
sb.Add(rootBone);
TransformHierarchyWalk(root.transform, sb);
return sb;
}
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
int GetRigRootBoneIndex(Avatar avatar, List<InternalSkeletonBone> rigBones)
{
if (avatar == null)
return 0;
var rootBoneName = avatar.GetRootMotionNodeName();
if (avatar.isHuman)
{
var hd = avatar.humanDescription;
var humanBoneIndexInDesc = Array.FindIndex(hd.human, x => x.humanName == "Hips");
rootBoneName = hd.human[humanBoneIndexInDesc].boneName;
}
var rv = rigBones.FindIndex(x => x.name == rootBoneName);
return math.max(rv, 0);
}
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
int DeepestHierarchyBoneCount(in BlobBuilderArray<RigBoneInfo> rigBones)
{
var rv = 0;
for (var i = 0; i < rigBones.Length; ++i)
{
var numBonesInHierarchy = 1;
var curBoneIndex = rigBones[i].parentBoneIndex;
while (curBoneIndex >= 0)
{
curBoneIndex = rigBones[curBoneIndex].parentBoneIndex;
++numBonesInHierarchy;
}
rv = math.max(rv, numBonesInHierarchy);
}
return rv;
}
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
bool IsBoneInOptimizationMask(string skeletonBonesPath, AvatarMask mask)
{
if (mask == null)
return true;
for (var i = 0; i < mask.transformCount; ++i)
{
var maskPath = mask.GetTransformPath(i);
var pathActive = mask.GetTransformActive(i);
if (maskPath == skeletonBonesPath)
return pathActive;
}
return true;
}
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
string[] MakeSkeletonBonesFullPaths(SkeletonBone[] skeletonBones)
{
var rv = new string[skeletonBones.Length];
if (skeletonBones.Length == 0)
return rv;
// First bone need to be empty, because it contains root transform name, but avatar masks have it empty string
rv[0] = "";
for (var i = 1; i < skeletonBones.Length; ++i)
{
var sb = skeletonBones[i];
var parentName = (string)parentBoneNameField.GetValue(sb);
var parentIndex = Array.FindIndex(skeletonBones, 0, i, x => x.name == parentName);
var fullName = sb.name;
if (parentIndex > 0)
fullName = rv[parentIndex] + "/" + sb.name;
rv[i] = fullName;
}
return rv;
}
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
List<InternalSkeletonBone> CreateInternalRigRepresentation(Avatar avatar, RigDefinitionAuthoring rigDef)
{
if (avatar == null)
{
return CreateAvatarFromObjectHierarchy(rigDef.gameObject);
}
var skeleton = avatar.humanDescription.skeleton;
// Validate avatar optimization mask
var rigOptimizationMask = rigDef.avatarOptimizationMask;
if (rigOptimizationMask != null)
{
if (rigOptimizationMask.transformCount != skeleton.Length)
{
Debug.LogWarning($"'{rigOptimizationMask.name}' bone count ({rigOptimizationMask.transformCount}) does not match rig avatar '{avatar.name}' bone count ({skeleton.Length}). Avatar mask was created for different avatar and ignored.");
rigOptimizationMask = null;
}
}
var skeletonBonesFullPaths = MakeSkeletonBonesFullPaths(skeleton);
var rv = new List<InternalSkeletonBone>();
for (var i = 0; i < skeleton.Length; ++i)
{
var boneIsObjectRoot = i == 0;
var sb = skeleton[i];
if (!IsBoneInOptimizationMask(skeletonBonesFullPaths[i], rigOptimizationMask))
continue;
var isb = new InternalSkeletonBone()
{
boneTransform = boneIsObjectRoot ? rigDef.transform : TransformUtils.FindChildRecursively(rigDef.transform, sb.name),
name = sb.name,
position = sb.position,
rotation = sb.rotation,
scale = sb.scale,
parentName = (string)parentBoneNameField.GetValue(sb)
};
rv.Add(isb);
}
return rv;
}
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
void CreateRigDefinitionFromRigAuthoring(Entity rigEntity, RigDefinitionAuthoring rigDef)
{
var avatar = rigDef.avatar;
var skeletonBones = CreateInternalRigRepresentation(avatar, rigDef);
if (skeletonBones.Count == 0)
{
Debug.LogError($"Unity avatar '{avatar.name}' setup is incorrect. Follow <a href=\"https://docs.rukhanka.com/getting_started#rig-definition\">documentation</a> about avatar setup process please.");
return;
}
var rv = new RigDefinitionComponent();
rv.applyRootMotion = rigDef.applyRootMotion;
var rigBlobHash = rigDef.CalculateRigHash();
var rigBlobExist = TryGetBlobAssetReference<RigDefinitionBlob>(rigBlobHash, out var rigBlob);
if (!rigBlobExist)
{
rigBlob = CreateRigBlob(avatar, rigDef, skeletonBones, rigBlobHash);
AddBlobAssetWithCustomHash(ref rigBlob, rigBlobHash);
}
rv.rigBlob = rigBlob;
AddComponent(rigEntity, rv);
AddBuffer<RootMotionAnimationStateComponent>(rigEntity);
var rmvc = new RootMotionVelocityComponent()
{
worldVelocity = float3.zero,
deltaPos = float3.zero,
deltaRot = quaternion.identity,
removeBuiltinEntityMovement = rigDef.rootMotionMode == RigDefinitionAuthoring.RootMotionMode.DisableBuiltinMovement
};
AddComponent(rigEntity, rmvc);
if (rigDef.hasAnimationEvents)
{
AddBuffer<AnimationEventComponent>(rigEntity);
AddBuffer<PreviousProcessedAnimationComponent>(rigEntity);
}
if (rigDef.animationCulling)
{
AddComponent<CullAnimationsTag>(rigEntity);
}
var isGPUAnimator = rigDef.animationEngine == RigDefinitionAuthoring.AnimationEngine.GPU;
AddComponent<GPUAnimationEngineTag>(rigEntity);
SetComponentEnabled<GPUAnimationEngineTag>(rigEntity, isGPUAnimator);
ProcessBoneStrippingMask(rigEntity, rigDef, skeletonBones);
CreateBoneEntityRefs(rigEntity, skeletonBones, rigDef);
}
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
void CreateBoneEntityRefs(Entity e, List<InternalSkeletonBone> skeletonBones, RigDefinitionAuthoring rigDef)
{
var transformFlags = TransformUsageFlags.Dynamic;
var manualBoneStripping = rigDef.boneEntityStrippingMode == RigDefinitionAuthoring.BoneEntityStrippingMode.Manual;
var boneEntityRefArr = AddBuffer<BoneEntityRef>(e);
for (var i = 0; i < skeletonBones.Count; ++i)
{
var boneTransformFlags = transformFlags | (manualBoneStripping && i != 0 ? TransformUsageFlags.WorldSpace : 0);
var skeletonBone = skeletonBones[i];
var boneEntity = GetEntityForBone(skeletonBone.boneTransform, boneTransformFlags, rigDef);
boneEntityRefArr.Add(new BoneEntityRef() {boneEntity = boneEntity, rigBoneIndex = i});
}
}
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
void CheckForDuplicatedBones(ref RigDefinitionBlob rdb)
{
var duplicateBoneChecker = new NativeHashSet<uint>(rdb.bones.Length, Allocator.Temp);
for (var i = 0; i < rdb.bones.Length; ++i)
{
ref var bone = ref rdb.bones[i];
if (!duplicateBoneChecker.Add(bone.hash))
{
#if RUKHANKA_DEBUG_INFO
Debug.LogError($"RigDefinitionBaker: Duplicate bone with name '{bone.name.ToString()}' in rig '{rdb.name.ToString()}'! This is not allowed!");
#else
Debug.LogError($"RigDefinitionBaker: Duplicate bone with hash '{bone.hash}' in rig '{rdb.hash}'! This is not allowed! Enable 'RUKHANKA_DEBUG_INFO' to see bone names.");
#endif
}
}
}
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
BlobAssetReference<RigDefinitionBlob> CreateRigBlob(Avatar avatar, RigDefinitionAuthoring rigDef, List<InternalSkeletonBone> skeletonBones, Hash128 rigHash)
{
#if RUKHANKA_DEBUG_INFO
var startTimeMarker = Time.realtimeSinceStartupAsDouble;
#endif
var bb = new BlobBuilder(Allocator.Temp);
ref var c = ref bb.ConstructRoot<RigDefinitionBlob>();
c.hash = rigHash;
c.rootBoneIndex = GetRigRootBoneIndex(avatar, skeletonBones);
#if RUKHANKA_DEBUG_INFO
var rigName = rigDef.gameObject.name;
if (rigName.Length > 0)
bb.AllocateString(ref c.name, rigName);
#endif
var bonesArrayBlob = bb.Allocate(ref c.bones, skeletonBones.Count);
for (int i = 0; i < skeletonBones.Count; ++i)
{
ref var boneBlob = ref bonesArrayBlob[i];
CreateRigBoneBlob(bb, ref boneBlob, skeletonBones, i);
}
var rigIsHuman = avatar != null && avatar.isHuman;
if (rigIsHuman)
{
CreateHumanoidData(bb, ref c, bonesArrayBlob, avatar, skeletonBones);
}
#if RUKHANKA_DEBUG_INFO
var dt = Time.realtimeSinceStartupAsDouble - startTimeMarker;
c.bakingTime = (float)dt;
#endif
var rv = bb.CreateBlobAssetReference<RigDefinitionBlob>(Allocator.Persistent);
CheckForDuplicatedBones(ref rv.Value);
return rv;
}
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
void CreateHumanoidData(BlobBuilder bb, ref RigDefinitionBlob rdb, BlobBuilderArray<RigBoneInfo> bonesArr, Avatar avatar, List<InternalSkeletonBone> skeletonBones)
{
ref var hdb = ref bb.Allocate(ref rdb.humanData);
var humanToRigArr = bb.Allocate(ref hdb.humanBoneToSkeletonBoneIndices, (int)HumanBodyBones.LastBone);
var humanRotArr = bb.Allocate(ref hdb.humanRotData, skeletonBones.Count);
//var mirroredIndicesArr = bb.Allocate(ref hdb.mirroredBoneIndices, skeletonBones.Count);
for (int j = 0; j < humanToRigArr.Length; ++j)
humanToRigArr[j] = -1;
for (int l = 0; l < humanRotArr.Length; ++l)
{
ref var hrd = ref humanRotArr[l];
var humanRigIndex = CreateHumanoidBoneRotationData(ref hrd, avatar, skeletonBones[l].name);
if (humanRigIndex >= 0)
{
humanToRigArr[humanRigIndex] = l;
// Make muscle neutral ref pose
ref var rbi = ref bonesArr[l];
rbi.refPose.rot = math.mul(hrd.preRot, hrd.postRot);
// Set human body part for this bone
rbi.humanBodyPart = humanPartToAvatarMaskPartRemapTable[humanRigIndex];
}
}
//CreateHumanoidMirrorData(humanToRigArr, mirroredIndicesArr);
SetHumanBodyBodyPartForNonAssignedBones(bonesArr, skeletonBones);
}
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
AvatarMaskBodyPart GetAvatarMaskBodyPartFromParent(int boneIndex, BlobBuilderArray<RigBoneInfo> bonesArr)
{
if (boneIndex < 0)
return (AvatarMaskBodyPart)(-1);
ref var rb = ref bonesArr[boneIndex];
if (rb.humanBodyPart >= 0)
return rb.humanBodyPart;
return GetAvatarMaskBodyPartFromParent(rb.parentBoneIndex, bonesArr);
}
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
void CreateHumanoidMirrorData(BlobBuilderArray<int> humanToRigArr, BlobBuilderArray<int> mirroredIndicesArr)
{
for (var i = 0; i < mirroredIndicesArr.Length; ++i)
mirroredIndicesArr[i] = i;
for (var i = 0; i < humanToRigArr.Length; ++i)
{
ref var v = ref humanToRigArr[i];
var mirroredPart = humanoidMirrorTable[i];
mirroredIndicesArr[v] = humanToRigArr[(int)mirroredPart];
}
}
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
void SetHumanBodyBodyPartForNonAssignedBones(BlobBuilderArray<RigBoneInfo> bonesArr, List<InternalSkeletonBone> skeletonBones)
{
// Root bone is a special case
bonesArr[0].humanBodyPart = AvatarMaskBodyPart.Root;
// For other bones search for parent with body part is set and set it to the same value
for (int i = 1; i < bonesArr.Length; ++i)
{
// Override human body part if explicitly specified
var t = skeletonBones[i].boneTransform;
HumanBodyPartOverrideAuthoring hbpo = null;
if (t != null)
hbpo = t.GetComponent<HumanBodyPartOverrideAuthoring>();
var humanBodyPart = hbpo != null ? hbpo.humanBodyPart : GetAvatarMaskBodyPartFromParent(i, bonesArr);
bonesArr[i].humanBodyPart = humanBodyPart;
}
}
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
int CreateHumanoidBoneRotationData(ref HumanRotationData hrd, Avatar a, string boneName)
{
hrd = HumanRotationData.Identity();
var hd = a.humanDescription;
var humanBoneInSkeletonIndex = Array.FindIndex(hd.human, x => x.boneName == boneName);
if (humanBoneInSkeletonIndex < 0)
return -1;
var humanBones = HumanTrait.BoneName;
var humanBoneDef = hd.human[humanBoneInSkeletonIndex];
var humanBoneId = Array.FindIndex(humanBones, x => x == humanBoneDef.humanName);
Debug.Assert(humanBoneId >= 0);
hrd.preRot = a.GetPreRotation(humanBoneId);
hrd.postRot = math.inverse(a.GetPostRotation(humanBoneId));
hrd.sign = a.GetLimitSign(humanBoneId);
var minA = humanBoneDef.limit.min;
var maxA = humanBoneDef.limit.max;
if (humanBoneDef.limit.useDefaultValues)
{
minA.x = HumanTrait.GetMuscleDefaultMin(HumanTrait.MuscleFromBone(humanBoneId, 0));
minA.y = HumanTrait.GetMuscleDefaultMin(HumanTrait.MuscleFromBone(humanBoneId, 1));
minA.z = HumanTrait.GetMuscleDefaultMin(HumanTrait.MuscleFromBone(humanBoneId, 2));
maxA.x = HumanTrait.GetMuscleDefaultMax(HumanTrait.MuscleFromBone(humanBoneId, 0));
maxA.y = HumanTrait.GetMuscleDefaultMax(HumanTrait.MuscleFromBone(humanBoneId, 1));
maxA.z = HumanTrait.GetMuscleDefaultMax(HumanTrait.MuscleFromBone(humanBoneId, 2));
}
hrd.minMuscleAngles = math.radians(minA);
hrd.maxMuscleAngles = math.radians(maxA);
return humanBoneId;
}
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
unsafe Entity GetEntityForBone(Transform t, TransformUsageFlags boneFlags, RigDefinitionAuthoring rigDef)
{
// Hierarchy root should be always included
if (t == rigDef.transform)
return GetEntity(t, boneFlags);
if (t == null || t.GetComponent<SkinnedMeshRenderer>() != null)
return Entity.Null;
var automaticBoneStripping = rigDef.boneEntityStrippingMode == RigDefinitionAuthoring.BoneEntityStrippingMode.Automatic;
var rv = automaticBoneStripping ? _State.BakedEntityData->GetEntity(t) : GetEntity(t, boneFlags);
return rv;
}
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
void CreateRigBoneBlob(BlobBuilder bb, ref RigBoneInfo rbi, List<InternalSkeletonBone> skeletonBones, int boneIndex)
{
var boneIsObjectRoot = boneIndex == 0;
var skeletonBone = skeletonBones[boneIndex];
var name = skeletonBone.name;
// Special handling of hierarchy root
if (boneIsObjectRoot)
{
name = SpecialBones.UnnamedRootBoneName;
}
var boneName = new FixedStringName(name);
var boneHash = boneName.CalculateHash32();
rbi.hash = boneHash;
rbi.refPose = CreateBoneTransformFromSkeletonBone(skeletonBone);
rbi.humanBodyPart = (AvatarMaskBodyPart)(-1);
var parentBoneIndex = skeletonBones.FindIndex(x => x.name == skeletonBone.parentName);
rbi.parentBoneIndex = parentBoneIndex;
#if RUKHANKA_DEBUG_INFO
if (name.Length > 0)
bb.AllocateString(ref rbi.name, name);
#endif
}
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
BoneTransform CreateBoneTransformFromSkeletonBone(InternalSkeletonBone skeletonBone)
{
var pose = new BoneTransform()
{
pos = skeletonBone.position,
rot = skeletonBone.rotation,
scale = skeletonBone.scale,
};
if (skeletonBone.boneTransform != null)
{
pose = new BoneTransform()
{
pos = skeletonBone.boneTransform.localPosition,
rot = skeletonBone.boneTransform.localRotation,
scale = skeletonBone.boneTransform.localScale,
};
}
return pose;
}
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
void ProcessBoneStrippingMask(Entity rigEntity, RigDefinitionAuthoring rda, List<InternalSkeletonBone> rigBones)
{
// Bone stripping mask is processed only in "Manual" stripping mode
if (rda.boneEntityStrippingMode != RigDefinitionAuthoring.BoneEntityStrippingMode.Manual || rda.boneStrippingMask == null)
return;
var m = rda.boneStrippingMask;
var bonesToRemove = AddBuffer<BoneEntitiesToRemove>(rigEntity);
for (int i = 0; i < m.transformCount; ++i)
{
var isActive = m.GetTransformActive(i);
if (isActive) continue;
var path = m.GetTransformPath(i);
var boneIndex = 0;
for (; boneIndex < rigBones.Count && !path.EndsWith(rigBones[boneIndex].name); ++boneIndex) { }
if (boneIndex < rigBones.Count)
{
var boneEntity = GetEntity(rigBones[boneIndex].boneTransform, TransformUsageFlags.None);
bonesToRemove.Add(new BoneEntitiesToRemove() { boneEntity = boneEntity });
}
}
}
}
}
@@ -0,0 +1,18 @@
fileFormatVersion: 2
guid: 3f120dd0f19b2524c97ce89535ad94f9
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/Rig/RigDefinitionBaker.cs
uploadId: 897522
@@ -0,0 +1,184 @@
using UnityEngine;
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
namespace Rukhanka.Hybrid
{
public partial class RigDefinitionBaker
{
public static readonly AvatarMaskBodyPart[] humanPartToAvatarMaskPartRemapTable =
{
// Hips = 0,
AvatarMaskBodyPart.Root,
// LeftUpperLeg = 1,
AvatarMaskBodyPart.LeftLeg,
// RightUpperLeg = 2,
AvatarMaskBodyPart.RightLeg,
// LeftLowerLeg = 3,
AvatarMaskBodyPart.LeftLeg,
// RightLowerLeg = 4,
AvatarMaskBodyPart.RightLeg,
// LeftFoot = 5,
AvatarMaskBodyPart.LeftLeg,
// RightFoot = 6,
AvatarMaskBodyPart.RightLeg,
// Spine = 7,
AvatarMaskBodyPart.Body,
// Chest = 8,
AvatarMaskBodyPart.Body,
// Neck = 9,
AvatarMaskBodyPart.Head,
// Head = 10,
AvatarMaskBodyPart.Head,
// LeftShoulder = 11,
AvatarMaskBodyPart.LeftArm,
// RightShoulder = 12,
AvatarMaskBodyPart.RightArm,
// LeftUpperArm = 13,
AvatarMaskBodyPart.LeftArm,
// RightUpperArm = 14,
AvatarMaskBodyPart.RightArm,
// LeftLowerArm = 15,
AvatarMaskBodyPart.LeftArm,
// RightLowerArm = 16,
AvatarMaskBodyPart.RightArm,
// LeftHand = 17,
AvatarMaskBodyPart.LeftArm,
// RightHand = 18,
AvatarMaskBodyPart.RightArm,
// LeftToes = 19,
AvatarMaskBodyPart.LeftLeg,
// RightToes = 20,
AvatarMaskBodyPart.RightLeg,
// LeftEye = 21,
AvatarMaskBodyPart.Head,
// RightEye = 22,
AvatarMaskBodyPart.Head,
// Jaw = 23,
AvatarMaskBodyPart.Head,
// LeftThumbProximal = 24,
AvatarMaskBodyPart.LeftFingers,
// LeftThumbIntermediate = 25,
AvatarMaskBodyPart.LeftFingers,
// LeftThumbDistal = 26,
AvatarMaskBodyPart.LeftFingers,
// LeftIndexProximal = 27,
AvatarMaskBodyPart.LeftFingers,
// LeftIndexIntermediate = 28,
AvatarMaskBodyPart.LeftFingers,
// LeftIndexDistal = 29,
AvatarMaskBodyPart.LeftFingers,
// LeftMiddleProximal = 30,
AvatarMaskBodyPart.LeftFingers,
// LeftMiddleIntermediate = 31,
AvatarMaskBodyPart.LeftFingers,
// LeftMiddleDistal = 32,
AvatarMaskBodyPart.LeftFingers,
// LeftRingProximal = 33,
AvatarMaskBodyPart.LeftFingers,
// LeftRingIntermediate = 34,
AvatarMaskBodyPart.LeftFingers,
// LeftRingDistal = 35,
AvatarMaskBodyPart.LeftFingers,
// LeftLittleProximal = 36,
AvatarMaskBodyPart.LeftFingers,
// LeftLittleIntermediate = 37,
AvatarMaskBodyPart.LeftFingers,
// LeftLittleDistal = 38,
AvatarMaskBodyPart.LeftFingers,
// RightThumbProximal = 39,
AvatarMaskBodyPart.RightFingers,
// RightThumbIntermediate = 40,
AvatarMaskBodyPart.RightFingers,
// RightThumbDistal = 41,
AvatarMaskBodyPart.RightFingers,
// RightIndexProximal = 42,
AvatarMaskBodyPart.RightFingers,
// RightIndexIntermediate = 43,
AvatarMaskBodyPart.RightFingers,
// RightIndexDistal = 44,
AvatarMaskBodyPart.RightFingers,
// RightMiddleProximal = 45,
AvatarMaskBodyPart.RightFingers,
// RightMiddleIntermediate = 46,
AvatarMaskBodyPart.RightFingers,
// RightMiddleDistal = 47,
AvatarMaskBodyPart.RightFingers,
// RightRingProximal = 48,
AvatarMaskBodyPart.RightFingers,
// RightRingIntermediate = 49,
AvatarMaskBodyPart.RightFingers,
// RightRingDistal = 50,
AvatarMaskBodyPart.RightFingers,
// RightLittleProximal = 51,
AvatarMaskBodyPart.RightFingers,
// RightLittleIntermediate = 52,
AvatarMaskBodyPart.RightFingers,
// RightLittleDistal = 53,
AvatarMaskBodyPart.RightFingers,
// UpperChest = 54,
AvatarMaskBodyPart.Body,
};
// Humanoid mirror remap array
public static readonly HumanBodyBones[] humanoidMirrorTable =
{
HumanBodyBones.Hips,
HumanBodyBones.RightUpperLeg,
HumanBodyBones.LeftUpperLeg,
HumanBodyBones.RightLowerLeg,
HumanBodyBones.LeftLowerLeg,
HumanBodyBones.RightFoot,
HumanBodyBones.LeftFoot,
HumanBodyBones.Spine,
HumanBodyBones.Chest,
HumanBodyBones.Neck,
HumanBodyBones.Head,
HumanBodyBones.RightShoulder,
HumanBodyBones.LeftShoulder,
HumanBodyBones.RightUpperArm,
HumanBodyBones.LeftUpperArm,
HumanBodyBones.RightLowerArm,
HumanBodyBones.LeftLowerArm,
HumanBodyBones.RightHand,
HumanBodyBones.LeftHand,
HumanBodyBones.RightToes,
HumanBodyBones.LeftToes,
HumanBodyBones.RightEye,
HumanBodyBones.LeftEye,
HumanBodyBones.Jaw,
HumanBodyBones.RightThumbProximal,
HumanBodyBones.RightThumbIntermediate,
HumanBodyBones.RightThumbDistal,
HumanBodyBones.RightIndexProximal,
HumanBodyBones.RightIndexIntermediate,
HumanBodyBones.RightIndexDistal,
HumanBodyBones.RightMiddleProximal,
HumanBodyBones.RightMiddleIntermediate,
HumanBodyBones.RightMiddleDistal,
HumanBodyBones.RightRingProximal,
HumanBodyBones.RightRingIntermediate,
HumanBodyBones.RightRingDistal,
HumanBodyBones.RightLittleProximal,
HumanBodyBones.RightLittleIntermediate,
HumanBodyBones.RightLittleDistal,
HumanBodyBones.LeftThumbProximal,
HumanBodyBones.LeftThumbIntermediate,
HumanBodyBones.LeftThumbDistal,
HumanBodyBones.LeftIndexProximal,
HumanBodyBones.LeftIndexIntermediate,
HumanBodyBones.LeftIndexDistal,
HumanBodyBones.LeftMiddleProximal,
HumanBodyBones.LeftMiddleIntermediate,
HumanBodyBones.LeftMiddleDistal,
HumanBodyBones.LeftRingProximal,
HumanBodyBones.LeftRingIntermediate,
HumanBodyBones.LeftRingDistal,
HumanBodyBones.LeftLittleProximal,
HumanBodyBones.LeftLittleIntermediate,
HumanBodyBones.LeftLittleDistal,
HumanBodyBones.UpperChest,
HumanBodyBones.LastBone,
};
}
}
@@ -0,0 +1,18 @@
fileFormatVersion: 2
guid: 2658459aaed9ec24d8ba4418614dc0d0
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/Rig/RigDefinitionBaker_HumanConversionData.cs
uploadId: 897522
@@ -0,0 +1,32 @@
using Unity.Collections;
using Unity.Entities;
using Unity.Entities.Hybrid.Baking;
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
namespace Rukhanka.Hybrid
{
[WorldSystemFilter(WorldSystemFilterFlags.BakingSystem)]
[RequireMatchingQueriesForUpdate]
[UpdateAfter(typeof(BakingOnlyEntityAuthoringBakingSystem))]
public partial class RigDefinitionConversionSystem : SystemBase
{
protected override void OnUpdate()
{
using var ecb = new EntityCommandBuffer(Allocator.TempJob);
var bakingOnlyEntityLookup = SystemAPI.GetComponentLookup<BakingOnlyEntity>(true);
var createComponentDatasJob = new CreateComponentDatasJob()
{
ecb = ecb.AsParallelWriter(),
bakingOnlyLookup = bakingOnlyEntityLookup
};
createComponentDatasJob.ScheduleParallel();
Dependency.Complete();
ecb.Playback(EntityManager);
}
}
}
@@ -0,0 +1,18 @@
fileFormatVersion: 2
guid: c918fab6ea7b35644bbaa4a816b9d131
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/Rig/RigDefinitionConversionSystem.cs
uploadId: 897522
@@ -0,0 +1,41 @@
using Unity.Burst;
using Unity.Collections;
using Unity.Entities;
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
namespace Rukhanka.Hybrid
{
public partial class RigDefinitionConversionSystem
{
[BurstCompile]
[WithOptions(EntityQueryOptions.IncludePrefab)]
partial struct CreateComponentDatasJob: IJobEntity
{
[ReadOnly]
public ComponentLookup<BakingOnlyEntity> bakingOnlyLookup;
public EntityCommandBuffer.ParallelWriter ecb;
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
void Execute(Entity e, [ChunkIndexInQuery] int chunkIndex, ref RigDefinitionComponent rdc, DynamicBuffer<BoneEntityRef> boneEntityRefs)
{
for (int l = 0; l < boneEntityRefs.Length; ++l)
{
var boneEntityRef = boneEntityRefs[l];
var boneEntity = boneEntityRef.boneEntity;
if (boneEntity != Entity.Null && !bakingOnlyLookup.HasComponent(boneEntity))
{
var animatorEntityRefComponent = new AnimatorEntityRefComponent()
{
animatorEntity = e,
boneIndexInAnimationRig = boneEntityRef.rigBoneIndex
};
ecb.AddComponent(chunkIndex, boneEntity, animatorEntityRefComponent);
}
}
}
}
}
}
@@ -0,0 +1,18 @@
fileFormatVersion: 2
guid: 5515884a3d61aa8469ca13156921cf8e
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/Rig/RigDefinitionConversionSystem_Jobs.cs
uploadId: 897522