void UpdateFootPlanting(
            Transform transform, PlayerFoley playerFoley, float speedSqr, float speedScalar, bool isJumping)
        {
            if (isJumping)
            {
                if ((_feetPlanted & 8) == 0)
                {
                    _feetPlanted = 0;
                }
            }
            else
            {
                var position          = transform.position;
                var footPlantPosition = new Vector3(position.x, 0f, position.z);

                float walkStepSqr = footPlanting.walkStepDistance * footPlanting.walkStepDistance;
                float runStepSqr  = footPlanting.runStepDistance * footPlanting.runStepDistance;
                float stepSqr     = (footPlantPosition - _lastFootPlantPosition).sqrMagnitude;
                float needStepSqr = Mathf.Lerp(walkStepSqr, runStepSqr, speedScalar);

                _feetPlanted &= ~8;

                if (_feetPlanted == 0)
                {
                    _feetPlanted            = 1;
                    _lastFootPlantPosition  = footPlantPosition;
                    _lastVegetationPosition = footPlantPosition;

                    Vector3        normal;
                    PhysicMaterial physicalMaterial;
                    FindFootPlacement(ref position, out normal, out physicalMaterial);
                    playerFoley.PlayFootstep(
                        transform, position, normal, physicalMaterial, _jumpSpeedScalar, landing: true);
                }
                else if (speedSqr <= footPlanting.stopSpeedThreshold * footPlanting.stopSpeedThreshold)
                {
                    if (_feetPlanted != 2)
                    {
                        _feetPlanted           = 2;
                        _lastFootPlantPosition = footPlantPosition;

                        Vector3        normal;
                        PhysicMaterial physicalMaterial;
                        FindFootPlacement(ref position, out normal, out physicalMaterial);
                        playerFoley.PlayFootstep(transform, position, normal, physicalMaterial, speedScalar: 0f);
                    }
                }
                else if (stepSqr >= needStepSqr)
                {
                    _feetPlanted           = 1;
                    _lastFootPlantPosition = footPlantPosition;

                    Vector3        normal;
                    PhysicMaterial physicalMaterial;
                    FindFootPlacement(ref position, out normal, out physicalMaterial);
                    playerFoley.PlayFootstep(transform, position, normal, physicalMaterial, speedScalar);
                }
                else if (playerFoley.intersectingVegetation)
                {
                    var vegDistSqr = (footPlantPosition - _lastVegetationPosition).sqrMagnitude;

                    if (vegDistSqr >= needStepSqr * 0.3f)
                    {
                        _lastVegetationPosition = footPlantPosition;

                        Vector3        normal;
                        PhysicMaterial physicalMaterial;
                        FindFootPlacement(ref position, out normal, out physicalMaterial);
                        playerFoley.PlayVegetation(transform, position, speedScalar);
                    }
                }
            }
        }
        public void Simulate(
            CharacterController characterController, PlayerFoley playerFoley, PlayerInput input)
        {
            var   transform = this.transform;
            float deltaTime = Time.deltaTime;

            bool isJumping = !characterController.isGrounded;
            bool jumpStart = false;

            if (!isJumping && input.jump)
            {
                _jumpingScalar = -jumping.force;
                jumpStart      = true;
            }
            else if ((_jumpingScalar += deltaTime * jumping.dampSpeed) >= jumping.gravityFactor)
            {
                _jumpingScalar = jumping.gravityFactor;
            }

            float frictionFactor = isJumping ? 0.99f : 0.1f;
            var   absMove        = new Vector2(Mathf.Abs(input.move.x), Mathf.Abs(input.move.y));
            var   signMove       = new Vector2(Mathf.Sign(input.move.x), Mathf.Sign(input.move.y));

            var wantSpeed = Vector2.one;

            wantSpeed *= Mathf.Lerp(locomotion.walkSpeed, locomotion.runSpeed, input.run);
            wantSpeed  = Vector2.Scale(wantSpeed, signMove);
            wantSpeed  = Vector2.Lerp(_movingSpeed, wantSpeed, isJumping ? 0f : deltaTime * 10f);
            wantSpeed  = Vector2.Scale(
                wantSpeed,
                Vector2.Lerp(
                    absMove,
                    new Vector2(
                        Mathf.Approximately(input.move.x, 0f) ? 0f : 1f,
                        Mathf.Approximately(input.move.y, 0f) ? 0f : 1f),
                    input.run));
            _movingSpeed = Vector2.Lerp(wantSpeed, wantSpeed * frictionFactor, !isJumping ? 0f : deltaTime);

            float angularSpeed = 360f * deltaTime * Mathf.Lerp(looking.lookSpeed, looking.runLookSpeed, input.run);
            float lookX        = input.look.x;
            float lookY        = input.look.y * Mathf.Sign(input.move.y);

            _lookingAngles.x = _lookingAngles.x + lookX * angularSpeed;
            _lookingAngles.y = _lookingAngles.y + lookY * angularSpeed;

            _lookingAngles.x = Angles.Unwind(
                Mathf.Clamp(
                    Angles.ToRelative(_lookingAngles.x),
                    looking.pitchLimitMin,
                    looking.pitchLimitMax));

            UpdateLooking(transform, deltaTime);
            UpdateMoving(transform, deltaTime, characterController);

            float walkSpeedSqr = locomotion.walkSpeed * locomotion.walkSpeed;
            float runSpeedSqr  = locomotion.runSpeed * locomotion.runSpeed;
            float speedSqr     = _movingSpeed.sqrMagnitude;
            float speedScalar  = (speedSqr - walkSpeedSqr) / (runSpeedSqr - walkSpeedSqr);

            if (speedScalar < Mathf.Epsilon)
            {
                speedScalar = 0f;
            }

            int hadFeetPlanted = _feetPlanted;

            isJumping = !characterController.isGrounded; // check again after moving

            if (jumpStart)
            {
                _jumpSpeedScalar = speedScalar;
            }

            UpdateFootPlanting(transform, playerFoley, speedSqr, speedScalar, isJumping);

            if (jumpStart)
            {
                playerFoley.OnJump();
            }
            else if (hadFeetPlanted == 0 && _feetPlanted != 0)
            {
                playerFoley.OnLand();
            }

            playerFoley.breathingIntensity = isJumping ? 1f : speedScalar;
            playerFoley.playerJumping      = jumpStart || _feetPlanted == 0;
        }