189 lines
8.0 KiB
C#
189 lines
8.0 KiB
C#
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();
|
|
}
|
|
}
|
|
}
|