From e01ccf7363f92c3b2bbb270f101247074871271a Mon Sep 17 00:00:00 2001 From: kaikoga Date: Sun, 29 Oct 2023 19:52:10 +0900 Subject: [PATCH 1/8] add runtime VRM defines and asmdef references --- Runtime/nadena.dev.ndmf.runtime.asmdef | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/Runtime/nadena.dev.ndmf.runtime.asmdef b/Runtime/nadena.dev.ndmf.runtime.asmdef index 2128280a..6ea337fe 100644 --- a/Runtime/nadena.dev.ndmf.runtime.asmdef +++ b/Runtime/nadena.dev.ndmf.runtime.asmdef @@ -1,7 +1,9 @@ { "name": "nadena.dev.ndmf.runtime", "references": [ - "lyuma.av3emulator" + "lyuma.av3emulator", + "VRM", + "VRM10" ], "includePlatforms": [], "excludePlatforms": [], @@ -20,6 +22,16 @@ "name": "lyuma.av3emulator", "expression": "", "define": "NDMF_LYUMA_AV3EMU" + }, + { + "name": "com.vrmc.univrm", + "expression": "", + "define": "NDMF_VRM0" + }, + { + "name": "com.vrmc.vrm", + "expression": "", + "define": "NDMF_VRM1" } ], "noEngineReferences": false From cebd6be828dc02318ff9e27105c4b6d64fd092c5 Mon Sep 17 00:00:00 2001 From: kaikoga Date: Mon, 12 Aug 2024 05:34:05 +0900 Subject: [PATCH 2/8] add test VRM defines and asmdef references --- UnitTests~/nadena.dev.ndmf.UnitTests.asmdef | 19 ++++++++++++++++--- 1 file changed, 16 insertions(+), 3 deletions(-) diff --git a/UnitTests~/nadena.dev.ndmf.UnitTests.asmdef b/UnitTests~/nadena.dev.ndmf.UnitTests.asmdef index 456ba559..e022e335 100644 --- a/UnitTests~/nadena.dev.ndmf.UnitTests.asmdef +++ b/UnitTests~/nadena.dev.ndmf.UnitTests.asmdef @@ -2,9 +2,12 @@ "name": "nadena.dev.ndmf.UnitTests", "rootNamespace": "", "references": [ - "nadena.dev.ndmf", - "nadena.dev.ndmf.vrchat", - "nadena.dev.ndmf.runtime" + "nadena.dev.ndmf", + "VRM", + "VRM10", + "UniHumanoid", + "nadena.dev.ndmf.vrchat", + "nadena.dev.ndmf.runtime" ], "includePlatforms": [ "Editor" @@ -20,6 +23,16 @@ "name": "com.vrchat.avatars", "expression": "(0,999)", "define": "NDMF_VRCSDK3_AVATARS" + }, + { + "name": "com.vrmc.univrm", + "expression": "", + "define": "NDMF_VRM0" + }, + { + "name": "com.vrmc.vrm", + "expression": "", + "define": "NDMF_VRM1" } ], "noEngineReferences": false From 25e047c09bc19f65c2e952b077b49c479dd0096d Mon Sep 17 00:00:00 2001 From: kaikoga Date: Sun, 29 Oct 2023 21:02:55 +0900 Subject: [PATCH 3/8] fix avatar root detection --- Runtime/RuntimeUtil.cs | 23 +++++++++++++++++------ UnitTests~/TestBase.cs | 33 ++++++++++++++++++++++++++++++--- 2 files changed, 47 insertions(+), 9 deletions(-) diff --git a/Runtime/RuntimeUtil.cs b/Runtime/RuntimeUtil.cs index 86ffbc8a..0cd05d3c 100644 --- a/Runtime/RuntimeUtil.cs +++ b/Runtime/RuntimeUtil.cs @@ -7,6 +7,12 @@ #if NDMF_VRCSDK3_AVATARS using VRC.SDK3.Avatars.Components; #endif +#if NDMF_VRM0 +using VRM; +#endif +#if NDMF_VRM1 +using UniVRM10; +#endif namespace nadena.dev.ndmf.runtime { @@ -95,8 +101,17 @@ public static string AvatarRootPath(GameObject child) public static bool IsAvatarRoot(Transform target) { #if NDMF_VRCSDK3_AVATARS - return target.GetComponent(); -#else + if (target.GetComponent()) return true; +#endif +#if NDMF_VRM0 + if (target.GetComponent()) return true; +#endif +#if NDMF_VRM1 + if (target.GetComponent()) return true; +#endif +#if NDMF_VRCSDK3_AVATARS || NDMF_VRM0 || NDMF_VRM1 + return false; +#else var an = target.GetComponent(); if (!an) return false; var parent = target.transform.parent; @@ -173,11 +188,7 @@ internal static IEnumerable FindAvatarsInScene(Scene scene) { foreach (var root in scene.GetRootGameObjects()) { -#if NDMF_VRCSDK3_AVATARS - foreach (var avatar in root.GetComponentsInChildren()) -#else foreach (var avatar in root.GetComponentsInChildren()) -#endif { if (IsAvatarRoot(avatar.transform)) yield return avatar.transform; } diff --git a/UnitTests~/TestBase.cs b/UnitTests~/TestBase.cs index a8c923b5..6795a781 100644 --- a/UnitTests~/TestBase.cs +++ b/UnitTests~/TestBase.cs @@ -12,6 +12,15 @@ using VRC.SDK3.Avatars.Components; #endif +#if NDMF_VRM0 +using VRM; +#endif + +#if NDMF_VRM1 +using UniVRM10; +using UniHumanoid; +#endif + namespace UnitTests { public class TestBase @@ -58,7 +67,9 @@ protected BuildContext CreateContext(GameObject root) return new BuildContext(root, TEMP_ASSET_PATH); // TODO - cleanup } - protected GameObject CreateRoot(string name) + protected GameObject CreateRoot(string name) => CreatePlatformRoot(name, isVRC: true, isVRM0: true, isVRM1: true); + + protected GameObject CreatePlatformRoot(string name, bool isVRC, bool isVRM0, bool isVRM1) { //var path = AssetDatabase.GUIDToAssetPath(MinimalAvatarGuid); //var go = GameObject.Instantiate(AssetDatabase.LoadAssetAtPath(path)); @@ -66,8 +77,24 @@ protected GameObject CreateRoot(string name) go.name = name; go.AddComponent(); #if NDMF_VRCSDK3_AVATARS - go.AddComponent(); - go.AddComponent(); + if (isVRC) + { + go.AddComponent(); + go.AddComponent(); + } +#endif +#if NDMF_VRM0 + if (isVRM0) + { + go.AddComponent(); + } +#endif +#if NDMF_VRM1 + if (isVRM1) + { + go.AddComponent(); + go.AddComponent(); + } #endif objects.Add(go); From 10545ea26ac9be282514ca42cf641194e6989e82 Mon Sep 17 00:00:00 2001 From: kaikoga Date: Wed, 1 Nov 2023 07:28:04 +0900 Subject: [PATCH 4/8] really add test VRM defines and asmdef references --- UnitTests~/AvatarRootTests.meta | 8 + UnitTests~/AvatarRootTests/AvatarRoot.cs | 197 ++++++++++++++++++ UnitTests~/AvatarRootTests/AvatarRoot.cs.meta | 3 + 3 files changed, 208 insertions(+) create mode 100644 UnitTests~/AvatarRootTests.meta create mode 100644 UnitTests~/AvatarRootTests/AvatarRoot.cs create mode 100644 UnitTests~/AvatarRootTests/AvatarRoot.cs.meta diff --git a/UnitTests~/AvatarRootTests.meta b/UnitTests~/AvatarRootTests.meta new file mode 100644 index 00000000..5fcc2d4b --- /dev/null +++ b/UnitTests~/AvatarRootTests.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 2a0151bd473a249e28c68198bc9ff878 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/UnitTests~/AvatarRootTests/AvatarRoot.cs b/UnitTests~/AvatarRootTests/AvatarRoot.cs new file mode 100644 index 00000000..07ead58c --- /dev/null +++ b/UnitTests~/AvatarRootTests/AvatarRoot.cs @@ -0,0 +1,197 @@ +using nadena.dev.ndmf.runtime; +using NUnit.Framework; +using UnityEngine; + +namespace UnitTests.AvatarRootTests +{ + public class AvatarRoot : TestBase + { + private GameObject CreateGenericRoot(string name) => CreatePlatformRoot(name, isVRC: false, isVRM0: false, isVRM1: false); + private GameObject CreateVRCRoot(string name) => CreatePlatformRoot(name, isVRC: true, isVRM0: false, isVRM1: false); + private GameObject CreateVRM0Root(string name) => CreatePlatformRoot(name, isVRC: false, isVRM0: true, isVRM1: false); + private GameObject CreateVRM1Root(string name) => CreatePlatformRoot(name, isVRC: false, isVRM0: false, isVRM1: true); + private GameObject CreateHybridRoot(string name) => CreatePlatformRoot(name, isVRC: true, isVRM0: true, isVRM1: true); + + private Transform parentAvatar; + private Transform childAvatar; + + private void NoAvatars() + { + Assert.That(RuntimeUtil.IsAvatarRoot(parentAvatar), Is.False); + Assert.That(RuntimeUtil.IsAvatarRoot(childAvatar), Is.False); + Assert.That(RuntimeUtil.FindAvatarInParents(parentAvatar), Is.Null); + Assert.That(RuntimeUtil.FindAvatarInParents(childAvatar), Is.Null); + Assert.That(RuntimeUtil.FindAvatarsInScene(parentAvatar.gameObject.scene), Is.EquivalentTo(System.Array.Empty())); + } + + private void ParentIsAvatar() + { + Assert.That(RuntimeUtil.IsAvatarRoot(parentAvatar), Is.True); + Assert.That(RuntimeUtil.IsAvatarRoot(childAvatar), Is.False); + Assert.That(RuntimeUtil.FindAvatarInParents(parentAvatar), Is.EqualTo(parentAvatar)); + Assert.That(RuntimeUtil.FindAvatarInParents(childAvatar), Is.EqualTo(parentAvatar)); + Assert.That(RuntimeUtil.FindAvatarsInScene(parentAvatar.gameObject.scene), Is.EquivalentTo(new [] { parentAvatar })); + } + + private void ChildIsAvatar() + { + Assert.That(RuntimeUtil.IsAvatarRoot(parentAvatar), Is.False); + Assert.That(RuntimeUtil.IsAvatarRoot(childAvatar), Is.True); + Assert.That(RuntimeUtil.FindAvatarInParents(parentAvatar), Is.EqualTo(null)); + Assert.That(RuntimeUtil.FindAvatarInParents(childAvatar), Is.EqualTo(childAvatar)); + Assert.That(RuntimeUtil.FindAvatarsInScene(parentAvatar.gameObject.scene), Is.EquivalentTo(new [] { childAvatar })); + } + + private void ParentAndChildAreAvatars() + { + Assert.That(RuntimeUtil.IsAvatarRoot(parentAvatar), Is.True); + Assert.That(RuntimeUtil.IsAvatarRoot(childAvatar), Is.True); + Assert.That(RuntimeUtil.FindAvatarInParents(parentAvatar), Is.EqualTo(parentAvatar)); + Assert.That(RuntimeUtil.FindAvatarInParents(childAvatar), Is.EqualTo(childAvatar)); + Assert.That(RuntimeUtil.FindAvatarsInScene(parentAvatar.gameObject.scene), Is.EquivalentTo(new [] { parentAvatar, childAvatar })); + } + + [Test] + public void TestGenericContainsGeneric() + { + parentAvatar = CreateGenericRoot("parent").transform; + childAvatar = CreateGenericRoot("child").transform; + + childAvatar.parent = parentAvatar; + +#if NDMF_VRCSDK3_AVATARS || NDMF_VRM0 || NDMF_VRM1 + NoAvatars(); +#else + // Use fallback heuristic + ParentIsAvatar(); +#endif + } + +#if NDMF_VRCSDK3_AVATARS + [Test] + public void TestGenericContainsVRC() + { + parentAvatar = CreateGenericRoot("parent").transform; + childAvatar = CreateVRCRoot("child").transform; + + childAvatar.parent = parentAvatar; + + ChildIsAvatar(); + } + + [Test] + public void TestVRCContainsGeneric() + { + parentAvatar = CreateVRCRoot("parent").transform; + childAvatar = CreateGenericRoot("child").transform; + + childAvatar.parent = parentAvatar; + + ParentIsAvatar(); + } + + [Test] + public void TestVRCContainsVRC() + { + parentAvatar = CreateVRCRoot("parent").transform; + childAvatar = CreateVRCRoot("child").transform; + + childAvatar.parent = parentAvatar; + + ParentAndChildAreAvatars(); + } +#endif + +#if NDMF_VRM0 + [Test] + public void TestGenericContainsVRM0() + { + parentAvatar = CreateGenericRoot("parent").transform; + childAvatar = CreateVRM1Root("child").transform; + + childAvatar.parent = parentAvatar; + + ChildIsAvatar(); + } + + [Test] + public void TestVRM0ContainsGeneric() + { + parentAvatar = CreateVRM1Root("parent").transform; + childAvatar = CreateGenericRoot("child").transform; + + childAvatar.parent = parentAvatar; + + ParentIsAvatar(); + } + + [Test] + public void TestVRM0ContainsVRM0() + { + parentAvatar = CreateVRM1Root("parent").transform; + childAvatar = CreateVRM1Root("child").transform; + + childAvatar.parent = parentAvatar; + + ParentAndChildAreAvatars(); + } +#endif + +#if NDMF_VRCSDK3_AVATARS && NDMF_VRM0 + [Test] + public void TestGenericContainsHybrid() + { + parentAvatar = CreateGenericRoot("parent").transform; + childAvatar = CreateHybridRoot("child").transform; + + childAvatar.parent = parentAvatar; + + ChildIsAvatar(); + } + + [Test] + public void TestHybridContainsGeneric() + { + parentAvatar = CreateHybridRoot("parent").transform; + childAvatar = CreateGenericRoot("child").transform; + + childAvatar.parent = parentAvatar; + + ParentIsAvatar(); + } + + [Test] + public void TestHybridContainsHybrid() + { + parentAvatar = CreateHybridRoot("parent").transform; + childAvatar = CreateHybridRoot("child").transform; + + childAvatar.parent = parentAvatar; + + ParentAndChildAreAvatars(); + } + + [Test] + public void TestVRCContainsVRM0() + { + parentAvatar = CreateVRCRoot("parent").transform; + childAvatar = CreateVRM0Root("child").transform; + + childAvatar.parent = parentAvatar; + + ParentAndChildAreAvatars(); + } + + [Test] + public void TestVRM0ContainsVRC() + { + parentAvatar = CreateVRM0Root("parent").transform; + childAvatar = CreateVRCRoot("child").transform; + + childAvatar.parent = parentAvatar; + + ParentAndChildAreAvatars(); + } +#endif + } +} \ No newline at end of file diff --git a/UnitTests~/AvatarRootTests/AvatarRoot.cs.meta b/UnitTests~/AvatarRootTests/AvatarRoot.cs.meta new file mode 100644 index 00000000..18b7fe73 --- /dev/null +++ b/UnitTests~/AvatarRootTests/AvatarRoot.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: 91be5b322ae443f8b87db1bf75ebcac1 +timeCreated: 1698577351 \ No newline at end of file From 0893781da32e47d9acba057cdc0ede83865d814c Mon Sep 17 00:00:00 2001 From: kaikoga Date: Wed, 1 Nov 2023 07:38:15 +0900 Subject: [PATCH 5/8] add changelog --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 1fc99067..414660a9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -310,6 +310,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Changed - Make Apply on Play non-persistent, as users seem to frequently have issues with it left turned off. +- Improve APIs for finding avatar roots, support non-VRChat avatars in VRCSDK projects (#71) ### Removed - Removed a vestigial "Avatar Toolkit -> Apply on Play" menu item, which didn't do anything when selected. (#70) From 3a3bcc18c5678816e23c9ac865166ee7286235e1 Mon Sep 17 00:00:00 2001 From: kaikoga Date: Sun, 26 Nov 2023 16:30:33 +0900 Subject: [PATCH 6/8] prevent modification while iteration --- Runtime/RuntimeUtil.cs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/Runtime/RuntimeUtil.cs b/Runtime/RuntimeUtil.cs index 0cd05d3c..83b69337 100644 --- a/Runtime/RuntimeUtil.cs +++ b/Runtime/RuntimeUtil.cs @@ -186,13 +186,15 @@ public static Transform FindAvatarInParents(Transform target) /// internal static IEnumerable FindAvatarsInScene(Scene scene) { + var list = new List(); foreach (var root in scene.GetRootGameObjects()) { foreach (var avatar in root.GetComponentsInChildren()) { - if (IsAvatarRoot(avatar.transform)) yield return avatar.transform; + if (IsAvatarRoot(avatar.transform)) list.Add(avatar.transform); } } + return list; } } } \ No newline at end of file From 3547fd30bcebb01c3edb694de3ae601c0c1db5ad Mon Sep 17 00:00:00 2001 From: kaikoga Date: Mon, 30 Sep 2024 08:03:26 +0900 Subject: [PATCH 7/8] allow generic avatars in VRChat and/or VRM projects --- Runtime/RuntimeUtil.cs | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/Runtime/RuntimeUtil.cs b/Runtime/RuntimeUtil.cs index 83b69337..9c452459 100644 --- a/Runtime/RuntimeUtil.cs +++ b/Runtime/RuntimeUtil.cs @@ -100,6 +100,7 @@ public static string AvatarRootPath(GameObject child) /// public static bool IsAvatarRoot(Transform target) { + // First, look for platform specific avatar descriptors #if NDMF_VRCSDK3_AVATARS if (target.GetComponent()) return true; #endif @@ -109,14 +110,12 @@ public static bool IsAvatarRoot(Transform target) #if NDMF_VRM1 if (target.GetComponent()) return true; #endif -#if NDMF_VRCSDK3_AVATARS || NDMF_VRM0 || NDMF_VRM1 - return false; -#else + + // Then, look for Animators, which is the generic avatar root as long as there are no Animators in its parents var an = target.GetComponent(); if (!an) return false; var parent = target.transform.parent; return !(parent && parent.GetComponentInParent()); -#endif } /// From 976dc74e42ba2cf833b457741e63c5c2003ff323 Mon Sep 17 00:00:00 2001 From: kaikoga Date: Mon, 30 Sep 2024 08:06:50 +0900 Subject: [PATCH 8/8] add TODO comment --- Runtime/RuntimeUtil.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Runtime/RuntimeUtil.cs b/Runtime/RuntimeUtil.cs index 9c452459..8a8c4fa7 100644 --- a/Runtime/RuntimeUtil.cs +++ b/Runtime/RuntimeUtil.cs @@ -101,6 +101,7 @@ public static string AvatarRootPath(GameObject child) public static bool IsAvatarRoot(Transform target) { // First, look for platform specific avatar descriptors + // TODO: ignore nested avatar descriptors? #if NDMF_VRCSDK3_AVATARS if (target.GetComponent()) return true; #endif @@ -144,6 +145,7 @@ public static IEnumerable FindAvatarRoots(GameObject root = null) else { GameObject priorRoot = null; + // TODO: allow generic avatars in VRChat projects? #if NDMF_VRCSDK3_AVATARS var candidates = root.GetComponentsInChildren(); #else