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