public ImageId SaveImageHDR(Texture2D texture, EHDRTextureType type, int maxSize = -1, string path = null)
        {
            if (_texture2d2ImageID.ContainsKey(texture))
            {
                return _texture2d2ImageID[texture];
            }

            string format = ".png";
            string[] pathes;
            if (path == null)
            {
                pathes = GetTextureOutPath(texture, format);
            }
            else
            {
                pathes = ExporterUtils.GetAssetOutPath(path, format);
            }
            var exportPath = pathes[0];
            var pathInGlTF = pathes[1];

            var newtex = GLTFTextureUtils.HDR2RGBD(texture);

            if (maxSize > 0 && (newtex.width > maxSize || newtex.height > maxSize))
            {
                TextureScale.Bilinear(newtex, maxSize, maxSize);
            }

            byte[] content = newtex.EncodeToPNG();

            return GenerateImage(content, exportPath, pathInGlTF);
        }
        public static string[] GetAssetOutPath(string assetPath, string format = null)
        {
            string pathInArchive = ExporterUtils.CleanPath(Path.GetDirectoryName(assetPath).Replace("Assets/Resources/", "").Replace("Assets/", ""));
            string exportDir     = Path.Combine(ExporterSettings.Export.folder, pathInArchive);

            if (!Directory.Exists(exportDir))
            {
                Directory.CreateDirectory(exportDir);
            }

            string outputFilename = "";

            if (format == null)
            {
                outputFilename = Path.GetFileName(assetPath);
            }
            else
            {
                outputFilename = Path.GetFileNameWithoutExtension(assetPath) + format;
            }

            outputFilename = ExporterUtils.CleanPath(outputFilename);

            string exportPath     = exportDir + "/" + outputFilename;
            string pathInGltfFile = pathInArchive + "/" + outputFilename;

            return(new string[] { exportPath, pathInGltfFile });
        }
        private void SaveIndices(UnityEngine.Mesh mesh, MeshPrimitive primitive, int i, ref EntryBufferView bufferView)
        {
            primitive.Mode = DrawMode.Triangles;

            if (_mesh2indices.ContainsKey(mesh) && _mesh2indices[mesh].ContainsKey(i))
            {
                primitive.Indices = _mesh2indices[mesh][i];
                return;
            }

            if (bufferView == null)
            {
                bufferView = CreateStreamBufferView(mesh.name + "-indices");
            }

            primitive.Indices = AccessorToId(
                ExporterUtils.PackToBuffer(bufferView.streamBuffer, mesh.GetTriangles(i), GLTFComponentType.UnsignedShort, (int[] data, int index) => {
                    var offset = index % 3;

                    return data[offset == 0 ? index : offset == 1 ? index + 1 : index - 1];
                }),
                bufferView
            );
            primitive.Indices.Value.Name += "-" + i;

            if (!_mesh2indices.ContainsKey(mesh))
            {
                _mesh2indices.Add(mesh, new Dictionary<int, AccessorId>());
            }

            _mesh2indices[mesh].Add(i, primitive.Indices);
        }
        public override void Serialize(ExporterEntry entry, Dictionary <string, Extension> extensions, UnityEngine.Object component = null, object options = null)
        {
            var material  = component as SeinCustomMaterial;
            var extension = new Sein_customMaterialExtension();

            if (material.matScriptPath != null)
            {
                if (entry.root.Extensions == null)
                {
                    entry.root.Extensions = new Dictionary <string, Extension>();
                }

                Sein_customMaterialExtension globalExtension;
                if (!entry.root.Extensions.ContainsKey(ExtensionName))
                {
                    globalExtension = new Sein_customMaterialExtension {
                        matScripts = new List <string>()
                    };
                    AddExtension(entry.root.Extensions, globalExtension);
                }
                else
                {
                    globalExtension = (Sein_customMaterialExtension)entry.root.Extensions[ExtensionName];
                }

                var pathes     = ExporterUtils.GetAssetOutPath(material.matScriptPath);
                var exportPath = pathes[0];
                var pathInGlTF = pathes[1];

                if (!File.Exists(exportPath))
                {
                    FileUtil.CopyFileOrDirectory(material.matScriptPath, exportPath);
                }

                globalExtension.matScripts.Add(pathInGlTF);
            }

            extension.className           = material.className;
            extension.cloneForInst        = material.cloneForInst;
            extension.renderOrder         = material.renderOrder;
            extension.transparent         = material.transparent;
            extension.customOptions       = material.customOptions;
            extension.uniformsColor       = material.uniformsColor;
            extension.uniformsTexture     = material.uniformsTexture;
            extension.uniformsCubeTexture = material.uniformsCubeTexture;
            extension.uniformsFloat       = material.uniformsFloat;
            extension.uniformsFloatVec2   = material.uniformsFloatVec2;
            extension.uniformsFloatVec3   = material.uniformsFloatVec3;
            extension.uniformsFloatVec4   = material.uniformsFloatVec4;
            extension.uniformsFloatMat2   = material.uniformsFloatMat2;
            extension.uniformsFloatMat3   = material.uniformsFloatMat3;
            extension.uniformsFloatMat4   = material.uniformsFloatMat4;
            extension.uniformsInt         = material.uniformsInt;
            extension.uniformsIntVec2     = material.uniformsIntVec2;
            extension.uniformsIntVec3     = material.uniformsIntVec3;
            extension.uniformsIntVec4     = material.uniformsIntVec4;

            AddExtension(extensions, extension);
        }
Пример #5
0
        public void Export()
        {
            List <ExporterEntry> entries = new List <ExporterEntry>();

            Transform[] transforms = Selection.GetTransforms(SelectionMode.TopLevel);

            if (transforms.Length == 0)
            {
                var scene = SceneManager.GetActiveScene();
                List <GameObject> rootObjects = new List <GameObject>();
                scene.GetRootGameObjects(rootObjects);
                transforms = new Transform[rootObjects.Count];
                for (int i = 0; i < rootObjects.Count; i += 1)
                {
                    transforms[i] = rootObjects[i].transform;
                }
            }

            foreach (Transform tr in transforms)
            {
                var go = tr.gameObject;
                if (go.GetComponent <SeinNode>() == null)
                {
                    go.AddComponent <SeinNode>();
                }
            }

            if (!ExporterSettings.Export.splitChunks)
            {
                List <Transform> allTrans = new List <Transform>();
                foreach (var rootTrans in transforms)
                {
                    allTrans.AddRange(rootTrans.GetComponentsInChildren <Transform>());
                }

                entries.Add(new ExporterEntry
                {
                    path       = ExporterSettings.Export.GetExportPath(),
                    name       = ExporterSettings.Export.name,
                    transforms = allTrans.ToArray()
                });
            }
            else
            {
                foreach (Transform tr in transforms)
                {
                    entries.Add(new ExporterEntry
                    {
                        path       = ExporterSettings.Export.GetExportPath(tr.name),
                        name       = ExporterUtils.CleanPath(tr.name),
                        transforms = tr.GetComponentsInChildren <Transform>()
                    });
                }
            }

            _export.Export(entries);
        }
        public SkinId SaveSkin(Transform tr)
        {
            if (root.Skins == null)
            {
                root.Skins = new List<GLTF.Schema.Skin>();
            }

            var skinMesh = tr.GetComponent<SkinnedMeshRenderer>();

            if (_skin2ID.ContainsKey(skinMesh))
            {
                return _skin2ID[skinMesh];
            }

            var node = tr2node[tr];
            var skin = new Skin();
            skin.Name = "skeleton-" + skinMesh.rootBone.name + "-" + tr.name;
            skin.Skeleton = new NodeId { Id = root.Nodes.IndexOf(node), Root = root };
            skin.Joints = new List<NodeId>();

            foreach (var bone in skinMesh.bones)
            {
                if (!tr2node.ContainsKey(bone))
                {
                    throw new Exception("You are expoting a skinned mesh '" + node.Name + "', but not select bones!");
                }

                skin.Joints.Add(new NodeId { Id = root.Nodes.IndexOf(tr2node[bone]) });
            }

            // Create invBindMatrices accessor
            var bufferView = CreateStreamBufferView("invBind-" + skinMesh.rootBone.name + "-" + tr.name);

            Matrix4x4[] invBindMatrices = new Matrix4x4[skin.Joints.Count];
            for (int i = 0; i < skinMesh.bones.Length; ++i)
            {
                // Generates inverseWorldMatrix in right-handed coordinate system
                Matrix4x4 invBind = skinMesh.sharedMesh.bindposes[i];
                invBindMatrices[i] = Utils.ConvertMat4LeftToRightHandedness(ref invBind);
            }

            skin.InverseBindMatrices = AccessorToId(
                ExporterUtils.PackToBuffer(bufferView.streamBuffer, invBindMatrices, GLTFComponentType.Float),
                bufferView
            );

            root.Skins.Add(skin);

            var id = new SkinId { Id = root.Skins.Count - 1, Root = root };
            _skin2ID.Add(skinMesh, id);

            return id;
        }
        public ImageId SaveImage(Texture2D texture, bool hasTransparency, string path = null)
        {
            if (_texture2d2ImageID.ContainsKey(texture))
            {
                return _texture2d2ImageID[texture];
            }

            string format = ".png";
            if (!hasTransparency && ExporterSettings.NormalTexture.opaqueType == ENormalTextureType.JPG)
            {
                format = ".jpg";
            }

            string[] pathes;

            if (path == null)
            {
                pathes = GetTextureOutPath(texture, format);
            }
            else
            {
                pathes = ExporterUtils.GetAssetOutPath(path, format);
            }

            var exportPath = pathes[0];
            var pathInGlTF = pathes[1];

            var tex = TextureFlipY(
                texture,
                null,
                newtex => {
                    var maxSize = ExporterSettings.NormalTexture.maxSize;
                    if (newtex.width > maxSize || newtex.height > maxSize)
                    {
                        TextureScale.Bilinear(newtex, maxSize, maxSize);
                    }
                }
            );

            byte[] content = { };

            if (format == ".png")
            {
                content = tex.EncodeToPNG();
            }
            else
            {
                content = tex.EncodeToJPG(ExporterSettings.NormalTexture.jpgQulity);
            }

            return GenerateImage(content, exportPath, pathInGlTF);
        }
		public MaterialId SaveComponentMaterial(SeinCustomMaterial material)
        {
            if (root.Materials == null)
            {
                root.Materials = new List<GLTF.Schema.Material>();
            }

            var mat = ExporterUtils.ConvertMaterial(material, this);
            root.Materials.Add(mat);

            var id = new MaterialId { Id = root.Materials.Count - 1, Root = root };
            return id;
        }
        private string SaveAudio(AudioClip clip)
        {
            string assetPath  = AssetDatabase.GetAssetPath(clip);
            var    pathes     = ExporterUtils.GetAssetOutPath(clip);
            var    exportPath = pathes[0];
            var    pathInGlTF = pathes[1];

            if (File.Exists(exportPath))
            {
                return(pathInGlTF);
            }

            FileUtil.CopyFileOrDirectory(assetPath, exportPath);

            return(pathInGlTF);
        }
        private Texture2D TextureFlipY(Texture2D texture, Func<Color, Color> convertColor = null, Action<Texture2D> processTexture = null)
        {
            Texture2D newTex;
            TextureImporter im = AssetImporter.GetAtPath(AssetDatabase.GetAssetPath(texture)) as TextureImporter;

            if (convertColor == null && (im == null || im.textureType != TextureImporterType.NormalMap))
            {
                // use gpu to speed up
                newTex = GLTFTextureUtils.flipTexture(texture);
            }
            else
            {
                int height = texture.height;
                int width = texture.width;
                Color[] newTextureColors = new Color[height * width];

                ExporterUtils.DoActionForTexture(ref texture, tex =>
                {
                    Color[] textureColors = tex.GetPixels();
                    for (int i = 0; i < height; ++i)
                    {
                        for (int j = 0; j < width; ++j)
                        {
                            var c = textureColors[(height - i - 1) * width + j];

                            newTextureColors[i * width + j] = convertColor != null ? convertColor(c) : c;
                        }
                    }
                }
                );

                newTex = new Texture2D(width, height);
                newTex.SetPixels(newTextureColors);
                newTex.Apply();
            }

            if (processTexture != null)
            {
                processTexture(newTex);
            }

            return newTex;
        }
        public MaterialId SaveNormalMaterial(UnityEngine.Material material)
        {
            if (root.Materials == null)
            {
                root.Materials = new List<GLTF.Schema.Material>();
            }

            var mid = material.GetInstanceID();
            if (_material2ID.ContainsKey(mid))
            {
                return _material2ID[mid];
            }

			var mat = ExporterUtils.ConvertMaterial(material, this);
			root.Materials.Add(mat);

			var id = new MaterialId { Id = root.Materials.Count - 1, Root = root };
            _material2ID.Add(mid, id);

			return id;
		}
 private void Clear()
 {
     ExporterUtils.FinishExport();
     ExtensionManager.FinishExport();
     Resources.UnloadUnusedAssets();
 }
        private List<string> BakeAnimationClip(GLTF.Schema.Animation anim, Transform tr, AnimationClip clip)
        {
            var needGenerate = !_animClip2Accessors.ContainsKey(clip);
            Dictionary<string, Dictionary<GLTFAnimationChannelPath, AnimationCurve[]>> curves = null;
            Dictionary<string, bool> rotationIsEuler = null;

            if (needGenerate)
            {
                curves = new Dictionary<string, Dictionary<GLTFAnimationChannelPath, AnimationCurve[]>>();
                rotationIsEuler = new Dictionary<string, bool>();
            }

            List<string> targets = new List<string>();

            foreach (var binding in AnimationUtility.GetCurveBindings(clip))
            {
                var path = binding.path;
                var curve = AnimationUtility.GetEditorCurve(clip, binding);

                if (!curves.ContainsKey(path))
                {
                    targets.Add(path);
                    if (needGenerate)
                    {
                        curves.Add(path, new Dictionary<GLTFAnimationChannelPath, AnimationCurve[]>());
                        rotationIsEuler.Add(path, false);
                    }
                }

                if (!needGenerate)
                {
                    continue;
                }

                var current = curves[path];
                if (binding.propertyName.Contains("m_LocalPosition"))
                {
                    if (!current.ContainsKey(GLTFAnimationChannelPath.translation))
                    {
                        current.Add(GLTFAnimationChannelPath.translation, new AnimationCurve[3]);
                    }
                    if (binding.propertyName.Contains(".x"))
                        current[GLTFAnimationChannelPath.translation][0] = curve;
                    else if (binding.propertyName.Contains(".y"))
                        current[GLTFAnimationChannelPath.translation][1] = curve;
                    else if (binding.propertyName.Contains(".z"))
                        current[GLTFAnimationChannelPath.translation][2] = curve;
                }
                else if (binding.propertyName.Contains("m_LocalScale"))
                {
                    if (!current.ContainsKey(GLTFAnimationChannelPath.scale))
                    {
                        current.Add(GLTFAnimationChannelPath.scale, new AnimationCurve[3]);
                    }
                    if (binding.propertyName.Contains(".x"))
                        current[GLTFAnimationChannelPath.scale][0] = curve;
                    else if (binding.propertyName.Contains(".y"))
                        current[GLTFAnimationChannelPath.scale][1] = curve;
                    else if (binding.propertyName.Contains(".z"))
                        current[GLTFAnimationChannelPath.scale][2] = curve;
                }
                else if (binding.propertyName.ToLower().Contains("localrotation"))
                {
                    if (!current.ContainsKey(GLTFAnimationChannelPath.rotation))
                    {
                        current.Add(GLTFAnimationChannelPath.rotation, new AnimationCurve[4]);
                    }
                    if (binding.propertyName.Contains(".x"))
                        current[GLTFAnimationChannelPath.rotation][0] = curve;
                    else if (binding.propertyName.Contains(".y"))
                        current[GLTFAnimationChannelPath.rotation][1] = curve;
                    else if (binding.propertyName.Contains(".z"))
                        current[GLTFAnimationChannelPath.rotation][2] = curve;
                    else if (binding.propertyName.Contains(".w"))
                        current[GLTFAnimationChannelPath.rotation][3] = curve;
                }
                // Takes into account 'localEuler', 'localEulerAnglesBaked' and 'localEulerAnglesRaw'
                else if (binding.propertyName.ToLower().Contains("localeuler"))
                {
                    if (!current.ContainsKey(GLTFAnimationChannelPath.rotation))
                    {
                        current.Add(GLTFAnimationChannelPath.rotation, new AnimationCurve[3]);
                        rotationIsEuler[path] = true;
                    }
                    if (binding.propertyName.Contains(".x"))
                        current[GLTFAnimationChannelPath.rotation][0] = curve;
                    else if (binding.propertyName.Contains(".y"))
                        current[GLTFAnimationChannelPath.rotation][1] = curve;
                    else if (binding.propertyName.Contains(".z"))
                        current[GLTFAnimationChannelPath.rotation][2] = curve;
                }
                //todo: weights
            }

            if (!needGenerate)
            {
                return targets;
            }

            
            var bufferView = CreateStreamBufferView("animation-" + anim.Name);
            int nbSamples = (int)(clip.length * 30);
            float deltaTime = clip.length / nbSamples;
            var accessors = new List<Dictionary<GLTFAnimationChannelPath, AccessorId>>();
            _animClip2Accessors.Add(clip, accessors);

            foreach (var path in curves.Keys)
            {
                var accessor = new Dictionary<GLTFAnimationChannelPath, AccessorId>();
                accessors.Add(accessor);
                float[] times = new float[nbSamples];
                Vector3[] translations = null;
                Vector3[] scales = null;
                Vector4[] rotations = null;
                foreach (var curve in curves[path])
                {
                    if (curve.Key == GLTFAnimationChannelPath.translation)
                    {
                        translations = new Vector3[nbSamples];
                    }
                    else if (curve.Key == GLTFAnimationChannelPath.scale)
                    {
                        scales = new Vector3[nbSamples];
                    }
                    else if (curve.Key == GLTFAnimationChannelPath.rotation)
                    {
                        rotations = new Vector4[nbSamples];
                    }
                }

                for (int i = 0; i < nbSamples; i += 1)
                {
                    var currentTime = i * deltaTime;
                    times[i] = currentTime;

                    if (translations != null)
                    {
                        var curve = curves[path][GLTFAnimationChannelPath.translation];
                        translations[i] = new Vector3(curve[0].Evaluate(currentTime), curve[1].Evaluate(currentTime), curve[2].Evaluate(currentTime));
                    }

                    if (scales != null)
                    {
                        var curve = curves[path][GLTFAnimationChannelPath.scale];
                        scales[i] = new Vector3(curve[0].Evaluate(currentTime), curve[1].Evaluate(currentTime), curve[2].Evaluate(currentTime));
                    }

                    if (rotations != null)
                    {
                        var curve = curves[path][GLTFAnimationChannelPath.rotation];
                        if (rotationIsEuler[path])
                        {
                            var q = Quaternion.Euler(curve[0].Evaluate(currentTime), curve[1].Evaluate(currentTime), curve[2].Evaluate(currentTime));
                            rotations[i] = new Vector4(q.x, q.y, q.z, q.w);
                        } else
                        {
                            rotations[i] = new Vector4(curve[0].Evaluate(currentTime), curve[1].Evaluate(currentTime), curve[2].Evaluate(currentTime), curve[3].Evaluate(currentTime));
                        }
                    }
                }

                if (!_animClip2TimeAccessor.ContainsKey(clip))
                {
                    var timeView = ExporterUtils.PackToBuffer(bufferView.streamBuffer, times, GLTFComponentType.Float);
                    _animClip2TimeAccessor.Add(clip, AccessorToId(timeView, bufferView));
                    timeView.Name += "-times";
                }

                AccessorId id = null;
                if (translations != null)
                {
                    id = AccessorToId(ExporterUtils.PackToBuffer(bufferView.streamBuffer, translations, GLTFComponentType.Float, (Vector3[] data, int i) => { return Utils.ConvertVector3LeftToRightHandedness(ref data[i]); }), bufferView);
                    accessor.Add(GLTFAnimationChannelPath.translation, id);
                    id.Value.Name += "-" + path + "-" + "translation";
                }

                if (scales != null)
                {
                    id = AccessorToId(ExporterUtils.PackToBuffer(bufferView.streamBuffer, scales, GLTFComponentType.Float), bufferView);
                    accessor.Add(GLTFAnimationChannelPath.scale, id);
                    id.Value.Name += "-" + path + "-" + "scales";
                }

                if (rotations != null)
                {
                    id = AccessorToId(ExporterUtils.PackToBuffer(bufferView.streamBuffer, rotations, GLTFComponentType.Float, (Vector4[] data, int i) => { return Utils.ConvertVector4LeftToRightHandedness(ref data[i]); }), bufferView);
                    accessor.Add(GLTFAnimationChannelPath.rotation, id);
                    id.Value.Name += "-" + path + "-" + "rotations";
                }
            }

            return targets;
        }
        public override void Serialize(ExporterEntry entry, Dictionary <string, Extension> extensions, UnityEngine.Object component = null, object options = null)
        {
            if (entry.root.Extensions == null)
            {
                entry.root.Extensions = new Dictionary <string, Extension>();
            }

            Sein_spriteExtension globalExtension;

            if (!entry.root.Extensions.ContainsKey(ExtensionName))
            {
                globalExtension = new Sein_spriteExtension {
                    isGlobal = true
                };
                entry.root.Extensions.Add(ExtensionName, globalExtension);
            }
            else
            {
                globalExtension = (Sein_spriteExtension)entry.root.Extensions[ExtensionName];
            }

            var sprite    = component as SeinSprite;
            var extension = new Sein_spriteExtension {
                isGlobal = false
            };
            var sp             = sprite;
            var customMaterial = sprite.GetComponent <SeinCustomMaterial>();
            var cacheId        = $"w{sp.width}-h{sp.height}-at{sp.atlas.GetInstanceID()}-fn{sp.frameName}-bb{sp.isBillboard}-ft{sp.frustumTest}";

            if (customMaterial != null)
            {
                cacheId += $"mat{customMaterial.GetInstanceID()}";
            }
            else
            {
                cacheId += $"mat{sprite.material.GetInstanceID()}";
            }
            if (!_CAHCE.ContainsKey(entry))
            {
                _CAHCE.Add(entry, new Dictionary <string, int>());
            }

            if (_CAHCE[entry].ContainsKey(cacheId))
            {
                extension.index = _CAHCE[entry][cacheId];
                AddExtension(extensions, extension);
                return;
            }

            // process atlases at first
            ExtensionManager.Serialize(ExtensionManager.GetExtensionName(typeof(Sein_atlasExtensionFactory)), entry, entry.root.Extensions, sprite.atlas);
            var s = new Sein_spriteExtension.Sprite();

            s.width       = sprite.width;
            s.height      = sprite.height;
            s.isBillboard = sprite.isBillboard;
            s.frustumTest = sprite.frustumTest;
            s.atlasId     = Sein_atlasExtensionFactory.GetAtlasIndex(entry, sprite.atlas);
            s.frameName   = sprite.frameName;

            GLTF.Schema.Material gltfMat = null;
            if (customMaterial != null)
            {
                gltfMat = ExporterUtils.ConvertMaterial(customMaterial, entry);
            }
            else if (sprite.material.shader.name != "Sein/Sprite" && sprite.material.shader.name.Contains("Sein/"))
            {
                gltfMat = ExporterUtils.ConvertSeinCustomMaterial(sprite.material, entry);
            }

            if (gltfMat != null)
            {
                var root = entry.root;
                if (root.Materials == null)
                {
                    root.Materials = new List <GLTF.Schema.Material>();
                }

                root.Materials.Add(gltfMat);
                var id = new MaterialId {
                    Id = root.Materials.Count - 1, Root = root
                };
                s.materialId = id;
            }

            globalExtension.sprites.Add(s);

            var index = globalExtension.sprites.Count - 1;

            _CAHCE[entry].Add(cacheId, index);
            extension.index = index;

            AddExtension(extensions, extension);
        }
 private string[] GetTextureOutPath(Texture2D texture, string format)
 {
     return ExporterUtils.GetAssetOutPath(texture, format);
 }
        private Dictionary<string, AccessorId> GenerateAttributes(UnityEngine.Mesh mesh)
        {
            if (_mesh2attrs.ContainsKey(mesh))
            {
                return _mesh2attrs[mesh];
            }

            var attrs= new Dictionary<string, AccessorId>();

            int stride = GetBufferLength(mesh);
            var bufferView = CreateByteBufferView(mesh.name + "-primitives", stride * mesh.vertexCount, stride);

            int offset = 0;
            attrs.Add("POSITION", PackAttrToBuffer(bufferView, mesh.vertices, offset, (Vector3[] data, int i) => { return Utils.ConvertVector3LeftToRightHandedness(ref data[i]); }));
            offset += 3 * 4;

            if (mesh.normals.Length > 0)
            {
                attrs.Add("NORMAL", PackAttrToBuffer(bufferView, mesh.normals, offset, (Vector3[] data, int i) => { return Utils.ConvertVector3LeftToRightHandedness(ref data[i]); }));
                offset += 3 * 4;
            }

            if (mesh.colors.Length > 0)
            {
                attrs.Add("COLOR_0", PackAttrToBuffer(bufferView, mesh.colors, offset));
                offset += 4 * 4;
            }

            if (mesh.uv.Length > 0)
            {
                attrs.Add("TEXCOORD_0", PackAttrToBuffer(bufferView, mesh.uv, offset));
                offset += 2 * 4;
            }

            if (mesh.uv2.Length > 0)
            {
                attrs.Add("TEXCOORD_1", PackAttrToBuffer(bufferView, mesh.uv2, offset));
                offset += 2 * 4;
            }

            if (mesh.tangents.Length > 0)
            {
                attrs.Add("TANGENT", PackAttrToBuffer(bufferView, mesh.tangents, offset, (Vector4[] data, int i) => { return Utils.ConvertVector4LeftToRightHandedness(ref data[i]); }));
                offset += 4 * 4;
            }

            if (mesh.boneWeights.Length > 0)
            {
                attrs.Add("JOINTS_0", PackAttrToBufferShort(bufferView, mesh.boneWeights, offset));
                offset += 2 * 4;
                attrs.Add("WEIGHTS_0", PackAttrToBuffer(bufferView, ExporterUtils.BoneWeightToWeightVec4(mesh.boneWeights), offset));
                offset += 4 * 4;
            }

            foreach(var attr in attrs)
            {
                attr.Value.Value.Name += "-" + attr.Key;
            }

            _mesh2attrs.Add(mesh, attrs);

            return attrs;
        }
        private AccessorId PackAttrToBufferShort<DataType>(EntryBufferView bufferView, DataType[] data, int offset, Func<DataType[], int, DataType> getValueByIndex = null)
        {
            var accessor = ExporterUtils.PackToBuffer(bufferView.byteBuffer, data, GLTFComponentType.UnsignedShort, offset, bufferView.view.ByteStride, getValueByIndex);

            return AccessorToId(accessor, bufferView);
        }