diff --git a/Assets/UniGLTF/MeshUtility/Editor/ExportDialog/Valildators/HierarchyValidator.cs b/Assets/UniGLTF/MeshUtility/Editor/ExportDialog/Valildators/HierarchyValidator.cs index 93ea547b37..d899d14c85 100644 --- a/Assets/UniGLTF/MeshUtility/Editor/ExportDialog/Valildators/HierarchyValidator.cs +++ b/Assets/UniGLTF/MeshUtility/Editor/ExportDialog/Valildators/HierarchyValidator.cs @@ -17,13 +17,17 @@ public enum ExportValidatorMessages [LangMsg(Languages.en, "ExportRoot must be topmost parent")] NO_PARENT, - [LangMsg(Languages.ja, "ヒエラルキーに active なメッシュが含まれていない")] + [LangMsg(Languages.ja, "ヒエラルキーに active なメッシュが含まれていません")] [LangMsg(Languages.en, "No active mesh")] NO_ACTIVE_MESH, - [LangMsg(Languages.ja, "ヒエラルキーの中に同じ名前のGameObjectが含まれている。 エクスポートした場合に自動でリネームする")] + [LangMsg(Languages.ja, "ヒエラルキーの中に同じ名前のGameObjectが含まれている。 エクスポートした場合に自動でリネームします")] [LangMsg(Languages.en, "There are bones with the same name in the hierarchy. They will be automatically renamed after export")] DUPLICATE_BONE_NAME_EXISTS, + + [LangMsg(Languages.ja, "SkinnedMeshRenderer.bones に重複する内容がある。エクスポートした場合に、bones の重複を取り除き、boneweights, bindposes を調整します")] + [LangMsg(Languages.en, "There are same bone in bones the SkinnedMeshRenderer. They will be automatically uniqued")] + NO_UNIQUE_JOINTS, } /// @@ -45,6 +49,19 @@ static bool DuplicateNodeNameExists(GameObject ExportRoot) return (duplicates.Any()); } + static bool HasNoUniqueJoints(Renderer r) + { + if (r is SkinnedMeshRenderer skin) + { + if (skin.bones != null && skin.bones.Length != skin.bones.Distinct().Count()) + { + return true; + } + } + + return false; + } + public static IEnumerable Validate(GameObject ExportRoot) { if (ExportRoot == null) @@ -66,6 +83,11 @@ public static IEnumerable Validate(GameObject ExportRoot) yield break; } + if (renderers.Any(x => HasNoUniqueJoints(x))) + { + yield return Validation.Warning(ExportValidatorMessages.NO_UNIQUE_JOINTS.Msg()); + } + if (DuplicateNodeNameExists(ExportRoot)) { yield return Validation.Warning(ExportValidatorMessages.DUPLICATE_BONE_NAME_EXISTS.Msg()); diff --git a/Assets/UniGLTF/Runtime/UniGLTF/IO/MeshExporter.cs b/Assets/UniGLTF/Runtime/UniGLTF/IO/MeshExporter.cs index f728094743..1b05dfa3a3 100644 --- a/Assets/UniGLTF/Runtime/UniGLTF/IO/MeshExporter.cs +++ b/Assets/UniGLTF/Runtime/UniGLTF/IO/MeshExporter.cs @@ -1,7 +1,6 @@ using System; using System.Collections.Generic; using System.Linq; -using UniJSON; using UnityEngine; namespace UniGLTF @@ -24,10 +23,10 @@ public struct MeshExportSettings public static class MeshExporter { static glTFMesh ExportPrimitives(glTF gltf, int bufferIndex, - string rendererName, - Mesh mesh, Material[] materials, - List unityMaterials) + MeshWithRenderer unityMesh, List unityMaterials) { + var mesh = unityMesh.Mesh; + var materials = unityMesh.Renderer.sharedMaterials; var positions = mesh.vertices.Select(y => y.ReverseZ()).ToArray(); var positionAccessorIndex = gltf.ExtendBufferAndGetAccessorIndex(bufferIndex, positions, glBufferTarget.ARRAY_BUFFER); gltf.accessors[positionAccessorIndex].min = positions.Aggregate(positions[0], (a, b) => new Vector3(Mathf.Min(a.x, b.x), Math.Min(a.y, b.y), Mathf.Min(a.z, b.z))).ToArray(); @@ -53,7 +52,13 @@ static glTFMesh ExportPrimitives(glTF gltf, int bufferIndex, var boneweights = mesh.boneWeights; var weightAccessorIndex = gltf.ExtendBufferAndGetAccessorIndex(bufferIndex, boneweights.Select(y => new Vector4(y.weight0, y.weight1, y.weight2, y.weight3)).ToArray(), glBufferTarget.ARRAY_BUFFER); - var jointsAccessorIndex = gltf.ExtendBufferAndGetAccessorIndex(bufferIndex, boneweights.Select(y => new UShort4((ushort)y.boneIndex0, (ushort)y.boneIndex1, (ushort)y.boneIndex2, (ushort)y.boneIndex3)).ToArray(), glBufferTarget.ARRAY_BUFFER); + var jointsAccessorIndex = gltf.ExtendBufferAndGetAccessorIndex(bufferIndex, boneweights.Select(y => + new UShort4( + (ushort)unityMesh.GetJointIndex(y.boneIndex0), + (ushort)unityMesh.GetJointIndex(y.boneIndex1), + (ushort)unityMesh.GetJointIndex(y.boneIndex2), + (ushort)unityMesh.GetJointIndex(y.boneIndex3)) + ).ToArray(), glBufferTarget.ARRAY_BUFFER); var attributes = new glTFAttributes { @@ -98,7 +103,7 @@ static glTFMesh ExportPrimitives(glTF gltf, int bufferIndex, if (j >= materials.Length) { - Debug.LogWarningFormat("{0}.materials is not enough", rendererName); + Debug.LogWarningFormat("{0}.materials is not enough", unityMesh.Renderer.name); break; } @@ -251,36 +256,23 @@ static gltfMorphTarget ExportMorphTarget(glTF gltf, int bufferIndex, }; } - public struct MeshWithRenderer - { - public Mesh Mesh; - [Obsolete("Use Renderer")] - public Renderer Rendererer { get { return Renderer; } set { Renderer = value; } } - public Renderer Renderer; - } - public static IEnumerable<(Mesh, glTFMesh, Dictionary)> ExportMeshes(glTF gltf, int bufferIndex, List unityMeshes, List unityMaterials, MeshExportSettings settings) { - for (int i = 0; i < unityMeshes.Count; ++i) + foreach (var unityMesh in unityMeshes) { - var x = unityMeshes[i]; - var mesh = x.Mesh; - var materials = x.Renderer.sharedMaterials; - var gltfMesh = ExportPrimitives(gltf, bufferIndex, - x.Renderer.name, - mesh, materials, unityMaterials); + unityMesh, unityMaterials); var targetNames = new List(); var blendShapeIndexMap = new Dictionary(); int exportBlendShapes = 0; - for (int j = 0; j < mesh.blendShapeCount; ++j) + for (int j = 0; j < unityMesh.Mesh.blendShapeCount; ++j) { var morphTarget = ExportMorphTarget(gltf, bufferIndex, - mesh, j, + unityMesh.Mesh, j, settings.UseSparseAccessorForMorphTarget, settings.ExportOnlyBlendShapePosition); if (morphTarget.POSITION < 0 && morphTarget.NORMAL < 0 && morphTarget.TANGENT < 0) @@ -289,7 +281,7 @@ public struct MeshWithRenderer } // maybe skip - var blendShapeName = mesh.GetBlendShapeName(j); + var blendShapeName = unityMesh.Mesh.GetBlendShapeName(j); blendShapeIndexMap.Add(j, exportBlendShapes++); targetNames.Add(blendShapeName); @@ -304,7 +296,7 @@ public struct MeshWithRenderer gltf_mesh_extras_targetNames.Serialize(gltfMesh, targetNames); - yield return (mesh, gltfMesh, blendShapeIndexMap); + yield return (unityMesh.Mesh, gltfMesh, blendShapeIndexMap); } } } diff --git a/Assets/UniGLTF/Runtime/UniGLTF/IO/MeshWithRenderer.cs b/Assets/UniGLTF/Runtime/UniGLTF/IO/MeshWithRenderer.cs new file mode 100644 index 0000000000..9c67c42cbf --- /dev/null +++ b/Assets/UniGLTF/Runtime/UniGLTF/IO/MeshWithRenderer.cs @@ -0,0 +1,89 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using UnityEngine; + +namespace UniGLTF +{ + public struct MeshWithRenderer + { + public readonly Mesh Mesh; + public readonly Renderer Renderer; + public readonly Transform[] UniqueBones; + readonly int[] JointIndexMap; + + public MeshWithRenderer(Transform x) + { + Mesh = x.GetSharedMesh(); + Renderer = x.GetComponent(); + + if (Renderer is SkinnedMeshRenderer skin && skin.bones != null && skin.bones.Length > 0) + { + // has joints + var uniqueBones = skin.bones.Distinct().ToArray(); + UniqueBones = uniqueBones; + JointIndexMap = new int[skin.bones.Length]; + + var bones = skin.bones; + for (int i = 0; i < bones.Length; ++i) + { + JointIndexMap[i] = Array.IndexOf(uniqueBones, bones[i]); + } + } + else + { + UniqueBones = null; + JointIndexMap = null; + } + } + + public int GetJointIndex(int index) + { + if (index < 0) + { + return index; + } + + if (JointIndexMap != null) + { + return JointIndexMap[index]; + } + else + { + return index; + } + } + + public IEnumerable GetBindPoses() + { + var used = new HashSet(); + for (int i = 0; i < JointIndexMap.Length; ++i) + { + var index = JointIndexMap[i]; + if (used.Add(index)) + { + yield return Mesh.bindposes[i]; + } + } + } + + public static IEnumerable FromNodes(IEnumerable nodes) + { + foreach (var node in nodes) + { + var x = new MeshWithRenderer(node); + if (x.Mesh == null) + { + continue; ; + } + if (x.Renderer.sharedMaterials == null + || x.Renderer.sharedMaterials.Length == 0) + { + continue; + } + + yield return x; + } + } + } +} diff --git a/Assets/UniGLTF/Runtime/UniGLTF/IO/MeshWithRenderer.cs.meta b/Assets/UniGLTF/Runtime/UniGLTF/IO/MeshWithRenderer.cs.meta new file mode 100644 index 0000000000..6ae443e420 --- /dev/null +++ b/Assets/UniGLTF/Runtime/UniGLTF/IO/MeshWithRenderer.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 6f745ee7743b23243bf6adcb740dab82 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/UniGLTF/Runtime/UniGLTF/IO/gltfExporter.cs b/Assets/UniGLTF/Runtime/UniGLTF/IO/gltfExporter.cs index 30a535fd04..8ae3762d80 100644 --- a/Assets/UniGLTF/Runtime/UniGLTF/IO/gltfExporter.cs +++ b/Assets/UniGLTF/Runtime/UniGLTF/IO/gltfExporter.cs @@ -252,27 +252,7 @@ public virtual void Export(MeshExportSettings meshExportSettings) #endregion #region Meshes - var unityMeshes = Nodes - .Select(x => new MeshExporter.MeshWithRenderer - { - Mesh = x.GetSharedMesh(), - Renderer = x.GetComponent(), - }) - .Where(x => - { - if (x.Mesh == null) - { - return false; - } - if (x.Renderer.sharedMaterials == null - || x.Renderer.sharedMaterials.Length == 0) - { - return false; - } - - return true; - }) - .ToList(); + var unityMeshes = MeshWithRenderer.FromNodes(Nodes).ToList(); MeshBlendShapeIndexMap = new Dictionary>(); foreach (var (mesh, gltfMesh, blendShapeIndexMap) in MeshExporter.ExportMeshes( @@ -289,13 +269,10 @@ public virtual void Export(MeshExportSettings meshExportSettings) #endregion #region Nodes and Skins - var unitySkins = Nodes - .Select(x => x.GetComponent()).Where(x => - x != null - && x.bones != null - && x.bones.Length > 0) + var unitySkins = unityMeshes + .Where(x => x.UniqueBones != null) .ToList(); - glTF.nodes = Nodes.Select(x => ExportNode(x, Nodes, unityMeshes.Select(y => y.Renderer).ToList(), unitySkins)).ToList(); + glTF.nodes = Nodes.Select(x => ExportNode(x, Nodes, unityMeshes.Select(y => y.Renderer).ToList(), unitySkins.Select(y => y.Renderer as SkinnedMeshRenderer).ToList())).ToList(); glTF.scenes = new List { new gltfScene @@ -306,19 +283,20 @@ public virtual void Export(MeshExportSettings meshExportSettings) foreach (var x in unitySkins) { - var matrices = x.sharedMesh.bindposes.Select(y => y.ReverseZ()).ToArray(); + var matrices = x.GetBindPoses().Select(y => y.ReverseZ()).ToArray(); var accessor = glTF.ExtendBufferAndGetAccessorIndex(bufferIndex, matrices, glBufferTarget.NONE); + var renderer = x.Renderer as SkinnedMeshRenderer; var skin = new glTFSkin { inverseBindMatrices = accessor, - joints = x.bones.Select(y => Nodes.IndexOf(y)).ToArray(), - skeleton = Nodes.IndexOf(x.rootBone), + joints = x.UniqueBones.Select(y => Nodes.IndexOf(y)).ToArray(), + skeleton = Nodes.IndexOf(renderer.rootBone), }; var skinIndex = glTF.skins.Count; glTF.skins.Add(skin); - foreach (var z in Nodes.Where(y => y.Has(x))) + foreach (var z in Nodes.Where(y => y.Has(x.Renderer))) { var nodeIndex = Nodes.IndexOf(z); var node = glTF.nodes[nodeIndex]; diff --git a/Assets/VRM/Runtime/SkinnedMeshUtility/VRMBoneNormalizer.cs b/Assets/VRM/Runtime/SkinnedMeshUtility/VRMBoneNormalizer.cs index 7c5bacee3d..6ff4265bd2 100644 --- a/Assets/VRM/Runtime/SkinnedMeshUtility/VRMBoneNormalizer.cs +++ b/Assets/VRM/Runtime/SkinnedMeshUtility/VRMBoneNormalizer.cs @@ -83,7 +83,10 @@ public static GameObject Execute(GameObject go, bool forceTPose) .ToDictionary(x => x.Key, x => boneMap[x.Value]) ; - var animator = dst.AddComponent(); + if (dst.GetComponent() == null) + { + var animator = dst.AddComponent(); + } var vrmHuman = go.GetComponent(); var avatarDescription = AvatarDescription.Create(); if (vrmHuman != null && vrmHuman.Description != null)