diff --git a/Assets/UniGLTF/Runtime/UniGLTF/IO/MeshExporterDivided.cs b/Assets/UniGLTF/Runtime/UniGLTF/IO/MeshExporterDivided.cs index 3b876f87a..2ca17055f 100644 --- a/Assets/UniGLTF/Runtime/UniGLTF/IO/MeshExporterDivided.cs +++ b/Assets/UniGLTF/Runtime/UniGLTF/IO/MeshExporterDivided.cs @@ -7,7 +7,7 @@ namespace UniGLTF { public static class MeshExporterDivided { - class BlendShapeBuffer + public class BlendShapeBuffer { readonly List m_positions; readonly List m_normals; @@ -40,8 +40,32 @@ namespace UniGLTF } } - class VertexBuffer + public class VertexBuffer { + /// + /// SubMeshで分割するので index が変わる。対応表 + /// + /// + /// + /// + readonly Dictionary m_vertexIndexMap = new Dictionary(); + public bool ContainsTriangle(int v0, int v1, int v2) + { + if (!m_vertexIndexMap.ContainsKey(v0)) + { + return false; + } + if (!m_vertexIndexMap.ContainsKey(v1)) + { + return false; + } + if (!m_vertexIndexMap.ContainsKey(v2)) + { + return false; + } + return true; + } + readonly List m_positions; readonly List m_normals; readonly List m_uv; @@ -50,22 +74,25 @@ namespace UniGLTF readonly List m_joints; readonly List m_weights; - public VertexBuffer(int reserve, Func getJointIndex) + public VertexBuffer(int vertexCount, Func getJointIndex) { - m_positions = new List(reserve); - m_normals = new List(reserve); + m_positions = new List(vertexCount); + m_normals = new List(vertexCount); m_uv = new List(); m_getJointIndex = getJointIndex; if (m_getJointIndex != null) { - m_joints = new List(reserve); - m_weights = new List(reserve); + m_joints = new List(vertexCount); + m_weights = new List(vertexCount); } } - public void Push(Vector3 position, Vector3 normal, Vector2 uv) + public void Push(int index, Vector3 position, Vector3 normal, Vector2 uv) { + var newIndex = m_positions.Count; + m_vertexIndexMap.Add(index, newIndex); + m_positions.Add(position); m_normals.Add(normal); m_uv.Add(uv); @@ -77,9 +104,9 @@ namespace UniGLTF m_weights.Add(new Vector4(boneWeight.weight0, boneWeight.weight1, boneWeight.weight2, boneWeight.weight3)); } - public glTFPrimitives ToGltf(glTF gltf, int bufferIndex, int materialIndex, IReadOnlyList indices) + public glTFPrimitives ToGltfPrimitive(glTF gltf, int bufferIndex, int materialIndex, IEnumerable indices) { - var indicesAccessorIndex = gltf.ExtendBufferAndGetAccessorIndex(bufferIndex, indices.ToArray(), glBufferTarget.ELEMENT_ARRAY_BUFFER); + var indicesAccessorIndex = gltf.ExtendBufferAndGetAccessorIndex(bufferIndex, indices.Select(x => (uint)m_vertexIndexMap[x]).ToArray(), glBufferTarget.ELEMENT_ARRAY_BUFFER); var positionAccessorIndex = gltf.ExtendBufferAndGetAccessorIndex(bufferIndex, m_positions.ToArray(), glBufferTarget.ARRAY_BUFFER); var normalAccessorIndex = gltf.ExtendBufferAndGetAccessorIndex(bufferIndex, m_normals.ToArray(), glBufferTarget.ARRAY_BUFFER); var uvAccessorIndex0 = gltf.ExtendBufferAndGetAccessorIndex(bufferIndex, m_uv.ToArray(), glBufferTarget.ARRAY_BUFFER); @@ -109,6 +136,7 @@ namespace UniGLTF material = materialIndex, mode = 4, }; + return primitive; } } @@ -148,14 +176,6 @@ namespace UniGLTF // mesh // index の順に attributes を蓄える var buffer = new VertexBuffer(indices.Length, getJointIndex); - // foreach (var k in indices) - // { - // buffer.Push(axisInverter.InvertVector3(positions[k]), axisInverter.InvertVector3(normals[k]), uv[k].ReverseUV()); - // if (getJointIndex != null) - // { - // buffer.Push(boneWeights[k]); - // } - // } // indices から参照される頂点だけを蓄える usedIndices.Clear(); for (int k = 0; k < positions.Length; ++k) @@ -163,7 +183,7 @@ namespace UniGLTF if (indices.Contains(k)) { usedIndices.Add(k); - buffer.Push(axisInverter.InvertVector3(positions[k]), axisInverter.InvertVector3(normals[k]), uv[k].ReverseUV()); + buffer.Push(k, axisInverter.InvertVector3(positions[k]), axisInverter.InvertVector3(normals[k]), uv[k].ReverseUV()); if (getJointIndex != null) { buffer.Push(boneWeights[k]); @@ -177,16 +197,25 @@ namespace UniGLTF { materialIndex = unityMaterials.IndexOf(material); } - var indexMap = usedIndices.Select((used, index) => (used, index)).ToDictionary(x => x.used, x => (uint)x.index); - var flipped = new List(); + var flipped = new List(); for (int j = 0; j < indices.Length; j += 3) { - flipped.Add((uint)indexMap[indices[j + 2]]); - flipped.Add((uint)indexMap[indices[j + 1]]); - flipped.Add((uint)indexMap[indices[j]]); + var t0 = indices[j]; + var t1 = indices[j + 1]; + var t2 = indices[j + 2]; + if (buffer.ContainsTriangle(t0, t1, t2)) + { + flipped.Add(t2); + flipped.Add(t1); + flipped.Add(t0); + } + else + { + Debug.LogWarning($"triangle not contains [{t0}, {t1}, {t2}]"); + } } - var gltfPrimitive = buffer.ToGltf(gltf, bufferIndex, materialIndex, flipped); + var gltfPrimitive = buffer.ToGltfPrimitive(gltf, bufferIndex, materialIndex, flipped); // blendShape for (int j = 0; j < mesh.blendShapeCount; ++j) diff --git a/Assets/VRM/Tests/VrmDividedMeshTests.cs b/Assets/VRM/Tests/VrmDividedMeshTests.cs new file mode 100644 index 000000000..aa7eb86a4 --- /dev/null +++ b/Assets/VRM/Tests/VrmDividedMeshTests.cs @@ -0,0 +1,86 @@ +using System.Collections.Generic; +using System.IO; +using System.Linq; +using NUnit.Framework; +using UniGLTF; +using UnityEngine; +using VRMShaders; + +namespace VRM +{ + public class DividedMeshTests + { + static string AliciaPath + { + get + { + return Path.GetFullPath(Application.dataPath + "/../Tests/Models/Alicia_vrm-0.51/AliciaSolid_vrm-0.51.vrm") + .Replace("\\", "/"); + } + } + + static GameObject Load(byte[] bytes, string path) + { + var parser = new GltfParser(); + parser.Parse(path, bytes); + + using (var loader = new VRMImporterContext(parser)) + { + loader.Load(); + loader.ShowMeshes(); + return loader.DisposeOnGameObjectDestroyed().gameObject; + } + } + + static IEnumerable GetMeshes(GameObject gameObject) + { + foreach (var r in gameObject.GetComponentsInChildren()) + { + if (r is SkinnedMeshRenderer smr) + { + yield return smr.sharedMesh; + } + else if (r is MeshRenderer mr) + { + yield return r.GetComponent().sharedMesh; + } + } + } + + /// + /// positions: [ + /// {1, 1, 0} + /// {1, 1, 1} + /// {1, 1, 2} + /// {1, 1, 3} + /// {1, 1, 4} + /// {1, 1, 5} + /// ] + /// submesh + /// 0 1 2 + /// submesh + /// 3 4 5 + /// + [Test] + public void ExportDividedMeshTest() + { + var path = AliciaPath; + var loaded = Load(File.ReadAllBytes(path), path); + + var exported = VRMExporter.Export(new UniGLTF.MeshExportSettings + { + DivideVertexBuffer = true, // test this + ExportOnlyBlendShapePosition = true, + ExportTangents = false, + UseSparseAccessorForMorphTarget = true, + }, loaded, AssetTextureUtil.IsTextureEditorAsset, AssetTextureUtil.GetTextureBytesWithMime); + var bytes = exported.ToGlbBytes(); + var divided = Load(bytes, path); + + var src = GetMeshes(loaded).ToArray(); + var div = GetMeshes(divided).ToArray(); + + Assert.AreEqual(src[0].triangles.Length, div[0].triangles.Length); + } + } +} diff --git a/Assets/VRM/Tests/VrmDividedMeshTests.cs.meta b/Assets/VRM/Tests/VrmDividedMeshTests.cs.meta new file mode 100644 index 000000000..44b379e26 --- /dev/null +++ b/Assets/VRM/Tests/VrmDividedMeshTests.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 5739a74d19763bb498ed1a035e25daf7 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: