using System; using System.Collections.Generic; using System.Linq; using System.Numerics; using UniGLTF; using VrmLib; namespace UniVRM10 { /// /// VrmLib.MeshGroup => GLTF /// public static class MeshWriter { static void Vec3MinMax(ArraySegment bytes, glTFAccessor accessor) { var positions = SpanLike.Wrap(bytes); var min = new Vector3(float.PositiveInfinity, float.PositiveInfinity, float.PositiveInfinity); var max = new Vector3(float.NegativeInfinity, float.NegativeInfinity, float.NegativeInfinity); foreach (var p in positions) { min = Vector3.Min(min, p); max = Vector3.Max(max, p); } accessor.min = min.ToFloat3(); accessor.max = max.ToFloat3(); } static int ExportIndices(Vrm10Storage storage, BufferAccessor x, int offset, int count, ExportArgs option) { if (x.Count <= ushort.MaxValue) { if (x.ComponentType == AccessorValueType.UNSIGNED_INT) { // ensure ushort var src = x.GetSpan().Slice(offset, count); var bytes = new byte[src.Length * 2]; var dst = SpanLike.Wrap(new ArraySegment(bytes)); for (int i = 0; i < src.Length; ++i) { dst[i] = (ushort)src[i]; } var accessor = new BufferAccessor(new ArraySegment(bytes), AccessorValueType.UNSIGNED_SHORT, AccessorVectorType.SCALAR, count); return accessor.AddAccessorTo(storage, 0, option.sparse, null, 0, count); } else { return x.AddAccessorTo(storage, 0, option.sparse, null, offset, count); } } else { return x.AddAccessorTo(storage, 0, option.sparse, null, offset, count); } } /// /// https://github.com/vrm-c/UniVRM/issues/800 /// /// SubMesh 単位に分割する。 /// SubMesh を Gltf の Primitive に対応させる。 /// /// /// /// /// /// static IEnumerable ExportMeshDivided(this VrmLib.Mesh mesh, List materials, ExportingGltfData writer, ExportArgs option) { var usedIndices = new List(); var meshIndices = SpanLike.CopyFrom(mesh.IndexBuffer.GetAsIntArray()); var positions = mesh.VertexBuffer.Positions.GetSpan().ToArray(); var normals = mesh.VertexBuffer.Normals.GetSpan().ToArray(); var uv = mesh.VertexBuffer.TexCoords.GetSpan().ToArray(); var hasSkin = mesh.VertexBuffer.Weights != null; var weights = mesh.VertexBuffer.Weights?.GetSpan().ToArray(); var joints = mesh.VertexBuffer.Joints?.GetSpan().ToArray(); Func getJointIndex = default; if (hasSkin) { getJointIndex = i => { return i; }; } foreach (var submesh in mesh.Submeshes) { var indices = meshIndices.Slice(submesh.Offset, submesh.DrawCount).ToArray(); var hash = new HashSet(indices); // mesh // index の順に attributes を蓄える var buffer = new MeshExportUtil.VertexBuffer(indices.Length, getJointIndex); usedIndices.Clear(); for (int k = 0; k < positions.Length; ++k) { if (hash.Contains(k)) { // indices から参照される頂点だけを蓄える usedIndices.Add(k); buffer.PushVertex(k, positions[k], normals[k], uv[k]); if (getJointIndex != null) { var j = joints[k]; var w = weights[k]; var boneWeight = new UnityEngine.BoneWeight { boneIndex0 = j.Joint0, boneIndex1 = j.Joint1, boneIndex2 = j.Joint2, boneIndex3 = j.Joint3, weight0 = w.x, weight1 = w.y, weight2 = w.z, weight3 = w.w, }; buffer.PushBoneWeight(boneWeight); } } } var materialIndex = submesh.Material; var gltfPrimitive = buffer.ToGltfPrimitive(writer, materialIndex, indices); // blendShape for (int j = 0; j < mesh.MorphTargets.Count; ++j) { var blendShape = new MeshExportUtil.BlendShapeBuffer(usedIndices.Count); // index の順に attributes を蓄える var morph = mesh.MorphTargets[j]; var blendShapePositions = morph.VertexBuffer.Positions.GetSpan(); SpanLike? blendShapeNormals = default; if (morph.VertexBuffer.Normals != null) { blendShapeNormals = morph.VertexBuffer.Normals.GetSpan(); } int l = 0; foreach (var k in usedIndices) { blendShape.Set(l++, blendShapePositions[k], blendShapeNormals.HasValue ? blendShapeNormals.Value[k] : UnityEngine.Vector3.zero ); } gltfPrimitive.targets.Add(blendShape.ToGltf(writer, !option.removeMorphNormal, option.sparse)); } yield return gltfPrimitive; } } /// /// ModelExporter.Export で作られた Model.MeshGroups[*] を GLTF 化する /// /// /// /// /// /// public static glTFMesh ExportMeshGroup(this MeshGroup src, List materials, ExportingGltfData writer, ExportArgs option) { var gltfMesh = new glTFMesh { name = src.Name }; if (src.Meshes.Count != 1) { throw new NotImplementedException(); } foreach (var prim in src.Meshes[0].ExportMeshDivided(materials, writer, option)) { gltfMesh.primitives.Add(prim); } var targetNames = src.Meshes[0].MorphTargets.Select(x => x.Name).ToArray(); gltf_mesh_extras_targetNames.Serialize(gltfMesh, targetNames); return gltfMesh; } } }