ADD : 1차 스트레치 테스트 스크립트 업데이트

This commit is contained in:
KINDNICK 2025-10-20 23:21:37 +09:00
parent 82a1df91a7
commit 097678e572
3 changed files with 106 additions and 14 deletions

View File

@ -129,6 +129,22 @@ namespace RootMotion.FinalIK
/// </summary>
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>
/// Target position of the hand. Will be overwritten if target is assigned.
/// </summary>
@ -305,15 +321,15 @@ namespace RootMotion.FinalIK
if (LOD < 1) Stretching();
Vector3 bendNormal = GetBendNormal(position - upperArm.solverPosition);
// 팔 부분만 IK 적용
// 팔 부분만 IK 적용 (Struggle 메커니즘 포함)
if (hasShoulder)
{
VirtualBone.SolveTrigonometric(bones, 1, 2, 3, position, bendNormal, positionWeight);
VirtualBone.SolveTrigonometric(bones, 1, 2, 3, position, bendNormal, positionWeight, struggleStart, struggleEnd);
}
else
{
VirtualBone.SolveTrigonometric(bones, 0, 1, 2, position, bendNormal, positionWeight);
VirtualBone.SolveTrigonometric(bones, 0, 1, 2, position, bendNormal, positionWeight, struggleStart, struggleEnd);
}
// 손 회전 적용

View File

@ -76,6 +76,22 @@ namespace RootMotion.FinalIK {
/// </summary>
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>
/// Target position of the toe/foot. Will be overwritten if target is assigned.
/// </summary>
@ -283,8 +299,8 @@ namespace RootMotion.FinalIK {
public void Solve(bool stretch) {
if (stretch && LOD < 1) Stretching();
// Foot pass
VirtualBone.SolveTrigonometric(bones, 0, 1, 2, footPosition, bendNormal, 1f);
// Foot pass (with Struggle mechanism)
VirtualBone.SolveTrigonometric(bones, 0, 1, 2, footPosition, bendNormal, 1f, struggleStart, struggleEnd);
// Rotate foot back to where it was before the last solving
RotateTo(foot, footRotation);
@ -295,10 +311,10 @@ namespace RootMotion.FinalIK {
FixTwistRotations();
return;
}
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
FixTwistRotations();

View File

@ -133,23 +133,83 @@ namespace RootMotion.FinalIK {
// Direction of the limb in solver
targetPosition = Vector3.Lerp(bones[third].solverPosition, targetPosition, weight);
Vector3 dir = targetPosition - bones[first].solverPosition;
// Distance between the first and the last transform solver positions
float sqrMag = dir.sqrMagnitude;
if (sqrMag == 0f) return;
float length = Mathf.Sqrt(sqrMag);
float sqrMag1 = (bones[second].solverPosition - bones[first].solverPosition).sqrMagnitude;
float sqrMag2 = (bones[third].solverPosition - bones[second].solverPosition).sqrMagnitude;
// 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
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
Quaternion q1 = Quaternion.FromToRotation(bones[second].solverPosition - bones[first].solverPosition, toBendPoint);
if (weight < 1f) q1 = Quaternion.Lerp(Quaternion.identity, q1, weight);