voidFAnimationRuntime::AccumulateMeshSpaceRotationAdditiveToLocalPoseInternal(FCompactPose& BasePose, const FCompactPose& MeshSpaceRotationAdditive, float Weight) { ... // Convert base pose from local space to mesh space rotation. FAnimationRuntime::ConvertPoseToMeshRotation(BasePose);
// Add MeshSpaceRotAdditive to it FAnimationRuntime::AccumulateLocalSpaceAdditivePoseInternal(BasePose, MeshSpaceRotationAdditive, Weight);
// Convert back to local space FAnimationRuntime::ConvertMeshRotationPoseToLocalSpace(BasePose); }
在UE中通过右键Animation/(Blend Space 1D/ Blend Space)创建,在创建时需要选择Blend Space对应的Skeleton Mesh。
下面以Blend Space 1D为例。Blend Space 1D的编辑界面如图所示。
其中,黄色区域为当前Skeleton Mesh对应的动画资源,蓝色区域为混合空间的编辑器。
对于Blend Space 1D混合空间为一个一维的坐标轴,这个坐标轴的取值范围可由Minimun Axis和Maximun Axis参数设置。同时这个坐标轴被划分成了若干个均匀网格(蓝色区域中灰色竖线),每条网格线上都可以放置一个动画(从黄色区域中拖拽到网格上)。
混合空间可以接受一个输入的坐标值,根据坐标值,混合空间找到坐标对应的网格以及器两端的动画并对动画线性插值。这可以实现游戏中某一类动作的平滑过度。例如,对于跑步/走路的动画,可以通过创建一个Blend Space 1D。在Blend Space 1D中沿着数轴放置 向后跑-向后走-静止-向前走-向前跑的动画,并将输入设置为当前角色的速度。
// This is our reach goal. FVector DesiredPos = Effector; FVector DesiredDelta = DesiredPos - RootPos; float DesiredLength = DesiredDelta.Size();
// Find lengths of upper and lower limb in the ref skeleton. // Use actual sizes instead of ref skeleton, so we take into account translation and scaling from other bone controllers. float MaxLimbLength = LowerLimbLength + UpperLimbLength;
// Check to handle case where DesiredPos is the same as RootPos. FVector DesiredDir; if (DesiredLength < (float)KINDA_SMALL_NUMBER) { DesiredLength = (float)KINDA_SMALL_NUMBER; DesiredDir = FVector(1, 0, 0); } else { DesiredDir = DesiredDelta.GetSafeNormal(); }
// Get joint target (used for defining plane that joint should be in). FVector JointTargetDelta = JointTarget - RootPos; // UT constfloat JointTargetLengthSqr = JointTargetDelta.SizeSquared();
// Same check as above, to cover case when JointTarget position is the same as RootPos. FVector JointPlaneNormal, JointBendDir; if (JointTargetLengthSqr < FMath::Square((float)KINDA_SMALL_NUMBER)) { ...//处理退化情况 } else { JointPlaneNormal = DesiredDir ^ JointTargetDelta;// UT' X UJ ... }
方向可以通过 减去其在 方向上投影的分量计算。
1 2 3 4 5 6 7 8 9 10 11 12 13 14
// we have to just pick 2 random vector perp to DesiredDir and each other. if (JointPlaneNormal.SizeSquared() < FMath::Square((float)KINDA_SMALL_NUMBER)) { ...//处理退化情况 } else { JointPlaneNormal.Normalize();
// Find the final member of the reference frame by removing any component of JointTargetDelta along DesiredDir. // This should never leave a zero vector, because we've checked DesiredDir and JointTargetDelta are not parallel. JointBendDir = JointTargetDelta - ((JointTargetDelta | DesiredDir) * DesiredDir); JointBendDir.Normalize(); }
// If we are trying to reach a goal beyond the length of the limb, clamp it to something solvable and extend limb fully. if (DesiredLength >= MaxLimbLength) { ...//处理第一种情况 } else { // So we have a triangle we know the side lengths of. We can work out the angle between DesiredDir and the direction of the upper limb // using the sin rule: constfloat TwoAB = 2.f * UpperLimbLength * DesiredLength;
// If CosAngle is less than 0, the upper arm actually points the opposite way to DesiredDir, so we handle that. constbool bReverseUpperBone = (CosAngle < 0.f);
// Angle between upper limb and DesiredDir // ACos clamps internally so we dont need to worry about out-of-range values here. constfloat Angle = FMath::Acos(CosAngle);
// Now we calculate the distance of the joint from the root -> effector line. // This forms a right-angle triangle, with the upper limb as the hypotenuse. constfloat JointLineDist = UpperLimbLength * FMath::Sin(Angle);
// And the final side of that triangle - distance along DesiredDir of perpendicular. // ProjJointDistSqr can't be neg, because JointLineDist must be <= UpperLimbLength because appSin(Angle) is <= 1. constfloat ProjJointDistSqr = (UpperLimbLength*UpperLimbLength) - (JointLineDist*JointLineDist); // although this shouldn't be ever negative, sometimes Xbox release produces -0.f, causing ProjJointDist to be NaN // so now I branch it. float ProjJointDist = (ProjJointDistSqr > 0.f) ? FMath::Sqrt(ProjJointDistSqr) : 0.f; if (bReverseUpperBone) { ProjJointDist *= -1.f; } // So now we can work out where to put the joint! ...//第三步 } }
// If CosAngle is less than 0, the upper arm actually points the opposite way to DesiredDir, so we handle that. constbool bReverseUpperBone = (CosAngle < 0.f);
// Angle between upper limb and DesiredDir // ACos clamps internally so we dont need to worry about out-of-range values here. constfloat Angle = FMath::Acos(CosAngle);
// And the final side of that triangle - distance along DesiredDir of perpendicular. // ProjJointDistSqr can't be neg, because JointLineDist must be <= UpperLimbLength because appSin(Angle) is <= 1. constfloat ProjJointDistSqr = (UpperLimbLength*UpperLimbLength) - (JointLineDist*JointLineDist); // although this shouldn't be ever negative, sometimes Xbox release produces -0.f, causing ProjJointDist to be NaN // so now I branch it. float ProjJointDist = (ProjJointDistSqr > 0.f) ? FMath::Sqrt(ProjJointDistSqr) : 0.f; if (bReverseUpperBone) { ProjJointDist *= -1.f; }