diff --git a/Assets/VRM/UniVRM/Editor/Format/RecordDisposer.cs b/Assets/VRM/UniVRM/Editor/Format/RecordDisposer.cs new file mode 100644 index 000000000..a824ac8bc --- /dev/null +++ b/Assets/VRM/UniVRM/Editor/Format/RecordDisposer.cs @@ -0,0 +1,147 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using UnityEditor; +using UnityEngine; + +namespace VRM +{ + public struct RecordDisposer : IDisposable + { + int _group; + public RecordDisposer(UnityEngine.Object[] objects, string msg) + { + Undo.IncrementCurrentGroup(); + _group = Undo.GetCurrentGroup(); + Undo.RecordObjects(objects, msg); + } + + public void Dispose() + { + Undo.RevertAllDownToGroup(_group); + } + + /// + /// VRMを構成するコンポーネントをコピーする。 + /// + /// コピー元 + /// コピー先 + /// コピー元とコピー先の対応関係 + public 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/Editor/Format/RecordDisposer.cs.meta b/Assets/VRM/UniVRM/Editor/Format/RecordDisposer.cs.meta new file mode 100644 index 000000000..5cf6bf1e3 --- /dev/null +++ b/Assets/VRM/UniVRM/Editor/Format/RecordDisposer.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: b304ed2aeece5a54191a5a8b69d9d113 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/VRM/UniVRM/Editor/Format/VRMEditorExporter.cs b/Assets/VRM/UniVRM/Editor/Format/VRMEditorExporter.cs new file mode 100644 index 000000000..0fd5b15a2 --- /dev/null +++ b/Assets/VRM/UniVRM/Editor/Format/VRMEditorExporter.cs @@ -0,0 +1,165 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using UniGLTF; +using UnityEditor; +using UnityEngine; + +namespace VRM +{ + public static class VRMEditorExporter + { + public static void Export(string path, VRMExportSettings settings) + { + List destroy = new List(); + try + { + Export(path, settings, destroy); + } + finally + { + foreach (var x in destroy) + { + Debug.LogFormat("destroy: {0}", x.name); + GameObject.DestroyImmediate(x); + } + } + } + + static bool IsPrefab(GameObject go) + { + return !go.scene.IsValid(); + } + + + static void Export(string path, VRMExportSettings settings, List destroy) + { + var target = settings.Source; + if (IsPrefab(target)) + { + using (new RecordDisposer(settings.Source.transform.Traverse().ToArray(), "before normalize")) + { + target = GameObject.Instantiate(target); + destroy.Add(target); + } + } + + if (settings.PoseFreeze) + { + using (new RecordDisposer(target.transform.Traverse().ToArray(), "before normalize")) + { + var normalized = BoneNormalizer.Execute(target, settings.ForceTPose, false); + RecordDisposer.CopyVRMComponents(target, normalized.Root, normalized.BoneMap); + target = normalized.Root; + destroy.Add(target); + } + } + + // remove unused blendShape + if (settings.ReduceBlendshapeSize) + { + var proxy = target.GetComponent(); + + // 元のBlendShapeClipに変更を加えないように複製 + var copyBlendShapeAvatar = GameObject.Instantiate(proxy.BlendShapeAvatar); + var copyBlendShapClips = new List(); + + foreach (var clip in proxy.BlendShapeAvatar.Clips) + { + copyBlendShapClips.Add(GameObject.Instantiate(clip)); + } + + var skinnedMeshRenderers = target.GetComponentsInChildren(); + + var names = new Dictionary(); + var vs = new Dictionary(); + var ns = new Dictionary(); + var ts = new Dictionary(); + + foreach (SkinnedMeshRenderer smr in skinnedMeshRenderers) + { + Mesh mesh = smr.sharedMesh; + if (mesh == null) continue; + if (mesh.blendShapeCount == 0) continue; + + var copyMesh = mesh.Copy(true); + var vCount = copyMesh.vertexCount; + names.Clear(); + + vs.Clear(); + ns.Clear(); + ts.Clear(); + + var usedBlendshapeIndexArray = copyBlendShapClips + .SelectMany(clip => clip.Values) + .Where(val => target.transform.Find(val.RelativePath) == smr.transform) + .Select(val => val.Index) + .Distinct() + .ToArray(); + + foreach (var i in usedBlendshapeIndexArray) + { + var name = copyMesh.GetBlendShapeName(i); + var vertices = new Vector3[vCount]; + var normals = new Vector3[vCount]; + var tangents = new Vector3[vCount]; + copyMesh.GetBlendShapeFrameVertices(i, 0, vertices, normals, tangents); + + names.Add(i, name); + vs.Add(i, vertices); + ns.Add(i, normals); + ts.Add(i, tangents); + } + + copyMesh.ClearBlendShapes(); + + foreach (var i in usedBlendshapeIndexArray) + { + copyMesh.AddBlendShapeFrame(names[i], 100f, vs[i], ns[i], ts[i]); + } + + var indexMapper = usedBlendshapeIndexArray + .Select((x, i) => new { x, i }) + .ToDictionary(pair => pair.x, pair => pair.i); + + foreach (var clip in copyBlendShapClips) + { + for (var i = 0; i < clip.Values.Length; ++i) + { + var value = clip.Values[i]; + if (target.transform.Find(value.RelativePath) != smr.transform) continue; + value.Index = indexMapper[value.Index]; + clip.Values[i] = value; + } + } + + copyBlendShapeAvatar.Clips = copyBlendShapClips; + + proxy.BlendShapeAvatar = copyBlendShapeAvatar; + + smr.sharedMesh = copyMesh; + } + } + + { + var sw = System.Diagnostics.Stopwatch.StartNew(); + var vrm = VRMExporter.Export(target, settings.ReduceBlendshapeSize); + vrm.extensions.VRM.meta.title = settings.Title; + vrm.extensions.VRM.meta.version = settings.Version; + vrm.extensions.VRM.meta.author = settings.Author; + vrm.extensions.VRM.meta.contactInformation = settings.ContactInformation; + vrm.extensions.VRM.meta.reference = settings.Reference; + + var bytes = vrm.ToGlbBytes(settings.UseExperimentalExporter ? SerializerTypes.Generated : SerializerTypes.UniJSON); + File.WriteAllBytes(path, bytes); + Debug.LogFormat("Export elapsed {0}", sw.Elapsed); + } + + if (path.StartsWithUnityAssetPath()) + { + AssetDatabase.ImportAsset(path.ToUnityRelativePath()); + } + } + } +} diff --git a/Assets/VRM/UniVRM/Editor/Format/VRMEditorExporter.cs.meta b/Assets/VRM/UniVRM/Editor/Format/VRMEditorExporter.cs.meta new file mode 100644 index 000000000..f6863a957 --- /dev/null +++ b/Assets/VRM/UniVRM/Editor/Format/VRMEditorExporter.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: cea266830a7f57843bb928d0ea37bcbc +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/VRM/UniVRM/Editor/Format/VRMExportSettings.cs b/Assets/VRM/UniVRM/Editor/Format/VRMExportSettings.cs index 511650f41..c6ffe529d 100644 --- a/Assets/VRM/UniVRM/Editor/Format/VRMExportSettings.cs +++ b/Assets/VRM/UniVRM/Editor/Format/VRMExportSettings.cs @@ -1,10 +1,8 @@ using System; using System.Collections.Generic; using System.Linq; -using System.IO; using UniGLTF; using UnityEngine; -using UnityEditor; namespace VRM @@ -54,7 +52,19 @@ namespace VRM } } - public IEnumerable CanExport() + //ここで重複ボーン名のチェックをする + bool DuplicateBoneNameExists() + { + var bones = Source.transform.Traverse().ToArray(); + var duplicates = bones + .GroupBy(p => p.name) + .Where(g => g.Count() > 1) + .Select(g => g.Key); + + return (duplicates.Any()); + } + + public IEnumerable Validate() { if (Source == null) { @@ -172,308 +182,5 @@ namespace VRM Version = "0.0"; } } - - // - // トップレベルのMonoBehaviourを移植する - // - public 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.Traverse().Select(x => x.GetComponent()).Where(x => x != null)) - { - 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.Traverse().SelectMany(x => x.GetComponents())) - { - // 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; - } - } - } - } - - public static bool IsPrefab(GameObject go) - { - return !go.scene.IsValid(); - } - -#if UNITY_EDITOR - public struct RecordDisposer : IDisposable - { - int _group; - public RecordDisposer(UnityEngine.Object[] objects, string msg) - { - Undo.IncrementCurrentGroup(); - _group = Undo.GetCurrentGroup(); - Undo.RecordObjects(objects, msg); - } - - public void Dispose() - { - Undo.RevertAllDownToGroup(_group); - } - } - - public void Export(string path) - { - List destroy = new List(); - try - { - Export(path, destroy); - } - finally - { - foreach (var x in destroy) - { - Debug.LogFormat("destroy: {0}", x.name); - GameObject.DestroyImmediate(x); - } - } - } - - void Export(string path, List destroy) - { - var target = Source; - if (IsPrefab(target)) - { - using (new RecordDisposer(Source.transform.Traverse().ToArray(), "before normalize")) - { - target = GameObject.Instantiate(target); - destroy.Add(target); - } - } - - if (PoseFreeze) - { - using (new RecordDisposer(target.transform.Traverse().ToArray(), "before normalize")) - { - var normalized = BoneNormalizer.Execute(target, ForceTPose, false); - CopyVRMComponents(target, normalized.Root, normalized.BoneMap); - target = normalized.Root; - destroy.Add(target); - } - } - - // remove unused blendShape - if (ReduceBlendshapeSize) - { - var proxy = target.GetComponent(); - - // 元のBlendShapeClipに変更を加えないように複製 - var copyBlendShapeAvatar = GameObject.Instantiate(proxy.BlendShapeAvatar); - var copyBlendShapClips = new List(); - - foreach (var clip in proxy.BlendShapeAvatar.Clips) - { - copyBlendShapClips.Add(GameObject.Instantiate(clip)); - } - - var skinnedMeshRenderers = target.GetComponentsInChildren(); - - var names = new Dictionary(); - var vs = new Dictionary(); - var ns = new Dictionary(); - var ts = new Dictionary(); - - foreach (SkinnedMeshRenderer smr in skinnedMeshRenderers) - { - Mesh mesh = smr.sharedMesh; - if (mesh == null) continue; - if (mesh.blendShapeCount == 0) continue; - - var copyMesh = mesh.Copy(true); - var vCount = copyMesh.vertexCount; - names.Clear(); - - vs.Clear(); - ns.Clear(); - ts.Clear(); - - var usedBlendshapeIndexArray = copyBlendShapClips - .SelectMany(clip => clip.Values) - .Where(val => target.transform.Find(val.RelativePath) == smr.transform) - .Select(val => val.Index) - .Distinct() - .ToArray(); - - foreach (var i in usedBlendshapeIndexArray) - { - var name = copyMesh.GetBlendShapeName(i); - var vertices = new Vector3[vCount]; - var normals = new Vector3[vCount]; - var tangents = new Vector3[vCount]; - copyMesh.GetBlendShapeFrameVertices(i, 0, vertices, normals, tangents); - - names.Add(i, name); - vs.Add(i, vertices); - ns.Add(i, normals); - ts.Add(i, tangents); - } - - copyMesh.ClearBlendShapes(); - - foreach (var i in usedBlendshapeIndexArray) - { - copyMesh.AddBlendShapeFrame(names[i], 100f, vs[i], ns[i], ts[i]); - } - - var indexMapper = usedBlendshapeIndexArray - .Select((x, i) => new { x, i }) - .ToDictionary(pair => pair.x, pair => pair.i); - - foreach (var clip in copyBlendShapClips) - { - for (var i = 0; i < clip.Values.Length; ++i) - { - var value = clip.Values[i]; - if (target.transform.Find(value.RelativePath) != smr.transform) continue; - value.Index = indexMapper[value.Index]; - clip.Values[i] = value; - } - } - - copyBlendShapeAvatar.Clips = copyBlendShapClips; - - proxy.BlendShapeAvatar = copyBlendShapeAvatar; - - smr.sharedMesh = copyMesh; - } - } - - { - var sw = System.Diagnostics.Stopwatch.StartNew(); - var vrm = VRMExporter.Export(target, ReduceBlendshapeSize); - vrm.extensions.VRM.meta.title = Title; - vrm.extensions.VRM.meta.version = Version; - vrm.extensions.VRM.meta.author = Author; - vrm.extensions.VRM.meta.contactInformation = ContactInformation; - vrm.extensions.VRM.meta.reference = Reference; - - - var bytes = vrm.ToGlbBytes(UseExperimentalExporter ? SerializerTypes.Generated : SerializerTypes.UniJSON); - File.WriteAllBytes(path, bytes); - Debug.LogFormat("Export elapsed {0}", sw.Elapsed); - } - - - if (path.StartsWithUnityAssetPath()) - { - AssetDatabase.ImportAsset(path.ToUnityRelativePath()); - } - } -#endif - - //ここで重複ボーン名のチェックをする - bool DuplicateBoneNameExists() - { - var bones = Source.transform.Traverse().ToArray(); - var duplicates = bones - .GroupBy(p => p.name) - .Where(g => g.Count() > 1) - .Select(g => g.Key); - - return (duplicates.Any()); - } } } \ No newline at end of file diff --git a/Assets/VRM/UniVRM/Editor/Format/VRMExporterWizard.cs b/Assets/VRM/UniVRM/Editor/Format/VRMExporterWizard.cs index 4e5e90243..90b1faa12 100644 --- a/Assets/VRM/UniVRM/Editor/Format/VRMExporterWizard.cs +++ b/Assets/VRM/UniVRM/Editor/Format/VRMExporterWizard.cs @@ -61,7 +61,7 @@ namespace VRM m_lastExportDir = Path.GetDirectoryName(path).Replace("\\", "/"); // export - m_settings.Export(path); + VRMEditorExporter.Export(path, m_settings); } void OnWizardUpdate() @@ -70,7 +70,7 @@ namespace VRM var helpBuilder = new StringBuilder(); var errorBuilder = new StringBuilder(); - foreach (var validation in m_settings.CanExport()) + foreach (var validation in m_settings.Validate()) { if (!validation.CanExport) { diff --git a/Assets/VRM/UniVRM/Editor/Format/VRMHumanoidNormalizerMenu.cs b/Assets/VRM/UniVRM/Editor/Format/VRMHumanoidNormalizerMenu.cs index 400dbb2f7..fbb6d8a32 100644 --- a/Assets/VRM/UniVRM/Editor/Format/VRMHumanoidNormalizerMenu.cs +++ b/Assets/VRM/UniVRM/Editor/Format/VRMHumanoidNormalizerMenu.cs @@ -49,10 +49,10 @@ namespace VRM var go = Selection.activeObject as GameObject; GameObject normalizedRoot = null; - using (new VRMExportSettings.RecordDisposer(go.transform.Traverse().ToArray(), "before normalize")) + using (new RecordDisposer(go.transform.Traverse().ToArray(), "before normalize")) { var normalized = BoneNormalizer.Execute(go, true, false); - VRMExportSettings.CopyVRMComponents(go, normalized.Root, normalized.BoneMap); + RecordDisposer.CopyVRMComponents(go, normalized.Root, normalized.BoneMap); normalizedRoot = normalized.Root; } Selection.activeGameObject = normalizedRoot;