diff --git a/Assets/VRM/UniVRM/Editor/SkinnedMeshUtility/BoneMeshEraserWizard.cs b/Assets/MeshUtility/Editor/BoneMeshEraserWizard.cs similarity index 95% rename from Assets/VRM/UniVRM/Editor/SkinnedMeshUtility/BoneMeshEraserWizard.cs rename to Assets/MeshUtility/Editor/BoneMeshEraserWizard.cs index 081118cbc7..6df4a7aa97 100644 --- a/Assets/VRM/UniVRM/Editor/SkinnedMeshUtility/BoneMeshEraserWizard.cs +++ b/Assets/MeshUtility/Editor/BoneMeshEraserWizard.cs @@ -6,7 +6,7 @@ using UnityEngine; -namespace VRM +namespace MeshUtility { [CustomPropertyDrawer(typeof(BoneMeshEraser.EraseBone))] public class EraseBoneDrawer : PropertyDrawer @@ -51,7 +51,7 @@ public class BoneMeshEraserWizard : ScriptableWizard [SerializeField] BoneMeshEraser.EraseBone[] m_eraseBones; - [MenuItem(SkinnedMeshUtility.MENU_KEY + "BoneMeshEraser Wizard", priority = SkinnedMeshUtility.MENU_PRIORITY)] + [MenuItem(MeshUtility.MENU_PARENT + "BoneMeshEraser Wizard", priority = MeshUtility.MENU_PRIORITY)] static void CreateWizard() { ScriptableWizard.DisplayWizard("BoneMeshEraser", "Erase triangles by bone", "Erase"); @@ -143,7 +143,7 @@ SkinnedMeshRenderer _Erase(GameObject go) var bones = m_skinnedMesh.bones; var eraseBones = m_eraseBones .Where(x => x.Erase) - .Select(x => bones.IndexOf(x.Bone)) + .Select(x => Array.IndexOf(bones, x.Bone)) .ToArray(); var meshNode = new GameObject("BoneMeshEraser"); @@ -168,7 +168,7 @@ void Erase() // save mesh to Assets var assetPath = string.Format("{0}{1}", go.name, ASSET_SUFFIX); - var prefab = SkinnedMeshUtility.GetPrefab(go); + var prefab = MeshUtility.GetPrefab(go); if (prefab != null) { var prefabPath = AssetDatabase.GetAssetPath(prefab); diff --git a/Assets/VRM/UniVRM/Editor/SkinnedMeshUtility/BoneMeshEraserWizard.cs.meta b/Assets/MeshUtility/Editor/BoneMeshEraserWizard.cs.meta similarity index 100% rename from Assets/VRM/UniVRM/Editor/SkinnedMeshUtility/BoneMeshEraserWizard.cs.meta rename to Assets/MeshUtility/Editor/BoneMeshEraserWizard.cs.meta diff --git a/Assets/MeshUtility/MeshUtility.asmdef b/Assets/MeshUtility/Editor/MeshUtility.Editor.asmdef similarity index 76% rename from Assets/MeshUtility/MeshUtility.asmdef rename to Assets/MeshUtility/Editor/MeshUtility.Editor.asmdef index e7afd82d1f..9854cd8f64 100644 --- a/Assets/MeshUtility/MeshUtility.asmdef +++ b/Assets/MeshUtility/Editor/MeshUtility.Editor.asmdef @@ -1,6 +1,8 @@ { - "name": "MeshUtility", - "references": [], + "name": "MeshUtility.Editor", + "references": [ + "MeshUtility" + ], "optionalUnityReferences": [], "includePlatforms": [ "Editor" diff --git a/Assets/MeshUtility/MeshUtility.asmdef.meta b/Assets/MeshUtility/Editor/MeshUtility.Editor.asmdef.meta similarity index 100% rename from Assets/MeshUtility/MeshUtility.asmdef.meta rename to Assets/MeshUtility/Editor/MeshUtility.Editor.asmdef.meta diff --git a/Assets/MeshUtility/Editor/MeshUtility.cs b/Assets/MeshUtility/Editor/MeshUtility.cs index 9a824220a6..3fe5356af3 100644 --- a/Assets/MeshUtility/Editor/MeshUtility.cs +++ b/Assets/MeshUtility/Editor/MeshUtility.cs @@ -6,12 +6,24 @@ namespace MeshUtility { - public class MeshUtility : MonoBehaviour + public class MeshUtility { + public const string MENU_PARENT = "Mesh Utility/"; + public const int MENU_PRIORITY = 11; + private const string ASSET_SUFFIX = ".mesh.asset"; - private const string MENU_NAME = "Mesh Utility/Separate Skinned Meshes Contained BlendShape"; + private const string MENU_NAME = MENU_PARENT + "Separate Skinned Meshes Contained BlendShape"; private static readonly Vector3 ZERO_MOVEMENT = Vector3.zero; + public static Object GetPrefab(GameObject instance) + { +#if UNITY_2018_2_OR_NEWER + return PrefabUtility.GetCorrespondingObjectFromSource(instance); +#else + return PrefabUtility.GetPrefabParent(go); +#endif + } + private enum BlendShapeLogic { WithBlendShape, @@ -51,7 +63,7 @@ public static void LinkToMeshSeparatorDocs() private static void SeparationProcessing(GameObject go) { - var outputObject = Instantiate(go); + var outputObject = GameObject.Instantiate(go); var skinnedMeshRenderers = outputObject.GetComponentsInChildren(); foreach (var skinnedMeshRenderer in skinnedMeshRenderers) { @@ -127,7 +139,7 @@ private static void SeparatePolyWithBlendShape(SkinnedMeshRenderer skinnedMeshRe // put the mesh without BlendShape in a new SkinnedMeshRenderer var srcGameObject = skinnedMeshRendererInput.gameObject; var srcTransform = skinnedMeshRendererInput.transform.parent; - var targetObjectForMeshWithoutBS = Instantiate(srcGameObject); + var targetObjectForMeshWithoutBS = GameObject.Instantiate(srcGameObject); targetObjectForMeshWithoutBS.name = srcGameObject.name + "_WithoutBlendShape"; targetObjectForMeshWithoutBS.transform.SetParent(srcTransform); var skinnedMeshRendererWithoutBS = targetObjectForMeshWithoutBS.GetComponent(); diff --git a/Assets/MeshUtility/README.md b/Assets/MeshUtility/README.md index fa403cc9b3..dadbfcd4a6 100644 --- a/Assets/MeshUtility/README.md +++ b/Assets/MeshUtility/README.md @@ -2,7 +2,9 @@ Mesh processing tool in Unity platform. -## MeshSeparator +## Utilities + +### MeshSeparator Separate the target mesh into different categories based on given conditions. @@ -10,6 +12,15 @@ Currently support BlendShape mesh separation. See [documentation](Documentation/ +### MeshIntegrator + +Integrate all mesh in hierarchy. + +### MeshNormalizer + +Bake hierarchy. This is VRM normalize backend. +MeshNormalizer can do blendShape bake. + ## Import MeshUtility There are two ways to import MeshUtility into a Unity project. diff --git a/Assets/MeshUtility/Runtime.meta b/Assets/MeshUtility/Runtime.meta new file mode 100644 index 0000000000..3b08e9593f --- /dev/null +++ b/Assets/MeshUtility/Runtime.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: fc6ddf08077eac64fb9e33738e1c314d +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/VRM/UniVRM/Scripts/SkinnedMeshUtility/VRMBindposeGizmo.cs b/Assets/MeshUtility/Runtime/BindposeGizmo.cs similarity index 98% rename from Assets/VRM/UniVRM/Scripts/SkinnedMeshUtility/VRMBindposeGizmo.cs rename to Assets/MeshUtility/Runtime/BindposeGizmo.cs index e882d73b96..23e233e5e6 100644 --- a/Assets/VRM/UniVRM/Scripts/SkinnedMeshUtility/VRMBindposeGizmo.cs +++ b/Assets/MeshUtility/Runtime/BindposeGizmo.cs @@ -1,16 +1,16 @@ using System.Collections.Generic; using System.Linq; using UnityEngine; -using UniGLTF; +// using UniGLTF; #if UNITY_EDITOR using UnityEditor; #endif -namespace VRM +namespace MeshUtility { [DisallowMultipleComponent] - public class VRMBindposeGizmo : MonoBehaviour + public class BindposeGizmo : MonoBehaviour { [SerializeField] Mesh m_target; diff --git a/Assets/VRM/UniVRM/Scripts/SkinnedMeshUtility/VRMBindposeGizmo.cs.meta b/Assets/MeshUtility/Runtime/BindposeGizmo.cs.meta similarity index 100% rename from Assets/VRM/UniVRM/Scripts/SkinnedMeshUtility/VRMBindposeGizmo.cs.meta rename to Assets/MeshUtility/Runtime/BindposeGizmo.cs.meta diff --git a/Assets/VRM/UniVRM/Scripts/SkinnedMeshUtility/BoneMeshEraser.cs b/Assets/MeshUtility/Runtime/BoneMeshEraser.cs similarity index 94% rename from Assets/VRM/UniVRM/Scripts/SkinnedMeshUtility/BoneMeshEraser.cs rename to Assets/MeshUtility/Runtime/BoneMeshEraser.cs index 556c9f6c51..64ee850d04 100644 --- a/Assets/VRM/UniVRM/Scripts/SkinnedMeshUtility/BoneMeshEraser.cs +++ b/Assets/MeshUtility/Runtime/BoneMeshEraser.cs @@ -1,10 +1,9 @@ using System; using System.Collections.Generic; -using System.Linq; using UnityEngine; -namespace VRM +namespace MeshUtility { public static class BoneMeshEraser { @@ -151,18 +150,6 @@ public static Mesh CreateErasedMesh(Mesh src, int[] eraseBoneIndices) return mesh; } - public static int IndexOf(this Transform[] list, Transform target) - { - for (int i = 0; i < list.Length; ++i) - { - if (list[i] == target) - { - return i; - } - } - return -1; - } - public static IEnumerable Ancestor(this Transform t) { yield return t; diff --git a/Assets/VRM/UniVRM/Scripts/SkinnedMeshUtility/BoneMeshEraser.cs.meta b/Assets/MeshUtility/Runtime/BoneMeshEraser.cs.meta similarity index 100% rename from Assets/VRM/UniVRM/Scripts/SkinnedMeshUtility/BoneMeshEraser.cs.meta rename to Assets/MeshUtility/Runtime/BoneMeshEraser.cs.meta diff --git a/Assets/VRM/UniVRM/Scripts/SkinnedMeshUtility/BoneNormalizer.cs b/Assets/MeshUtility/Runtime/BoneNormalizer.cs similarity index 66% rename from Assets/VRM/UniVRM/Scripts/SkinnedMeshUtility/BoneNormalizer.cs rename to Assets/MeshUtility/Runtime/BoneNormalizer.cs index b4b51ee25e..b5f5231cd0 100644 --- a/Assets/VRM/UniVRM/Scripts/SkinnedMeshUtility/BoneNormalizer.cs +++ b/Assets/MeshUtility/Runtime/BoneNormalizer.cs @@ -1,77 +1,19 @@ using System; using System.Collections.Generic; using System.Linq; -using UniHumanoid; using UnityEngine; -namespace VRM +namespace MeshUtility { public static class BoneNormalizer { - /// - /// 回転とスケールを除去したヒエラルキーをコピーする - /// - /// - /// - static void CopyAndBuild(Transform src, Transform dst, Dictionary boneMap) - { - boneMap[src] = dst; - - foreach (Transform child in src) - { - if (child.gameObject.activeSelf) - { - var dstChild = new GameObject(child.name); - dstChild.transform.SetParent(dst); - dstChild.transform.position = child.position; // copy position only + public delegate Avatar CreateAvatarFunc(GameObject original, GameObject normalized, Dictionary boneMap); - CopyAndBuild(child, dstChild.transform, boneMap); - } - } - } - - static IEnumerable Traverse(this Transform t) - { - yield return t; - foreach (Transform child in t) - { - foreach (var x in child.Traverse()) - { - yield return x; - } - } - } - - static void EnforceTPose(GameObject go) + static (GameObject, Dictionary) NormalizeHierarchy(GameObject go, CreateAvatarFunc createAvatar) { - var animator = go.GetComponent(); - if (animator == null) - { - throw new ArgumentException("Animator with avatar is required"); - } - - var avatar = animator.avatar; - if (avatar == null) - { - throw new ArgumentException("avatar is required"); - } - - if (!avatar.isValid) - { - throw new ArgumentException("invalid avatar"); - } + var boneMap = new Dictionary(); - if (!avatar.isHuman) - { - throw new ArgumentException("avatar is not human"); - } - - HumanPoseTransfer.SetTPose(avatar, go.transform); - } - - static GameObject NormalizeHierarchy(GameObject go, Dictionary boneMap) - { // // 回転・スケールの無いヒエラルキーをコピーする // @@ -83,46 +25,35 @@ static GameObject NormalizeHierarchy(GameObject go, Dictionary(); + var animator = normalized.AddComponent(); + var avatar = createAvatar(go, normalized, boneMap); + avatar.name = go.name + ".normalized"; + animator.avatar = avatar; + } - var srcHumanBones = Enum.GetValues(typeof(HumanBodyBones)) - .Cast() - .Where(x => x != HumanBodyBones.LastBone) - .Select(x => new { Key = x, Value = src.GetBoneTransform(x) }) - .Where(x => x.Value != null) - ; + return (normalized, boneMap); + } - var map = - srcHumanBones - .Where(x => boneMap.ContainsKey(x.Value)) - .ToDictionary(x => x.Key, x => boneMap[x.Value]) - ; + /// + /// 回転とスケールを除去したヒエラルキーをコピーする。 + /// + /// + /// + static void CopyAndBuild(Transform src, Transform dst, Dictionary boneMap) + { + boneMap[src] = dst; - var animator = normalized.AddComponent(); - var vrmHuman = go.GetComponent(); - var avatarDescription = AvatarDescription.Create(); - if (vrmHuman != null && vrmHuman.Description != null) + foreach (Transform child in src) + { + if (child.gameObject.activeSelf) { - avatarDescription.armStretch = vrmHuman.Description.armStretch; - avatarDescription.legStretch = vrmHuman.Description.legStretch; - avatarDescription.upperArmTwist = vrmHuman.Description.upperArmTwist; - avatarDescription.lowerArmTwist = vrmHuman.Description.lowerArmTwist; - avatarDescription.upperLegTwist = vrmHuman.Description.upperLegTwist; - avatarDescription.lowerLegTwist = vrmHuman.Description.lowerLegTwist; - avatarDescription.feetSpacing = vrmHuman.Description.feetSpacing; - avatarDescription.hasTranslationDoF = vrmHuman.Description.hasTranslationDoF; - } - avatarDescription.SetHumanBones(map); - var avatar = avatarDescription.CreateAvatar(normalized.transform); - - avatar.name = go.name + ".normalized"; - animator.avatar = avatar; + var dstChild = new GameObject(child.name); + dstChild.transform.SetParent(dst); + dstChild.transform.position = child.position; // copy position only - var humanPoseTransfer = normalized.AddComponent(); - humanPoseTransfer.Avatar = avatar; + CopyAndBuild(child, dstChild.transform, boneMap); + } } - - return normalized; } class BlendShapeReport @@ -233,7 +164,7 @@ Transform[] dstBones if (boneMap.TryGetValue(srcBone, out Transform dstBone)) { // 対応するボーンが存在する - var dstIndex = dstBones.IndexOf(dstBone); + var dstIndex = Array.IndexOf(dstBones, dstBone); if (dstIndex == -1) { // ありえない。バグ @@ -539,45 +470,20 @@ static void NormalizeNoneSkinnedMesh(Transform src, Transform dst) dstRenderer.sharedMaterials = srcRenderer.sharedMaterials; } - public struct NormalizedResult - { - public GameObject Root; - public Dictionary BoneMap; - } - /// - /// モデルの正規化を実行する + /// 回転とスケールを除去したヒエラルキーのコピーを作成する(MeshをBakeする) /// - /// 対象モデルのルート - /// 強制的にT-Pose化するか - /// 正規化済みのモデル - public static GameObject Execute(GameObject go, bool forceTPose, bool clearBlendShapeBeforeNormalize) + /// 対象のヒエラルキーのルート + /// BlendShapeを0クリアするか否か。false の場合 BlendShape の現状を Bake する + /// Avatarを作る関数 + /// + public static (GameObject, Dictionary) Execute(GameObject go, + bool clearBlendShapeBeforeNormalize, CreateAvatarFunc createAvatar) { - Dictionary boneMap = new Dictionary(); - - // - // T-Poseにする - // - if (forceTPose) - { - var hips = go.GetComponent().GetBoneTransform(HumanBodyBones.Hips); - var hipsPosition = hips.position; - var hipsRotation = hips.rotation; - try - { - EnforceTPose(go); - } - finally - { - hips.position = hipsPosition; // restore hipsPosition - hips.rotation = hipsRotation; - } - } - // // 正規化されたヒエラルキーを作る // - var normalized = NormalizeHierarchy(go, boneMap); + var (normalized, boneMap) = NormalizeHierarchy(go, createAvatar); // // 各メッシュから回転・スケールを取り除いてBinding行列を再計算する @@ -595,137 +501,7 @@ public static GameObject Execute(GameObject go, bool forceTPose, bool clearBlend NormalizeNoneSkinnedMesh(src, dst); } - CopyVRMComponents(go, normalized, boneMap); - - // return new NormalizedResult - // { - // Root = normalized, - // BoneMap = boneMap - // }; - return normalized; - } - - /// - /// VRMを構成するコンポーネントをコピーする。 - /// - /// コピー元 - /// コピー先 - /// コピー元とコピー先の対応関係 - static void CopyVRMComponents(GameObject go, GameObject root, - Dictionary map) - { - { - // blendshape - var src = go.GetComponent(); - if (src != null) - { - var dst = root.AddComponent(); - dst.BlendShapeAvatar = src.BlendShapeAvatar; - } - } - - { - var secondary = go.transform.Find("secondary"); - if (secondary == null) - { - secondary = go.transform; - } - - var dstSecondary = root.transform.Find("secondary"); - if (dstSecondary == null) - { - dstSecondary = new GameObject("secondary").transform; - dstSecondary.SetParent(root.transform, false); - } - - // 揺れモノ - foreach (var src in go.transform.GetComponentsInChildren()) - { - var dst = map[src.transform]; - var dstColliderGroup = dst.gameObject.AddComponent(); - dstColliderGroup.Colliders = src.Colliders.Select(y => - { - var offset = dst.worldToLocalMatrix.MultiplyPoint(src.transform.localToWorldMatrix.MultiplyPoint(y.Offset)); - return new VRMSpringBoneColliderGroup.SphereCollider - { - Offset = offset, - Radius = y.Radius - }; - }).ToArray(); - } - - foreach (var src in go.transform.GetComponentsInChildren()) - { - // Copy VRMSpringBone - var dst = dstSecondary.gameObject.AddComponent(); - dst.m_comment = src.m_comment; - dst.m_stiffnessForce = src.m_stiffnessForce; - dst.m_gravityPower = src.m_gravityPower; - dst.m_gravityDir = src.m_gravityDir; - dst.m_dragForce = src.m_dragForce; - if (src.m_center != null) - { - dst.m_center = map[src.m_center]; - } - - dst.RootBones = src.RootBones.Select(x => map[x]).ToList(); - dst.m_hitRadius = src.m_hitRadius; - if (src.ColliderGroups != null) - { - dst.ColliderGroups = src.ColliderGroups - .Select(x => map[x.transform].GetComponent()).ToArray(); - } - } - } - -#pragma warning disable 0618 - { - // meta(obsolete) - var src = go.GetComponent(); - if (src != null) - { - src.CopyTo(root); - } - } -#pragma warning restore 0618 - - { - // meta - var src = go.GetComponent(); - if (src != null) - { - var dst = root.AddComponent(); - dst.Meta = src.Meta; - } - } - - { - // firstPerson - var src = go.GetComponent(); - if (src != null) - { - src.CopyTo(root, map); - } - } - - { - // humanoid - var dst = root.AddComponent(); - var src = go.GetComponent(); - if (src != null) - { - dst.Avatar = src.Avatar; - dst.Description = src.Description; - } - else - { - var animator = go.GetComponent(); - if (animator != null) - { - dst.Avatar = animator.avatar; - } - } - } + return (normalized, boneMap); } } } diff --git a/Assets/VRM/UniVRM/Scripts/SkinnedMeshUtility/BoneNormalizer.cs.meta b/Assets/MeshUtility/Runtime/BoneNormalizer.cs.meta similarity index 100% rename from Assets/VRM/UniVRM/Scripts/SkinnedMeshUtility/BoneNormalizer.cs.meta rename to Assets/MeshUtility/Runtime/BoneNormalizer.cs.meta diff --git a/Assets/VRM/UniVRM/Scripts/SkinnedMeshUtility/MeshExtensions.cs b/Assets/MeshUtility/Runtime/MeshExtensions.cs similarity index 99% rename from Assets/VRM/UniVRM/Scripts/SkinnedMeshUtility/MeshExtensions.cs rename to Assets/MeshUtility/Runtime/MeshExtensions.cs index d2f07911de..aa458d5241 100644 --- a/Assets/VRM/UniVRM/Scripts/SkinnedMeshUtility/MeshExtensions.cs +++ b/Assets/MeshUtility/Runtime/MeshExtensions.cs @@ -2,7 +2,7 @@ using System.Linq; -namespace VRM +namespace MeshUtility { public static class MeshExtensions { diff --git a/Assets/VRM/UniVRM/Scripts/SkinnedMeshUtility/MeshExtensions.cs.meta b/Assets/MeshUtility/Runtime/MeshExtensions.cs.meta similarity index 100% rename from Assets/VRM/UniVRM/Scripts/SkinnedMeshUtility/MeshExtensions.cs.meta rename to Assets/MeshUtility/Runtime/MeshExtensions.cs.meta diff --git a/Assets/MeshUtility/Runtime/MeshIntegrationResult.cs b/Assets/MeshUtility/Runtime/MeshIntegrationResult.cs new file mode 100644 index 0000000000..91742fc8b4 --- /dev/null +++ b/Assets/MeshUtility/Runtime/MeshIntegrationResult.cs @@ -0,0 +1,13 @@ +using System.Collections.Generic; +using UnityEngine; + +namespace MeshUtility +{ + [System.Serializable] + public class MeshIntegrationResult + { + public List SourceSkinnedMeshRenderers = new List(); + public List SourceMeshRenderers = new List(); + public SkinnedMeshRenderer IntegratedRenderer; + } +} diff --git a/Assets/MeshUtility/Runtime/MeshIntegrationResult.cs.meta b/Assets/MeshUtility/Runtime/MeshIntegrationResult.cs.meta new file mode 100644 index 0000000000..5f32450bcf --- /dev/null +++ b/Assets/MeshUtility/Runtime/MeshIntegrationResult.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: e1c66a21d479b3e4a92eedd622d27f4f +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/VRM/UniVRM/Scripts/SkinnedMeshUtility/MeshIntegrator.cs b/Assets/MeshUtility/Runtime/MeshIntegrator.cs similarity index 99% rename from Assets/VRM/UniVRM/Scripts/SkinnedMeshUtility/MeshIntegrator.cs rename to Assets/MeshUtility/Runtime/MeshIntegrator.cs index 8c0dd6f726..34ba70052d 100644 --- a/Assets/VRM/UniVRM/Scripts/SkinnedMeshUtility/MeshIntegrator.cs +++ b/Assets/MeshUtility/Runtime/MeshIntegrator.cs @@ -2,7 +2,7 @@ using System.Linq; using UnityEngine; -namespace VRM +namespace MeshUtility { public class MeshIntegrator { diff --git a/Assets/VRM/UniVRM/Scripts/SkinnedMeshUtility/MeshIntegrator.cs.meta b/Assets/MeshUtility/Runtime/MeshIntegrator.cs.meta similarity index 100% rename from Assets/VRM/UniVRM/Scripts/SkinnedMeshUtility/MeshIntegrator.cs.meta rename to Assets/MeshUtility/Runtime/MeshIntegrator.cs.meta diff --git a/Assets/VRM/UniVRM/Scripts/SkinnedMeshUtility/MeshIntegratorUtility.cs b/Assets/MeshUtility/Runtime/MeshIntegratorUtility.cs similarity index 52% rename from Assets/VRM/UniVRM/Scripts/SkinnedMeshUtility/MeshIntegratorUtility.cs rename to Assets/MeshUtility/Runtime/MeshIntegratorUtility.cs index e9ab1dc38d..cfb1742e8e 100644 --- a/Assets/VRM/UniVRM/Scripts/SkinnedMeshUtility/MeshIntegratorUtility.cs +++ b/Assets/MeshUtility/Runtime/MeshIntegratorUtility.cs @@ -1,108 +1,21 @@ using System.Collections.Generic; using System.Linq; -using UniGLTF; using UnityEngine; -namespace VRM +namespace MeshUtility { public static class MeshIntegratorUtility { - [System.Serializable] - public class MeshIntegrationResult - { - public List SourceSkinnedMeshRenderers = new List(); - public List SourceMeshRenderers = new List(); - public SkinnedMeshRenderer IntegratedRenderer; - } - - public static bool IntegrateRuntime(GameObject vrmRootObject) - { - if (vrmRootObject == null) return false; - var proxy = vrmRootObject.GetComponent(); - if (proxy == null) return false; - var avatar = proxy.BlendShapeAvatar; - if (avatar == null) return false; - var clips = avatar.Clips; - - var results = Integrate(vrmRootObject, clips); - if (results.Any(x => x.IntegratedRenderer == null)) return false; - - foreach (var result in results) - { - foreach (var renderer in result.SourceSkinnedMeshRenderers) - { - Object.Destroy(renderer); - } - - foreach (var renderer in result.SourceMeshRenderers) - { - Object.Destroy(renderer); - } - } - - return true; - } - - public static List Integrate(GameObject root, List blendshapeClips) - { - var result = new List(); - - var withoutBlendShape = IntegrateInternal(root, onlyBlendShapeRenderers: false); - if (withoutBlendShape.IntegratedRenderer != null) - { - result.Add(withoutBlendShape); - } - - var onlyBlendShape = IntegrateInternal(root, onlyBlendShapeRenderers: true); - if (onlyBlendShape.IntegratedRenderer != null) - { - result.Add(onlyBlendShape); - FollowBlendshapeRendererChange(blendshapeClips, onlyBlendShape, root); - } - - return result; - } - - private static void FollowBlendshapeRendererChange(List clips, MeshIntegrationResult result, GameObject root) - { - if (clips == null || result == null || result.IntegratedRenderer == null || root == null) return; - - var rendererDict = result.SourceSkinnedMeshRenderers - .ToDictionary(x => x.transform.RelativePathFrom(root.transform), x => x); - - var dstPath = result.IntegratedRenderer.transform.RelativePathFrom(root.transform); - - foreach (var clip in clips) - { - if (clip == null) continue; - - for (var i = 0; i < clip.Values.Length; ++i) - { - var val = clip.Values[i]; - if (rendererDict.ContainsKey(val.RelativePath)) - { - var srcRenderer = rendererDict[val.RelativePath]; - var name = srcRenderer.sharedMesh.GetBlendShapeName(val.Index); - var newIndex = result.IntegratedRenderer.sharedMesh.GetBlendShapeIndex(name); - - val.RelativePath = dstPath; - val.Index = newIndex; - } - - clip.Values[i] = val; - } - } - } - - private static MeshIntegrationResult IntegrateInternal(GameObject go, bool onlyBlendShapeRenderers) + /// + /// go を root としたヒエラルキーから Renderer を集めて、統合された Mesh 作成する + /// + /// + /// BlendShapeを保持するSkinnedMeshRendererのみ/BlendShapeを保持しないSkinnedMeshRenderer + MeshRenderer + /// + public static MeshIntegrationResult Integrate(GameObject go, bool onlyBlendShapeRenderers) { var result = new MeshIntegrationResult(); - -#if UNITY_2017_3_OR_NEWER -#else - return result; -#endif - + var meshNode = new GameObject(); if (onlyBlendShapeRenderers) { @@ -115,7 +28,7 @@ private static MeshIntegrationResult IntegrateInternal(GameObject go, bool onlyB meshNode.transform.SetParent(go.transform, false); // レンダラから情報を集める - var integrator = new MeshIntegrator(); + var integrator = new MeshUtility.MeshIntegrator(); if (onlyBlendShapeRenderers) { @@ -145,12 +58,8 @@ private static MeshIntegrationResult IntegrateInternal(GameObject go, bool onlyB if (integrator.Positions.Count > ushort.MaxValue) { -#if UNITY_2017_3_OR_NEWER Debug.LogFormat("exceed 65535 vertices: {0}", integrator.Positions.Count); mesh.indexFormat = UnityEngine.Rendering.IndexFormat.UInt32; -#else - throw new NotImplementedException(String.Format("exceed 65535 vertices: {0}", integrator.Positions.Count.ToString())); -#endif } mesh.vertices = integrator.Positions.ToArray(); @@ -175,10 +84,10 @@ private static MeshIntegrationResult IntegrateInternal(GameObject go, bool onlyB integrated.sharedMaterials = integrator.SubMeshes.Select(x => x.Material).ToArray(); integrated.bones = integrator.Bones.ToArray(); result.IntegratedRenderer = integrated; - + return result; } - + public static IEnumerable EnumerateSkinnedMeshRenderer(Transform root, bool hasBlendShape) { foreach (var x in Traverse(root)) @@ -201,7 +110,7 @@ public static IEnumerable EnumerateMeshRenderer(Transform root) { var renderer = x.GetComponent(); var filter = x.GetComponent(); - + if (renderer != null && filter != null && renderer.gameObject.activeInHierarchy && diff --git a/Assets/VRM/UniVRM/Scripts/SkinnedMeshUtility/MeshIntegratorUtility.cs.meta b/Assets/MeshUtility/Runtime/MeshIntegratorUtility.cs.meta similarity index 100% rename from Assets/VRM/UniVRM/Scripts/SkinnedMeshUtility/MeshIntegratorUtility.cs.meta rename to Assets/MeshUtility/Runtime/MeshIntegratorUtility.cs.meta diff --git a/Assets/MeshUtility/Runtime/MeshUtility.asmdef b/Assets/MeshUtility/Runtime/MeshUtility.asmdef new file mode 100644 index 0000000000..f161fcafe4 --- /dev/null +++ b/Assets/MeshUtility/Runtime/MeshUtility.asmdef @@ -0,0 +1,3 @@ +{ + "name": "MeshUtility" +} diff --git a/Assets/MeshUtility/Runtime/MeshUtility.asmdef.meta b/Assets/MeshUtility/Runtime/MeshUtility.asmdef.meta new file mode 100644 index 0000000000..4085faa92a --- /dev/null +++ b/Assets/MeshUtility/Runtime/MeshUtility.asmdef.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: 71ab1919192903d44971eedbc26b24d1 +AssemblyDefinitionImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/MeshUtility/Runtime/UnityExtensions.cs b/Assets/MeshUtility/Runtime/UnityExtensions.cs new file mode 100644 index 0000000000..a772980d3d --- /dev/null +++ b/Assets/MeshUtility/Runtime/UnityExtensions.cs @@ -0,0 +1,319 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using UnityEngine; +#if UNITY_EDITOR +using UnityEditor; +#endif + + +namespace MeshUtility +{ + public struct PosRot + { + public Vector3 Position; + public Quaternion Rotation; + + public static PosRot FromGlobalTransform(Transform t) + { + return new PosRot + { + Position = t.position, + Rotation = t.rotation, + }; + } + } + + public class BlendShape + { + public string Name; + + public BlendShape(string name) + { + Name = name; + } + + public List Positions = new List(); + public List Normals = new List(); + public List Tangents = new List(); + } + + public static class UnityExtensions + { + public static Vector4 ReverseZ(this Vector4 v) + { + return new Vector4(v.x, v.y, -v.z, v.w); + } + + public static Vector3 ReverseZ(this Vector3 v) + { + return new Vector3(v.x, v.y, -v.z); + } + + [Obsolete] + public static Vector2 ReverseY(this Vector2 v) + { + return new Vector2(v.x, -v.y); + } + + public static Vector2 ReverseUV(this Vector2 v) + { + return new Vector2(v.x, 1.0f - v.y); + } + + public static Quaternion ReverseZ(this Quaternion q) + { + float angle; + Vector3 axis; + q.ToAngleAxis(out angle, out axis); + return Quaternion.AngleAxis(-angle, ReverseZ(axis)); + } + + public static Matrix4x4 Matrix4x4FromColumns(Vector4 c0, Vector4 c1, Vector4 c2, Vector4 c3) + { +#if UNITY_2017_1_OR_NEWER + return new Matrix4x4(c0, c1, c2, c3); +#else + var m = default(Matrix4x4); + m.SetColumn(0, c0); + m.SetColumn(1, c1); + m.SetColumn(2, c2); + m.SetColumn(3, c3); + return m; +#endif + } + + public static Matrix4x4 Matrix4x4FromRotation(Quaternion q) + { +#if UNITY_2017_1_OR_NEWER + return Matrix4x4.Rotate(q); +#else + var m = default(Matrix4x4); + m.SetTRS(Vector3.zero, q, Vector3.one); + return m; +#endif + } + + public static Matrix4x4 ReverseZ(this Matrix4x4 m) + { + m.SetTRS(m.ExtractPosition().ReverseZ(), m.ExtractRotation().ReverseZ(), m.ExtractScale()); + return m; + } + + public static Matrix4x4 MatrixFromArray(float[] values) + { + var m = new Matrix4x4(); + m.m00 = values[0]; + m.m10 = values[1]; + m.m20 = values[2]; + m.m30 = values[3]; + m.m01 = values[4]; + m.m11 = values[5]; + m.m21 = values[6]; + m.m31 = values[7]; + m.m02 = values[8]; + m.m12 = values[9]; + m.m22 = values[10]; + m.m32 = values[11]; + m.m03 = values[12]; + m.m13 = values[13]; + m.m23 = values[14]; + m.m33 = values[15]; + return m; + } + + // https://forum.unity.com/threads/how-to-assign-matrix4x4-to-transform.121966/ + public static Quaternion ExtractRotation(this Matrix4x4 matrix) + { + Vector3 forward; + forward.x = matrix.m02; + forward.y = matrix.m12; + forward.z = matrix.m22; + + Vector3 upwards; + upwards.x = matrix.m01; + upwards.y = matrix.m11; + upwards.z = matrix.m21; + + return Quaternion.LookRotation(forward, upwards); + } + + public static Vector3 ExtractPosition(this Matrix4x4 matrix) + { + Vector3 position; + position.x = matrix.m03; + position.y = matrix.m13; + position.z = matrix.m23; + return position; + } + + public static Vector3 ExtractScale(this Matrix4x4 matrix) + { + Vector3 scale; + scale.x = new Vector4(matrix.m00, matrix.m10, matrix.m20, matrix.m30).magnitude; + scale.y = new Vector4(matrix.m01, matrix.m11, matrix.m21, matrix.m31).magnitude; + scale.z = new Vector4(matrix.m02, matrix.m12, matrix.m22, matrix.m32).magnitude; + return scale; + } + + public static string RelativePathFrom(this Transform self, Transform root) + { + var path = new List(); + for (var current = self; current != null; current = current.parent) + { + if (current == root) + { + return String.Join("/", path.ToArray()); + } + + path.Insert(0, current.name); + } + + throw new Exception("no RelativePath"); + } + + public static Transform GetChildByName(this Transform self, string childName) + { + foreach (Transform child in self) + { + if (child.name == childName) + { + return child; + } + } + + throw new KeyNotFoundException(); + } + + public static Transform GetFromPath(this Transform self, string path) + { + var current = self; + + var split = path.Split('/'); + + foreach (var childName in split) + { + current = current.GetChildByName(childName); + } + + return current; + } + + public static IEnumerable GetChildren(this Transform self) + { + foreach (Transform child in self) + { + yield return child; + } + } + + public static IEnumerable Traverse(this Transform t) + { + yield return t; + foreach (Transform x in t) + { + foreach (Transform y in x.Traverse()) + { + yield return y; + } + } + } + + [Obsolete("Use FindDescendant(name)")] + public static Transform FindDescenedant(this Transform t, string name) + { + return FindDescendant(t, name); + } + + public static Transform FindDescendant(this Transform t, string name) + { + return t.Traverse().First(x => x.name == name); + } + + public static IEnumerable Ancestors(this Transform t) + { + yield return t; + if (t.parent != null) + { + foreach (Transform x in t.parent.Ancestors()) + { + yield return x; + } + } + } + + public static float[] ToArray(this Quaternion q) + { + return new float[] { q.x, q.y, q.z, q.w }; + } + + public static float[] ToArray(this Vector3 v) + { + return new float[] { v.x, v.y, v.z }; + } + + public static float[] ToArray(this Vector4 v) + { + return new float[] { v.x, v.y, v.z, v.w }; + } + + public static float[] ToArray(this Color c) + { + return new float[] { c.r, c.g, c.b, c.a }; + } + + public static void ReverseZRecursive(this Transform root) + { + var globalMap = root.Traverse().ToDictionary(x => x, x => PosRot.FromGlobalTransform(x)); + + foreach (var x in root.Traverse()) + { + x.position = globalMap[x].Position.ReverseZ(); + x.rotation = globalMap[x].Rotation.ReverseZ(); + } + } + + public static Mesh GetSharedMesh(this Transform t) + { + var meshFilter = t.GetComponent(); + if (meshFilter != null) + { + return meshFilter.sharedMesh; + } + + var skinnedMeshRenderer = t.GetComponent(); + if (skinnedMeshRenderer != null) + { + return skinnedMeshRenderer.sharedMesh; + } + + return null; + } + + public static Material[] GetSharedMaterials(this Transform t) + { + var renderer = t.GetComponent(); + if (renderer != null) + { + return renderer.sharedMaterials; + } + + return new Material[] { }; + } + + public static bool Has(this Transform transform, T t) where T : Component + { + return transform.GetComponent() == t; + } + + public static T GetOrAddComponent(this GameObject go) where T : Component + { + var c = go.GetComponent(); + if (c != null) + { + return c; + } + return go.AddComponent(); + } + } +} diff --git a/Assets/MeshUtility/Runtime/UnityExtensions.cs.meta b/Assets/MeshUtility/Runtime/UnityExtensions.cs.meta new file mode 100644 index 0000000000..07785c58f6 --- /dev/null +++ b/Assets/MeshUtility/Runtime/UnityExtensions.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 5294813527b3278458026afc820dd63d +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/MeshUtility/package.json b/Assets/MeshUtility/package.json index f7ed33c4ea..bd217070ae 100644 --- a/Assets/MeshUtility/package.json +++ b/Assets/MeshUtility/package.json @@ -1,9 +1,14 @@ { - "name" : "com.vrmc.meshutility", - "displayName" : "MeshUtility", - "version" : "0.0.1", - "unity" : "2018.4", - "description" : "MeshUtility is a package for mesh separation, etc. \n\nCheck out the latest information here: ", - "keywords" : [ "mesh" ], - "author" : { "name": "VRM Consortium" } -} \ No newline at end of file +{ + "name": "com.vrmc.meshutility", + "version": "0.59.0", + "displayName": "MeshUtility", + "unity": "2018.4", + "description": "MeshUtility is a package for mesh separation, etc. \n\nCheck out the latest information here: ", + "keywords": [ + "mesh" + ], + "author": { + "name": "VRM Consortium" + } +} diff --git a/Assets/VRM.Samples/Editor/Tests/VRM.Samples.Editor.Tests.asmdef b/Assets/VRM.Samples/Editor/Tests/VRM.Samples.Editor.Tests.asmdef index f80e8a0a66..aef7e873af 100644 --- a/Assets/VRM.Samples/Editor/Tests/VRM.Samples.Editor.Tests.asmdef +++ b/Assets/VRM.Samples/Editor/Tests/VRM.Samples.Editor.Tests.asmdef @@ -4,7 +4,8 @@ "VRM", "VRM.Samples", "UniVRM.Editor.Tests", - "UniJSON" + "UniJSON", + "MeshUtility" ], "optionalUnityReferences": [ "TestAssemblies" @@ -13,5 +14,9 @@ "Editor" ], "excludePlatforms": [], - "allowUnsafeCode": false + "allowUnsafeCode": false, + "overrideReferences": false, + "precompiledReferences": [], + "autoReferenced": true, + "defineConstraints": [] } \ No newline at end of file diff --git a/Assets/VRM.Samples/Editor/Tests/VRMImportExportTests.cs b/Assets/VRM.Samples/Editor/Tests/VRMImportExportTests.cs index cf25c4252f..6f3ec57215 100644 --- a/Assets/VRM.Samples/Editor/Tests/VRMImportExportTests.cs +++ b/Assets/VRM.Samples/Editor/Tests/VRMImportExportTests.cs @@ -3,7 +3,7 @@ using UniGLTF; using UniJSON; using UnityEngine; - +using MeshUtility; namespace VRM.Samples { diff --git a/Assets/VRM/UniGLTF/Scripts/Extensions/UnityExtensions.cs.meta b/Assets/VRM/UniGLTF/Scripts/Extensions/UnityExtensions.cs.meta index 062aed4659..0421f42464 100644 --- a/Assets/VRM/UniGLTF/Scripts/Extensions/UnityExtensions.cs.meta +++ b/Assets/VRM/UniGLTF/Scripts/Extensions/UnityExtensions.cs.meta @@ -1,7 +1,5 @@ fileFormatVersion: 2 -guid: 5294813527b3278458026afc820dd63d -timeCreated: 1515606586 -licenseType: Free +guid: bbcda9130f35803408b216dbc6be05b7 MonoImporter: externalObjects: {} serializedVersion: 2 diff --git a/Assets/VRM/UniVRM/DevOnly/Editor/VRMExportUnityPackage.cs b/Assets/VRM/UniVRM/DevOnly/Editor/VRMExportUnityPackage.cs index 77386bdbdc..c124904cde 100644 --- a/Assets/VRM/UniVRM/DevOnly/Editor/VRMExportUnityPackage.cs +++ b/Assets/VRM/UniVRM/DevOnly/Editor/VRMExportUnityPackage.cs @@ -131,9 +131,18 @@ static IEnumerable GlobFiles(string path) } [MenuItem(VRMVersion.MENU + "/Export unitypackage")] + static void CreateUnityPackageWithoutBuild() + { + var folder = GetProjectRoot(); + if (!Directory.Exists(folder)) + { + Directory.CreateDirectory(folder); + } + CreateUnityPackages(folder); + } + public static void CreateUnityPackageWithBuild() { - //var folder = GetDesktop(); var folder = GetProjectRoot(); if (!Directory.Exists(folder)) { @@ -194,6 +203,7 @@ public static void CreateUnityPackages(string outputDir) List = new []{ new GlobList("Assets/VRM"), new GlobList("Assets/VRMShaders"), + new GlobList("Assets/MeshUtility"), } }, new PackageInfo("UniVRM-samples") diff --git a/Assets/VRM/UniVRM/Editor/Format/VRMEditorExporter.cs b/Assets/VRM/UniVRM/Editor/Format/VRMEditorExporter.cs index c9f88f170c..cf3b144d17 100644 --- a/Assets/VRM/UniVRM/Editor/Format/VRMEditorExporter.cs +++ b/Assets/VRM/UniVRM/Editor/Format/VRMEditorExporter.cs @@ -77,7 +77,7 @@ static void ReplaceMesh(GameObject target, SkinnedMeshRenderer smr, BlendShapeAv .Distinct() .ToArray(); - var copyMesh = mesh.Copy(copyBlendShape: false); + var copyMesh = MeshUtility.MeshExtensions.Copy(mesh, copyBlendShape: false); // 使われている BlendShape だけをコピーする foreach (var i in usedBlendshapeIndexArray) { @@ -182,7 +182,7 @@ static void Export(string path, GameObject exportRoot, VRMExportSettings setting if (settings.PoseFreeze) { // BoneNormalizer.Execute は Copy を作って正規化する。UNDO無用 - target = BoneNormalizer.Execute(target, settings.ForceTPose, false); + target = VRMBoneNormalizer.Execute(target, settings.ForceTPose, false); destroy.Add(target); } diff --git a/Assets/VRM/UniVRM/Editor/Format/VRMExporterWizard.cs b/Assets/VRM/UniVRM/Editor/Format/VRMExporterWizard.cs index b498f9463b..9820deb65c 100644 --- a/Assets/VRM/UniVRM/Editor/Format/VRMExporterWizard.cs +++ b/Assets/VRM/UniVRM/Editor/Format/VRMExporterWizard.cs @@ -141,7 +141,7 @@ static Vector3 GetForward(Transform l, Transform r) static bool EnableRenderer(Renderer renderer) { - if (renderer.transform.Ancestor().Any(x => !x.gameObject.activeSelf)) + if (renderer.transform.GetComponentsInParent().Any(x => !x.gameObject.activeSelf)) { // 自分か祖先に !activeSelf がいる return false; diff --git a/Assets/VRM/UniVRM/Editor/Format/VRMHumanoidNormalizerMenu.cs b/Assets/VRM/UniVRM/Editor/Format/VRMHumanoidNormalizerMenu.cs index b02b6ce4ee..6dbb8bc295 100644 --- a/Assets/VRM/UniVRM/Editor/Format/VRMHumanoidNormalizerMenu.cs +++ b/Assets/VRM/UniVRM/Editor/Format/VRMHumanoidNormalizerMenu.cs @@ -49,7 +49,7 @@ private static void ExportFromMenu() var go = Selection.activeObject as GameObject; // BoneNormalizer.Execute はコピーを正規化する。UNDO無用 - Selection.activeGameObject = BoneNormalizer.Execute(go, true, false); + Selection.activeGameObject = VRMBoneNormalizer.Execute(go, true, false); } } } diff --git a/Assets/VRM/UniVRM/Editor/Format/VRMVersionMenu.cs b/Assets/VRM/UniVRM/Editor/Format/VRMVersionMenu.cs index b0956d809b..95e3b51cf2 100644 --- a/Assets/VRM/UniVRM/Editor/Format/VRMVersionMenu.cs +++ b/Assets/VRM/UniVRM/Editor/Format/VRMVersionMenu.cs @@ -37,6 +37,24 @@ public static partial class VRMVersion }} }} "; + + const string MeshUtilityPath = "Assets/MeshUtility/package.json"; + const string MeshUtilityTemplate = @"{{ +{{ + ""name"": ""com.vrmc.meshutility"", + ""version"": ""{0}.{1}.{2}"", + ""displayName"": ""MeshUtility"", + ""unity"": ""2018.4"", + ""description"": ""MeshUtility is a package for mesh separation, etc. \n\nCheck out the latest information here: "", + ""keywords"": [ + ""mesh"" + ], + ""author"": {{ + ""name"": ""VRM Consortium"" + }} +}} +"; + const string VRMPackagePath = "Assets/VRM/package.json"; const string VRMPackageTemplate = @"{{ ""name"": ""com.vrmc.univrm"", @@ -54,7 +72,8 @@ public static partial class VRMVersion ""name"": ""VRM Consortium"" }}, ""dependencies"": {{ - ""com.vrmc.vrmshaders"": ""{0}.{1}.{2}"" + ""com.vrmc.vrmshaders"": ""{0}.{1}.{2}"", + ""com.vrmc.meshutility"": ""{0}.{1}.{2}"" }} }} "; @@ -96,6 +115,10 @@ void OnGUI() values[0], values[1], values[2]), utf8); + File.WriteAllText(MeshUtilityPath, string.Format(MeshUtilityTemplate, + values[0], + values[1], + values[2]), utf8); File.WriteAllText(VRMPackagePath, string.Format(VRMPackageTemplate, values[0], values[1], diff --git a/Assets/VRM/UniVRM/Editor/SkinnedMeshUtility/MeshIntegratorEditor.cs b/Assets/VRM/UniVRM/Editor/SkinnedMeshUtility/MeshIntegratorEditor.cs index 23518027b3..28664bd01c 100644 --- a/Assets/VRM/UniVRM/Editor/SkinnedMeshUtility/MeshIntegratorEditor.cs +++ b/Assets/VRM/UniVRM/Editor/SkinnedMeshUtility/MeshIntegratorEditor.cs @@ -11,7 +11,7 @@ namespace VRM { /// - /// 複数のメッシュをまとめる + /// 複数のメッシュをまとめて、BlendShapeClipに変更を反映する /// [DisallowMultipleComponent] public static class MeshIntegratorEditor @@ -39,12 +39,12 @@ private static void ExportFromMenu() private static void Foo() { var go = Selection.activeObject as GameObject; - + Debug.Log(SkinnedMeshUtility.IsPrefab(go)); } - public static List Integrate(GameObject prefab) + public static List Integrate(GameObject prefab) { Undo.RecordObject(prefab, "Mesh Integration"); var instance = SkinnedMeshUtility.InstantiatePrefab(prefab); @@ -63,18 +63,18 @@ private static void Foo() // Backup Exists BackupVrmPrefab(prefab); - + // Execute - var results = MeshIntegratorUtility.Integrate(instance, clips); + var results = VRMMeshIntegratorUtility.Integrate(instance, clips); foreach (var res in results) { if (res.IntegratedRenderer == null) continue; - + SaveMeshAsset(res.IntegratedRenderer.sharedMesh, instance, res.IntegratedRenderer.gameObject.name); Undo.RegisterCreatedObjectUndo(res.IntegratedRenderer.gameObject, "Integrate Renderers"); } - + // destroy source renderers foreach (var res in results) { @@ -90,31 +90,31 @@ private static void Foo() renderer.gameObject.SetActive(false); } } - + // Apply to Prefab SkinnedMeshUtility.ApplyChangesToPrefab(instance); Object.DestroyImmediate(instance); - + return results; } private static void BackupVrmPrefab(GameObject rootPrefab) { var proxy = rootPrefab.GetComponent(); - + var srcAvatar = proxy.BlendShapeAvatar; - var dstAvatar = (BlendShapeAvatar) BackupAsset(srcAvatar, rootPrefab); + var dstAvatar = (BlendShapeAvatar)BackupAsset(srcAvatar, rootPrefab); - var clipMapper = srcAvatar.Clips.ToDictionary(x => x, x => (BlendShapeClip) BackupAsset(x, rootPrefab)); + var clipMapper = srcAvatar.Clips.ToDictionary(x => x, x => (BlendShapeClip)BackupAsset(x, rootPrefab)); dstAvatar.Clips = clipMapper.Values.ToList(); - + var dstPrefab = BackupAsset(rootPrefab, rootPrefab); var dstInstance = SkinnedMeshUtility.InstantiatePrefab(dstPrefab); dstInstance.GetComponent().BlendShapeAvatar = dstAvatar; SkinnedMeshUtility.ApplyChangesToPrefab(dstInstance); Object.DestroyImmediate(dstInstance); } - + private static T BackupAsset(T asset, GameObject rootPrefab) where T : UnityEngine.Object { var srcAssetPath = UnityPath.FromAsset(asset); @@ -124,7 +124,7 @@ private static T BackupAsset(T asset, GameObject rootPrefab) where T : UnityE var backupPath = UnityPath.FromAsset(rootPrefab).Parent.Child(backupDir); backupPath.EnsureFolder(); var dstAssetPath = backupPath.Child(assetName); - + AssetDatabase.CopyAsset(srcAssetPath.Value, dstAssetPath.Value); return dstAssetPath.LoadAsset(); } diff --git a/Assets/VRM/UniVRM/Editor/SkinnedMeshUtility/MeshIntegratorWizard.cs b/Assets/VRM/UniVRM/Editor/SkinnedMeshUtility/MeshIntegratorWizard.cs index cd93e32372..f931b281ec 100644 --- a/Assets/VRM/UniVRM/Editor/SkinnedMeshUtility/MeshIntegratorWizard.cs +++ b/Assets/VRM/UniVRM/Editor/SkinnedMeshUtility/MeshIntegratorWizard.cs @@ -58,7 +58,7 @@ public MaterialList(Material[] list) MaterialList[] m_duplicateMaterials; [Header("Result")] - public MeshIntegratorUtility.MeshIntegrationResult[] integrationResults; + public MeshUtility.MeshIntegrationResult[] integrationResults; [MenuItem(MENU_KEY)] static void CreateWizard() @@ -124,7 +124,7 @@ void OnValidate() return; } - m_uniqueMaterials = MeshIntegratorUtility.EnumerateSkinnedMeshRenderer(m_root.transform, false) + m_uniqueMaterials = MeshUtility.MeshIntegratorUtility.EnumerateSkinnedMeshRenderer(m_root.transform, false) .SelectMany(x => x.sharedMaterials) .Distinct() .ToArray(); diff --git a/Assets/VRM/UniVRM/Editor/Tests/MeshTests.cs b/Assets/VRM/UniVRM/Editor/Tests/MeshTests.cs index c1e36c22e4..6be712b09c 100644 --- a/Assets/VRM/UniVRM/Editor/Tests/MeshTests.cs +++ b/Assets/VRM/UniVRM/Editor/Tests/MeshTests.cs @@ -55,7 +55,7 @@ public void MeshCopyTest() var src = new Mesh(); src.AddBlendShapeFrame("blendShape", 100.0f, null, null, null); - var dst = src.Copy(true); + var dst = MeshUtility.MeshExtensions.Copy(src, true); MeshEquals(src, dst); } diff --git a/Assets/VRM/UniVRM/Editor/Tests/NormalizeTests.cs b/Assets/VRM/UniVRM/Editor/Tests/NormalizeTests.cs index 51510cfb73..1126b647aa 100644 --- a/Assets/VRM/UniVRM/Editor/Tests/NormalizeTests.cs +++ b/Assets/VRM/UniVRM/Editor/Tests/NormalizeTests.cs @@ -58,7 +58,7 @@ public void MapBoneWeightTest() map.Add(null, new GameObject("null")); // map.Add(new GameObject("c"), null); // ありえないので Exception にしてある var boneWeights = map.CreateBoneWeight(64).ToArray(); - var newBoneWeight = BoneNormalizer.MapBoneWeight(boneWeights, map.Map, + var newBoneWeight = MeshUtility.BoneNormalizer.MapBoneWeight(boneWeights, map.Map, map.SrcBones.ToArray(), map.DstBones.ToArray()); // 正常系 @@ -74,7 +74,7 @@ public void MapBoneWeightTest() map.Add(null, new GameObject("null")); // map.Add(new GameObject("c"), null); // ありえないので Exception にしてある var boneWeights = map.CreateBoneWeight(64).ToArray(); - var newBoneWeight = BoneNormalizer.MapBoneWeight(boneWeights, map.Map, + var newBoneWeight = MeshUtility.BoneNormalizer.MapBoneWeight(boneWeights, map.Map, map.SrcBones.ToArray(), map.DstBones.ToArray()); // 4 つめが 0 になる diff --git a/Assets/VRM/UniVRM/Editor/Tests/UniVRM.Editor.Tests.asmdef b/Assets/VRM/UniVRM/Editor/Tests/UniVRM.Editor.Tests.asmdef index 0aa9a540b2..d10983bcb7 100644 --- a/Assets/VRM/UniVRM/Editor/Tests/UniVRM.Editor.Tests.asmdef +++ b/Assets/VRM/UniVRM/Editor/Tests/UniVRM.Editor.Tests.asmdef @@ -3,7 +3,8 @@ "references": [ "VRM", "UniJSON", - "UniVRM.Editor" + "UniVRM.Editor", + "MeshUtility" ], "optionalUnityReferences": [ "TestAssemblies" diff --git a/Assets/VRM/UniVRM/Editor/UniVRM.Editor.asmdef b/Assets/VRM/UniVRM/Editor/UniVRM.Editor.asmdef index b0fde8cc95..af853f37e0 100644 --- a/Assets/VRM/UniVRM/Editor/UniVRM.Editor.asmdef +++ b/Assets/VRM/UniVRM/Editor/UniVRM.Editor.asmdef @@ -3,7 +3,8 @@ "references": [ "VRM", "UniJSON", - "UniHumanoid" + "UniHumanoid", + "MeshUtility" ], "optionalUnityReferences": [], "includePlatforms": [ diff --git a/Assets/VRM/UniVRM/Scripts/FirstPerson/VRMFirstPerson.cs b/Assets/VRM/UniVRM/Scripts/FirstPerson/VRMFirstPerson.cs index d9d46c89ae..7777830494 100644 --- a/Assets/VRM/UniVRM/Scripts/FirstPerson/VRMFirstPerson.cs +++ b/Assets/VRM/UniVRM/Scripts/FirstPerson/VRMFirstPerson.cs @@ -208,7 +208,7 @@ private static Mesh CreateHeadlessModelForSkinnedMeshRenderer(SkinnedMeshRendere var eraseBones = bones.Select((x, i) => { // 祖先に削除対象が存在するか - bool erase = x.Ancestor().Any(y => y == eraseRoot); + bool erase = x.GetComponentsInParent().Any(y => y == eraseRoot); return new { i, @@ -229,7 +229,7 @@ private static Mesh CreateHeadlessModelForSkinnedMeshRenderer(SkinnedMeshRendere renderer.gameObject.layer = THIRDPERSON_ONLY_LAYER; // 削除対象のボーンに対するウェイトを保持する三角形を除外して、一人称用のモデルを複製する - var headlessMesh = BoneMeshEraser.CreateErasedMesh(renderer.sharedMesh, eraseBones); + var headlessMesh = MeshUtility.BoneMeshEraser.CreateErasedMesh(renderer.sharedMesh, eraseBones); if (headlessMesh.triangles.Length == 0) { // 一人称用のmeshには描画すべき部分が無い(全部削除された) diff --git a/Assets/VRM/UniVRM/Scripts/Format/VRMVersion.cs b/Assets/VRM/UniVRM/Scripts/Format/VRMVersion.cs index 01b2e60f44..77872c4f47 100644 --- a/Assets/VRM/UniVRM/Scripts/Format/VRMVersion.cs +++ b/Assets/VRM/UniVRM/Scripts/Format/VRMVersion.cs @@ -4,8 +4,8 @@ namespace VRM public static partial class VRMVersion { public const int MAJOR = 0; - public const int MINOR = 58; - public const int PATCH = 1; - public const string VERSION = "0.58.1"; + public const int MINOR = 59; + public const int PATCH = 0; + public const string VERSION = "0.59.0"; } } diff --git a/Assets/VRM/UniVRM/Scripts/SkinnedMeshUtility/VRMBoneNormalizer.cs b/Assets/VRM/UniVRM/Scripts/SkinnedMeshUtility/VRMBoneNormalizer.cs new file mode 100644 index 0000000000..cc6b6cdd96 --- /dev/null +++ b/Assets/VRM/UniVRM/Scripts/SkinnedMeshUtility/VRMBoneNormalizer.cs @@ -0,0 +1,238 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using UniHumanoid; +using UnityEngine; + + +namespace VRM +{ + public static class VRMBoneNormalizer + { + static void EnforceTPose(GameObject go) + { + var animator = go.GetComponent(); + if (animator == null) + { + throw new ArgumentException("Animator with avatar is required"); + } + + var avatar = animator.avatar; + if (avatar == null) + { + throw new ArgumentException("avatar is required"); + } + + if (!avatar.isValid) + { + throw new ArgumentException("invalid avatar"); + } + + if (!avatar.isHuman) + { + throw new ArgumentException("avatar is not human"); + } + + HumanPoseTransfer.SetTPose(avatar, go.transform); + } + + /// + /// モデルの正規化を実行する + /// + /// 対象モデルのルート + /// 強制的にT-Pose化するか + /// 正規化済みのモデル + public static GameObject Execute(GameObject go, bool forceTPose, bool clearBlendShapeBeforeNormalize) + { + // + // T-Poseにする + // + if (forceTPose) + { + var hips = go.GetComponent().GetBoneTransform(HumanBodyBones.Hips); + var hipsPosition = hips.position; + var hipsRotation = hips.rotation; + try + { + EnforceTPose(go); + } + finally + { + hips.position = hipsPosition; // restore hipsPosition + hips.rotation = hipsRotation; + } + } + + // + // 正規化されたヒエラルキーを作る + // + var (normalized, bMap) = MeshUtility.BoneNormalizer.Execute(go, clearBlendShapeBeforeNormalize, (_src, dst, boneMap) => + { + var src = _src.GetComponent(); + + var srcHumanBones = Enum.GetValues(typeof(HumanBodyBones)) + .Cast() + .Where(x => x != HumanBodyBones.LastBone) + .Select(x => new { Key = x, Value = src.GetBoneTransform(x) }) + .Where(x => x.Value != null) + ; + + var map = + srcHumanBones + .Where(x => boneMap.ContainsKey(x.Value)) + .ToDictionary(x => x.Key, x => boneMap[x.Value]) + ; + + var animator = dst.AddComponent(); + var vrmHuman = go.GetComponent(); + var avatarDescription = AvatarDescription.Create(); + if (vrmHuman != null && vrmHuman.Description != null) + { + avatarDescription.armStretch = vrmHuman.Description.armStretch; + avatarDescription.legStretch = vrmHuman.Description.legStretch; + avatarDescription.upperArmTwist = vrmHuman.Description.upperArmTwist; + avatarDescription.lowerArmTwist = vrmHuman.Description.lowerArmTwist; + avatarDescription.upperLegTwist = vrmHuman.Description.upperLegTwist; + avatarDescription.lowerLegTwist = vrmHuman.Description.lowerLegTwist; + avatarDescription.feetSpacing = vrmHuman.Description.feetSpacing; + avatarDescription.hasTranslationDoF = vrmHuman.Description.hasTranslationDoF; + } + avatarDescription.SetHumanBones(map); + var avatar = avatarDescription.CreateAvatar(dst.transform); + return avatar; + }); + + // humanPoseTransfer + // var animator = normalized.GetComponent(); + // var humanPoseTransfer = normalized.AddComponent(); + // humanPoseTransfer.Avatar = animator.avatar; + + CopyVRMComponents(go, normalized, bMap); + + return normalized; + } + + /// + /// VRMを構成するコンポーネントをコピーする。 + /// + /// コピー元 + /// コピー先 + /// コピー元とコピー先の対応関係 + static void CopyVRMComponents(GameObject go, GameObject root, + Dictionary map) + { + { + // blendshape + var src = go.GetComponent(); + if (src != null) + { + var dst = root.AddComponent(); + dst.BlendShapeAvatar = src.BlendShapeAvatar; + } + } + + { + var secondary = go.transform.Find("secondary"); + if (secondary == null) + { + secondary = go.transform; + } + + var dstSecondary = root.transform.Find("secondary"); + if (dstSecondary == null) + { + dstSecondary = new GameObject("secondary").transform; + dstSecondary.SetParent(root.transform, false); + } + + // 揺れモノ + foreach (var src in go.transform.GetComponentsInChildren()) + { + var dst = map[src.transform]; + var dstColliderGroup = dst.gameObject.AddComponent(); + dstColliderGroup.Colliders = src.Colliders.Select(y => + { + var offset = dst.worldToLocalMatrix.MultiplyPoint(src.transform.localToWorldMatrix.MultiplyPoint(y.Offset)); + return new VRMSpringBoneColliderGroup.SphereCollider + { + Offset = offset, + Radius = y.Radius + }; + }).ToArray(); + } + + foreach (var src in go.transform.GetComponentsInChildren()) + { + // Copy VRMSpringBone + var dst = dstSecondary.gameObject.AddComponent(); + dst.m_comment = src.m_comment; + dst.m_stiffnessForce = src.m_stiffnessForce; + dst.m_gravityPower = src.m_gravityPower; + dst.m_gravityDir = src.m_gravityDir; + dst.m_dragForce = src.m_dragForce; + if (src.m_center != null) + { + dst.m_center = map[src.m_center]; + } + + dst.RootBones = src.RootBones.Select(x => map[x]).ToList(); + dst.m_hitRadius = src.m_hitRadius; + if (src.ColliderGroups != null) + { + dst.ColliderGroups = src.ColliderGroups + .Select(x => map[x.transform].GetComponent()).ToArray(); + } + } + } + +#pragma warning disable 0618 + { + // meta(obsolete) + var src = go.GetComponent(); + if (src != null) + { + src.CopyTo(root); + } + } +#pragma warning restore 0618 + + { + // meta + var src = go.GetComponent(); + if (src != null) + { + var dst = root.AddComponent(); + dst.Meta = src.Meta; + } + } + + { + // firstPerson + var src = go.GetComponent(); + if (src != null) + { + src.CopyTo(root, map); + } + } + + { + // humanoid + var dst = root.AddComponent(); + var src = go.GetComponent(); + if (src != null) + { + dst.Avatar = src.Avatar; + dst.Description = src.Description; + } + else + { + var animator = go.GetComponent(); + if (animator != null) + { + dst.Avatar = animator.avatar; + } + } + } + } + } +} diff --git a/Assets/VRM/UniVRM/Scripts/SkinnedMeshUtility/VRMBoneNormalizer.cs.meta b/Assets/VRM/UniVRM/Scripts/SkinnedMeshUtility/VRMBoneNormalizer.cs.meta new file mode 100644 index 0000000000..64a247ec44 --- /dev/null +++ b/Assets/VRM/UniVRM/Scripts/SkinnedMeshUtility/VRMBoneNormalizer.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 7c0aa7232aa52244aa546dcabf17fadd +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/VRM/UniVRM/Scripts/SkinnedMeshUtility/VRMMeshIntegratorUtility.cs b/Assets/VRM/UniVRM/Scripts/SkinnedMeshUtility/VRMMeshIntegratorUtility.cs new file mode 100644 index 0000000000..1dbbc7ea17 --- /dev/null +++ b/Assets/VRM/UniVRM/Scripts/SkinnedMeshUtility/VRMMeshIntegratorUtility.cs @@ -0,0 +1,92 @@ +using System.Collections.Generic; +using System.Linq; +using UniGLTF; +using UnityEngine; + +namespace VRM +{ + /// + /// Meshを統合し、統合後のMeshのBlendShapeの変化をVRMのBlendShapeClipに反映する + /// + public static class VRMMeshIntegratorUtility + { + public static bool IntegrateRuntime(GameObject vrmRootObject) + { + if (vrmRootObject == null) return false; + var proxy = vrmRootObject.GetComponent(); + if (proxy == null) return false; + var avatar = proxy.BlendShapeAvatar; + if (avatar == null) return false; + var clips = avatar.Clips; + + var results = Integrate(vrmRootObject, clips); + if (results.Any(x => x.IntegratedRenderer == null)) return false; + + foreach (var result in results) + { + foreach (var renderer in result.SourceSkinnedMeshRenderers) + { + Object.Destroy(renderer); + } + + foreach (var renderer in result.SourceMeshRenderers) + { + Object.Destroy(renderer); + } + } + + return true; + } + + public static List Integrate(GameObject root, List blendshapeClips) + { + var result = new List(); + + var withoutBlendShape = MeshUtility.MeshIntegratorUtility.Integrate(root, onlyBlendShapeRenderers: false); + if (withoutBlendShape.IntegratedRenderer != null) + { + result.Add(withoutBlendShape); + } + + var onlyBlendShape = MeshUtility.MeshIntegratorUtility.Integrate(root, onlyBlendShapeRenderers: true); + if (onlyBlendShape.IntegratedRenderer != null) + { + result.Add(onlyBlendShape); + FollowBlendshapeRendererChange(blendshapeClips, onlyBlendShape, root); + } + + return result; + } + + private static void FollowBlendshapeRendererChange(List clips, MeshUtility.MeshIntegrationResult result, GameObject root) + { + if (clips == null || result == null || result.IntegratedRenderer == null || root == null) return; + + var rendererDict = result.SourceSkinnedMeshRenderers + .ToDictionary(x => x.transform.RelativePathFrom(root.transform), x => x); + + var dstPath = result.IntegratedRenderer.transform.RelativePathFrom(root.transform); + + foreach (var clip in clips) + { + if (clip == null) continue; + + for (var i = 0; i < clip.Values.Length; ++i) + { + var val = clip.Values[i]; + if (rendererDict.ContainsKey(val.RelativePath)) + { + var srcRenderer = rendererDict[val.RelativePath]; + var name = srcRenderer.sharedMesh.GetBlendShapeName(val.Index); + var newIndex = result.IntegratedRenderer.sharedMesh.GetBlendShapeIndex(name); + + val.RelativePath = dstPath; + val.Index = newIndex; + } + + clip.Values[i] = val; + } + } + } + } +} \ No newline at end of file diff --git a/Assets/VRM/UniVRM/Scripts/SkinnedMeshUtility/VRMMeshIntegratorUtility.cs.meta b/Assets/VRM/UniVRM/Scripts/SkinnedMeshUtility/VRMMeshIntegratorUtility.cs.meta new file mode 100644 index 0000000000..49f2e8d748 --- /dev/null +++ b/Assets/VRM/UniVRM/Scripts/SkinnedMeshUtility/VRMMeshIntegratorUtility.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 8eebeb093136b7f429c0e9e7295816b3 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/VRM/VRM.asmdef b/Assets/VRM/VRM.asmdef index 661c2b1381..02098b76fb 100644 --- a/Assets/VRM/VRM.asmdef +++ b/Assets/VRM/VRM.asmdef @@ -6,10 +6,15 @@ "UniUnlit", "UniHumanoid", "UniJSON", - "ShaderProperty.Runtime" + "ShaderProperty.Runtime", + "MeshUtility" ], "optionalUnityReferences": [], "includePlatforms": [], "excludePlatforms": [], - "allowUnsafeCode": false + "allowUnsafeCode": false, + "overrideReferences": false, + "precompiledReferences": [], + "autoReferenced": true, + "defineConstraints": [] } \ No newline at end of file diff --git a/Assets/VRM/package.json b/Assets/VRM/package.json index 35cc618764..67b4e71d2f 100644 --- a/Assets/VRM/package.json +++ b/Assets/VRM/package.json @@ -1,6 +1,6 @@ { "name": "com.vrmc.univrm", - "version": "0.58.1", + "version": "0.59.0", "displayName": "VRM", "description": "VRM importer", "unity": "2018.4", @@ -14,6 +14,7 @@ "name": "VRM Consortium" }, "dependencies": { - "com.vrmc.vrmshaders": "0.58.1" + "com.vrmc.vrmshaders": "0.59.0", + "com.vrmc.meshutility": "0.59.0" } } diff --git a/Assets/VRMShaders/package.json b/Assets/VRMShaders/package.json index d46fc49834..c8a10224e9 100644 --- a/Assets/VRMShaders/package.json +++ b/Assets/VRMShaders/package.json @@ -1,6 +1,6 @@ { "name": "com.vrmc.vrmshaders", - "version": "0.58.1", + "version": "0.59.0", "displayName": "VRM Shaders", "description": "VRM Shaders", "unity": "2018.4", diff --git a/ProjectSettings/ProjectVersion.txt b/ProjectSettings/ProjectVersion.txt index 886d7e558b..44c6f16be5 100644 --- a/ProjectSettings/ProjectVersion.txt +++ b/ProjectSettings/ProjectVersion.txt @@ -1 +1 @@ -m_EditorVersion: 2018.4.23f1 +m_EditorVersion: 2018.4.25f1