private void AnimateParachute(ParachuteInput input, CharacterMuscleLimits limits, CharacterParts parts)
        {
            CharacterMuscleOutput o = new CharacterMuscleOutput();

            Vector2 lineInput = Max(input.Brakes, input.FrontRisers, input.RearRisers);

            /* Todo: Lower body has to try and cancel out oscillations about harness pivot
             * I suspect that if we can get a rough version of this going it'll be enough
             */

            o.ArmLClose       = -lineInput.x * 0.6f;
            o.ArmRClose       = lineInput.y * 0.6f;
            o.LegLUpperPitch  = Mathf.Abs(input.WeightShift.y) * 0.66f;
            o.LegRUpperPitch  = Mathf.Abs(input.WeightShift.y) * 0.66f;
            o.LegLLowerPitch  = input.WeightShift.y;
            o.LegRLowerPitch  = input.WeightShift.y;
            o.LegLUpperClose -= input.WeightShift.x;
            o.LegRUpperClose -= input.WeightShift.x;
            o.TorsoPitch      = -input.WeightShift.y;
            o.TorsoRoll       = input.WeightShift.x * 0.66f;

            CharacterMuscleOutput.Clamp(ref o);
            CharacterMuscleOutput.ScaleToLimits(ref o, limits);
            CharacterMuscleOutput.ApplyPose(ref o, _parachutePose);
            CharacterMuscleOutput.Output(ref o, parts);
        }
        public static void ScaleToLimits(ref CharacterMuscleOutput o, CharacterMuscleLimits l)
        {
            // linear motion, meters
            o.ShoulderLPitch *= l.ShoulderPitch;
            o.ShoulderRPitch *= l.ShoulderPitch;

            // angular motion, degrees
            o.ArmLUpperPitch *= l.ArmUpperPitch;
            o.ArmRUpperPitch *= l.ArmUpperPitch;
            o.ArmLUpperRoll  *= l.ArmUpperRoll;
            o.ArmRUpperRoll  *= l.ArmUpperRoll;
            o.ArmLLowerPitch *= l.ArmLowerPitch;
            o.ArmRLowerPitch *= l.ArmLowerPitch;
            o.ArmLClose      *= l.ArmClose;
            o.ArmRClose      *= l.ArmClose;
            o.LegLUpperPitch *= l.LegUpperPitch;
            o.LegRUpperPitch *= l.LegUpperPitch;
            o.LegLUpperRoll  *= l.LegUpperRoll;
            o.LegRUpperRoll  *= l.LegUpperRoll;
            o.LegLUpperClose *= l.LegUpperClose;
            o.LegRUpperClose *= l.LegUpperClose;
            o.LegLLowerPitch *= l.LegLowerPitch;
            o.LegRLowerPitch *= l.LegLowerPitch;
            o.TorsoPitch     *= l.TorsoPitch;
            o.TorsoRoll      *= l.TorsoRoll;
            o.TorsoYaw       *= l.TorsoYaw;
        }
        private void AnimateWingsuit(CharacterInput input, CharacterMuscleLimits limits, CharacterParts parts)
        {
            PlayerPose.Lerp(ref _currentPose, _defaultPose, _curledPose, input.Cannonball);

            float normalizedSpeed = Mathf.Clamp01(_target.RelativeVelocity.magnitude / _wingsuitStabilizerConfig.MaxSpeed);

            input.CloseLeftArm  += Mathf.Pow(normalizedSpeed, 1.5f) * 0.25f;
            input.CloseRightArm += Mathf.Pow(normalizedSpeed, 1.5f) * 0.25f;

            float pitchUp   = Mathf.Min(0f, input.Pitch);
            float pitchDown = Mathf.Min(0f, -input.Pitch);

            CharacterMuscleOutput o = new CharacterMuscleOutput();

            /* Transform user input into joint angle modifications. (still in unit values) */
            o.ShoulderLPitch = input.Pitch + input.Roll - input.CloseLeftArm;
            o.ShoulderRPitch = input.Pitch - input.Roll - input.CloseRightArm;
            o.ArmLUpperPitch = input.Roll * 0.3f + pitchDown * 0f - input.CloseLeftArm * 0.5f;
            o.ArmRUpperPitch = -input.Roll * 0.3f + pitchDown * 0f - input.CloseRightArm * 0.5f;
            o.ArmLUpperRoll  = -input.Pitch;
            o.ArmRUpperRoll  = input.Pitch;
            o.ArmLLowerPitch = -input.Pitch * 1f + -input.Roll * 0.5f + input.CloseLeftArm * 0.0f;
            o.ArmRLowerPitch = -input.Pitch * 1f + input.Roll * 0.5f + input.CloseRightArm * 0.0f;
            o.ArmLClose      = -input.CloseLeftArm * 1f + pitchDown * 1f + input.Roll * (input.Roll < 0f ? 1f : 0f);
            o.ArmRClose      = input.CloseRightArm * 1f - pitchDown * 1f + input.Roll * (input.Roll > 0f ? 1f : 0f);
            o.LegLUpperPitch = pitchUp * 1.0f + Mathf.Pow(pitchDown, 2f) * 0.5f + input.Yaw * 0.25f;
            o.LegRUpperPitch = pitchUp * 1.0f + Mathf.Pow(pitchDown, 2f) * 0.5f - input.Yaw * 0.25f;
            o.LegLUpperRoll  = input.Roll;
            o.LegRUpperRoll  = input.Roll;
            o.LegLLowerPitch = pitchUp * 1f + Mathf.Pow(pitchDown, 2f) + input.Yaw * 1f;
            o.LegRLowerPitch = pitchUp * 1f + Mathf.Pow(pitchDown, 2f) - input.Yaw * 1f;
            o.TorsoPitch     = pitchUp * 0.5f;
            o.TorsoRoll      = input.Roll * 1f + input.Yaw * 1f;
            o.TorsoYaw       = input.Yaw * 0.5f;

            CharacterMuscleOutput.Clamp(ref o);
            CharacterMuscleOutput.ScaleToLimits(ref o, limits);
            CharacterMuscleOutput.ApplyPose(ref o, _currentPose);

            /* Apply a specific stability effect to just the arms (Todo: move this elsewhere, make configurable) */

            float armUpperPitchHeadingStability = 10f * Mathf.InverseLerp(15f, -5f, _target.AngleOfAttack);

            o.ArmLUpperPitch += armUpperPitchHeadingStability;
            o.ArmRUpperPitch += armUpperPitchHeadingStability;

            float armLowerPitchHeadingStability = 15f * Mathf.InverseLerp(20f, -5f, _target.AngleOfAttack);

            o.ArmLLowerPitch += armLowerPitchHeadingStability;
            o.ArmRLowerPitch += armLowerPitchHeadingStability;

            /* Apply user-configured pitch attitude offset */

            o.TorsoPitch     += limits.PitchAttitudeTorsoMax * _userConfig.PitchAttitude;
            o.LegLUpperPitch += limits.PitchAttitudeLegsMax * _userConfig.PitchAttitude;
            o.LegRUpperPitch += limits.PitchAttitudeLegsMax * _userConfig.PitchAttitude;

            /* Send new targets to all the joints */

            CharacterMuscleOutput.Output(ref o, parts);
        }