示例#1
0
        /// <summary>
        /// オートアイムーブメントが有効化される条件を揃えます。
        /// </summary>
        /// <remarks>
        /// 参照:
        /// 100の人さんのツイート: “Body当たりでした! オートアイムーブメントの条件解明! • ルート直下に、BlendShapeが4つ以上設定された「Body」という名前のオブジェクトが存在する • ルート直下に Armature/Hips/Spine/Chest/Neck/Head/RightEyeとLeftEye  ※すべて空のオブジェクトで良い  ※目のボーンの名称は何でも良い… https://t.co/dLnHl7QjJk”
        /// <https://twitter.com/esperecyan/status/1045713562348347392>
        /// </remarks>
        /// <param name="avatar"></param>
        private static void EnableAutoEyeMovement(GameObject avatar)
        {
            // ダミーの階層構造の作成
            foreach (var path in VRChatUtility.RequiredPathForAutoEyeMovement.Concat(new string[] { VRChatUtility.AutoBlinkMeshPath }))
            {
                var current = avatar.transform;
                foreach (var name in path.Split(separator: '/'))
                {
                    Transform child = current.Find(name);
                    if (!child)
                    {
                        child        = new GameObject(name).transform;
                        child.parent = current;
                    }
                    current = child;
                }
            }

            // ダミーのまばたき用ブレンドシェイプの作成
            Mesh mesh = avatar.transform.Find(VRChatUtility.AutoBlinkMeshPath).GetSharedMesh();

            if (mesh.blendShapeCount >= BlendShapeReplacer.OrderedBlinkGeneratedByCatsBlenderPlugin.Count())
            {
                return;
            }

            foreach (var name in BlendShapeReplacer.OrderedBlinkGeneratedByCatsBlenderPlugin.Skip(count: mesh.blendShapeCount))
            {
                BlendShapeReplacer.AddDummyShapeKey(mesh: mesh, name: name);
            }

            EditorUtility.SetDirty(mesh);
        }
        /// <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);
        }
        /// <summary>
        /// <see cref="BlendShapePreset.Neutral"/>、および<see cref="BlendShapePreset.Blink"/>を変換します。
        /// </summary>
        /// <remarks>
        /// 参照:
        /// 最新版(10/02時点)自動まばたきの実装【VRChat技術情報】 — VRChatパブリックログ
        /// <https://jellyfish-qrage.hatenablog.com/entry/2018/10/02/152316>
        /// VRchatでMMDモデルをアバターとして使う方法——上級者編 — 東屋書店
        /// <http://www.hushimero.xyz/entry/vrchat-EyeTracking#%E5%A4%A7%E5%8F%A3%E9%96%8B%E3%81%91%E3%82%8B%E5%95%8F%E9%A1%8C%E3%81%AE%E8%A7%A3%E6%B1%BA>
        /// 技術勢の元怒さんのツイート: “自動まばたきはまばたきシェイプキーを、まばたき防止のほうは自動まばたきのエナブルONOFFを操作してますね。 欲しければサンプル渡せますよ。… ”
        /// <https://twitter.com/gend_VRchat/status/1100155987216879621>
        /// momoma/ナル@VRChatter/VTuberさんのツイート: “3F目にBehavior 1のキーを追加したら重複しなくなったわ、なるほどな… ”
        /// <https://twitter.com/momoma_creative/status/1137917887262339073>
        /// </remarks>
        /// <param name="avatar"></param>
        /// <param name="clips"></param>
        /// <paramref name="useShapeKeyNormalsAndTangents"/>
        private static void SetNeutralAndBlink(
            GameObject avatar,
            IEnumerable <VRMBlendShapeClip> clips,
            bool useShapeKeyNormalsAndTangents
            )
        {
            AnimatorController neutralAndBlinkController
                = BlendShapeReplacer.CreateSingleAnimatorController(avatar: avatar, name: "blink");
            AnimationClip animationClip = neutralAndBlinkController.animationClips[0];

            VRMBlendShapeClip blinkClip = null;

            foreach (var preset in new[] { BlendShapePreset.Blink, BlendShapePreset.Neutral })
            {
                VRMBlendShapeClip clip = clips.FirstOrDefault(c => c.Preset == preset);
                if (!clip)
                {
                    continue;
                }

                if (preset == BlendShapePreset.Blink)
                {
                    blinkClip = clip;
                }

                foreach (var(shapeKeyName, shapeKeyWeight) in clip.ShapeKeyValues)
                {
                    var keys = BlendShapeReplacer.BlinkWeights;
                    if (preset == BlendShapePreset.Neutral)
                    {
                        if (blinkClip && blinkClip.ShapeKeyValues.ContainsKey(shapeKeyName))
                        {
                            // NEUTRALとBlinkが同一のシェイプキーを参照していた場合
                            float blinkShapeKeyWeight = blinkClip.ShapeKeyValues[shapeKeyName];
                            var   animationCurve      = new AnimationCurve();
                            foreach (var(seconds, blendShapePreset) in BlendShapeReplacer.NeutralAndBlinkWeights)
                            {
                                float weight;
                                switch (blendShapePreset)
                                {
                                case BlendShapePreset.Neutral:
                                    weight = shapeKeyWeight;
                                    break;

                                case BlendShapePreset.Blink:
                                    weight = blinkShapeKeyWeight;
                                    break;

                                default:
                                    weight = 0;
                                    break;
                                }
                                animationCurve.AddKey(new Keyframe(seconds, weight));
                            }

                            animationClip.SetCurve(
                                "",
                                typeof(SkinnedMeshRenderer),
                                "blendShape." + shapeKeyName,
                                animationCurve
                                );
                            continue;
                        }

                        keys = BlendShapeReplacer.NeutralWeights;
                    }

                    SetBlendShapeCurve(animationClip, shapeKeyName, shapeKeyWeight, keys, setRelativePath: false);
                }

                foreach (MaterialValueBinding binding in clip.MaterialValues)
                {
                    // TODO
                }
            }

            Transform transform = avatar.transform.Find(VRChatUtility.AutoBlinkMeshPath);

            transform.gameObject.AddComponent <Animator>().runtimeAnimatorController = neutralAndBlinkController;

            // VRChat側の自動まばたきを回避
            Mesh mesh = transform.GetSharedMesh();
            IEnumerable <BlendShape> shapeKeys = BlendShapeReplacer.GetAllShapeKeys(mesh, useShapeKeyNormalsAndTangents);

            mesh.ClearBlendShapes();
            foreach (string name in BlendShapeReplacer.OrderedBlinkGeneratedByCatsBlenderPlugin)
            {
                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);
        }