diff --git a/Assets/UniGLTF/Runtime/MeshUtility/BoneMeshEraser.cs b/Assets/UniGLTF/Runtime/MeshUtility/BoneMeshEraser.cs index 32952aead..eb4976284 100644 --- a/Assets/UniGLTF/Runtime/MeshUtility/BoneMeshEraser.cs +++ b/Assets/UniGLTF/Runtime/MeshUtility/BoneMeshEraser.cs @@ -1,7 +1,8 @@ using System; using System.Collections.Generic; +using System.Threading.Tasks; using UnityEngine; - +using VRMShaders; namespace UniGLTF.MeshUtility { @@ -35,6 +36,13 @@ namespace UniGLTF.MeshUtility } } + /// + /// skip triangles that has weight for exclude, return valid triangle count + /// + /// + /// + /// + /// static int ExcludeTriangles(int[] triangles, BoneWeight[] bws, int[] exclude) { int count = 0; @@ -116,8 +124,28 @@ namespace UniGLTF.MeshUtility return new ExcludeBoneIndex(b0, b1, b2, b3); } - public static Mesh CreateErasedMesh(Mesh src, int[] eraseBoneIndices) + /// + /// Erase triangles that has boneWeight for eraseBone + /// + /// + /// + /// + /// + public static int[] GetExcludedIndices(int[] indices, BoneWeight[] boneWeights, int[] eraseBoneIndices) { + var count = ExcludeTriangles(indices, boneWeights, eraseBoneIndices); + var dst = new int[count]; + Array.Copy(indices, 0, dst, 0, count); + return dst; + } + + public static async Task CreateErasedMeshAsync(Mesh src, int[] eraseBoneIndices, IAwaitCaller awaitCaller) + { + if (awaitCaller == null) + { + awaitCaller = new ImmediateCaller(); + } + /* Debug.LogFormat("{0} exclude: {1}", src.name, @@ -140,16 +168,20 @@ namespace UniGLTF.MeshUtility mesh.subMeshCount = src.subMeshCount; for (int i = 0; i < src.subMeshCount; ++i) { - var indices = src.GetIndices(i); - var count = ExcludeTriangles(indices, mesh.boneWeights, eraseBoneIndices); - var dst = new int[count]; - Array.Copy(indices, 0, dst, 0, count); + var dst = await awaitCaller.Run(() => GetExcludedIndices(src.GetIndices(i), mesh.boneWeights, eraseBoneIndices)); mesh.SetIndices(dst, MeshTopology.Triangles, i); } return mesh; } + public static Mesh CreateErasedMesh(Mesh src, int[] eraseBoneIndices) + { + var task = CreateErasedMeshAsync(src, eraseBoneIndices, new ImmediateCaller()); + task.Wait(); + return task.Result; + } + public static IEnumerable Ancestor(this Transform t) { yield return t; diff --git a/Assets/UniGLTF/Runtime/UniGLTF/RuntimeGltfInstance.cs b/Assets/UniGLTF/Runtime/UniGLTF/RuntimeGltfInstance.cs index 5ec350bdb..18202ab75 100644 --- a/Assets/UniGLTF/Runtime/UniGLTF/RuntimeGltfInstance.cs +++ b/Assets/UniGLTF/Runtime/UniGLTF/RuntimeGltfInstance.cs @@ -126,6 +126,11 @@ namespace UniGLTF return loaded; } + public void AddRenderers(IEnumerable renderers) + { + _renderers.AddRange(renderers); + } + public void ShowMeshes() { foreach (var r in Renderers) diff --git a/Assets/VRM10/Runtime/Components/FirstPerson/Vrm10FirstPersonLayerSettings.cs b/Assets/VRM10/Runtime/Components/FirstPerson/Vrm10FirstPersonLayerSettings.cs new file mode 100644 index 000000000..93379de2f --- /dev/null +++ b/Assets/VRM10/Runtime/Components/FirstPerson/Vrm10FirstPersonLayerSettings.cs @@ -0,0 +1,37 @@ +using UnityEngine; + +namespace UniVRM10 +{ + public static class Vrm10FirstPersonLayerSettings + { + public const int DEFAULT_FIRSTPERSON_ONLY_LAYER = 9; + public const string FIRSTPERSON_ONLY_LAYER_NAME = "VRMFirstPersonOnly"; + + public const int DEFAULT_THIRDPERSON_ONLY_LAYER = 10; + public const string THIRDPERSON_ONLY_LAYER_NAME = "VRMThirdPersonOnly"; + + public static int GetLayer(int? arg, string name, int fallback) + { + if (arg.HasValue) + { + return arg.Value; + } + var layer = LayerMask.NameToLayer(name); + if (layer != -1) + { + return layer; + } + return fallback; + } + + public static int GetFirstPersonOnlyLayer(int? arg) + { + return GetLayer(arg, FIRSTPERSON_ONLY_LAYER_NAME, DEFAULT_FIRSTPERSON_ONLY_LAYER); + } + + public static int GetThirdPersonOnlyLayer(int? arg) + { + return GetLayer(arg, THIRDPERSON_ONLY_LAYER_NAME, DEFAULT_THIRDPERSON_ONLY_LAYER); + } + } +} diff --git a/Assets/VRM10/Runtime/Components/FirstPerson/Vrm10FirstPersonLayerSettings.cs.meta b/Assets/VRM10/Runtime/Components/FirstPerson/Vrm10FirstPersonLayerSettings.cs.meta new file mode 100644 index 000000000..ddc5a467a --- /dev/null +++ b/Assets/VRM10/Runtime/Components/FirstPerson/Vrm10FirstPersonLayerSettings.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 8ca895b6d3e950a47a3166f832146d8b +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/VRM10/Runtime/Components/VRM10Object/VRM10ObjectFirstPerson.cs b/Assets/VRM10/Runtime/Components/VRM10Object/VRM10ObjectFirstPerson.cs index 8e80e8f84..2d71026dc 100644 --- a/Assets/VRM10/Runtime/Components/VRM10Object/VRM10ObjectFirstPerson.cs +++ b/Assets/VRM10/Runtime/Components/VRM10Object/VRM10ObjectFirstPerson.cs @@ -1,8 +1,10 @@ using System; using System.Collections.Generic; using System.Linq; +using System.Threading.Tasks; using UniGLTF.MeshUtility; using UnityEngine; +using VRMShaders; namespace UniVRM10 { @@ -27,24 +29,6 @@ namespace UniVRM10 } } - // If no layer names are set, use the default layer IDs. - // Otherwise use the two Unity layers called "VRMFirstPersonOnly" and "VRMThirdPersonOnly". - public static bool TriedSetupLayer = false; - public static int FIRSTPERSON_ONLY_LAYER = 9; - public static int THIRDPERSON_ONLY_LAYER = 10; - - public static void SetupLayers() - { - if (!TriedSetupLayer) - { - TriedSetupLayer = true; - int layer = LayerMask.NameToLayer("VRMFirstPersonOnly"); - FIRSTPERSON_ONLY_LAYER = (layer == -1) ? FIRSTPERSON_ONLY_LAYER : layer; - layer = LayerMask.NameToLayer("VRMThirdPersonOnly"); - THIRDPERSON_ONLY_LAYER = (layer == -1) ? THIRDPERSON_ONLY_LAYER : layer; - } - } - static int[] GetBonesThatHasAncestor(SkinnedMeshRenderer smr, Transform ancestor) { var eraseBones = smr.bones @@ -59,9 +43,9 @@ namespace UniVRM10 // // renderer: 元になるSkinnedMeshRenderer // eraseBones: 削除対象になるボーンのindex - private static SkinnedMeshRenderer CreateHeadlessMesh(SkinnedMeshRenderer renderer, int[] eraseBones) + private async static Task CreateHeadlessMeshAsync(SkinnedMeshRenderer renderer, int[] eraseBones, IAwaitCaller awaitCaller) { - var mesh = BoneMeshEraser.CreateErasedMesh(renderer.sharedMesh, eraseBones); + var mesh = await BoneMeshEraser.CreateErasedMeshAsync(renderer.sharedMesh, eraseBones, awaitCaller); var go = new GameObject("_headless_" + renderer.name); var erased = go.AddComponent(); @@ -76,77 +60,111 @@ namespace UniVRM10 bool m_done; - /// - /// 配下のモデルのレイヤー設定など - /// - public void Setup(GameObject go) + async Task SetupRendererAsync(GameObject go, Transform FirstPersonBone, RendererFirstPersonFlags x, + (int FirstPersonOnly, int ThirdPersonOnly) layer, IAwaitCaller awaitCaller = null) { - SetupLayers(); + switch (x.FirstPersonFlag) + { + case UniGLTF.Extensions.VRMC_vrm.FirstPersonType.auto: + { + if (x.GetRenderer(go.transform) is SkinnedMeshRenderer smr) + { + var eraseBones = GetBonesThatHasAncestor(smr, FirstPersonBone); + if (eraseBones.Any()) + { + // オリジナルのモデルを3人称用にする + smr.gameObject.layer = layer.ThirdPersonOnly; + + // 頭を取り除いた複製モデルを作成し、1人称用にする + var headless = await CreateHeadlessMeshAsync(smr, eraseBones, awaitCaller); + headless.enabled = false; + headless.gameObject.layer = layer.FirstPersonOnly; + headless.transform.SetParent(smr.transform, false); + return headless; + } + else + { + // 削除対象が含まれないので何もしない + } + } + else if (x.GetRenderer(go.transform) is MeshRenderer mr) + { + if (mr.transform.Ancestors().Any(y => y == FirstPersonBone)) + { + // 頭の子孫なので1人称では非表示に + mr.gameObject.layer = layer.ThirdPersonOnly; + } + else + { + // 特に変更しない => 両方表示 + } + } + else + { + throw new NotImplementedException(); + } + } + break; + + case UniGLTF.Extensions.VRMC_vrm.FirstPersonType.firstPersonOnly: + // 1人称のカメラでだけ描画されるようにする + x.GetRenderer(go.transform).gameObject.layer = layer.FirstPersonOnly; + break; + + case UniGLTF.Extensions.VRMC_vrm.FirstPersonType.thirdPersonOnly: + // 3人称のカメラでだけ描画されるようにする + x.GetRenderer(go.transform).gameObject.layer = layer.ThirdPersonOnly; + break; + + case UniGLTF.Extensions.VRMC_vrm.FirstPersonType.both: + // 特に何もしない。すべてのカメラで描画される + break; + } + + return null; + } + + /// + /// Setup first person + /// + /// * SetupLayers + /// * Each renderer is set according to the first person flag. If the flag is `auto`, headless mesh creation will be performed. It's a heavy process. + /// * If visible is false, the created headless mesh will be hidden. + /// + /// + /// + /// + /// + /// + /// + public async Task> SetupAsync(GameObject go, int? firstPersonOnlyLayer = default, int? thirdPersonOnlyLayer = default, IAwaitCaller awaitCaller = default) + { + if (awaitCaller == null) + { + awaitCaller = new ImmediateCaller(); + } + + var layer = ( + Vrm10FirstPersonLayerSettings.GetFirstPersonOnlyLayer(firstPersonOnlyLayer), + Vrm10FirstPersonLayerSettings.GetThirdPersonOnlyLayer(thirdPersonOnlyLayer)); + + var created = new List(); if (m_done) { - return; + return created; } m_done = true; var FirstPersonBone = go.GetComponent().GetBoneTransform(HumanBodyBones.Head); foreach (var x in Renderers) { - switch (x.FirstPersonFlag) + var renderer = await SetupRendererAsync(go, FirstPersonBone, x, layer, awaitCaller); + if (renderer) { - case UniGLTF.Extensions.VRMC_vrm.FirstPersonType.auto: - { - if (x.GetRenderer(go.transform) is SkinnedMeshRenderer smr) - { - var eraseBones = GetBonesThatHasAncestor(smr, FirstPersonBone); - if (eraseBones.Any()) - { - // オリジナルのモデルを3人称用にする - smr.gameObject.layer = THIRDPERSON_ONLY_LAYER; - - // 頭を取り除いた複製モデルを作成し、1人称用にする - var headless = CreateHeadlessMesh(smr, eraseBones); - headless.gameObject.layer = FIRSTPERSON_ONLY_LAYER; - headless.transform.SetParent(smr.transform, false); - } - else - { - // 削除対象が含まれないので何もしない - } - } - else if (x.GetRenderer(go.transform) is MeshRenderer mr) - { - if (mr.transform.Ancestors().Any(y => y == FirstPersonBone)) - { - // 頭の子孫なので1人称では非表示に - mr.gameObject.layer = THIRDPERSON_ONLY_LAYER; - } - else - { - // 特に変更しない => 両方表示 - } - } - else - { - throw new NotImplementedException(); - } - } - break; - - case UniGLTF.Extensions.VRMC_vrm.FirstPersonType.firstPersonOnly: - // 1人称のカメラでだけ描画されるようにする - x.GetRenderer(go.transform).gameObject.layer = FIRSTPERSON_ONLY_LAYER; - break; - - case UniGLTF.Extensions.VRMC_vrm.FirstPersonType.thirdPersonOnly: - // 3人称のカメラでだけ描画されるようにする - x.GetRenderer(go.transform).gameObject.layer = THIRDPERSON_ONLY_LAYER; - break; - - case UniGLTF.Extensions.VRMC_vrm.FirstPersonType.both: - // 特に何もしない。すべてのカメラで描画される - break; + created.Add(renderer); } } + return created; } } } diff --git a/Assets/VRM10/Samples/VRM10FirstPersonSample.meta b/Assets/VRM10/Samples/VRM10FirstPersonSample.meta new file mode 100644 index 000000000..8aef9decc --- /dev/null +++ b/Assets/VRM10/Samples/VRM10FirstPersonSample.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 63991f54f0175cf4294770b968b5b456 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/VRM10/Samples/VRM10FirstPersonSample/VRM10CanvasManager.cs b/Assets/VRM10/Samples/VRM10FirstPersonSample/VRM10CanvasManager.cs new file mode 100644 index 000000000..9cded7de8 --- /dev/null +++ b/Assets/VRM10/Samples/VRM10FirstPersonSample/VRM10CanvasManager.cs @@ -0,0 +1,22 @@ +using System.Linq; +using UnityEngine; +using UnityEngine.UI; + + +namespace UniVRM10.FirstPersonSample +{ + public class VRM10CanvasManager : MonoBehaviour + { + [SerializeField] + public Button LoadVRMButton; + + [SerializeField] + public Button LoadBVHButton; + + private void Reset() + { + LoadVRMButton = GameObject.FindObjectsOfType