该项目的目的是同时测试和演示来自 Unity DOTS 技术堆栈的多个新包。
您最多选择25个主题 主题必须以中文或者字母或数字开头,可以包含连字符 (-),并且长度不得超过35个字符
 
 
 
 

263 行
12 KiB

using System;
using UnityEngine;
namespace Unity.Animation
{
using Burst;
using Mathematics;
using DataFlowGraph;
using Profiling;
using Entities;
public class StandIkNode
: NodeDefinition<StandIkNode.Data, StandIkNode.SimPorts, StandIkNode.KernelData, StandIkNode.KernelDefs, StandIkNode.Kernel>
, IMsgHandler<BlobAssetReference<RigDefinition>>
, IMsgHandler<StandIkNode.StandIkData>
{
public struct SimPorts : ISimulationPortDefinition
{
public MessageInput<StandIkNode, BlobAssetReference<RigDefinition>> RigDefinition;
public MessageInput<StandIkNode, StandIkData> StandIkSetup;
}
const float k_Epsilon = 0.000001F;
const float k_Rad2Deg = 360 / (math.PI * 2);
static readonly ProfilerMarker k_ProfileMarker = new ProfilerMarker("Animation.StandIkNode");
public struct KernelDefs : IKernelPortDefinition
{
public DataInput<StandIkNode, Buffer<float>> Input;
public DataOutput<StandIkNode, Buffer<float>> Output;
public DataInput<StandIkNode, float> Weight;
}
public struct Data : INodeData
{
}
public struct StandIkData
{
public Settings Settings;
public int LeftToeIdx;
public int RightToeIdx;
public int LeftFootIkIdx;
public int RightFootIkIdx;
public int HipsIdx;
public float2 ikOffset;
public float3 normalLeftFoot;
public float3 normalRightFoot;
public float Weight;
}
[Serializable]
public struct Settings
{
[Tooltip("Place over toe bone when in it's stand idle pose. " +
"Note that the Foot IK should not be enabled while adjusting!")]
public float3 leftToeStandPos;
[Tooltip("Place over toe bone when in it's stand idle pose. " +
"Note that the Foot IK should not be enabled while adjusting!")]
public float3 rightToeStandPos;
[Range(0, 1)]
public int debugIdlePos;
[Space(10)]
[Range(0, 1)]
public int enabled;
[Space(10)]
[Range(0f, 1f)]
public float emitRayOffset;
[Range(0f, 20f)]
public float maxRayDistance;
[Range(0, 1)]
public int debugRayCast;
[Space(10)]
[Range(0f, 1f)]
public float maxStepSize;
[Range(-90f, 90f)]
public float weightShiftAngle;
[Range(-1f, 1f)]
public float weightShiftHorizontal;
[Range(-1f, 1f)]
public float weightShiftVertical;
[Range(5f, 50f)]
public float maxFootRotationOffset;
[Space(10)]
[Range(0f, 1f)]
public float enterStateEaseIn;
}
public struct KernelData : IKernelData
{
public BlobAssetReference<RigDefinition> RigDefinition;
public ProfilerMarker ProfilerMarker;
public StandIkData Data;
public float3 VectorForward;
public float3 VectorOne;
public quaternion OffsetRotation;
}
[BurstCompile]
public struct Kernel : IGraphKernel<KernelData, KernelDefs>
{
public void Execute(RenderContext ctx, KernelData data, ref KernelDefs ports)
{
data.ProfilerMarker.Begin();
var output = ctx.Resolve(ref ports.Output);
output.CopyFrom(ctx.Resolve(in ports.Input));
var weightValue = ctx.Resolve(ports.Weight);
if (weightValue > 0f)
{
var stream = AnimationStreamProvider.Create(data.RigDefinition, output);
if (stream.IsNull)
{
data.ProfilerMarker.End();
return;
}
// TODO: (sunek) Get a real number! Requires replicating and incrementing the IK weight in update
// ikWeight = Mathf.Clamp01(ikWeight + (1 - settings.enterStateEaseIn));
var ikWeight = data.Data.Weight;
var settings = data.Data.Settings;
var leftToePos = stream.GetLocalToRigTranslation(data.Data.LeftToeIdx);
var rightToePos = stream.GetLocalToRigTranslation(data.Data.RightToeIdx);
var leftIkOffset = data.Data.ikOffset.x * ikWeight;
var rightIkOffset = data.Data.ikOffset.y * ikWeight;
leftToePos += new float3(0f, leftIkOffset, 0f);
rightToePos += new float3(0f, rightIkOffset, 0f);
var leftAnklePos = stream.GetLocalToRigTranslation(data.Data.LeftFootIkIdx);
var rightAnklePos = stream.GetLocalToRigTranslation(data.Data.RightFootIkIdx);
var leftAnkleRot = stream.GetLocalToRigRotation(data.Data.LeftFootIkIdx);
var rightAnkleRot = stream.GetLocalToRigRotation(data.Data.RightFootIkIdx);
var leftAnkleIkPos = new float3(leftAnklePos.x, leftAnklePos.y + leftIkOffset, leftAnklePos.z);
var rightAnkleIkPos = new float3(rightAnklePos.x, rightAnklePos.y + rightIkOffset, rightAnklePos.z);
var hipHeightOffset = (leftIkOffset + rightIkOffset) * 0.5f;
var forwardBackBias = (leftIkOffset - rightIkOffset) * settings.weightShiftHorizontal;
// TODO: (sunek) Rework weight shift to move towards actual lower foot?
hipHeightOffset += Mathf.Abs(leftIkOffset - rightIkOffset) * settings.weightShiftVertical;
var standAngle = math.mul(quaternion.AxisAngle(Vector3.up, math.radians(settings.weightShiftAngle)), data.VectorForward);
var hipsPosition = stream.GetLocalToRigTranslation(data.Data.HipsIdx);
hipsPosition += new float3(standAngle.x * forwardBackBias, hipHeightOffset, standAngle.z * forwardBackBias);
stream.SetLocalToRigTranslation(data.Data.HipsIdx, hipsPosition);
// Figure out the normal rotation
var leftNormalRot = quaternion.identity;
var rightNormalRot = quaternion.identity;
if (!data.Data.normalLeftFoot.Equals(float3.zero))
{
leftNormalRot = quaternion.LookRotationSafe(data.Data.normalLeftFoot, data.VectorForward);
rightNormalRot = quaternion.LookRotationSafe(data.Data.normalRightFoot, data.VectorForward);
leftNormalRot = math.mul(leftNormalRot, data.OffsetRotation);
rightNormalRot = math.mul(rightNormalRot, data.OffsetRotation);
}
// Clamp normal rotation
var leftAngle = Angle(quaternion.identity, leftNormalRot);
var rightAngle = Angle(quaternion.identity, rightNormalRot);
if (leftAngle > settings.maxFootRotationOffset && settings.maxFootRotationOffset > 0f)
{
var fraction = settings.maxFootRotationOffset / leftAngle;
leftNormalRot = math.nlerp(Quaternion.identity, leftNormalRot, fraction);
}
if (rightAngle > settings.maxFootRotationOffset && settings.maxFootRotationOffset > 0f)
{
var fraction = settings.maxFootRotationOffset / rightAngle;
rightNormalRot = math.nlerp(Quaternion.identity, rightNormalRot, fraction);
}
// Apply rotation to ankle
var leftToesMatrix = Matrix4x4.TRS(leftToePos, quaternion.identity, data.VectorOne);
var rightToesMatrix = Matrix4x4.TRS(rightToePos, quaternion.identity, data.VectorOne);
leftNormalRot = math.normalize(leftNormalRot);
rightNormalRot = math.normalize(rightNormalRot);
leftAnkleRot = math.normalize(leftAnkleRot);
rightAnkleRot = math.normalize(rightAnkleRot);
var leftToesNormalDeltaMatrix = Matrix4x4.TRS(leftToePos, leftNormalRot, data.VectorOne) * leftToesMatrix.inverse;
var rightToesNormalDeltaMatrix = Matrix4x4.TRS(rightToePos, rightNormalRot, data.VectorOne) * rightToesMatrix.inverse;
var leftAnkleMatrix = Matrix4x4.TRS(leftAnkleIkPos, leftAnkleRot, data.VectorOne) * leftToesMatrix.inverse;
var rightAnkleMatrix = Matrix4x4.TRS(rightAnkleIkPos, rightAnkleRot, data.VectorOne) * rightToesMatrix.inverse;
leftAnkleMatrix = leftToesNormalDeltaMatrix * leftAnkleMatrix * leftToesMatrix;
rightAnkleMatrix = rightToesNormalDeltaMatrix * rightAnkleMatrix * rightToesMatrix;
// Todo:Find a better way to do this?
var leftColumn = leftAnkleMatrix.GetColumn(3);
leftAnkleIkPos = new float3(leftColumn[0], leftColumn[1], leftColumn[2]); // TODO: (sunek) Is there a slicing syntax instead?
var rightColumn = rightAnkleMatrix.GetColumn(3);
rightAnkleIkPos = new float3(rightColumn[0], rightColumn[1], rightColumn[2]);
leftAnkleRot = math.nlerp(leftAnkleRot, leftAnkleMatrix.rotation, ikWeight);
rightAnkleRot = math.nlerp(rightAnkleRot, rightAnkleMatrix.rotation, ikWeight);
// Update ik position
// TODO: (sunek) Consider combating leg overstretch
var leftPosition = math.lerp(leftAnklePos, leftAnkleIkPos, ikWeight);
var rightPosition = math.lerp(rightAnklePos, rightAnkleIkPos, ikWeight);
stream.SetLocalToRigTranslation(data.Data.LeftFootIkIdx, leftPosition);
stream.SetLocalToRigTranslation(data.Data.RightFootIkIdx, rightPosition);
stream.SetLocalToRigRotation(data.Data.LeftFootIkIdx, leftAnkleRot);
stream.SetLocalToRigRotation(data.Data.RightFootIkIdx, rightAnkleRot);
}
data.ProfilerMarker.End();
}
private static float Angle(quaternion a, quaternion b)
{
// TODO: Return in radians (and convert the user param from deg to rad)?
float dot = math.dot(a, b);
return IsEqualUsingDot(dot) ? 0.0f : math.acos(math.min(math.abs(dot), 1.0F)) * 2.0F * k_Rad2Deg;
}
private static bool IsEqualUsingDot(float dot)
{
// Returns false in the presence of NaN values.
return dot > 1.0f - k_Epsilon;
}
}
public override void Init(InitContext ctx)
{
ref var kData = ref GetKernelData(ctx.Handle);
kData.ProfilerMarker = k_ProfileMarker;
kData.VectorForward = new float3(0f, 0f, 1f);
kData.VectorOne = new float3(1f, 1f, 1f);
// kData.OffsetRotation = quaternion.Euler(math.radians(-90f), 0f, math.radians(180f));
kData.OffsetRotation = new quaternion(0f, 0.7f, 0.7f, 0f);
}
public void HandleMessage(in MessageContext ctx, in BlobAssetReference<RigDefinition> rigBindings)
{
GetKernelData(ctx.Handle).RigDefinition = rigBindings;
Set.SetBufferSize(ctx.Handle, (OutputPortID)KernelPorts.Output, Buffer<float>.SizeRequest(rigBindings.Value.Bindings.CurveCount));
}
public void HandleMessage(in MessageContext ctx, in StandIkData data)
{
GetKernelData(ctx.Handle).Data = data;
}
}
}