/// <summary>
        /// 【SDK3】<see cref="BlendShapePreset.Blink"/>を変換し、視線追従を有効化します。
        /// </summary>
        /// <param name="avatar"></param>
        /// <param name="clips"></param>
        /// <param name="useShapeKeyNormalsAndTangents"/>
        private static void EnableEyeLook(
            GameObject avatar,
            IEnumerable <VRMBlendShapeClip> clips,
            bool useShapeKeyNormalsAndTangents
            )
        {
            VRMBlendShapeClip clip = clips.FirstOrDefault(c => c.Preset == BlendShapePreset.Blink);
            var lookAtBoneApplyer  = avatar.GetComponent <VRMLookAtBoneApplyer>();

            if (!clip && !lookAtBoneApplyer)
            {
                return;
            }

            var renderer = avatar.transform.Find(VRChatUtility.AutoBlinkMeshPath).GetComponent <SkinnedMeshRenderer>();
            var mesh     = renderer.sharedMesh;

            if (clip)
            {
                mesh.AddBlendShapeFrame(
                    BlendShapeReplacer.BlinkShapeKeyName,
                    1,
                    BlendShapeReplacer.GenerateShapeKey(
                        clip.ShapeKeyValues,
                        BlendShapeReplacer.GetAllShapeKeys(mesh, useShapeKeyNormalsAndTangents)
                        ),
                    null,
                    null
                    );
                EditorUtility.SetDirty(mesh);
            }

#if VRC_SDK_VRCSDK3
            var descriptor = avatar.GetComponent <VRCAvatarDescriptor>();
            descriptor.enableEyeLook = true;

            var settings = new VRCAvatarDescriptor.CustomEyeLookSettings();

            if (clip)
            {
                settings.eyelidType         = VRCAvatarDescriptor.EyelidType.Blendshapes;
                settings.eyelidsSkinnedMesh = renderer;
                settings.eyelidsBlendshapes = new[] { mesh.blendShapeCount - 1, -1, -1 };
            }

            if (lookAtBoneApplyer)
            {
                settings.eyeMovement = new VRCAvatarDescriptor.CustomEyeLookSettings.EyeMovements()
                {
                    excitement = 0.5f,
                    confidence = 0,
                };
                settings.leftEye       = lookAtBoneApplyer.LeftEye.Transform;
                settings.rightEye      = lookAtBoneApplyer.RightEye.Transform;
                settings.eyesLookingUp = new VRCAvatarDescriptor.CustomEyeLookSettings.EyeRotations()
                {
                    left  = Quaternion.Euler(x: -lookAtBoneApplyer.VerticalUp.CurveYRangeDegree, 0, 0),
                    right = Quaternion.Euler(x: -lookAtBoneApplyer.VerticalUp.CurveYRangeDegree, 0, 0),
                };
                settings.eyesLookingDown = new VRCAvatarDescriptor.CustomEyeLookSettings.EyeRotations()
                {
                    left  = Quaternion.Euler(x: lookAtBoneApplyer.VerticalUp.CurveYRangeDegree, 0, 0),
                    right = Quaternion.Euler(x: lookAtBoneApplyer.VerticalUp.CurveYRangeDegree, 0, 0),
                };
                settings.eyesLookingLeft = new VRCAvatarDescriptor.CustomEyeLookSettings.EyeRotations()
                {
                    left  = Quaternion.Euler(0, y: -lookAtBoneApplyer.HorizontalOuter.CurveYRangeDegree, 0),
                    right = Quaternion.Euler(0, y: -lookAtBoneApplyer.HorizontalInner.CurveYRangeDegree, 0),
                };
                settings.eyesLookingRight = new VRCAvatarDescriptor.CustomEyeLookSettings.EyeRotations()
                {
                    left  = Quaternion.Euler(0, y: lookAtBoneApplyer.HorizontalInner.CurveYRangeDegree, 0),
                    right = Quaternion.Euler(0, y: lookAtBoneApplyer.HorizontalOuter.CurveYRangeDegree, 0),
                };
            }

            descriptor.customEyeLookSettings = settings;
#endif
        }
        /// <summary>
        /// Animatorコンポーネントを使用せずに、<see cref="BlendShapePreset.Neutral"/>、および<see cref="BlendShapePreset.Blink"/>を変換します。
        /// </summary>
        /// <remarks>
        /// <see cref="BlendShapePreset.Blink"/>が関連付けられたメッシュが見つからない、またはそのメッシュに
        /// <see cref="BlendShapeReplacer.OrderedBlinkGeneratedByCatsBlenderPlugin"/>がそろっていれば何もしません。
        /// それらのキーが存在せず、<see cref="BlendShapePreset.Blink_L"/>、<see cref="BlendShapePreset.Blink_R"/>がいずれも設定されていればそれを優先します。
        /// </remarks>
        /// <param name="avatar"></param>
        /// <param name="clips"></param>
        /// <param name="useShapeKeyNormalsAndTangents"></param>
        private static void SetBlinkWithoutAnimator(
            GameObject avatar,
            IEnumerable <VRMBlendShapeClip> clips,
            bool useShapeKeyNormalsAndTangents
            )
        {
            var  renderer = avatar.transform.Find(VRChatUtility.AutoBlinkMeshPath).GetComponent <SkinnedMeshRenderer>();
            Mesh mesh     = renderer.sharedMesh;

            if (BlendShapeReplacer.GetBlendShapeNames(mesh: mesh)
                .Take(BlendShapeReplacer.OrderedBlinkGeneratedByCatsBlenderPlugin.Count())
                .SequenceEqual(BlendShapeReplacer.OrderedBlinkGeneratedByCatsBlenderPlugin))
            {
                return;
            }

            IEnumerable <BlendShape> shapeKeys = BlendShapeReplacer.GetAllShapeKeys(mesh, useShapeKeyNormalsAndTangents);

            mesh.ClearBlendShapes();

            var dummyShapeKeyNames = new List <string>();

            if (clips.FirstOrDefault(c => c.Preset == BlendShapePreset.Blink_L) &&
                clips.FirstOrDefault(c => c.Preset == BlendShapePreset.Blink_R))
            {
                mesh.AddBlendShapeFrame(
                    BlendShapeReplacer.OrderedBlinkGeneratedByCatsBlenderPlugin.ElementAt(0),
                    BlendShapeReplacer.MaxBlendShapeFrameWeight,
                    BlendShapeReplacer.GenerateShapeKey(
                        namesAndWeights: clips.First(c => c.Preset == BlendShapePreset.Blink_L).ShapeKeyValues,
                        shapeKeys: shapeKeys
                        ),
                    null,
                    null
                    );
                mesh.AddBlendShapeFrame(
                    BlendShapeReplacer.OrderedBlinkGeneratedByCatsBlenderPlugin.ElementAt(1),
                    BlendShapeReplacer.MaxBlendShapeFrameWeight,
                    BlendShapeReplacer.GenerateShapeKey(
                        namesAndWeights: clips.First(c => c.Preset == BlendShapePreset.Blink_R).ShapeKeyValues,
                        shapeKeys: shapeKeys
                        ),
                    null,
                    null
                    );
            }
            else
            {
                mesh.AddBlendShapeFrame(
                    BlendShapeReplacer.OrderedBlinkGeneratedByCatsBlenderPlugin.ElementAt(0),
                    BlendShapeReplacer.MaxBlendShapeFrameWeight,
                    BlendShapeReplacer.GenerateShapeKey(
                        namesAndWeights: clips.First(c => c.Preset == BlendShapePreset.Blink).ShapeKeyValues,
                        shapeKeys: shapeKeys
                        ),
                    null,
                    null
                    );
                dummyShapeKeyNames.Add(BlendShapeReplacer.OrderedBlinkGeneratedByCatsBlenderPlugin.ElementAt(1));
            }
            dummyShapeKeyNames.AddRange(BlendShapeReplacer.OrderedBlinkGeneratedByCatsBlenderPlugin.Skip(2));
            foreach (string name in dummyShapeKeyNames)
            {
                BlendShapeReplacer.AddDummyShapeKey(mesh: mesh, name: name);
            }

            foreach (BlendShape shapeKey in shapeKeys)
            {
                if (BlendShapeReplacer.OrderedBlinkGeneratedByCatsBlenderPlugin.Contains(shapeKey.Name))
                {
                    continue;
                }

                mesh.AddBlendShapeFrame(
                    shapeKey.Name,
                    BlendShapeReplacer.MaxBlendShapeFrameWeight,
                    shapeKey.Positions.ToArray(),
                    shapeKey.Normals.ToArray(),
                    shapeKey.Tangents.ToArray()
                    );
            }

            EditorUtility.SetDirty(mesh);
        }