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,23 @@
using Unity.Entities;
using Unity.Mathematics;
/////////////////////////////////////////////////////////////////////////////////
namespace Rukhanka
{
public struct AimIKAffectedBoneComponent : IBufferElementData
{
public Entity boneEntity;
public float weight;
}
/////////////////////////////////////////////////////////////////////////////////
public struct AimIKComponent: IComponentData, IEnableableComponent
{
public Entity target;
public float2 angleLimits;
public float3 forwardVector;
public float weight;
}
}
@@ -0,0 +1,18 @@
fileFormatVersion: 2
guid: 3798f7affc8ae8346a6562b94b67c8cd
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
AssetOrigin:
serializedVersion: 1
productId: 298480
packageName: Rukhanka Animation System 2
packageVersion: 2.9.0
assetPath: Packages/com.rukhanka.animation/Rukhanka.Runtime/IK/AimIKComponents.cs
uploadId: 897522
@@ -0,0 +1,120 @@
using Unity.Burst;
using Unity.Entities;
using Unity.Mathematics;
using Unity.Transforms;
using Unity.Collections;
using Unity.Collections.LowLevel.Unsafe;
using UnityEngine;
/////////////////////////////////////////////////////////////////////////////////
namespace Rukhanka
{
[UpdateInGroup(typeof(RukhankaAnimationInjectionSystemGroup))]
public partial struct AimIKSystem: ISystem
{
[BurstCompile]
partial struct AimIKJob : IJobEntity
{
[ReadOnly]
public ComponentLookup<RigDefinitionComponent> rigDefLookup;
[ReadOnly]
public ComponentLookup<LocalTransform> localTransformLookup;
[ReadOnly]
public ComponentLookup<Parent> parentLookup;
[ReadOnly]
public ComponentLookup<AnimatorEntityRefComponent> boneEntityRefLookup;
[NativeDisableContainerSafetyRestriction]
public RuntimeAnimationData runtimeData;
/////////////////////////////////////////////////////////////////////////////////
void Execute(AimIKComponent aik, in AnimatorEntityRefComponent aer, in DynamicBuffer<AimIKAffectedBoneComponent> aimedBones)
{
if (aik.weight < math.EPSILON)
return;
var rigDef = rigDefLookup[aer.animatorEntity];
using var animStream = AnimationStream.Create(runtimeData, rigDef);
var targetEntityRigRelativePose = IKCommon.GetRigRelativeEntityPose
(
aik.target,
aer.animatorEntity,
animStream.GetWorldPose(0),
runtimeData,
localTransformLookup,
parentLookup,
boneEntityRefLookup,
rigDefLookup
);
for (var i = 0; i < aimedBones.Length; ++i)
{
var aimedBone = aimedBones[i];
if (!boneEntityRefLookup.TryGetComponent(aimedBone.boneEntity, out var aimedBoneEntity))
{
#if RUKHANKA_DEBUG_INFO
Debug.LogWarning($"Aimed entity '{aimedBone.boneEntity}' does not have AnimatorEntityRefComponent.");
#endif
continue;
}
var ikBoneWorldPose = animStream.GetWorldPose(aimedBoneEntity.boneIndexInAnimationRig);
var toTargetDir = math.normalize(targetEntityRigRelativePose.pos - ikBoneWorldPose.pos);
var originalForward = math.rotate(ikBoneWorldPose.rot, aik.forwardVector);
var acos = math.dot(toTargetDir, originalForward);
if (math.abs(acos) < 0.99999f)
{
var crossDir = math.normalize(math.cross(originalForward, toTargetDir));
var angle = math.acos(acos);
angle = math.clamp(angle, aik.angleLimits.x, aik.angleLimits.y);
var correctedRot = quaternion.AxisAngle(crossDir, angle);
var compositeWeight = aik.weight * aimedBone.weight;
correctedRot = math.slerp(quaternion.identity, correctedRot, compositeWeight);
ikBoneWorldPose.rot = math.mul(correctedRot, ikBoneWorldPose.rot);
animStream.SetWorldPose(aimedBoneEntity.boneIndexInAnimationRig, ikBoneWorldPose);
}
}
}
}
/////////////////////////////////////////////////////////////////////////////////
[BurstCompile]
public void OnCreate(ref SystemState ss)
{
var q = SystemAPI.QueryBuilder()
.WithAll<AimIKComponent, AnimatorEntityRefComponent, AimIKAffectedBoneComponent>()
.Build();
ss.RequireForUpdate(q);
}
/////////////////////////////////////////////////////////////////////////////////
[BurstCompile]
public void OnUpdate(ref SystemState ss)
{
var rigDefLookup = SystemAPI.GetComponentLookup<RigDefinitionComponent>(true);
var localTransformLookup = SystemAPI.GetComponentLookup<LocalTransform>(true);
var parentLookup = SystemAPI.GetComponentLookup<Parent>(true);
var aerLookup = SystemAPI.GetComponentLookup<AnimatorEntityRefComponent>(true);
ref var runtimeData = ref SystemAPI.GetSingletonRW<RuntimeAnimationData>().ValueRW;
var ikJob = new AimIKJob()
{
rigDefLookup = rigDefLookup,
runtimeData = runtimeData,
localTransformLookup = localTransformLookup,
parentLookup = parentLookup,
boneEntityRefLookup = aerLookup
};
ikJob.ScheduleParallel();
}
}
}
@@ -0,0 +1,18 @@
fileFormatVersion: 2
guid: 5b586d291c3ba8540ba45bbd45901a05
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
AssetOrigin:
serializedVersion: 1
productId: 298480
packageName: Rukhanka Animation System 2
packageVersion: 2.9.0
assetPath: Packages/com.rukhanka.animation/Rukhanka.Runtime/IK/AimIKSystem.cs
uploadId: 897522
@@ -0,0 +1,29 @@
using Unity.Entities;
using Unity.Mathematics;
/////////////////////////////////////////////////////////////////////////////////
namespace Rukhanka
{
public struct DynamicBoneChainNode: IBufferElementData
{
public int parentIndex;
public float3 position;
public float3 prevPosition;
public BoneTransform referenceLocalPose;
public Entity boneEntity;
}
/////////////////////////////////////////////////////////////////////////////////
public struct DynamicBoneChainComponent: IComponentData, IEnableableComponent
{
public float inertia;
public float damping;
public float elasticity;
public float stiffness;
public float timeAccumulator;
// Previous entity position. Used to simulate inertial motion of whole chain
public float3 prevPosition;
}
}
@@ -0,0 +1,9 @@
fileFormatVersion: 2
guid: 7d66897e34c41a043887768428234143
AssetOrigin:
serializedVersion: 1
productId: 298480
packageName: Rukhanka Animation System 2
packageVersion: 2.9.0
assetPath: Packages/com.rukhanka.animation/Rukhanka.Runtime/IK/DynamicBoneChainComponents.cs
uploadId: 897522
@@ -0,0 +1,289 @@
using System;
using Rukhanka.Toolbox;
using Unity.Burst;
using Unity.Entities;
using Unity.Mathematics;
using Unity.Transforms;
using Unity.Collections;
using Unity.Collections.LowLevel.Unsafe;
/////////////////////////////////////////////////////////////////////////////////
namespace Rukhanka
{
[UpdateInGroup(typeof(RukhankaAnimationInjectionSystemGroup), OrderFirst = true)]
public partial struct DynamicBoneChainSystem: ISystem
{
[BurstCompile]
partial struct DynamicBoneChainVerletSolverJob : IJobEntity
{
[ReadOnly]
public ComponentLookup<RigDefinitionComponent> rigDefLookup;
[ReadOnly]
public ComponentLookup<AnimatorEntityRefComponent> boneEntityRefLookup;
[ReadOnly]
public ComponentLookup<LocalTransform> localTransformLookup;
[ReadOnly]
public ComponentLookup<Parent> parentLookup;
[NativeDisableContainerSafetyRestriction]
public RuntimeAnimationData runtimeData;
public float deltaTime;
static readonly float fixedUpdateRate = 1 / 60.0f;
/////////////////////////////////////////////////////////////////////////////////
void Execute(Entity e, ref DynamicBoneChainComponent dbcc, in AnimatorEntityRefComponent aer, ref DynamicBuffer<DynamicBoneChainNode> dynamicChain)
{
var rigDef = rigDefLookup[aer.animatorEntity];
using var animStream = AnimationStream.Create(runtimeData, rigDef);
Span<int> boneIndicesInRig = stackalloc int[dynamicChain.Length];
Span<float3> initialPositions = stackalloc float3[dynamicChain.Length];
var dca = dynamicChain.AsNativeArray();
InitBoneFrameData(dca, boneIndicesInRig, initialPositions, animStream);
var inertia = ComputeEntityInertia(e, ref dbcc, runtimeData);
dbcc.timeAccumulator += deltaTime;
var simulationCount = 0;
while (dbcc.timeAccumulator >= fixedUpdateRate)
{
var modInertia = math.select(0, inertia * dbcc.inertia, simulationCount == 0);
Integrate(dbcc, dca, modInertia, boneIndicesInRig, animStream);
Elasticity(dbcc, dca, boneIndicesInRig, animStream);
ConstrainDistance(dca, boneIndicesInRig, animStream);
dbcc.timeAccumulator -= fixedUpdateRate;
simulationCount++;
}
if (simulationCount == 0)
{
ApplyMovementOffset(inertia, dca);
Elasticity(dbcc, dca, boneIndicesInRig, animStream);
ConstrainDistance(dca, boneIndicesInRig, animStream);
}
MakeChainRotations(dca, boneIndicesInRig, animStream);
for (var i = 0; i < dynamicChain.Length; ++i)
{
animStream.SetWorldPosition(boneIndicesInRig[i], dynamicChain[i].position);
}
}
/////////////////////////////////////////////////////////////////////////////////
void ApplyMovementOffset(float3 inertia, NativeArray<DynamicBoneChainNode> dynamicChain)
{
for (var i = 1; i < dynamicChain.Length; ++i)
{
var dc = dynamicChain[i];
dc.position += inertia;
dc.prevPosition += inertia;
dynamicChain[i] = dc;
}
}
/////////////////////////////////////////////////////////////////////////////////
void InitBoneFrameData(NativeArray<DynamicBoneChainNode> dynamicChain, Span<int> boneIndicesInRig, Span<float3> initialPositions, AnimationStream animStream)
{
for (var i = 0; i < dynamicChain.Length; ++i)
{
var dbe = dynamicChain[i];
if (!boneEntityRefLookup.TryGetComponent(dbe.boneEntity, out var boneEntityRef))
return;
animStream.SetLocalPose(boneEntityRef.boneIndexInAnimationRig, dbe.referenceLocalPose);
if (i == 0)
{
var rootPose = animStream.GetWorldPose(boneEntityRef.boneIndexInAnimationRig);
dbe.position = dbe.prevPosition = rootPose.pos;
dynamicChain[0] = dbe;
}
boneIndicesInRig[i] = boneEntityRef.boneIndexInAnimationRig;
}
for (var i = 0; i < dynamicChain.Length; ++i)
{
initialPositions[i] = animStream.GetWorldPosition(boneIndicesInRig[i]);
}
}
/////////////////////////////////////////////////////////////////////////////////
float3 ComputeEntityInertia(Entity e, ref DynamicBoneChainComponent dbcc, RuntimeAnimationData runtimeAnimationData)
{
BoneTransform bt = BoneTransform.Identity();
IKCommon.GetEntityWorldTransform
(
e,
ref bt,
runtimeAnimationData,
localTransformLookup,
parentLookup,
boneEntityRefLookup,
rigDefLookup
);
var rv = bt.pos - dbcc.prevPosition;
dbcc.prevPosition = bt.pos;
return rv;
}
/////////////////////////////////////////////////////////////////////////////////
void Integrate(DynamicBoneChainComponent dbcc, NativeArray<DynamicBoneChainNode> dynamicChain, float3 inertia, Span<int> boneIndicesInRig, AnimationStream animStream)
{
// Root bone is stationary
var dc0 = dynamicChain[0];
dc0.prevPosition = dc0.position;
dc0.position = animStream.GetWorldPosition(boneIndicesInRig[0]);
for (var i = 1; i < dynamicChain.Length; ++i)
{
var dc = dynamicChain[i];
var dv = dc.position - dc.prevPosition;
dc.prevPosition = dc.position + inertia;
dc.position += inertia + dv * (1 - dbcc.damping);
dynamicChain[i] = dc;
}
}
/////////////////////////////////////////////////////////////////////////////////
void ConstrainDistance(NativeArray<DynamicBoneChainNode> dynamicChain, Span<int> boneIndicesInRig, AnimationStream animStream)
{
// Distance constraints
for (var i = 1; i < dynamicChain.Length; ++i)
{
var dc = dynamicChain[i];
var bonePose = animStream.GetWorldPose(boneIndicesInRig[i]);
var parentBonePose = animStream.GetWorldPose(boneIndicesInRig[dc.parentIndex]);
var refDeltaPos = parentBonePose.pos - bonePose.pos;
var refDeltaPosLen = math.length(refDeltaPos);
var curDeltaPos = dynamicChain[dc.parentIndex].position - dc.position;
var curDeltaPosLenSq = math.lengthsq(curDeltaPos);
if (curDeltaPosLenSq > 0)
{
var curDeltaPosLen = math.sqrt(curDeltaPosLenSq);
var slm = math.rcp(curDeltaPosLen);
dc.position += curDeltaPos * slm * (curDeltaPosLen - refDeltaPosLen);
dynamicChain[i] = dc;
}
}
}
/////////////////////////////////////////////////////////////////////////////////
void Elasticity(DynamicBoneChainComponent dbcc, NativeArray<DynamicBoneChainNode> dynamicChain, Span<int> boneIndicesInRig, AnimationStream animStream)
{
for (var i = 1; i < dynamicChain.Length; ++i)
{
var dc = dynamicChain[i];
var targetPos = ComputeRigidPos(i, dynamicChain, boneIndicesInRig, animStream);
var delta = targetPos - dc.position;
dc.position += delta * dbcc.elasticity;
dc.position += Stiffness(targetPos, dc.position, dbcc.stiffness);
dynamicChain[i] = dc;
}
}
/////////////////////////////////////////////////////////////////////////////////
float3 Stiffness(float3 rigidPos, float3 simPos, float stiffnessFactor)
{
if (stiffnessFactor <= 0)
return float3.zero;
var delta = rigidPos - simPos;
var deltaLengthSq = math.lengthsq(delta);
var deltaLength = math.sqrt(deltaLengthSq);
if (deltaLengthSq < math.EPSILON)
return float3.zero;
var rcpDeltaLength = math.rcp(deltaLength);
var stiffness = (2 - 2 * stiffnessFactor) * deltaLength;
var dl = math.max(deltaLength - stiffness, 0);
var rv = dl * delta * rcpDeltaLength;
return rv;
}
/////////////////////////////////////////////////////////////////////////////////
float3 ComputeRigidPos(int boneIndex, NativeArray<DynamicBoneChainNode> dynamicChain, Span<int> boneIndicesInRig, AnimationStream animStream)
{
var dc1 = dynamicChain[boneIndex];
var dc0 = dynamicChain[dc1.parentIndex];
var wpp0 = animStream.GetWorldPose(boneIndicesInRig[dc1.parentIndex]);
var lpp1 = animStream.GetLocalPosition(boneIndicesInRig[boneIndex]);
wpp0.pos = dc0.position;
var rv = BoneTransform.TransformPoint(wpp0, lpp1);
return rv;
}
/////////////////////////////////////////////////////////////////////////////////
void MakeChainRotations(NativeArray<DynamicBoneChainNode> dynamicChain, Span<int> boneIndicesInRig, AnimationStream animStream)
{
for (var i = 2; i < dynamicChain.Length; ++i)
{
var dc1 = dynamicChain[i];
var dc0 = dynamicChain[dc1.parentIndex];
var dv = dc1.position - dc0.position;
var ndv = math.normalizesafe(dv);
var wp0 = animStream.GetWorldPose(boneIndicesInRig[dc1.parentIndex]);
var lp0 = animStream.GetLocalPose(boneIndicesInRig[dc1.parentIndex]);
var wf0 = math.rotate(wp0.rot, lp0.pos);
var nwf0 = math.normalizesafe(wf0);
var q = MathUtils.FromToRotationForNormalizedVectors(nwf0, ndv);
var newRot = math.mul(q, wp0.rot);
animStream.SetWorldRotation(boneIndicesInRig[dc1.parentIndex], newRot);
}
}
}
/////////////////////////////////////////////////////////////////////////////////
[BurstCompile]
public void OnCreate(ref SystemState ss)
{
var q = SystemAPI.QueryBuilder()
.WithAll<DynamicBoneChainComponent, AnimatorEntityRefComponent, DynamicBoneChainNode>()
.Build();
ss.RequireForUpdate(q);
}
/////////////////////////////////////////////////////////////////////////////////
[BurstCompile]
public void OnUpdate(ref SystemState ss)
{
var rigDefLookup = SystemAPI.GetComponentLookup<RigDefinitionComponent>(true);
var aerLookup = SystemAPI.GetComponentLookup<AnimatorEntityRefComponent>(true);
var ltLookup = SystemAPI.GetComponentLookup<LocalTransform>(true);
var parentLookup = SystemAPI.GetComponentLookup<Parent>(true);
ref var runtimeData = ref SystemAPI.GetSingletonRW<RuntimeAnimationData>().ValueRW;
var ikJob = new DynamicBoneChainVerletSolverJob()
{
runtimeData = runtimeData,
rigDefLookup = rigDefLookup,
boneEntityRefLookup = aerLookup,
parentLookup = parentLookup,
localTransformLookup = ltLookup,
deltaTime = SystemAPI.Time.DeltaTime
};
ikJob.ScheduleParallel();
}
}
}
@@ -0,0 +1,9 @@
fileFormatVersion: 2
guid: 7f1e853ee9ad07e4fa1e6e32a4b717c4
AssetOrigin:
serializedVersion: 1
productId: 298480
packageName: Rukhanka Animation System 2
packageVersion: 2.9.0
assetPath: Packages/com.rukhanka.animation/Rukhanka.Runtime/IK/DynamicBoneChainSystem.cs
uploadId: 897522
@@ -0,0 +1,13 @@
using Unity.Entities;
/////////////////////////////////////////////////////////////////////////////////
namespace Rukhanka
{
public struct FABRIKComponent: IComponentData, IEnableableComponent
{
public Entity tip, target;
public int numIterations;
public float weight, threshold;
}
}
@@ -0,0 +1,18 @@
fileFormatVersion: 2
guid: 6c091fd7a225a7d43bde886f3ef8cc87
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
AssetOrigin:
serializedVersion: 1
productId: 298480
packageName: Rukhanka Animation System 2
packageVersion: 2.9.0
assetPath: Packages/com.rukhanka.animation/Rukhanka.Runtime/IK/FABRIKComponents.cs
uploadId: 897522
@@ -0,0 +1,59 @@
using Unity.Burst;
using Unity.Collections;
using Unity.Entities;
using Unity.Mathematics;
using Unity.Transforms;
/////////////////////////////////////////////////////////////////////////////////
namespace Rukhanka
{
[UpdateInGroup(typeof(RukhankaAnimationInjectionSystemGroup))]
public partial struct FABRIKSystem: ISystem
{
struct FABRIKData
{
public NativeArray<float3> chainWorldPositions;
public NativeArray<float3> chainInitialDirections;
public NativeArray<float> chainLengths;
public NativeArray<int> chainBoneIndices;
}
/////////////////////////////////////////////////////////////////////////////////
[BurstCompile]
public void OnCreate(ref SystemState ss)
{
var q = SystemAPI.QueryBuilder()
.WithAll<FABRIKComponent, AnimatorEntityRefComponent>()
.Build();
ss.RequireForUpdate(q);
}
/////////////////////////////////////////////////////////////////////////////////
[BurstCompile]
public void OnUpdate(ref SystemState ss)
{
var rigDefLookup = SystemAPI.GetComponentLookup<RigDefinitionComponent>(true);
var localTransformLookup = SystemAPI.GetComponentLookup<LocalTransform>(true);
var parentLookup = SystemAPI.GetComponentLookup<Parent>(true);
var animatorEntityRefLookup = SystemAPI.GetComponentLookup<AnimatorEntityRefComponent>(true);
ref var runtimeData = ref SystemAPI.GetSingletonRW<RuntimeAnimationData>().ValueRW;
var ikJob = new FABRIKJob()
{
parentLookup = parentLookup,
localTransformLookup = localTransformLookup,
rigDefLookup = rigDefLookup,
animatorEntityRefLookup = animatorEntityRefLookup,
runtimeData = runtimeData
};
ikJob.ScheduleParallel();
}
}
}
@@ -0,0 +1,18 @@
fileFormatVersion: 2
guid: 5b79c61d80e67ed479e8273ed043b123
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
AssetOrigin:
serializedVersion: 1
productId: 298480
packageName: Rukhanka Animation System 2
packageVersion: 2.9.0
assetPath: Packages/com.rukhanka.animation/Rukhanka.Runtime/IK/FABRIKSystem.cs
uploadId: 897522
@@ -0,0 +1,208 @@
using Rukhanka.Toolbox;
using Unity.Burst;
using Unity.Collections;
using Unity.Collections.LowLevel.Unsafe;
using Unity.Entities;
using Unity.Mathematics;
using Unity.Transforms;
using UnityEngine;
/////////////////////////////////////////////////////////////////////////////////
namespace Rukhanka
{
public partial struct FABRIKSystem
{
[BurstCompile]
partial struct FABRIKJob : IJobEntity
{
[ReadOnly]
public ComponentLookup<RigDefinitionComponent> rigDefLookup;
[ReadOnly]
public ComponentLookup<LocalTransform> localTransformLookup;
[ReadOnly]
public ComponentLookup<Parent> parentLookup;
[ReadOnly]
public ComponentLookup<AnimatorEntityRefComponent> animatorEntityRefLookup;
[NativeDisableContainerSafetyRestriction]
public RuntimeAnimationData runtimeData;
/////////////////////////////////////////////////////////////////////////////////
void Execute(FABRIKComponent ikc, in AnimatorEntityRefComponent aer)
{
if (ikc.weight <= math.EPSILON)
return;
var rigDef = rigDefLookup[aer.animatorEntity];
using var animStream = AnimationStream.Create(runtimeData, rigDef);
var targetEntityRigRootRelativePose = IKCommon.GetRigRelativeEntityPose
(
ikc.target,
aer.animatorEntity,
animStream.GetWorldPose(0),
runtimeData,
localTransformLookup,
parentLookup,
animatorEntityRefLookup,
rigDefLookup
);
var targetEntityRigRelativePosition = targetEntityRigRootRelativePose.pos;
var ikData = PrepareIKData(ikc, rigDef, aer);
IKChainToWorld(ikData, animStream);
if (Solve(ikData, ikc.numIterations, ikc.threshold, targetEntityRigRelativePosition))
IKDataToBonePoses(ikData, animStream, ikc.weight);
}
/////////////////////////////////////////////////////////////////////////////////
FABRIKData PrepareIKData(FABRIKComponent ikComponent, RigDefinitionComponent rb, AnimatorEntityRefComponent rootBoneAER)
{
var rv = new FABRIKData();
var chainEndBoneIndex = animatorEntityRefLookup[ikComponent.tip].boneIndexInAnimationRig;
var chainRootBoneIndex = rootBoneAER.boneIndexInAnimationRig;
var curBoneIndex = chainEndBoneIndex;
var chainBoneIndices = new NativeList<int>(Allocator.Temp);
while (chainRootBoneIndex != curBoneIndex && curBoneIndex >= 0)
{
chainBoneIndices.Add(curBoneIndex);
curBoneIndex = rb.rigBlob.Value.bones[curBoneIndex].parentBoneIndex;
}
chainBoneIndices.Add(chainRootBoneIndex);
var numBonesInChain = chainBoneIndices.Length;
rv.chainLengths = new NativeArray<float>(numBonesInChain, Allocator.Temp);
rv.chainBoneIndices = chainBoneIndices.AsArray();
rv.chainWorldPositions = new NativeArray<float3>(numBonesInChain, Allocator.Temp);
rv.chainInitialDirections = new NativeArray<float3>(numBonesInChain, Allocator.Temp);
return rv;
}
/////////////////////////////////////////////////////////////////////////////////
void IKChainToWorld(FABRIKData ikData, AnimationStream animStream)
{
ikData.chainLengths[^1] = 0;
// Fill world positions
for (var i = 0; i < ikData.chainBoneIndices.Length; ++i)
{
var boneIndex = ikData.chainBoneIndices[i];
var boneWorldPos = animStream.GetWorldPosition(boneIndex);
ikData.chainWorldPositions[i] = boneWorldPos;
}
// Fill chain lengths and directions
for (var i = 0; i < ikData.chainBoneIndices.Length - 1; ++i)
{
var curBonePos = ikData.chainWorldPositions[i];
var nextBonePos = ikData.chainWorldPositions[i + 1];
var lv = nextBonePos - curBonePos;
var l = math.length(lv);
ikData.chainInitialDirections[i] = lv / l;
ikData.chainLengths[i] = l;
}
}
/////////////////////////////////////////////////////////////////////////////////
void IKDataToBonePoses(FABRIKData ikData, AnimationStream animationStream, float weight)
{
var initialIndex = ikData.chainWorldPositions.Length - 1;
//var initialIndex = 0;
var prevBoneRot = quaternion.identity;
for (var i = initialIndex; i > 0; --i)
{
var pos = ikData.chainWorldPositions[i];
var boneIndex = ikData.chainBoneIndices[i];
var bonePoseOrig = animationStream.GetWorldPose(boneIndex);
var bonePose = bonePoseOrig;
var boneDir = ikData.chainInitialDirections[i - 1];
var nextBonePos = ikData.chainWorldPositions[i - 1];
var boneVec = math.normalize(pos - nextBonePos);
var rot = MathUtils.FromToRotationForNormalizedVectors(boneDir, boneVec);
bonePose.rot = math.mul(math.inverse(prevBoneRot), bonePose.rot);
bonePose.rot = math.mul(rot, bonePose.rot);
prevBoneRot = rot;
bonePose.rot = math.slerp(bonePoseOrig.rot, bonePose.rot, weight);
var resultTransform = BoneTransform.Lerp(bonePoseOrig, bonePose, weight);
animationStream.SetWorldPose(boneIndex, resultTransform);
}
}
/////////////////////////////////////////////////////////////////////////////////
void ForwardPass(FABRIKData ikData, float3 origin)
{
ikData.chainWorldPositions[^1] = origin;
for (var i = ikData.chainWorldPositions.Length - 2; i >= 0; --i)
{
var p0 = ikData.chainWorldPositions[i];
var p1 = ikData.chainWorldPositions[i + 1];
var dir = math.normalize(p0 - p1);
var offset = dir * ikData.chainLengths[i];
ikData.chainWorldPositions[i] = ikData.chainWorldPositions[i + 1] + offset;
}
}
/////////////////////////////////////////////////////////////////////////////////
void BackwardPass(FABRIKData ikData, float3 goalInWorldSpace)
{
ikData.chainWorldPositions[0] = goalInWorldSpace;
for (var i = 1; i < ikData.chainWorldPositions.Length; ++i)
{
var p0 = ikData.chainWorldPositions[i - 1];
var p1 = ikData.chainWorldPositions[i];
var dir = math.normalize(p1 - p0);
var offset = dir * ikData.chainLengths[i - 1];
ikData.chainWorldPositions[i] = ikData.chainWorldPositions[i - 1] + offset;
}
}
/////////////////////////////////////////////////////////////////////////////////
void ValidateIKData(FABRIKData ikData)
{
for (var i = 0; i < ikData.chainWorldPositions.Length; ++i)
{
if (math.any(math.isnan(ikData.chainWorldPositions[i])))
Debug.Log("NAN!");
if (math.any(math.isnan(ikData.chainInitialDirections[i])))
Debug.Log("NAN!");
}
}
/////////////////////////////////////////////////////////////////////////////////
bool Solve(FABRIKData ikData, int numIterations, float threshold, float3 goalPos)
{
var chainOrigin = ikData.chainWorldPositions[^1];
for (var i = 0; i < numIterations; ++i)
{
var tipPos = ikData.chainWorldPositions[0];
if (math.lengthsq(goalPos - tipPos) <= threshold)
return i > math.EPSILON;
BackwardPass(ikData, goalPos);
ForwardPass(ikData, chainOrigin);
}
return true;
}
}
}
}
@@ -0,0 +1,18 @@
fileFormatVersion: 2
guid: 103c9a5e7f8477145a93dec6fd184175
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
AssetOrigin:
serializedVersion: 1
productId: 298480
packageName: Rukhanka Animation System 2
packageVersion: 2.9.0
assetPath: Packages/com.rukhanka.animation/Rukhanka.Runtime/IK/FABRIKSystemJobs.cs
uploadId: 897522
@@ -0,0 +1,68 @@
using Unity.Entities;
using Unity.Transforms;
/////////////////////////////////////////////////////////////////////////////////
namespace Rukhanka
{
public static class IKCommon
{
public static void GetEntityWorldTransform
(
Entity e,
ref BoneTransform t,
in RuntimeAnimationData runtimeAnimationData,
ComponentLookup<LocalTransform> ltl,
ComponentLookup<Parent> pl,
ComponentLookup<AnimatorEntityRefComponent> aerc,
ComponentLookup<RigDefinitionComponent> rd
)
{
if (!ltl.TryGetComponent(e, out var lt)) return;
// If current entity is a part of the rig (bone entity) use its animated pose
if (aerc.TryGetComponent(e, out var aer))
{
var boneWorldPoses = RuntimeAnimationData.GetAnimationDataForRigRO(runtimeAnimationData.worldSpaceBonesBuffer, rd[aer.animatorEntity]);
if (boneWorldPoses.Length > aer.boneIndexInAnimationRig)
{
var bt = boneWorldPoses[aer.boneIndexInAnimationRig];
t = BoneTransform.Multiply(bt, t);
}
// We have got rig relative position so must continue with parent entities of animated rig
e = aer.animatorEntity;
}
else
{
t = BoneTransform.Multiply(new BoneTransform(lt), t);
}
if (pl.TryGetComponent(e, out var p))
{
GetEntityWorldTransform(p.Value, ref t, runtimeAnimationData, ltl, pl, aerc, rd);
}
}
/////////////////////////////////////////////////////////////////////////////////
public static BoneTransform GetRigRelativeEntityPose
(
Entity target,
Entity animatorEntity,
BoneTransform rigRootWorldPose,
in RuntimeAnimationData runtimeAnimationData,
ComponentLookup<LocalTransform> ltl,
ComponentLookup<Parent> pl,
ComponentLookup<AnimatorEntityRefComponent> aerc,
ComponentLookup<RigDefinitionComponent> rd
)
{
var targetEntityWorldPose = BoneTransform.Identity();
GetEntityWorldTransform(target, ref targetEntityWorldPose, runtimeAnimationData, ltl, pl, aerc, rd);
var animatedEntityWorldPose = BoneTransform.Inverse(rigRootWorldPose);
GetEntityWorldTransform(animatorEntity, ref animatedEntityWorldPose, runtimeAnimationData, ltl, pl, aerc, rd);
var rv = BoneTransform.Multiply(BoneTransform.Inverse(animatedEntityWorldPose), targetEntityWorldPose);
return rv;
}
}
}
@@ -0,0 +1,18 @@
fileFormatVersion: 2
guid: 8fef388c445806448874a5058408c748
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
AssetOrigin:
serializedVersion: 1
productId: 298480
packageName: Rukhanka Animation System 2
packageVersion: 2.9.0
assetPath: Packages/com.rukhanka.animation/Rukhanka.Runtime/IK/IKCommon.cs
uploadId: 897522
@@ -0,0 +1,11 @@
using Unity.Entities;
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
namespace Rukhanka
{
[DisableAutoCreation]
[UpdateAfter(typeof(AnimationProcessSystem))]
public partial class RukhankaAnimationInjectionSystemGroup: ComponentSystemGroup { }
}
@@ -0,0 +1,18 @@
fileFormatVersion: 2
guid: 4ecb24b3c3252594593580b8628863b5
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
AssetOrigin:
serializedVersion: 1
productId: 298480
packageName: Rukhanka Animation System 2
packageVersion: 2.9.0
assetPath: Packages/com.rukhanka.animation/Rukhanka.Runtime/IK/IKSystemGroup.cs
uploadId: 897522
@@ -0,0 +1,13 @@
using Unity.Entities;
/////////////////////////////////////////////////////////////////////////////////
namespace Rukhanka
{
public struct OverrideTransformIKComponent: IComponentData, IEnableableComponent
{
public Entity target;
public float positionWeight;
public float rotationWeight;
}
}
@@ -0,0 +1,18 @@
fileFormatVersion: 2
guid: 22e9a073053bf24408ebc5456c6efd7e
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
AssetOrigin:
serializedVersion: 1
productId: 298480
packageName: Rukhanka Animation System 2
packageVersion: 2.9.0
assetPath: Packages/com.rukhanka.animation/Rukhanka.Runtime/IK/OverrideTransformIK.cs
uploadId: 897522
@@ -0,0 +1,96 @@
using Unity.Burst;
using Unity.Entities;
using Unity.Mathematics;
using Unity.Transforms;
using Unity.Collections;
using Unity.Collections.LowLevel.Unsafe;
/////////////////////////////////////////////////////////////////////////////////
namespace Rukhanka
{
[UpdateInGroup(typeof(RukhankaAnimationInjectionSystemGroup))]
[UpdateAfter(typeof(FABRIKSystem))]
public partial struct OverrideTransformIKSystem: ISystem
{
[BurstCompile]
partial struct OverrideTransformIKJob : IJobEntity
{
[ReadOnly]
public ComponentLookup<RigDefinitionComponent> rigDefLookup;
[ReadOnly]
public ComponentLookup<LocalTransform> localTransformLookup;
[ReadOnly]
public ComponentLookup<Parent> parentLookup;
[ReadOnly]
public ComponentLookup<AnimatorEntityRefComponent> animatorEntityRefLookup;
/////////////////////////////////////////////////////////////////////////////////
[NativeDisableContainerSafetyRestriction]
public RuntimeAnimationData runtimeData;
void Execute(OverrideTransformIKComponent ikc, in AnimatorEntityRefComponent aer)
{
var rigDef = rigDefLookup[aer.animatorEntity];
using var animStream = AnimationStream.Create(runtimeData, rigDef);
var targetEntityRigRootRelativePose = IKCommon.GetRigRelativeEntityPose
(
ikc.target,
aer.animatorEntity,
animStream.GetWorldPose(0),
runtimeData,
localTransformLookup,
parentLookup,
animatorEntityRefLookup,
rigDefLookup
);
var bonePose = animStream.GetWorldPose(aer.boneIndexInAnimationRig);
targetEntityRigRootRelativePose.pos = math.lerp(bonePose.pos, targetEntityRigRootRelativePose.pos, ikc.positionWeight);
targetEntityRigRootRelativePose.rot = math.slerp(bonePose.rot, targetEntityRigRootRelativePose.rot, ikc.rotationWeight);
targetEntityRigRootRelativePose.scale = 1;
animStream.SetWorldPose(aer.boneIndexInAnimationRig, targetEntityRigRootRelativePose);
}
}
/////////////////////////////////////////////////////////////////////////////////
[BurstCompile]
public void OnCreate(ref SystemState ss)
{
var q = SystemAPI.QueryBuilder()
.WithAll<OverrideTransformIKComponent, AnimatorEntityRefComponent>()
.Build();
ss.RequireForUpdate(q);
}
/////////////////////////////////////////////////////////////////////////////////
[BurstCompile]
public void OnUpdate(ref SystemState ss)
{
var rigDefLookup = SystemAPI.GetComponentLookup<RigDefinitionComponent>(true);
var localTransformLookup = SystemAPI.GetComponentLookup<LocalTransform>(true);
var animatorEntityRefLookup = SystemAPI.GetComponentLookup<AnimatorEntityRefComponent>(true);
var parentLookup = SystemAPI.GetComponentLookup<Parent>(true);
ref var runtimeData = ref SystemAPI.GetSingletonRW<RuntimeAnimationData>().ValueRW;
var ikJob = new OverrideTransformIKJob()
{
rigDefLookup = rigDefLookup,
runtimeData = runtimeData,
localTransformLookup = localTransformLookup,
parentLookup = parentLookup,
animatorEntityRefLookup = animatorEntityRefLookup
};
ikJob.ScheduleParallel();
}
}
}
@@ -0,0 +1,18 @@
fileFormatVersion: 2
guid: 2bf390f7f6397b04b97ae25ec7191690
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
AssetOrigin:
serializedVersion: 1
productId: 298480
packageName: Rukhanka Animation System 2
packageVersion: 2.9.0
assetPath: Packages/com.rukhanka.animation/Rukhanka.Runtime/IK/OverrideTransformIKSystem.cs
uploadId: 897522
@@ -0,0 +1,12 @@
using Unity.Entities;
/////////////////////////////////////////////////////////////////////////////////
namespace Rukhanka
{
public struct TwoBoneIKComponent: IComponentData, IEnableableComponent
{
public Entity mid, tip, target, midBentHint;
public float weight;
}
}
@@ -0,0 +1,18 @@
fileFormatVersion: 2
guid: b57af7854a95c2b448d661235574e355
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
AssetOrigin:
serializedVersion: 1
productId: 298480
packageName: Rukhanka Animation System 2
packageVersion: 2.9.0
assetPath: Packages/com.rukhanka.animation/Rukhanka.Runtime/IK/TwoBoneIKComponents.cs
uploadId: 897522
@@ -0,0 +1,188 @@
using Rukhanka.Toolbox;
using Unity.Burst;
using Unity.Collections;
using Unity.Entities;
using Unity.Mathematics;
using Unity.Transforms;
using Unity.Collections.LowLevel.Unsafe;
/////////////////////////////////////////////////////////////////////////////////
namespace Rukhanka
{
[UpdateInGroup(typeof(RukhankaAnimationInjectionSystemGroup))]
[UpdateAfter(typeof(AimIKSystem))]
[UpdateAfter(typeof(FABRIKSystem))]
public partial struct TwoBoneIKSystem: ISystem
{
[BurstCompile]
partial struct TwoBoneIKJob : IJobEntity
{
[ReadOnly]
public ComponentLookup<RigDefinitionComponent> rigDefLookup;
[ReadOnly]
public ComponentLookup<AnimatorEntityRefComponent> animatorEntityRefLookup;
[ReadOnly]
public ComponentLookup<LocalTransform> localTransformLookup;
[ReadOnly]
public ComponentLookup<Parent> parentLookup;
[NativeDisableContainerSafetyRestriction]
public RuntimeAnimationData runtimeData;
/////////////////////////////////////////////////////////////////////////////////
void Execute(TwoBoneIKComponent ikc, in AnimatorEntityRefComponent aer)
{
if (ikc.weight == 0)
return;
var rigDef = rigDefLookup[aer.animatorEntity];
using var animStream = AnimationStream.Create(runtimeData, rigDef);
var targetEntityRigRootRelativePose = IKCommon.GetRigRelativeEntityPose
(
ikc.target,
aer.animatorEntity,
animStream.GetWorldPose(0),
runtimeData,
localTransformLookup,
parentLookup,
animatorEntityRefLookup,
rigDefLookup
);
var midEntityRef = animatorEntityRefLookup[ikc.mid];
var tipEntityRef = animatorEntityRefLookup[ikc.tip];
var rootWorldPose = animStream.GetWorldPose(aer.boneIndexInAnimationRig);
var midWorldPose = animStream.GetWorldPose(midEntityRef.boneIndexInAnimationRig);
var tipWorldPose = animStream.GetWorldPose(tipEntityRef.boneIndexInAnimationRig);
var targetWorldPos = math.lerp(tipWorldPose.pos, targetEntityRigRootRelativePose.pos, ikc.weight);
var initialTipRotation = tipWorldPose.rot;
var rootToMidVec = midWorldPose.pos - rootWorldPose.pos;
var rootToMidVecLen = math.length(rootToMidVec);
var midToTipVec = tipWorldPose.pos - midWorldPose.pos;
var midToTipVecLen = math.length(midToTipVec);
var rootToTipVec = tipWorldPose.pos - rootWorldPose.pos;
var rootToTipVecLen = math.length(rootToTipVec);
var rootToTargetVec = targetWorldPos - rootWorldPose.pos;
var rootToTargetVecLen = math.length(rootToTargetVec);
var curBendAngle = GetAngleFromCosineLaw(rootToMidVecLen, midToTipVecLen, rootToTipVecLen);
var targetBendAngle = GetAngleFromCosineLaw(rootToMidVecLen, midToTipVecLen, rootToTargetVecLen);
var bendAxis = math.cross(rootToMidVec, midToTipVec);
var bendAxisLenSqr = math.lengthsq(bendAxis);
if (bendAxisLenSqr < math.EPSILON)
{
bendAxis = math.cross(rootToTargetVec, midToTipVec);
bendAxisLenSqr = math.lengthsq(bendAxis);
if (bendAxisLenSqr <= math.EPSILON)
bendAxis = math.up();
}
bendAxis = math.normalize(bendAxis);
var deltaAngle = curBendAngle - targetBendAngle;
var midRotDelta = quaternion.AxisAngle(bendAxis, deltaAngle);
var midRot = math.mul(midRotDelta, midWorldPose.rot);
midRot = math.normalize(midRot);
animStream.SetWorldRotation(midEntityRef.boneIndexInAnimationRig, midRot);
tipWorldPose = animStream.GetWorldPose(tipEntityRef.boneIndexInAnimationRig);
var updatedRootToTipVec = tipWorldPose.pos - rootWorldPose.pos;
var rootRotDelta = MathUtils.FromToRotation(updatedRootToTipVec, rootToTargetVec);
var rootRot = math.mul(rootRotDelta, rootWorldPose.rot);
animStream.SetWorldRotation(aer.boneIndexInAnimationRig, rootRot);
float rootToTipLenSqr = math.lengthsq(rootToTipVec);
if (ikc.midBentHint != Entity.Null && rootToTipLenSqr > 0)
{
var hintRigRelativePose = IKCommon.GetRigRelativeEntityPose
(
ikc.midBentHint,
aer.animatorEntity,
animStream.GetWorldPose(0),
runtimeData,
localTransformLookup,
parentLookup,
animatorEntityRefLookup,
rigDefLookup
);
var tipPose = animStream.GetWorldPose(tipEntityRef.boneIndexInAnimationRig);
var midPose = animStream.GetWorldPose(midEntityRef.boneIndexInAnimationRig);
rootToMidVec = midPose.pos - rootWorldPose.pos;
rootToTipVec = tipPose.pos - rootWorldPose.pos;
var rootToTipVecNormalized = math.normalize(rootToTipVec);
var rootToHintVec = hintRigRelativePose.pos - rootWorldPose.pos;
var p0 = rootToMidVec - rootToTipVecNormalized * math.dot(rootToMidVec, rootToTipVecNormalized);
var p1 = rootToHintVec - rootToTipVecNormalized * math.dot(rootToHintVec, rootToTipVecNormalized);
float jointMaxLen = rootToMidVecLen + midToTipVecLen;
var p0LenSqr = math.lengthsq(p0);
var p1LenSqr = math.lengthsq(p1);
var maxProjLen = jointMaxLen * jointMaxLen;
if (p0LenSqr > maxProjLen * 0.001f && p1LenSqr > 0)
{
var hintRotation = MathUtils.FromToRotation(p0, p1);
rootWorldPose = animStream.GetWorldPose(aer.boneIndexInAnimationRig);
animStream.SetWorldRotation(aer.boneIndexInAnimationRig, math.mul(hintRotation, rootWorldPose.rot));
}
}
var finalTipRot = math.slerp(initialTipRotation, targetEntityRigRootRelativePose.rot, ikc.weight);
animStream.SetWorldRotation(tipEntityRef.boneIndexInAnimationRig, finalTipRot);
}
/////////////////////////////////////////////////////////////////////////////////
float GetAngleFromCosineLaw(float aLen, float bLen, float cLen)
{
var cosC = (aLen * aLen + bLen * bLen - cLen * cLen) / (aLen * bLen) * 0.5f;
cosC = math.clamp(cosC, -1.0f, 1.0f);
var rv = math.acos(cosC);
return rv;
}
}
//==============================================================================//
[BurstCompile]
public void OnCreate(ref SystemState ss)
{
var q = SystemAPI.QueryBuilder()
.WithAll<TwoBoneIKComponent, AnimatorEntityRefComponent>()
.Build();
ss.RequireForUpdate(q);
}
/////////////////////////////////////////////////////////////////////////////////
[BurstCompile]
public void OnUpdate(ref SystemState ss)
{
var rigDefLookup = SystemAPI.GetComponentLookup<RigDefinitionComponent>(true);
var localTransformLookup = SystemAPI.GetComponentLookup<LocalTransform>(true);
var parentLookup = SystemAPI.GetComponentLookup<Parent>(true);
var animatorEntityRefLookup = SystemAPI.GetComponentLookup<AnimatorEntityRefComponent>(true);
ref var runtimeData = ref SystemAPI.GetSingletonRW<RuntimeAnimationData>().ValueRW;
var ikJob = new TwoBoneIKJob()
{
rigDefLookup = rigDefLookup,
runtimeData = runtimeData,
localTransformLookup = localTransformLookup,
animatorEntityRefLookup = animatorEntityRefLookup,
parentLookup = parentLookup
};
ikJob.ScheduleParallel();
}
}
}
@@ -0,0 +1,18 @@
fileFormatVersion: 2
guid: 53563f8e07d29ff4394b6e031fcd443b
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
AssetOrigin:
serializedVersion: 1
productId: 298480
packageName: Rukhanka Animation System 2
packageVersion: 2.9.0
assetPath: Packages/com.rukhanka.animation/Rukhanka.Runtime/IK/TwoBoneIKSystem.cs
uploadId: 897522