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>
@ -306,14 +322,14 @@ namespace RootMotion.FinalIK
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);
@ -298,7 +314,7 @@ namespace RootMotion.FinalIK {
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

@ -162,6 +162,66 @@ namespace RootMotion.FinalIK {
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);
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);
}
//Calculates the bend direction based on the law of cosines. NB! Magnitude of the returned vector does not equal to the length of the first bone!
private static Vector3 GetDirectionToBendPoint(Vector3 direction, float directionMag, Vector3 bendDirection, float sqrMag1, float sqrMag2) {
float x = ((directionMag * directionMag) + (sqrMag1 - sqrMag2)) / 2f / directionMag;