From 9a0a8498e5272d1181d1f5171abe2aa257a3b60c Mon Sep 17 00:00:00 2001 From: ousttrue Date: Mon, 26 Apr 2021 17:48:53 +0900 Subject: [PATCH 1/3] VertexBuffer --- .../Runtime/UniGLTF/IO/MeshExporterDivided.cs | 37 ++++++++------- .../UniGLTF/Tests/UniGLTF/DividedMeshTests.cs | 47 +++++++++++++++++++ .../Tests/UniGLTF/DividedMeshTests.cs.meta | 11 +++++ 3 files changed, 79 insertions(+), 16 deletions(-) create mode 100644 Assets/UniGLTF/Tests/UniGLTF/DividedMeshTests.cs create mode 100644 Assets/UniGLTF/Tests/UniGLTF/DividedMeshTests.cs.meta diff --git a/Assets/UniGLTF/Runtime/UniGLTF/IO/MeshExporterDivided.cs b/Assets/UniGLTF/Runtime/UniGLTF/IO/MeshExporterDivided.cs index 3b876f87a..dd72e601b 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,7 +40,7 @@ namespace UniGLTF } } - class VertexBuffer + public class VertexBuffer { readonly List m_positions; readonly List m_normals; @@ -50,17 +50,17 @@ 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); } } @@ -77,9 +77,11 @@ 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, int) ToGltfPrimitive(glTF gltf, int bufferIndex, int materialIndex, IEnumerable indices, int vertexOffset) { - var indicesAccessorIndex = gltf.ExtendBufferAndGetAccessorIndex(bufferIndex, indices.ToArray(), glBufferTarget.ELEMENT_ARRAY_BUFFER); + var vertexCount = m_positions.Count; + + var indicesAccessorIndex = gltf.ExtendBufferAndGetAccessorIndex(bufferIndex, indices.Select(x => (uint)(x - vertexOffset)).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,7 +111,8 @@ namespace UniGLTF material = materialIndex, mode = 4, }; - return primitive; + + return (primitive, vertexCount); } } @@ -141,6 +144,7 @@ namespace UniGLTF Vector3[] blendShapeNormals = new Vector3[mesh.vertexCount]; var usedIndices = new List(); + var vertexOffset = 0; for (int i = 0; i < mesh.subMeshCount; ++i) { var indices = mesh.GetIndices(i); @@ -177,16 +181,17 @@ namespace UniGLTF { materialIndex = unityMaterials.IndexOf(material); } - var indexMap = usedIndices.Select((used, index) => (used, index)).ToDictionary(x => x.used, x => (uint)x.index); + var indexMap = usedIndices.Select((used, index) => (used, index)).ToDictionary(x => x.used, x => 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]]); + flipped.Add(indexMap[indices[j + 2]]); + flipped.Add(indexMap[indices[j + 1]]); + flipped.Add(indexMap[indices[j]]); } - var gltfPrimitive = buffer.ToGltf(gltf, bufferIndex, materialIndex, flipped); + var (gltfPrimitive, vertexCount) = buffer.ToGltfPrimitive(gltf, bufferIndex, materialIndex, flipped, vertexOffset); + vertexOffset += vertexCount; // blendShape for (int j = 0; j < mesh.blendShapeCount; ++j) diff --git a/Assets/UniGLTF/Tests/UniGLTF/DividedMeshTests.cs b/Assets/UniGLTF/Tests/UniGLTF/DividedMeshTests.cs new file mode 100644 index 000000000..531f6b6ff --- /dev/null +++ b/Assets/UniGLTF/Tests/UniGLTF/DividedMeshTests.cs @@ -0,0 +1,47 @@ +using NUnit.Framework; +using UnityEngine; + +namespace UniGLTF +{ + public class DividedMeshTests + { + /// + /// 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 gltf = new glTF(); + gltf.AddBuffer(new UniGLTF.ArrayByteBuffer()); + + { + var buffer = new UniGLTF.MeshExporterDivided.VertexBuffer(6, null); + buffer.Push(new Vector3(1, 1, 0), Vector3.up, Vector2.zero); + buffer.Push(new Vector3(1, 1, 1), Vector3.up, Vector2.zero); + buffer.Push(new Vector3(1, 1, 2), Vector3.up, Vector2.zero); + var (prim, count) = buffer.ToGltfPrimitive(gltf, 0, 0, new[] { 0, 1, 2 }, 0); + Assert.AreEqual(3, count); + } + + { + var buffer = new UniGLTF.MeshExporterDivided.VertexBuffer(6, null); + buffer.Push(new Vector3(1, 1, 3), Vector3.up, Vector2.zero); + buffer.Push(new Vector3(1, 1, 4), Vector3.up, Vector2.zero); + buffer.Push(new Vector3(1, 1, 5), Vector3.up, Vector2.zero); + var (prim, count) = buffer.ToGltfPrimitive(gltf, 0, 0, new[] { 3, 4, 5 }, 3); + Assert.AreEqual(3, count); + } + } + } +} diff --git a/Assets/UniGLTF/Tests/UniGLTF/DividedMeshTests.cs.meta b/Assets/UniGLTF/Tests/UniGLTF/DividedMeshTests.cs.meta new file mode 100644 index 000000000..c56c41760 --- /dev/null +++ b/Assets/UniGLTF/Tests/UniGLTF/DividedMeshTests.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 1e859e08abedf9346bdadedd79eb3e67 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: From 1354414209953db7fd641c9174d10e3470d57302 Mon Sep 17 00:00:00 2001 From: ousttrue Date: Mon, 26 Apr 2021 18:08:41 +0900 Subject: [PATCH 2/3] =?UTF-8?q?submesh=E5=88=86=E5=89=B2=E3=81=99=E3=82=8B?= =?UTF-8?q?=E3=81=93=E3=81=A8=E3=81=A7=E9=A0=82=E7=82=B9=20index=20?= =?UTF-8?q?=E3=81=8C=E5=A4=89=E3=82=8F=E3=82=8B=E3=81=93=E3=81=A8=E3=81=8C?= =?UTF-8?q?=E5=AE=9F=E8=A3=85=E3=81=A7=E3=81=8D=E3=81=A6=E3=81=84=E3=81=AA?= =?UTF-8?q?=E3=81=8B=E3=81=A3=E3=81=9F=E3=80=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Runtime/UniGLTF/IO/MeshExporterDivided.cs | 64 +++++++++++++------ .../UniGLTF/Tests/UniGLTF/DividedMeshTests.cs | 18 +++--- 2 files changed, 51 insertions(+), 31 deletions(-) diff --git a/Assets/UniGLTF/Runtime/UniGLTF/IO/MeshExporterDivided.cs b/Assets/UniGLTF/Runtime/UniGLTF/IO/MeshExporterDivided.cs index dd72e601b..2a9a77277 100644 --- a/Assets/UniGLTF/Runtime/UniGLTF/IO/MeshExporterDivided.cs +++ b/Assets/UniGLTF/Runtime/UniGLTF/IO/MeshExporterDivided.cs @@ -42,6 +42,30 @@ namespace UniGLTF 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; @@ -64,8 +88,11 @@ namespace UniGLTF } } - 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,11 +104,9 @@ namespace UniGLTF m_weights.Add(new Vector4(boneWeight.weight0, boneWeight.weight1, boneWeight.weight2, boneWeight.weight3)); } - public (glTFPrimitives, int) ToGltfPrimitive(glTF gltf, int bufferIndex, int materialIndex, IEnumerable indices, int vertexOffset) + public glTFPrimitives ToGltfPrimitive(glTF gltf, int bufferIndex, int materialIndex, IEnumerable indices) { - var vertexCount = m_positions.Count; - - var indicesAccessorIndex = gltf.ExtendBufferAndGetAccessorIndex(bufferIndex, indices.Select(x => (uint)(x - vertexOffset)).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); @@ -112,7 +137,7 @@ namespace UniGLTF mode = 4, }; - return (primitive, vertexCount); + return primitive; } } @@ -144,7 +169,6 @@ namespace UniGLTF Vector3[] blendShapeNormals = new Vector3[mesh.vertexCount]; var usedIndices = new List(); - var vertexOffset = 0; for (int i = 0; i < mesh.subMeshCount; ++i) { var indices = mesh.GetIndices(i); @@ -152,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) @@ -167,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]); @@ -186,12 +202,18 @@ namespace UniGLTF var flipped = new List(); for (int j = 0; j < indices.Length; j += 3) { - flipped.Add(indexMap[indices[j + 2]]); - flipped.Add(indexMap[indices[j + 1]]); - flipped.Add(indexMap[indices[j]]); + if (buffer.ContainsTriangle(j, j + 1, j + 2)) + { + flipped.Add(indexMap[indices[j + 2]]); + flipped.Add(indexMap[indices[j + 1]]); + flipped.Add(indexMap[indices[j]]); + } + else + { + Debug.LogWarning($"triangle not contains [{j}, {j + 1}, {j + 2}]"); + } } - var (gltfPrimitive, vertexCount) = buffer.ToGltfPrimitive(gltf, bufferIndex, materialIndex, flipped, vertexOffset); - vertexOffset += vertexCount; + var gltfPrimitive = buffer.ToGltfPrimitive(gltf, bufferIndex, materialIndex, flipped); // blendShape for (int j = 0; j < mesh.blendShapeCount; ++j) diff --git a/Assets/UniGLTF/Tests/UniGLTF/DividedMeshTests.cs b/Assets/UniGLTF/Tests/UniGLTF/DividedMeshTests.cs index 531f6b6ff..cf4945556 100644 --- a/Assets/UniGLTF/Tests/UniGLTF/DividedMeshTests.cs +++ b/Assets/UniGLTF/Tests/UniGLTF/DividedMeshTests.cs @@ -27,20 +27,18 @@ namespace UniGLTF { var buffer = new UniGLTF.MeshExporterDivided.VertexBuffer(6, null); - buffer.Push(new Vector3(1, 1, 0), Vector3.up, Vector2.zero); - buffer.Push(new Vector3(1, 1, 1), Vector3.up, Vector2.zero); - buffer.Push(new Vector3(1, 1, 2), Vector3.up, Vector2.zero); - var (prim, count) = buffer.ToGltfPrimitive(gltf, 0, 0, new[] { 0, 1, 2 }, 0); - Assert.AreEqual(3, count); + buffer.Push(0, new Vector3(1, 1, 0), Vector3.up, Vector2.zero); + buffer.Push(1, new Vector3(1, 1, 1), Vector3.up, Vector2.zero); + buffer.Push(2, new Vector3(1, 1, 2), Vector3.up, Vector2.zero); + var prim = buffer.ToGltfPrimitive(gltf, 0, 0, new[] { 0, 1, 2 }); } { var buffer = new UniGLTF.MeshExporterDivided.VertexBuffer(6, null); - buffer.Push(new Vector3(1, 1, 3), Vector3.up, Vector2.zero); - buffer.Push(new Vector3(1, 1, 4), Vector3.up, Vector2.zero); - buffer.Push(new Vector3(1, 1, 5), Vector3.up, Vector2.zero); - var (prim, count) = buffer.ToGltfPrimitive(gltf, 0, 0, new[] { 3, 4, 5 }, 3); - Assert.AreEqual(3, count); + buffer.Push(3, new Vector3(1, 1, 3), Vector3.up, Vector2.zero); + buffer.Push(4, new Vector3(1, 1, 4), Vector3.up, Vector2.zero); + buffer.Push(5, new Vector3(1, 1, 5), Vector3.up, Vector2.zero); + var prim = buffer.ToGltfPrimitive(gltf, 0, 0, new[] { 3, 4, 5 }); } } } From 67811f720bf4469ab883caeb1e253b5522beea09 Mon Sep 17 00:00:00 2001 From: ousttrue Date: Mon, 26 Apr 2021 18:45:58 +0900 Subject: [PATCH 3/3] fix flip --- .../Runtime/UniGLTF/IO/MeshExporterDivided.cs | 14 +-- .../UniGLTF/Tests/UniGLTF/DividedMeshTests.cs | 45 ---------- Assets/VRM/Tests/VrmDividedMeshTests.cs | 86 +++++++++++++++++++ .../Tests/VrmDividedMeshTests.cs.meta} | 2 +- 4 files changed, 95 insertions(+), 52 deletions(-) delete mode 100644 Assets/UniGLTF/Tests/UniGLTF/DividedMeshTests.cs create mode 100644 Assets/VRM/Tests/VrmDividedMeshTests.cs rename Assets/{UniGLTF/Tests/UniGLTF/DividedMeshTests.cs.meta => VRM/Tests/VrmDividedMeshTests.cs.meta} (83%) diff --git a/Assets/UniGLTF/Runtime/UniGLTF/IO/MeshExporterDivided.cs b/Assets/UniGLTF/Runtime/UniGLTF/IO/MeshExporterDivided.cs index 2a9a77277..2ca17055f 100644 --- a/Assets/UniGLTF/Runtime/UniGLTF/IO/MeshExporterDivided.cs +++ b/Assets/UniGLTF/Runtime/UniGLTF/IO/MeshExporterDivided.cs @@ -197,20 +197,22 @@ namespace UniGLTF { materialIndex = unityMaterials.IndexOf(material); } - var indexMap = usedIndices.Select((used, index) => (used, index)).ToDictionary(x => x.used, x => x.index); var flipped = new List(); for (int j = 0; j < indices.Length; j += 3) { - if (buffer.ContainsTriangle(j, j + 1, j + 2)) + var t0 = indices[j]; + var t1 = indices[j + 1]; + var t2 = indices[j + 2]; + if (buffer.ContainsTriangle(t0, t1, t2)) { - flipped.Add(indexMap[indices[j + 2]]); - flipped.Add(indexMap[indices[j + 1]]); - flipped.Add(indexMap[indices[j]]); + flipped.Add(t2); + flipped.Add(t1); + flipped.Add(t0); } else { - Debug.LogWarning($"triangle not contains [{j}, {j + 1}, {j + 2}]"); + Debug.LogWarning($"triangle not contains [{t0}, {t1}, {t2}]"); } } var gltfPrimitive = buffer.ToGltfPrimitive(gltf, bufferIndex, materialIndex, flipped); diff --git a/Assets/UniGLTF/Tests/UniGLTF/DividedMeshTests.cs b/Assets/UniGLTF/Tests/UniGLTF/DividedMeshTests.cs deleted file mode 100644 index cf4945556..000000000 --- a/Assets/UniGLTF/Tests/UniGLTF/DividedMeshTests.cs +++ /dev/null @@ -1,45 +0,0 @@ -using NUnit.Framework; -using UnityEngine; - -namespace UniGLTF -{ - public class DividedMeshTests - { - /// - /// 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 gltf = new glTF(); - gltf.AddBuffer(new UniGLTF.ArrayByteBuffer()); - - { - var buffer = new UniGLTF.MeshExporterDivided.VertexBuffer(6, null); - buffer.Push(0, new Vector3(1, 1, 0), Vector3.up, Vector2.zero); - buffer.Push(1, new Vector3(1, 1, 1), Vector3.up, Vector2.zero); - buffer.Push(2, new Vector3(1, 1, 2), Vector3.up, Vector2.zero); - var prim = buffer.ToGltfPrimitive(gltf, 0, 0, new[] { 0, 1, 2 }); - } - - { - var buffer = new UniGLTF.MeshExporterDivided.VertexBuffer(6, null); - buffer.Push(3, new Vector3(1, 1, 3), Vector3.up, Vector2.zero); - buffer.Push(4, new Vector3(1, 1, 4), Vector3.up, Vector2.zero); - buffer.Push(5, new Vector3(1, 1, 5), Vector3.up, Vector2.zero); - var prim = buffer.ToGltfPrimitive(gltf, 0, 0, new[] { 3, 4, 5 }); - } - } - } -} 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/UniGLTF/Tests/UniGLTF/DividedMeshTests.cs.meta b/Assets/VRM/Tests/VrmDividedMeshTests.cs.meta similarity index 83% rename from Assets/UniGLTF/Tests/UniGLTF/DividedMeshTests.cs.meta rename to Assets/VRM/Tests/VrmDividedMeshTests.cs.meta index c56c41760..44b379e26 100644 --- a/Assets/UniGLTF/Tests/UniGLTF/DividedMeshTests.cs.meta +++ b/Assets/VRM/Tests/VrmDividedMeshTests.cs.meta @@ -1,5 +1,5 @@ fileFormatVersion: 2 -guid: 1e859e08abedf9346bdadedd79eb3e67 +guid: 5739a74d19763bb498ed1a035e25daf7 MonoImporter: externalObjects: {} serializedVersion: 2