Merge remote-tracking branch 'origin/ik-test-script'
This commit is contained in:
commit
f6731f98ac
@ -129,6 +129,22 @@ namespace RootMotion.FinalIK
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public AnimationCurve stretchCurve = new AnimationCurve();
|
public AnimationCurve stretchCurve = new AnimationCurve();
|
||||||
|
|
||||||
|
[LargeHeader("Struggle (Anti-Popping)")]
|
||||||
|
|
||||||
|
[Tooltip("When the arm reaches near maximum extension, this feature smoothly transitions to prevent sudden 'popping' or 'snapping'. Start value represents the percentage of arm length (0.99 = 99%) where the smoothing begins. Inspired by hvr-ik's struggle mechanism.")]
|
||||||
|
/// <summary>
|
||||||
|
/// When the arm reaches near maximum extension, this feature smoothly transitions to prevent sudden 'popping' or 'snapping'. Start value represents the percentage of arm length where the smoothing begins.
|
||||||
|
/// </summary>
|
||||||
|
[Range(0.9f, 1.0f)]
|
||||||
|
public float struggleStart = 0.99f;
|
||||||
|
|
||||||
|
[Tooltip("The end threshold for the struggle zone. Represents the percentage of arm length (1.04 = 104%) where the smoothing ends. The arm will smoothly approach full extension within the struggle zone (struggleStart to struggleEnd).")]
|
||||||
|
/// <summary>
|
||||||
|
/// The end threshold for the struggle zone. The arm will smoothly approach full extension within this range.
|
||||||
|
/// </summary>
|
||||||
|
[Range(1.0f, 1.2f)]
|
||||||
|
public float struggleEnd = 1.04f;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Target position of the hand. Will be overwritten if target is assigned.
|
/// Target position of the hand. Will be overwritten if target is assigned.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@ -305,15 +321,15 @@ namespace RootMotion.FinalIK
|
|||||||
if (LOD < 1) Stretching();
|
if (LOD < 1) Stretching();
|
||||||
|
|
||||||
Vector3 bendNormal = GetBendNormal(position - upperArm.solverPosition);
|
Vector3 bendNormal = GetBendNormal(position - upperArm.solverPosition);
|
||||||
|
|
||||||
// 팔 부분만 IK 적용
|
// 팔 부분만 IK 적용 (Struggle 메커니즘 포함)
|
||||||
if (hasShoulder)
|
if (hasShoulder)
|
||||||
{
|
{
|
||||||
VirtualBone.SolveTrigonometric(bones, 1, 2, 3, position, bendNormal, positionWeight);
|
VirtualBone.SolveTrigonometric(bones, 1, 2, 3, position, bendNormal, positionWeight, struggleStart, struggleEnd);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
VirtualBone.SolveTrigonometric(bones, 0, 1, 2, position, bendNormal, positionWeight);
|
VirtualBone.SolveTrigonometric(bones, 0, 1, 2, position, bendNormal, positionWeight, struggleStart, struggleEnd);
|
||||||
}
|
}
|
||||||
|
|
||||||
// 손 회전 적용
|
// 손 회전 적용
|
||||||
|
|||||||
@ -76,6 +76,22 @@ namespace RootMotion.FinalIK {
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public AnimationCurve stretchCurve = new AnimationCurve();
|
public AnimationCurve stretchCurve = new AnimationCurve();
|
||||||
|
|
||||||
|
[LargeHeader("Struggle (Anti-Popping)")]
|
||||||
|
|
||||||
|
[Tooltip("When the leg reaches near maximum extension, this feature smoothly transitions to prevent sudden 'popping' or 'snapping'. Start value represents the percentage of leg length (0.99 = 99%) where the smoothing begins. Inspired by hvr-ik's struggle mechanism.")]
|
||||||
|
/// <summary>
|
||||||
|
/// When the leg reaches near maximum extension, this feature smoothly transitions to prevent sudden 'popping' or 'snapping'. Start value represents the percentage of leg length where the smoothing begins.
|
||||||
|
/// </summary>
|
||||||
|
[Range(0.9f, 1.0f)]
|
||||||
|
public float struggleStart = 0.99f;
|
||||||
|
|
||||||
|
[Tooltip("The end threshold for the struggle zone. Represents the percentage of leg length (1.04 = 104%) where the smoothing ends. The leg will smoothly approach full extension within the struggle zone (struggleStart to struggleEnd).")]
|
||||||
|
/// <summary>
|
||||||
|
/// The end threshold for the struggle zone. The leg will smoothly approach full extension within this range.
|
||||||
|
/// </summary>
|
||||||
|
[Range(1.0f, 1.2f)]
|
||||||
|
public float struggleEnd = 1.04f;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Target position of the toe/foot. Will be overwritten if target is assigned.
|
/// Target position of the toe/foot. Will be overwritten if target is assigned.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@ -283,8 +299,8 @@ namespace RootMotion.FinalIK {
|
|||||||
public void Solve(bool stretch) {
|
public void Solve(bool stretch) {
|
||||||
if (stretch && LOD < 1) Stretching();
|
if (stretch && LOD < 1) Stretching();
|
||||||
|
|
||||||
// Foot pass
|
// Foot pass (with Struggle mechanism)
|
||||||
VirtualBone.SolveTrigonometric(bones, 0, 1, 2, footPosition, bendNormal, 1f);
|
VirtualBone.SolveTrigonometric(bones, 0, 1, 2, footPosition, bendNormal, 1f, struggleStart, struggleEnd);
|
||||||
|
|
||||||
// Rotate foot back to where it was before the last solving
|
// Rotate foot back to where it was before the last solving
|
||||||
RotateTo(foot, footRotation);
|
RotateTo(foot, footRotation);
|
||||||
@ -295,10 +311,10 @@ namespace RootMotion.FinalIK {
|
|||||||
FixTwistRotations();
|
FixTwistRotations();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
Vector3 b = Vector3.Cross(foot.solverPosition - thigh.solverPosition, toes.solverPosition - foot.solverPosition).normalized;
|
Vector3 b = Vector3.Cross(foot.solverPosition - thigh.solverPosition, toes.solverPosition - foot.solverPosition).normalized;
|
||||||
|
|
||||||
VirtualBone.SolveTrigonometric(bones, 0, 2, 3, position, b, 1f);
|
VirtualBone.SolveTrigonometric(bones, 0, 2, 3, position, b, 1f, struggleStart, struggleEnd);
|
||||||
|
|
||||||
// Fix thigh twist relative to target rotation
|
// Fix thigh twist relative to target rotation
|
||||||
FixTwistRotations();
|
FixTwistRotations();
|
||||||
|
|||||||
@ -133,23 +133,83 @@ namespace RootMotion.FinalIK {
|
|||||||
|
|
||||||
// Direction of the limb in solver
|
// Direction of the limb in solver
|
||||||
targetPosition = Vector3.Lerp(bones[third].solverPosition, targetPosition, weight);
|
targetPosition = Vector3.Lerp(bones[third].solverPosition, targetPosition, weight);
|
||||||
|
|
||||||
Vector3 dir = targetPosition - bones[first].solverPosition;
|
Vector3 dir = targetPosition - bones[first].solverPosition;
|
||||||
|
|
||||||
// Distance between the first and the last transform solver positions
|
// Distance between the first and the last transform solver positions
|
||||||
float sqrMag = dir.sqrMagnitude;
|
float sqrMag = dir.sqrMagnitude;
|
||||||
if (sqrMag == 0f) return;
|
if (sqrMag == 0f) return;
|
||||||
float length = Mathf.Sqrt(sqrMag);
|
float length = Mathf.Sqrt(sqrMag);
|
||||||
|
|
||||||
float sqrMag1 = (bones[second].solverPosition - bones[first].solverPosition).sqrMagnitude;
|
float sqrMag1 = (bones[second].solverPosition - bones[first].solverPosition).sqrMagnitude;
|
||||||
float sqrMag2 = (bones[third].solverPosition - bones[second].solverPosition).sqrMagnitude;
|
float sqrMag2 = (bones[third].solverPosition - bones[second].solverPosition).sqrMagnitude;
|
||||||
|
|
||||||
// Get the general world space bending direction
|
// Get the general world space bending direction
|
||||||
Vector3 bendDir = Vector3.Cross(dir, bendNormal);
|
Vector3 bendDir = Vector3.Cross(dir, bendNormal);
|
||||||
|
|
||||||
// Get the direction to the trigonometrically solved position of the second transform
|
// Get the direction to the trigonometrically solved position of the second transform
|
||||||
Vector3 toBendPoint = GetDirectionToBendPoint(dir, length, bendDir, sqrMag1, sqrMag2);
|
Vector3 toBendPoint = GetDirectionToBendPoint(dir, length, bendDir, sqrMag1, sqrMag2);
|
||||||
|
|
||||||
|
// Position the second transform
|
||||||
|
Quaternion q1 = Quaternion.FromToRotation(bones[second].solverPosition - bones[first].solverPosition, toBendPoint);
|
||||||
|
if (weight < 1f) q1 = Quaternion.Lerp(Quaternion.identity, q1, weight);
|
||||||
|
|
||||||
|
RotateAroundPoint(bones, first, bones[first].solverPosition, q1);
|
||||||
|
|
||||||
|
Quaternion q2 = Quaternion.FromToRotation(bones[third].solverPosition - bones[second].solverPosition, targetPosition - bones[second].solverPosition);
|
||||||
|
if (weight < 1f) q2 = Quaternion.Lerp(Quaternion.identity, q2, weight);
|
||||||
|
|
||||||
|
RotateAroundPoint(bones, second, bones[second].solverPosition, q2);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Overload with struggle parameters (inspired by hvr-ik)
|
||||||
|
/// <summary>
|
||||||
|
/// Solve the bone chain virtually with struggle mechanism to prevent popping when near maximum extension.
|
||||||
|
/// </summary>
|
||||||
|
public static void SolveTrigonometric(VirtualBone[] bones, int first, int second, int third, Vector3 targetPosition, Vector3 bendNormal, float weight, float struggleStart, float struggleEnd) {
|
||||||
|
if (weight <= 0f) return;
|
||||||
|
|
||||||
|
// Direction of the limb in solver
|
||||||
|
targetPosition = Vector3.Lerp(bones[third].solverPosition, targetPosition, weight);
|
||||||
|
|
||||||
|
Vector3 rootPos = bones[first].solverPosition;
|
||||||
|
float limbLength1 = Mathf.Sqrt((bones[second].solverPosition - bones[first].solverPosition).sqrMagnitude);
|
||||||
|
float limbLength2 = Mathf.Sqrt((bones[third].solverPosition - bones[second].solverPosition).sqrMagnitude);
|
||||||
|
float totalLength = limbLength1 + limbLength2;
|
||||||
|
|
||||||
|
// Apply Struggle Correction (hvr-ik style)
|
||||||
|
float distance = Vector3.Distance(rootPos, targetPosition);
|
||||||
|
if (distance >= totalLength * struggleStart) {
|
||||||
|
float finalLength;
|
||||||
|
if (struggleStart != struggleEnd) {
|
||||||
|
// Map distance to 0-1 in the struggle zone
|
||||||
|
float lerpAmount = Mathf.Clamp01((distance - totalLength * struggleStart) / (totalLength * struggleEnd - totalLength * struggleStart));
|
||||||
|
// Use pow(1-x, 4) curve for smooth transition (capacitor charge curve)
|
||||||
|
float easedAmount = 1f - Mathf.Pow(1f - lerpAmount, 4f);
|
||||||
|
finalLength = Mathf.Lerp(totalLength * struggleStart, totalLength, easedAmount);
|
||||||
|
} else {
|
||||||
|
finalLength = totalLength;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Pull target position inward to the corrected distance
|
||||||
|
targetPosition = rootPos + (targetPosition - rootPos).normalized * finalLength;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Continue with original trigonometric solving
|
||||||
|
Vector3 dir = targetPosition - rootPos;
|
||||||
|
float sqrMag = dir.sqrMagnitude;
|
||||||
|
if (sqrMag == 0f) return;
|
||||||
|
float length = Mathf.Sqrt(sqrMag);
|
||||||
|
|
||||||
|
float sqrMag1 = limbLength1 * limbLength1;
|
||||||
|
float sqrMag2 = limbLength2 * limbLength2;
|
||||||
|
|
||||||
|
// Get the general world space bending direction
|
||||||
|
Vector3 bendDir = Vector3.Cross(dir, bendNormal);
|
||||||
|
|
||||||
|
// Get the direction to the trigonometrically solved position of the second transform
|
||||||
|
Vector3 toBendPoint = GetDirectionToBendPoint(dir, length, bendDir, sqrMag1, sqrMag2);
|
||||||
|
|
||||||
// Position the second transform
|
// Position the second transform
|
||||||
Quaternion q1 = Quaternion.FromToRotation(bones[second].solverPosition - bones[first].solverPosition, toBendPoint);
|
Quaternion q1 = Quaternion.FromToRotation(bones[second].solverPosition - bones[first].solverPosition, toBendPoint);
|
||||||
if (weight < 1f) q1 = Quaternion.Lerp(Quaternion.identity, q1, weight);
|
if (weight < 1f) q1 = Quaternion.Lerp(Quaternion.identity, q1, weight);
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user