private void ApplyLimbData(AnimationHumanStream stream, IKLimbData.JobData data, AvatarIKGoal goal, AvatarIKHint hint)
        {
            if (data.positionWeight > 0)
            {
                if (data.positionWeight >= 1)
                {
                    stream.SetGoalPosition(goal, data.position);
                    stream.SetGoalWeightPosition(goal, 1f);
                }
                else
                {
                    var position       = Vector3.Lerp(stream.GetGoalPosition(goal), data.position, data.positionWeight);
                    var positionWeight = Mathf.Lerp(stream.GetGoalWeightPosition(goal), 1f, data.positionWeight);

                    stream.SetGoalPosition(goal, position);
                    stream.SetGoalWeightPosition(goal, positionWeight);
                }
            }
            else
            {
                // For some reason, IK will be broken if same values are not reapplied before SolveIK is called
                stream.SetGoalPosition(goal, stream.GetGoalPosition(goal));
                stream.SetGoalWeightPosition(goal, stream.GetGoalWeightPosition(goal));
            }

            if (data.rotationWeight > 0)
            {
                if (data.rotationWeight >= 1)
                {
                    stream.SetGoalRotation(goal, data.rotation);
                    stream.SetGoalWeightRotation(goal, 1f);
                }
                else
                {
                    var rotation       = Quaternion.Slerp(stream.GetGoalRotation(goal), data.rotation, data.rotationWeight);
                    var rotationWeight = Mathf.Lerp(stream.GetGoalWeightRotation(goal), 1f, data.rotationWeight);

                    stream.SetGoalRotation(goal, rotation);
                    stream.SetGoalWeightRotation(goal, rotationWeight);
                }
            }
            else
            {
                // Same as above
                stream.SetGoalRotation(goal, stream.GetGoalRotation(goal));
                stream.SetGoalWeightRotation(goal, stream.GetGoalWeightRotation(goal));
            }

            if (data.hintWeight > 0)
            {
                if (data.hintWeight >= 1)
                {
                    stream.SetHintPosition(hint, data.hintPosition);
                    stream.SetHintWeightPosition(hint, 1f);
                }
                else
                {
                    var hintPosition = Vector3.Lerp(stream.GetHintPosition(hint), data.hintPosition, data.hintWeight);
                    var hintWeight   = Mathf.Lerp(stream.GetHintWeightPosition(hint), 1f, data.hintWeight);

                    stream.SetHintPosition(hint, hintPosition);
                    stream.SetHintWeightPosition(hint, hintWeight);
                }
            }
            else
            {
                // Same as above
                stream.SetHintPosition(hint, stream.GetHintPosition(hint));
                stream.SetHintWeightPosition(hint, stream.GetHintWeightPosition(hint));
            }
        }
        public static void MirrorPose(this AnimationHumanStream humanStream)
        {
            // mirror body
            for (int i = 0; i < (int)BodyDof.LastBodyDof; i++)
            {
                humanStream.MultMuscle(new MuscleHandle((BodyDof)i), BodyDoFMirror[i]);
            }

            // mirror head
            for (int i = 0; i < (int)HeadDof.LastHeadDof; i++)
            {
                humanStream.MultMuscle(new MuscleHandle((HeadDof)i), HeadDoFMirror[i]);
            }

            // swap arms
            for (int i = 0; i < (int)ArmDof.LastArmDof; i++)
            {
                humanStream.SwapMuscles(
                    new MuscleHandle(HumanPartDof.LeftArm, (ArmDof)i),
                    new MuscleHandle(HumanPartDof.RightArm, (ArmDof)i));
            }

            // swap legs
            for (int i = 0; i < (int)LegDof.LastLegDof; i++)
            {
                humanStream.SwapMuscles(
                    new MuscleHandle(HumanPartDof.LeftLeg, (LegDof)i),
                    new MuscleHandle(HumanPartDof.RightLeg, (LegDof)i));
            }

            // swap fingers
            for (int i = 0; i < (int)FingerDof.LastFingerDof; i++)
            {
                humanStream.SwapMuscles(
                    new MuscleHandle(HumanPartDof.LeftThumb, (FingerDof)i),
                    new MuscleHandle(HumanPartDof.RightThumb, (FingerDof)i));
                humanStream.SwapMuscles(
                    new MuscleHandle(HumanPartDof.LeftIndex, (FingerDof)i),
                    new MuscleHandle(HumanPartDof.RightIndex, (FingerDof)i));
                humanStream.SwapMuscles(
                    new MuscleHandle(HumanPartDof.LeftMiddle, (FingerDof)i),
                    new MuscleHandle(HumanPartDof.RightMiddle, (FingerDof)i));
                humanStream.SwapMuscles(
                    new MuscleHandle(HumanPartDof.LeftRing, (FingerDof)i),
                    new MuscleHandle(HumanPartDof.RightRing, (FingerDof)i));
                humanStream.SwapMuscles(
                    new MuscleHandle(HumanPartDof.LeftLittle, (FingerDof)i),
                    new MuscleHandle(HumanPartDof.RightLittle, (FingerDof)i));
            }


            humanStream.bodyLocalPosition = Mirrored(humanStream.bodyLocalPosition);

            // mirror rotation (invert Y axis angle)
            humanStream.bodyLocalRotation = Mirrored(humanStream.bodyLocalRotation);

            // swap ik
            Vector3[]    goalPositions       = new Vector3[4];
            Quaternion[] goalRotations       = new Quaternion[4];
            float[]      goalWeightPositons  = new float[4];
            float[]      goalWeightRotations = new float[4];
            Vector3[]    hintPositions       = new Vector3[4];
            float[]      hintWeightPositions = new float[4];
            for (int i = 0; i < 4; i++)
            {
                goalPositions[i]       = humanStream.GetGoalLocalPosition(AvatarIKGoal.LeftFoot + i);
                goalRotations[i]       = humanStream.GetGoalLocalRotation(AvatarIKGoal.LeftFoot + i);
                goalWeightPositons[i]  = humanStream.GetGoalWeightPosition(AvatarIKGoal.LeftFoot + i);
                goalWeightRotations[i] = humanStream.GetGoalWeightRotation(AvatarIKGoal.LeftFoot + i);
                hintPositions[i]       = humanStream.GetHintPosition(AvatarIKHint.LeftKnee + i);
                hintWeightPositions[i] = humanStream.GetHintWeightPosition(AvatarIKHint.LeftKnee + i);
            }
            for (int i = 0; i < 4; i++)
            {
                int j = (i + 1) % 2 + (i / 2) * 2;                  // make [1, 0, 3, 2]
                humanStream.SetGoalLocalPosition(AvatarIKGoal.LeftFoot + i, Mirrored(goalPositions[j]));
                humanStream.SetGoalLocalRotation(AvatarIKGoal.LeftFoot + i, Mirrored(goalRotations[j]));
                humanStream.SetGoalWeightPosition(AvatarIKGoal.LeftFoot + i, goalWeightPositons[j]);
                humanStream.SetGoalWeightRotation(AvatarIKGoal.LeftFoot + i, goalWeightRotations[j]);
                humanStream.SetHintPosition(AvatarIKHint.LeftKnee + i, hintPositions[j]);
                humanStream.SetHintWeightPosition(AvatarIKHint.LeftKnee + i, hintWeightPositions[j]);
            }
        }