You cannot select more than 25 topics
			Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
		
		
		
		
		
			
		
			
				
	
	
		
			345 lines
		
	
	
		
			15 KiB
		
	
	
	
		
			C#
		
	
			
		
		
	
	
			345 lines
		
	
	
		
			15 KiB
		
	
	
	
		
			C#
		
	
using UnityEngine;
 | 
						|
 | 
						|
namespace RootMotion.FinalIK
 | 
						|
{
 | 
						|
    public partial class IKSolverVR : IKSolver
 | 
						|
    {
 | 
						|
        public partial class Locomotion
 | 
						|
        {
 | 
						|
            [Tooltip("Start moving (horizontal distance to HMD + HMD velocity) threshold.")]
 | 
						|
            /// <summary>
 | 
						|
            /// Start moving (horizontal distance to HMD + HMD velocity) threshold.
 | 
						|
            /// </summary>
 | 
						|
            [ShowIf("mode", Mode.Animated)]
 | 
						|
            public float moveThreshold = 0.3f;
 | 
						|
 | 
						|
            // ANIMATION
 | 
						|
            [ShowLargeHeaderIf("Animation", "mode", Mode.Animated)] [SerializeField] byte animationHeader;
 | 
						|
 | 
						|
            [Tooltip("Minimum locomotion animation speed.")]
 | 
						|
            /// <summary>
 | 
						|
            /// Minimum locomotion animation speed.
 | 
						|
            /// </summary>
 | 
						|
            [ShowRangeIf(0.1f, 1f, "mode", Mode.Animated)]
 | 
						|
            public float minAnimationSpeed = 0.2f;
 | 
						|
 | 
						|
            [Tooltip("Maximum locomotion animation speed.")]
 | 
						|
            /// <summary>
 | 
						|
            /// Maximum locomotion animation speed.
 | 
						|
            /// </summary>
 | 
						|
            [ShowRangeIf(1f, 10f, "mode", Mode.Animated)]
 | 
						|
            public float maxAnimationSpeed = 3f;
 | 
						|
 | 
						|
            [Tooltip("Smoothing time for Vector3.SmoothDamping 'VRIK_Horizontal' and 'VRIK_Vertical' parameters. Larger values make animation smoother, but less responsive.")]
 | 
						|
            /// <summary>
 | 
						|
            /// Smoothing time for Vector3.SmoothDamping 'VRIK_Horizontal' and 'VRIK_Vertical' parameters. Larger values make animation smoother, but less responsive.
 | 
						|
            /// </summary>
 | 
						|
            [ShowRangeIf(0.05f, 0.2f, "mode", Mode.Animated)]
 | 
						|
            public float animationSmoothTime = 0.1f;
 | 
						|
 | 
						|
            [ShowLargeHeaderIf("Root Position", "mode", Mode.Animated)] [SerializeField] byte rootPositionHeader;
 | 
						|
 | 
						|
            [Tooltip("X and Z standing offset from the horizontal position of the HMD.")]
 | 
						|
            /// <summary>
 | 
						|
            /// X and Z standing offset from the horizontal position of the HMD.
 | 
						|
            /// </summary>
 | 
						|
            [ShowIf("mode", Mode.Animated)]
 | 
						|
            public Vector2 standOffset;
 | 
						|
 | 
						|
            [Tooltip("Lerp root towards the horizontal position of the HMD with this speed while moving.")]
 | 
						|
            /// <summary>
 | 
						|
            /// Lerp root towards the horizontal position of the HMD with this speed while moving.
 | 
						|
            /// </summary>
 | 
						|
            [ShowRangeIf(0f, 50f, "mode", Mode.Animated)]
 | 
						|
            public float rootLerpSpeedWhileMoving = 30f;
 | 
						|
 | 
						|
            [Tooltip("Lerp root towards the horizontal position of the HMD with this speed while in transition from locomotion to idle state.")]
 | 
						|
            /// <summary>
 | 
						|
            /// Lerp root towards the horizontal position of the HMD with this speed while in transition from locomotion to idle state.
 | 
						|
            /// </summary>
 | 
						|
            [ShowRangeIf(0f, 50f, "mode", Mode.Animated)]
 | 
						|
            public float rootLerpSpeedWhileStopping = 10f;
 | 
						|
 | 
						|
            [Tooltip("Lerp root towards the horizontal position of the HMD with this speed while turning on spot.")]
 | 
						|
            /// <summary>
 | 
						|
            /// Lerp root towards the horizontal position of the HMD with this speed while turning on spot.
 | 
						|
            /// </summary>
 | 
						|
            [ShowRangeIf(0f, 50f, "mode", Mode.Animated)]
 | 
						|
            public float rootLerpSpeedWhileTurning = 10f;
 | 
						|
 | 
						|
            [Tooltip("Max horizontal distance from the root to the HMD.")]
 | 
						|
            /// <summary>
 | 
						|
            /// Max horizontal distance from the root to the HMD.
 | 
						|
            /// </summary>
 | 
						|
            [ShowIf("mode", Mode.Animated)]
 | 
						|
            public float maxRootOffset = 0.5f;
 | 
						|
 | 
						|
            [ShowLargeHeaderIf("Root Rotation", "mode", Mode.Animated)] [SerializeField] byte rootRotationHeader;
 | 
						|
 | 
						|
            [Tooltip("Max root angle from head forward while moving (ik.solver.spine.maxRootAngle).")]
 | 
						|
            /// <summary>
 | 
						|
            /// Max root angle from head forward while moving (ik.solver.spine.maxRootAngle).
 | 
						|
            /// </summary>
 | 
						|
            [ShowRangeIf(0f, 180f, "mode", Mode.Animated)]
 | 
						|
            public float maxRootAngleMoving = 10f;
 | 
						|
 | 
						|
            [Tooltip("Max root angle from head forward while standing (ik.solver.spine.maxRootAngle.")]
 | 
						|
            /// <summary>
 | 
						|
            /// Max root angle from head forward while standing (ik.solver.spine.maxRootAngle.
 | 
						|
            /// </summary>
 | 
						|
            [ShowRangeIf(0f, 180f, "mode", Mode.Animated)]
 | 
						|
            public float maxRootAngleStanding = 90f;
 | 
						|
 | 
						|
            /// <summary>
 | 
						|
            /// Multiplies "VRIK_Horizontal" and "VRIK_Vertical" parameters. Larger values make steps longer and animation slower.
 | 
						|
            /// </summary>
 | 
						|
            [HideInInspector][SerializeField] public float stepLengthMlp = 1f;
 | 
						|
 | 
						|
            private Animator animator;
 | 
						|
            private Vector3 velocityLocal, velocityLocalV;
 | 
						|
            private Vector3 lastCorrection;
 | 
						|
            private Vector3 lastHeadTargetPos;
 | 
						|
            private Vector3 lastSpeedRootPos;
 | 
						|
            private Vector3 lastEndRootPos;
 | 
						|
            private float rootLerpSpeed, rootVelocityV;
 | 
						|
            private float animSpeed = 1f;
 | 
						|
            private float animSpeedV;
 | 
						|
            private float stopMoveTimer;
 | 
						|
            private float turn;
 | 
						|
            private float maxRootAngleV;
 | 
						|
            private float currentAnimationSmoothTime = 0.05f;
 | 
						|
            private bool isMoving;
 | 
						|
            private bool firstFrame = true;
 | 
						|
 | 
						|
            private static int VRIK_Horizontal;
 | 
						|
            private static int VRIK_Vertical;
 | 
						|
            private static int VRIK_IsMoving;
 | 
						|
            private static int VRIK_Speed;
 | 
						|
            private static int VRIK_Turn;
 | 
						|
            private static bool isHashed;
 | 
						|
 | 
						|
            public void Initiate_Animated(Animator animator, Vector3[] positions)
 | 
						|
            {
 | 
						|
                this.animator = animator;
 | 
						|
 | 
						|
                if (animator == null && mode == Mode.Animated)
 | 
						|
                {
 | 
						|
                    Debug.LogError("VRIK is in Animated locomotion mode, but cannot find Animator on the VRIK root gameobject.");
 | 
						|
                }
 | 
						|
 | 
						|
                ResetParams(positions);
 | 
						|
            }
 | 
						|
 | 
						|
            private void ResetParams(Vector3[] positions)
 | 
						|
            {
 | 
						|
                lastHeadTargetPos = positions[5];
 | 
						|
                lastSpeedRootPos = positions[0];
 | 
						|
                lastEndRootPos = lastSpeedRootPos;
 | 
						|
                lastCorrection = Vector3.zero;
 | 
						|
                isMoving = false;
 | 
						|
                currentAnimationSmoothTime = 0.05f;
 | 
						|
                stopMoveTimer = 1f;
 | 
						|
            }
 | 
						|
 | 
						|
            public void Reset_Animated(Vector3[] positions)
 | 
						|
            {
 | 
						|
                ResetParams(positions);
 | 
						|
 | 
						|
                if (animator == null) return;
 | 
						|
 | 
						|
                if (!isHashed)
 | 
						|
                {
 | 
						|
                    VRIK_Horizontal = Animator.StringToHash("VRIK_Horizontal");
 | 
						|
                    VRIK_Vertical = Animator.StringToHash("VRIK_Vertical");
 | 
						|
                    VRIK_IsMoving = Animator.StringToHash("VRIK_IsMoving");
 | 
						|
                    VRIK_Speed = Animator.StringToHash("VRIK_Speed");
 | 
						|
                    VRIK_Turn = Animator.StringToHash("VRIK_Turn");
 | 
						|
                    isHashed = true;
 | 
						|
                }
 | 
						|
 | 
						|
                if (!firstFrame)
 | 
						|
                {
 | 
						|
                    animator.SetFloat(VRIK_Horizontal, 0f);
 | 
						|
                    animator.SetFloat(VRIK_Vertical, 0f);
 | 
						|
                    animator.SetBool(VRIK_IsMoving, false);
 | 
						|
                    animator.SetFloat(VRIK_Speed, 1f);
 | 
						|
                    animator.SetFloat(VRIK_Turn, 0f);
 | 
						|
                }
 | 
						|
            }
 | 
						|
 | 
						|
            private void AddDeltaRotation_Animated(Quaternion delta, Vector3 pivot)
 | 
						|
            {
 | 
						|
                Vector3 toLastEndRootPos = lastEndRootPos - pivot;
 | 
						|
                lastEndRootPos = pivot + delta * toLastEndRootPos;
 | 
						|
 | 
						|
                Vector3 toLastSpeedRootPos = lastSpeedRootPos - pivot;
 | 
						|
                lastSpeedRootPos = pivot + delta * toLastSpeedRootPos;
 | 
						|
 | 
						|
                Vector3 toLastHeadTargetPos = lastHeadTargetPos - pivot;
 | 
						|
                lastHeadTargetPos = pivot + delta * toLastHeadTargetPos;
 | 
						|
            }
 | 
						|
 | 
						|
            private void AddDeltaPosition_Animated(Vector3 delta)
 | 
						|
            {
 | 
						|
                lastEndRootPos += delta;
 | 
						|
                lastSpeedRootPos += delta;
 | 
						|
                lastHeadTargetPos += delta;
 | 
						|
            }
 | 
						|
 | 
						|
            private float lastVelLocalMag;
 | 
						|
 | 
						|
            public void Solve_Animated(IKSolverVR solver, float scale, float deltaTime)
 | 
						|
            {
 | 
						|
                if (animator == null)
 | 
						|
                {
 | 
						|
                    Debug.LogError("VRIK cannot find Animator on the VRIK root gameobject.", solver.root);
 | 
						|
                    return;
 | 
						|
                }
 | 
						|
 | 
						|
                if (deltaTime <= 0f) return;
 | 
						|
 | 
						|
                // Root up vector
 | 
						|
                Vector3 rootUp = solver.rootBone.solverRotation * Vector3.up;
 | 
						|
                
 | 
						|
                // Substract any motion from parent transforms
 | 
						|
                Vector3 externalDelta = solver.rootBone.solverPosition - lastEndRootPos;
 | 
						|
                externalDelta -= animator.deltaPosition;
 | 
						|
 | 
						|
                // Head target position
 | 
						|
                Vector3 headTargetPos = solver.spine.headPosition;
 | 
						|
                Vector3 standOffsetWorld = solver.rootBone.solverRotation * new Vector3(standOffset.x, 0f, standOffset.y) * scale;
 | 
						|
                headTargetPos += standOffsetWorld;
 | 
						|
 | 
						|
                if (firstFrame)
 | 
						|
                {
 | 
						|
                    lastHeadTargetPos = headTargetPos;
 | 
						|
 | 
						|
                    firstFrame = false;
 | 
						|
                }
 | 
						|
 | 
						|
                // Head target velocity
 | 
						|
                Vector3 headTargetVelocity = (headTargetPos - lastHeadTargetPos) / deltaTime;
 | 
						|
                lastHeadTargetPos = headTargetPos;
 | 
						|
                headTargetVelocity = V3Tools.Flatten(headTargetVelocity, rootUp);
 | 
						|
 | 
						|
                // Head target offset
 | 
						|
                Vector3 offset = headTargetPos - solver.rootBone.solverPosition;
 | 
						|
                offset -= externalDelta;
 | 
						|
                offset -= lastCorrection;
 | 
						|
                offset = V3Tools.Flatten(offset, rootUp);
 | 
						|
                
 | 
						|
                // Turning
 | 
						|
                Vector3 headForward = (solver.spine.IKRotationHead * solver.spine.anchorRelativeToHead) * Vector3.forward;
 | 
						|
                headForward.y = 0f;
 | 
						|
                Vector3 headForwardLocal = Quaternion.Inverse(solver.rootBone.solverRotation) * headForward;
 | 
						|
                float angle = Mathf.Atan2(headForwardLocal.x, headForwardLocal.z) * Mathf.Rad2Deg;
 | 
						|
                angle += solver.spine.rootHeadingOffset;
 | 
						|
                float turnTarget = angle / 90f;
 | 
						|
                bool isTurning = true;
 | 
						|
                if (Mathf.Abs(turnTarget) < 0.2f)
 | 
						|
                {
 | 
						|
                    turnTarget = 0f;
 | 
						|
                    isTurning = false;
 | 
						|
                }
 | 
						|
 | 
						|
                turn = Mathf.Lerp(turn, turnTarget, Time.deltaTime * 3f);
 | 
						|
                animator.SetFloat(VRIK_Turn, turn * 2f);
 | 
						|
 | 
						|
                // Local Velocity, animation smoothing
 | 
						|
                Vector3 velocityLocalTarget = Quaternion.Inverse(solver.readRotations[0]) * (headTargetVelocity + offset);
 | 
						|
                velocityLocalTarget *= weight * stepLengthMlp;
 | 
						|
 | 
						|
                float animationSmoothTimeTarget = isTurning && !isMoving ? 0.2f : animationSmoothTime;
 | 
						|
                currentAnimationSmoothTime = Mathf.Lerp(currentAnimationSmoothTime, animationSmoothTimeTarget, deltaTime * 20f);
 | 
						|
                
 | 
						|
                velocityLocal = Vector3.SmoothDamp(velocityLocal, velocityLocalTarget, ref velocityLocalV, currentAnimationSmoothTime, Mathf.Infinity, deltaTime);
 | 
						|
                float velLocalMag = velocityLocal.magnitude / stepLengthMlp;
 | 
						|
 | 
						|
                //animator.SetBool("VRIK_StartWithRightFoot", velocityLocal.x >= 0f);
 | 
						|
                animator.SetFloat(VRIK_Horizontal, velocityLocal.x / scale);
 | 
						|
                animator.SetFloat(VRIK_Vertical, velocityLocal.z / scale);
 | 
						|
 | 
						|
                // Is Moving
 | 
						|
                float m = moveThreshold * scale;
 | 
						|
                if (isMoving) m *= 0.9f;
 | 
						|
                bool isMovingRaw = velocityLocal.sqrMagnitude > m * m;
 | 
						|
                if (isMovingRaw) stopMoveTimer = 0f;
 | 
						|
                else stopMoveTimer += deltaTime;
 | 
						|
                isMoving = stopMoveTimer < 0.05f;
 | 
						|
 | 
						|
                // Max root angle
 | 
						|
                float maxRootAngleTarget = isMoving ? maxRootAngleMoving : maxRootAngleStanding;
 | 
						|
                solver.spine.maxRootAngle = Mathf.SmoothDamp(solver.spine.maxRootAngle, maxRootAngleTarget, ref maxRootAngleV, 0.2f, Mathf.Infinity, deltaTime);
 | 
						|
 | 
						|
                animator.SetBool(VRIK_IsMoving, isMoving);
 | 
						|
 | 
						|
                // Animation speed
 | 
						|
                Vector3 currentRootPos = solver.rootBone.solverPosition;
 | 
						|
                currentRootPos -= externalDelta;
 | 
						|
                currentRootPos -= lastCorrection;
 | 
						|
 | 
						|
                Vector3 rootVelocity = (currentRootPos - lastSpeedRootPos) / deltaTime;
 | 
						|
                lastSpeedRootPos = solver.rootBone.solverPosition;
 | 
						|
                float rootVelocityMag = rootVelocity.magnitude;
 | 
						|
 | 
						|
                float animSpeedTarget = minAnimationSpeed;
 | 
						|
                if (rootVelocityMag > 0f && isMovingRaw)
 | 
						|
                {
 | 
						|
                    animSpeedTarget = animSpeed * (velLocalMag / rootVelocityMag);
 | 
						|
                }
 | 
						|
                animSpeedTarget = Mathf.Clamp(animSpeedTarget, minAnimationSpeed, maxAnimationSpeed);
 | 
						|
                animSpeed = Mathf.SmoothDamp(animSpeed, animSpeedTarget, ref animSpeedV, 0.05f, Mathf.Infinity, deltaTime);
 | 
						|
                animSpeed = Mathf.Lerp(1f, animSpeed, weight);
 | 
						|
 | 
						|
                animator.SetFloat(VRIK_Speed, animSpeed);
 | 
						|
 | 
						|
                // Is Stopping
 | 
						|
                AnimatorTransitionInfo transInfo = animator.GetAnimatorTransitionInfo(0);
 | 
						|
                bool isStopping = transInfo.IsUserName("VRIK_Stop");
 | 
						|
                
 | 
						|
                // Root lerp speed
 | 
						|
                float rootLerpSpeedTarget = 0;
 | 
						|
                if (isMoving) rootLerpSpeedTarget = rootLerpSpeedWhileMoving;
 | 
						|
                if (isStopping) rootLerpSpeedTarget = rootLerpSpeedWhileStopping;
 | 
						|
                if (isTurning) rootLerpSpeedTarget = rootLerpSpeedWhileTurning;
 | 
						|
 | 
						|
                rootLerpSpeedTarget *= Mathf.Max(headTargetVelocity.magnitude, 0.2f);
 | 
						|
                rootLerpSpeed = Mathf.Lerp(rootLerpSpeed, rootLerpSpeedTarget, deltaTime * 20f);
 | 
						|
 | 
						|
                // Root lerp and limits
 | 
						|
                headTargetPos += V3Tools.ExtractVertical(solver.rootBone.solverPosition - headTargetPos, rootUp, 1f);
 | 
						|
 | 
						|
                if (maxRootOffset > 0f)
 | 
						|
                {
 | 
						|
                    // Lerp towards head target position
 | 
						|
                    Vector3 p = solver.rootBone.solverPosition;
 | 
						|
                    
 | 
						|
                    if (rootLerpSpeed > 0f)
 | 
						|
                    {
 | 
						|
                        solver.rootBone.solverPosition = Vector3.Lerp(solver.rootBone.solverPosition, headTargetPos, rootLerpSpeed * deltaTime * weight);
 | 
						|
                    }
 | 
						|
 | 
						|
                    lastCorrection = solver.rootBone.solverPosition - p;
 | 
						|
 | 
						|
                    // Max offset
 | 
						|
                    offset = headTargetPos - solver.rootBone.solverPosition;
 | 
						|
                    offset = V3Tools.Flatten(offset, rootUp);
 | 
						|
                    float offsetMag = offset.magnitude;
 | 
						|
 | 
						|
                    if (offsetMag > maxRootOffset)
 | 
						|
                    {
 | 
						|
                        lastCorrection += (offset - (offset / offsetMag) * maxRootOffset) * weight;
 | 
						|
                        solver.rootBone.solverPosition += lastCorrection;
 | 
						|
                    }
 | 
						|
                } else
 | 
						|
                {
 | 
						|
                    // Snap to head target position
 | 
						|
                    lastCorrection = (headTargetPos - solver.rootBone.solverPosition) * weight;
 | 
						|
                    solver.rootBone.solverPosition += lastCorrection;
 | 
						|
                }
 | 
						|
 | 
						|
                lastEndRootPos = solver.rootBone.solverPosition;
 | 
						|
            }
 | 
						|
        }
 | 
						|
    }
 | 
						|
} |