using System; using System.Collections.Generic; using System.IO; using GLTF.Schema; using UnityEngine; using UnityGLTF.Extensions; using UnityEditor; using System.Text.RegularExpressions; namespace UnityGLTF { public class GLTFEditorExporter { private Transform[] _rootTransforms; private GLTFRoot _root; private BufferId _bufferId; private GLTF.Schema.Buffer _buffer; private BinaryWriter _bufferWriter; private List _images; private List _textures; private List _materials; private Dictionary _exportedTransforms; private List _animatedNodes; private List _skinnedNodes; private Dictionary _bakedMeshes; // Temporary setting to avoid validation issue 'not multiple of 4' in bufferView.offset private bool _forceIndicesUint = true; private bool _exportAnimation = true; private bool _bakeSkinnedMeshes = false; private Dictionary _exportedFiles; protected struct PrimKey { public UnityEngine.Mesh Mesh; public UnityEngine.Material Material; } private readonly Dictionary _primOwner = new Dictionary(); private readonly Dictionary _meshToPrims = new Dictionary(); private GLTFTextureUtilsCache _textureCache; public bool ExportNames = true; public enum MaterialDoubleSidedExport { Default, FrontSide, DoubleSide } public static MaterialDoubleSidedExport materialExportOption = MaterialDoubleSidedExport.Default; // Progress public enum EXPORT_STEP { NODES, ANIMATIONS, SKINNING, IMAGES } public delegate void ProgressCallback(EXPORT_STEP step, float current, float total); public delegate void FinishCallback(); ProgressCallback _progressCallback; FinishCallback _finishCallback; /// /// Create a GLTFExporter that exports out a transform /// public GLTFEditorExporter() { initializeStructures(); } public void setProgressCallback(ProgressCallback cb) { _progressCallback = cb; } private void updateProgress(EXPORT_STEP step, float current, float total) { if(_progressCallback != null) { _progressCallback(step, current, total); } } public void setExportFinishCallback(FinishCallback cb) { _finishCallback = cb; } public void triggerFinishCallback() { if (_finishCallback != null) _finishCallback(); } /// /// Create a GLTFExporter that exports out a transform /// public GLTFEditorExporter(string generator) { initializeStructures(generator); } /// /// Create a GLTFExporter that exports out a transform /// /// Root transform of object to export public GLTFEditorExporter(Transform[] rootTransforms, string generator = "") { initializeStructures(generator); _rootTransforms = rootTransforms; } public void setTransforms(Transform[] rootTransforms) { _rootTransforms = rootTransforms; } public void enableAnimation(bool enable) { _exportAnimation = enable; } private void initializeStructures(string generator = "") { _exportedTransforms = new Dictionary(); _animatedNodes = new List(); _skinnedNodes = new List(); _exportedFiles = new Dictionary(); _textureCache = new GLTFTextureUtilsCache(); _bakedMeshes = new Dictionary(); _root = new GLTFRoot { Accessors = new List(), Animations = new List(), Asset = new Asset { Version = "2.0", Generator = generator.Length > 0 ? generator : "Plattar UnityGLTF (" + Application.unityVersion + ")" }, Buffers = new List(), BufferViews = new List(), ExtensionsUsed = new List(), ExtensionsRequired = new List(), Images = new List(), Materials = new List(), Meshes = new List(), Nodes = new List(), Samplers = new List(), Scenes = new List(), Skins = new List(), Textures = new List(), }; _images = new List(); _materials = new List(); _textures = new List(); _buffer = new GLTF.Schema.Buffer(); _bufferId = new BufferId { Id = _root.Buffers.Count, Root = _root }; _root.Buffers.Add(_buffer); } /// /// Gets the root object of the exported GLTF /// /// Root parsed GLTF Json public GLTFRoot GetRoot() { return _root; } public Dictionary getExportedFilesList() { return _exportedFiles; } public void clear() { initializeStructures(); } /// /// Specifies the path and filename for the GLTF Json and binary /// /// File path for saving the GLTF and binary files /// The name of the GLTF file public void SaveGLTFandBin(string path, string fileName) { string binPath = Path.Combine(path, fileName + ".bin"); var binFile = File.Create(binPath); _bufferWriter = new BinaryWriter(binFile); _root.Scene = ExportScene(fileName, _rootTransforms); if (_exportAnimation) { exportAnimation(); // Export skins for (int i = 0; i < _skinnedNodes.Count; ++i) { Transform t = _skinnedNodes[i]; exportSkinFromNode(t); updateProgress(EXPORT_STEP.SKINNING, i, _skinnedNodes.Count); } } _buffer.Uri = fileName + ".bin"; _buffer.ByteLength = (int)_bufferWriter.BaseStream.Length; _exportedFiles.Add(binPath, ""); string gltfPath = Path.Combine(path, fileName + ".gltf"); var gltfFile = File.CreateText(gltfPath); _root.Serialize(gltfFile); #if WINDOWS_UWP gltfFile.Dispose(); binFile.Dispose(); #else gltfFile.Close(); binFile.Close(); #endif _exportedFiles.Add(gltfPath, ""); bool backup = GL.sRGBWrite; GL.sRGBWrite = true; foreach (var image in _images) { //Should filter regarding channel that use it string outputPath = Path.Combine(path, GLTFUtils.buildImageName(image)); // Png by default, but will be changed in write function string finalOutputPath = GLTFTextureUtils.writeTextureOnDisk(_textureCache.flipTexture(image), outputPath, true); try { _exportedFiles.Add(finalOutputPath, ""); } catch (Exception e) { throw new Exception("The File " + outputPath + " has alredy been used. Ensure your file names are unique."); } updateProgress(EXPORT_STEP.IMAGES, _images.IndexOf(image), _images.Count); } GL.sRGBWrite = backup; triggerFinishCallback(); } private void exportAnimation() { for (int i = 0; i < _animatedNodes.Count; ++i) { Transform t = _animatedNodes[i]; exportAnimationFromNode(ref t); updateProgress(EXPORT_STEP.ANIMATIONS, i, _animatedNodes.Count); } } private UnityEngine.Mesh getMesh(GameObject gameObject) { if(gameObject.GetComponent()) { return gameObject.GetComponent().sharedMesh; } SkinnedMeshRenderer skinMesh = gameObject.GetComponent(); if (skinMesh) { if(!_exportAnimation && _bakeSkinnedMeshes) { if(!_bakedMeshes.ContainsKey(skinMesh)) { UnityEngine.Mesh bakedMesh = new UnityEngine.Mesh(); skinMesh.BakeMesh(bakedMesh); _bakedMeshes.Add(skinMesh, bakedMesh); } return _bakedMeshes[skinMesh]; } return gameObject.GetComponent().sharedMesh; } return null; } private UnityEngine.Material getMaterial(GameObject gameObject) { if (gameObject.GetComponent()) { return gameObject.GetComponent().sharedMaterial; } if (gameObject.GetComponent()) { return gameObject.GetComponent().sharedMaterial; } return null; } private UnityEngine.Material[] getMaterials(GameObject gameObject) { if (gameObject.GetComponent()) { return gameObject.GetComponent().sharedMaterials; } if (gameObject.GetComponent()) { return gameObject.GetComponent().sharedMaterials; } return null; } private int countNodes(Transform[] trs) { int ct = 0; foreach (Transform tr in trs) { countRecursive(tr, ref ct); } return ct; } private void countRecursive(Transform tr, ref int count) { count += 1; if (tr.childCount > 0) { for (int i = 0; i < tr.childCount; ++i) { countRecursive(tr.GetChild(i), ref count); } } } private SceneId ExportScene(string name, Transform[] rootObjTransforms) { var scene = new Scene(); if (ExportNames) { scene.Name = name; } int nodeCount = countNodes(rootObjTransforms); scene.Nodes = new List(rootObjTransforms.Length); foreach (var transform in rootObjTransforms) { scene.Nodes.Add(ExportNode(transform, nodeCount)); } _root.Scenes.Add(scene); return new SceneId { Id = _root.Scenes.Count - 1, Root = _root }; } private NodeId ExportNode(Transform nodeTransform, int nodeCount) { var node = new Node(); if (ExportNames) { node.Name = nodeTransform.name; } if(nodeTransform.GetComponent() || nodeTransform.GetComponent()) { _animatedNodes.Add(nodeTransform); } if(nodeTransform.GetComponent()) { _skinnedNodes.Add(nodeTransform); } // If object is on top of the selection, use global transform bool useLocal = !Array.Exists(_rootTransforms, element => element == nodeTransform); node.SetUnityTransform(nodeTransform, useLocal); var id = new NodeId { Id = _root.Nodes.Count, Root = _root }; // Register nodes for animation parsing (could be disabled is animation is disables) _exportedTransforms.Add(nodeTransform.GetInstanceID(), _root.Nodes.Count); _root.Nodes.Add(node); // Update progress updateProgress(EXPORT_STEP.NODES, _root.Nodes.Count, nodeCount); // children that are primitives get put in a mesh GameObject[] primitives, nonPrimitives; FilterPrimitives(nodeTransform, out primitives, out nonPrimitives); if (primitives.Length > 0) { node.Mesh = ExportMesh(nodeTransform.name, primitives); // associate unity meshes with gltf mesh id foreach (var prim in primitives) { _primOwner[new PrimKey { Mesh = getMesh(prim), Material = getMaterial(prim) }] = node.Mesh; } } // children that are not primitives get added as child nodes if (nonPrimitives.Length > 0) { node.Children = new List(nonPrimitives.Length); foreach(var child in nonPrimitives) { node.Children.Add(ExportNode(child.transform, nodeCount)); } } return id; } private void FilterPrimitives(Transform transform, out GameObject[] primitives, out GameObject[] nonPrimitives) { var childCount = transform.childCount; var prims = new List(childCount+1); var nonPrims = new List(childCount); // add another primitive if the root object also has a mesh if (GLTFUtils.isValidMeshObject(transform.gameObject)) { prims.Add(transform.gameObject); } for (var i = 0; i < childCount; i++) { var go = transform.GetChild(i).gameObject; nonPrims.Add(go); } primitives = prims.ToArray(); nonPrimitives = nonPrims.ToArray(); } private static bool IsPrimitive(GameObject gameObject) { /* * Primitives have the following properties: * - have no children * - have no non-default local transform properties * - have MeshFilter and MeshRenderer components */ return gameObject.transform.childCount == 0 && gameObject.transform.localPosition == Vector3.zero && gameObject.transform.localRotation == Quaternion.identity && gameObject.transform.localScale == Vector3.one && GLTFUtils.isValidMeshObject(gameObject); } private MeshId ExportMesh(string name, GameObject[] primitives) { // check if this set of primitives is already a mesh MeshId existingMeshId = null; var key = new PrimKey(); foreach (var prim in primitives) { key.Mesh = getMesh(prim); key.Material = getMaterial(prim); MeshId tempMeshId; if (_primOwner.TryGetValue(key, out tempMeshId) && (existingMeshId == null || tempMeshId == existingMeshId)) { existingMeshId = tempMeshId; } else { existingMeshId = null; break; } } // if so, return that mesh id if(existingMeshId != null) return existingMeshId; // if not, create new mesh and return its id var mesh = new GLTF.Schema.Mesh(); if (ExportNames) { mesh.Name = name; } mesh.Primitives = new List(primitives.Length); foreach (var prim in primitives) { mesh.Primitives.AddRange(ExportPrimitive(prim)); } var id = new MeshId { Id = _root.Meshes.Count, Root = _root }; _root.Meshes.Add(mesh); return id; } // a mesh *might* decode to multiple prims if there are submeshes private MeshPrimitive[] ExportPrimitive(GameObject gameObject) { var meshObj = getMesh(gameObject); var materialsObj = getMaterials(gameObject); var prims = new MeshPrimitive[meshObj.subMeshCount]; // don't export any more accessors if this mesh is already exported MeshPrimitive[] primVariations; if (_meshToPrims.TryGetValue(meshObj, out primVariations) && meshObj.subMeshCount == primVariations.Length) { for (var i = 0; i < primVariations.Length; i++) { prims[i] = primVariations[i].Clone(); if(materialsObj[i]) prims[i].Material = ExportMaterial(materialsObj[i]); } return prims; } AccessorId aPosition = null, aNormal = null, aTangent = null, aTexcoord0 = null, aTexcoord1 = null, aColor0 = null; aPosition = ExportVerticesAccessor(meshObj.vertices, gameObject, true); if (meshObj.normals.Length != 0) aNormal = ExportAccessor(meshObj.normals, true); if (meshObj.tangents.Length != 0) aTangent = ExportAccessor(meshObj.tangents, true); if (meshObj.uv.Length != 0) aTexcoord0 = ExportAccessor(meshObj.uv); if (meshObj.uv2.Length != 0) aTexcoord1 = ExportAccessor(meshObj.uv2); if (meshObj.colors.Length != 0) aColor0 = ExportAccessor(meshObj.colors); MaterialId lastMaterialId = null; for (var submesh = 0; submesh < meshObj.subMeshCount; submesh++) { var primitive = new MeshPrimitive(); var triangles = meshObj.GetTriangles(submesh); primitive.Indices = ExportAccessor(FlipFaces(triangles), true); primitive.Attributes = new Dictionary(); primitive.Attributes.Add(SemanticProperties.POSITION, aPosition); if (aNormal != null) primitive.Attributes.Add(SemanticProperties.NORMAL, aNormal); if (aTangent != null) primitive.Attributes.Add(SemanticProperties.TANGENT, aTangent); if (aTexcoord0 != null) primitive.Attributes.Add(SemanticProperties.TexCoord(0), aTexcoord0); if (aTexcoord1 != null) primitive.Attributes.Add(SemanticProperties.TexCoord(1), aTexcoord1); if (aColor0 != null) primitive.Attributes.Add(SemanticProperties.Color(0), aColor0); if (submesh < materialsObj.Length && materialsObj[submesh]) { primitive.Material = ExportMaterial(materialsObj[submesh]); lastMaterialId = primitive.Material; } else { primitive.Material = lastMaterialId; } prims[submesh] = primitive; } _meshToPrims[meshObj] = prims; return prims; } private MaterialId ExportMaterial(UnityEngine.Material materialObj) { MaterialId id = GetMaterialId(_root, materialObj); if (id != null) { return id; } var material = new GLTF.Schema.Material(); if (ExportNames) { material.Name = materialObj.name; } if (materialObj.HasProperty("_Cutoff")) { material.AlphaCutoff = materialObj.GetFloat("_Cutoff"); } switch (materialObj.GetTag("RenderType", false, "")) { case "TransparentCutout": material.AlphaMode = AlphaMode.MASK; break; case "Transparent": material.AlphaMode = AlphaMode.BLEND; break; default: material.AlphaMode = AlphaMode.OPAQUE; break; } if (materialExportOption == MaterialDoubleSidedExport.DoubleSide) { material.DoubleSided = true; } else if (materialExportOption == MaterialDoubleSidedExport.FrontSide) { material.DoubleSided = false; } else { material.DoubleSided = materialObj.HasProperty("_Cull") && materialObj.GetInt("_Cull") == (float)UnityEngine.Rendering.CullMode.Off; } if (materialObj.IsKeywordEnabled("_EMISSION") && materialObj.HasProperty("_EmissionColor")) { material.EmissiveFactor = materialObj.GetColor("_EmissionColor").ToNumericsColor(); } if (materialObj.HasProperty("_EmissionMap")) { var emissionTex = materialObj.GetTexture("_EmissionMap"); if (emissionTex != null) { material.EmissiveTexture = ExportTextureInfo(emissionTex); ExportTextureTransform(material.EmissiveTexture, materialObj, "_EmissionMap"); } } if (materialObj.HasProperty("_BumpMap")) { var normalTex = materialObj.GetTexture("_BumpMap"); if (normalTex != null) { material.NormalTexture = ExportNormalTextureInfo(normalTex, materialObj); ExportTextureTransform(material.NormalTexture, materialObj, "_BumpMap"); } } switch (materialObj.shader.name) { case "Standard": case "Standard (Roughness setup)": case "GLTF/GLTFStandard": material.PbrMetallicRoughness = ExportPBRMetallicRoughness(materialObj); if (materialObj.HasProperty("_OcclusionMap")) { var occTex = materialObj.GetTexture("_OcclusionMap"); if (occTex != null) { // Pack occlusion with metallicRoughness if any if (material.PbrMetallicRoughness.MetallicRoughnessTexture != null) { var info = new OcclusionTextureInfo(); if (materialObj.HasProperty("_OcclusionStrength")) { info.Strength = materialObj.GetFloat("_OcclusionStrength"); } info.Index = material.PbrMetallicRoughness.MetallicRoughnessTexture.Index; material.OcclusionTexture = info; } else { material.OcclusionTexture = ExportOcclusionTextureInfo(occTex, materialObj); } } } break; case "Standard (Specular setup)": KHR_materials_pbrSpecularGlossinessExtension pbr = convertSpecular(materialObj); material.Extensions.Add(KHR_materials_pbrSpecularGlossinessExtensionFactory.EXTENSION_NAME, pbr); registerExtension(KHR_materials_pbrSpecularGlossinessExtensionFactory.EXTENSION_NAME); if (materialObj.HasProperty("_OcclusionMap")) { var occTex = materialObj.GetTexture("_OcclusionMap"); if (occTex != null) { material.OcclusionTexture = ExportOcclusionTextureInfo(occTex, materialObj); ExportTextureTransform(material.OcclusionTexture, materialObj, "_OcclusionMap"); } } break; case "GLTF/GLTFConstant": material.CommonConstant = ExportCommonConstant(materialObj); break; } _materials.Add(materialObj); id = new MaterialId { Id = _root.Materials.Count, Root = _root }; _root.Materials.Add(material); return id; } private void ExportTextureTransform(TextureInfo def, UnityEngine.Material mat, string texName) { Vector2 offset = mat.GetTextureOffset(texName); Vector2 scale = mat.GetTextureScale(texName); if (offset == Vector2.zero && scale == Vector2.one) return; if (_root.ExtensionsUsed == null) { _root.ExtensionsUsed = new List( new string[] { ExtTextureTransformExtensionFactory.EXTENSION_NAME } ); } else if (!_root.ExtensionsUsed.Contains(ExtTextureTransformExtensionFactory.EXTENSION_NAME)) { _root.ExtensionsUsed.Add(ExtTextureTransformExtensionFactory.EXTENSION_NAME); } if (def.Extensions == null) def.Extensions = new Dictionary(); def.Extensions[ExtTextureTransformExtensionFactory.EXTENSION_NAME] = new ExtTextureTransformExtension( new GLTF.Math.Vector2(offset.x, -offset.y), new GLTF.Math.Vector2(scale.x, scale.y), 0 // TODO: support UV channels ); } private void registerExtension(string extension, bool isRequired=false) { if(!_root.ExtensionsUsed.Contains(extension)) { _root.ExtensionsUsed.Add(extension); } if(isRequired && !_root.ExtensionsRequired.Contains(extension)) { _root.ExtensionsRequired.Add(extension); } } private KHR_materials_pbrSpecularGlossinessExtension convertSpecular(UnityEngine.Material mat) { GLTF.Math.Color diffuseFactor = mat.GetColor("_Color").ToNumericsColor(); TextureInfo diffuseTexture = mat.GetTexture("_MainTex") != null ? ExportTextureInfo(mat.GetTexture("_MainTex")) : null; TextureInfo specularGlossinessTexture = null; GLTF.Math.Color specularColor = Color.white.ToNumericsColor(); float glossinessFactor = 1.0f; if (mat.GetTexture("_SpecGlossMap")) { specularGlossinessTexture = ExportTextureInfo(mat.GetTexture("_SpecGlossMap")); if(mat.HasProperty("_GlossMapScale")) glossinessFactor = mat.GetFloat("_GlossMapScale"); } else { specularColor = mat.GetColor("_SpecColor").ToNumericsColor(); if (mat.HasProperty("_Glossiness")) glossinessFactor = mat.GetFloat("_Glossiness"); } GLTF.Math.Vector3 specularFactor = new GLTF.Math.Vector3(specularColor.R, specularColor.G, specularColor.B); return new KHR_materials_pbrSpecularGlossinessExtension(diffuseFactor, diffuseTexture, specularFactor, glossinessFactor, specularGlossinessTexture); } private NormalTextureInfo ExportNormalTextureInfo(UnityEngine.Texture texture, UnityEngine.Material material) { var info = new NormalTextureInfo(); info.Index = ExportTexture(_textureCache.handleNormalMap((Texture2D)texture)); if (material.HasProperty("_BumpScale")) { info.Scale = material.GetFloat("_BumpScale"); } return info; } private OcclusionTextureInfo ExportOcclusionTextureInfo(UnityEngine.Texture texture, UnityEngine.Material material) { var info = new OcclusionTextureInfo(); info.Index = ExportTexture(texture); if (material.HasProperty("_OcclusionStrength")) { info.Strength = material.GetFloat("_OcclusionStrength"); } return info; } private PbrMetallicRoughness ExportPBRMetallicRoughness(UnityEngine.Material material) { var pbr = new PbrMetallicRoughness(); if (material.HasProperty("_Color")) { pbr.BaseColorFactor = material.GetColor("_Color").ToNumericsColor(); } if (material.HasProperty("_MainTex")) { var mainTex = material.GetTexture("_MainTex"); if (mainTex != null) { pbr.BaseColorTexture = ExportTextureInfo(mainTex); ExportTextureTransform(pbr.BaseColorTexture, material, "_MainTex"); } } if (material.HasProperty("_Metallic")) { pbr.MetallicFactor = material.GetFloat("_Metallic"); } if (material.HasProperty("_Roughness")) { pbr.RoughnessFactor = material.GetFloat("_Roughness"); } else if (material.HasProperty("_Glossiness")) { pbr.RoughnessFactor = 1.0 - material.GetFloat("_Glossiness"); } if (material.HasProperty("_MetallicRoughnessMap")) { var mrTex = material.GetTexture("_MetallicRoughnessMap"); if (mrTex != null) { if (material.HasProperty("_GlossMapScale")) { pbr.RoughnessFactor = material.GetFloat("_GlossMapScale"); } pbr.MetallicRoughnessTexture = ExportTextureInfo(mrTex); // writing 1.0 does not export this for whatever reason pbr.MetallicFactor = 0.99; ExportTextureTransform(pbr.MetallicRoughnessTexture, material, "_MetallicRoughnessMap"); } } else if (material.HasProperty("_MetallicGlossMap")) { var mgTex = material.GetTexture("_MetallicGlossMap") as Texture2D; if (mgTex != null) { if (material.HasProperty("_GlossMapScale")) { pbr.RoughnessFactor = material.GetFloat("_GlossMapScale"); } var occTex = (material.HasProperty("_OcclusionMap") ? material.GetTexture("_OcclusionMap") as Texture2D : null); pbr.MetallicRoughnessTexture = ExportTextureInfo(_textureCache.packOcclusionMetalRough(mgTex, occTex)); // writing 1.0 does not export this for whatever reason pbr.MetallicFactor = 0.99; } } return pbr; } private MaterialCommonConstant ExportCommonConstant(UnityEngine.Material materialObj) { if (_root.ExtensionsUsed == null) { _root.ExtensionsUsed = new List(new string[] { "KHR_materials_common" }); } else if(!_root.ExtensionsUsed.Contains("KHR_materials_common")) _root.ExtensionsUsed.Add("KHR_materials_common"); var constant = new MaterialCommonConstant(); if (materialObj.HasProperty("_AmbientFactor")) { constant.AmbientFactor = materialObj.GetColor("_AmbientFactor").ToNumericsColor(); } if (materialObj.HasProperty("_LightMap")) { var lmTex = materialObj.GetTexture("_LightMap"); if (lmTex != null) { constant.LightmapTexture = ExportTextureInfo(lmTex); ExportTextureTransform(constant.LightmapTexture, materialObj, "_LightMap"); } } if (materialObj.HasProperty("_LightFactor")) { constant.LightmapFactor = materialObj.GetColor("_LightFactor").ToNumericsColor(); } return constant; } private TextureInfo ExportTextureInfo(UnityEngine.Texture texture) { var info = new TextureInfo(); info.Index = ExportTexture(texture); return info; } private TextureId ExportTexture(UnityEngine.Texture textureObj) { TextureId id = GetTextureId(_root, textureObj); if (id != null) { return id; } var texture = new GLTF.Schema.Texture(); //If texture name not set give it a unique name using count if (textureObj.name == "") { textureObj.name = (_root.Textures.Count + 1).ToString(); } if (ExportNames) { texture.Name = textureObj.name; } texture.Source = ExportImage(textureObj); texture.Sampler = ExportSampler(textureObj); _textures.Add(textureObj); id = new TextureId { Id = _root.Textures.Count, Root = _root }; _root.Textures.Add(texture); return id; } private ImageId ExportImage(UnityEngine.Texture texture) { string imagePath = GLTFUtils.buildImageName((Texture2D)texture); ImageId id = GetImageId(_root, texture); if(id != null) { return id; } var image = new Image(); if (ExportNames) { image.Name = texture.name; } _images.Add(texture as Texture2D); image.Uri = imagePath; id = new ImageId { Id = _root.Images.Count, Root = _root }; _root.Images.Add(image); return id; } private SamplerId ExportSampler(UnityEngine.Texture texture) { var samplerId = GetSamplerId(_root, texture); if (samplerId != null) return samplerId; var sampler = new Sampler(); if (texture.wrapMode == TextureWrapMode.Clamp) { sampler.WrapS = GLTF.Schema.WrapMode.ClampToEdge; sampler.WrapT = GLTF.Schema.WrapMode.ClampToEdge; } else { sampler.WrapS = GLTF.Schema.WrapMode.Repeat; sampler.WrapT = GLTF.Schema.WrapMode.Repeat; } if(texture.filterMode == FilterMode.Point) { sampler.MinFilter = MinFilterMode.NearestMipmapNearest; sampler.MagFilter = MagFilterMode.Nearest; } else if(texture.filterMode == FilterMode.Bilinear) { sampler.MinFilter = MinFilterMode.Linear; sampler.MagFilter = MagFilterMode.Linear; } else { sampler.MinFilter = MinFilterMode.LinearMipmapLinear; sampler.MagFilter = MagFilterMode.Linear; } samplerId = new SamplerId { Id = _root.Samplers.Count, Root = _root }; _root.Samplers.Add(sampler); return samplerId; } private Vector2[] FlipY(Vector2[] arr) { var len = arr.Length; for(var i = 0; i < len; i++) { arr[i].y = 1 - arr[i].y; } return arr; } private Vector3[] InvertZ(Vector3[] arr) { var len = arr.Length; for(var i = 0; i < len; i++) { arr[i].z = -arr[i].z; } return arr; } private Vector4[] InvertW(Vector4[] arr) { var len = arr.Length; for(var i = 0; i < len; i++) { arr[i].w = -arr[i].w; } return arr; } private int[] FlipFaces(int[] arr) { var triangles = new int[arr.Length]; for (int i = 0; i < arr.Length; i += 3) { triangles[i + 2] = arr[i]; triangles[i + 1] = arr[i + 1]; triangles[i] = arr[i + 2]; } return triangles; } private AccessorId ExportAccessor(int[] arr, bool isIndices = false) { var count = arr.Length; if (count == 0) { throw new Exception("Accessors can not have a count of 0."); } var accessor = new Accessor(); accessor.Count = count; accessor.Type = GLTFAccessorAttributeType.SCALAR; int min = arr[0]; int max = arr[0]; for (var i = 1; i < count; i++) { var cur = arr[i]; if (cur < min) { min = cur; } if (cur > max) { max = cur; } } var byteOffset = _bufferWriter.BaseStream.Position; if (_forceIndicesUint) { accessor.ComponentType = GLTFComponentType.UnsignedInt; foreach (var v in arr) { _bufferWriter.Write((uint)v); } } else if (max <= byte.MaxValue && min >= byte.MinValue) { accessor.ComponentType = GLTFComponentType.UnsignedByte; foreach (var v in arr) { _bufferWriter.Write((byte)v); } } else if (max <= sbyte.MaxValue && min >= sbyte.MinValue && !isIndices) { accessor.ComponentType = GLTFComponentType.Byte; foreach (var v in arr) { _bufferWriter.Write((sbyte)v); } } else if (max <= short.MaxValue && min >= short.MinValue && !isIndices) { accessor.ComponentType = GLTFComponentType.Short; foreach (var v in arr) { _bufferWriter.Write((short)v); } } else if (max <= ushort.MaxValue && min >= ushort.MinValue) { accessor.ComponentType = GLTFComponentType.UnsignedShort; foreach (var v in arr) { _bufferWriter.Write((ushort)v); } } else if (min >= uint.MinValue) { accessor.ComponentType = GLTFComponentType.UnsignedInt; foreach (var v in arr) { _bufferWriter.Write((uint)v); } } else { accessor.ComponentType = GLTFComponentType.Float; foreach (var v in arr) { _bufferWriter.Write((float)v); } } accessor.Min = new List { min }; accessor.Max = new List { max }; var byteLength = _bufferWriter.BaseStream.Position - byteOffset; accessor.BufferView = ExportBufferView((int)byteOffset, (int)byteLength); var id = new AccessorId { Id = _root.Accessors.Count, Root = _root }; _root.Accessors.Add(accessor); return id; } private AccessorId ExportAccessor(float[] arr) { var count = arr.Length; if (count == 0) { throw new Exception("Accessors can not have a count of 0."); } var accessor = new Accessor(); accessor.ComponentType = GLTFComponentType.Float; accessor.Count = count; accessor.Type = GLTFAccessorAttributeType.SCALAR; float min = arr[0]; float max = arr[0]; for (var i = 1; i < count; i++) { var cur = arr[i]; if (cur < min) { min = cur; } if (cur > max) { max = cur; } } accessor.Min = new List { min }; accessor.Max = new List { max }; var byteOffset = _bufferWriter.BaseStream.Position; foreach (var value in arr) { _bufferWriter.Write(value); } var byteLength = _bufferWriter.BaseStream.Position - byteOffset; accessor.BufferView = ExportBufferView((int)byteOffset, (int)byteLength); var id = new AccessorId { Id = _root.Accessors.Count, Root = _root }; _root.Accessors.Add(accessor); return id; } private AccessorId ExportAccessor(Vector2[] arr) { var count = arr.Length; if (count == 0) { throw new Exception("Accessors can not have a count of 0."); } var accessor = new Accessor(); accessor.ComponentType = GLTFComponentType.Float; accessor.Count = count; accessor.Type = GLTFAccessorAttributeType.VEC2; float minX = arr[0].x; float minY = arr[0].y; float maxX = arr[0].x; float maxY = arr[0].y; for (var i = 1; i < count; i++) { var cur = arr[i]; if (cur.x < minX) { minX = cur.x; } if (cur.y < minY) { minY = cur.y; } if (cur.x > maxX) { maxX = cur.x; } if (cur.y > maxY) { maxY = cur.y; } } accessor.Min = new List { minX, minY }; accessor.Max = new List { maxX, maxY }; var byteOffset = _bufferWriter.BaseStream.Position; foreach (var vec in arr) { _bufferWriter.Write(vec.x); _bufferWriter.Write(vec.y); } var byteLength = _bufferWriter.BaseStream.Position - byteOffset; accessor.BufferView = ExportBufferView((int)byteOffset, (int)byteLength); var id = new AccessorId { Id = _root.Accessors.Count, Root = _root }; _root.Accessors.Add(accessor); return id; } private AccessorId ExportAccessor(Vector3[] arr, bool switchHandedness=false) { var count = arr.Length; if (count == 0) { throw new Exception("Accessors can not have a count of 0."); } var accessor = new Accessor(); accessor.ComponentType = GLTFComponentType.Float; accessor.Count = count; accessor.Type = GLTFAccessorAttributeType.VEC3; float minX = arr[0].x; float minY = arr[0].y; float minZ = arr[0].z; float maxX = arr[0].x; float maxY = arr[0].y; float maxZ = arr[0].z; for (var i = 1; i < count; i++) { var cur = arr[i]; if (cur.x < minX) { minX = cur.x; } if (cur.y < minY) { minY = cur.y; } if (cur.z < minZ) { minZ = cur.z; } if (cur.x > maxX) { maxX = cur.x; } if (cur.y > maxY) { maxY = cur.y; } if (cur.z > maxZ) { maxZ = cur.z; } } accessor.Min = new List { minX, minY, minZ }; accessor.Max = new List { maxX, maxY, maxZ }; var byteOffset = _bufferWriter.BaseStream.Position; foreach (var vec in arr) { if(switchHandedness) { Vector3 vect = vec.switchHandedness(); _bufferWriter.Write(vect.x); _bufferWriter.Write(vect.y); _bufferWriter.Write(vect.z); } else { _bufferWriter.Write(vec.x); _bufferWriter.Write(vec.y); _bufferWriter.Write(vec.z); } } var byteLength = _bufferWriter.BaseStream.Position - byteOffset; accessor.BufferView = ExportBufferView((int)byteOffset, (int)byteLength); var id = new AccessorId { Id = _root.Accessors.Count, Root = _root }; _root.Accessors.Add(accessor); return id; } private AccessorId ExportVerticesAccessor(Vector3[] arr, GameObject parent, bool switchHandedness=false) { var count = arr.Length; if (count == 0) { throw new Exception("Accessors can not have a count of 0."); } var accessor = new Accessor(); accessor.ComponentType = GLTFComponentType.Float; accessor.Count = count; accessor.Type = GLTFAccessorAttributeType.VEC3; if (GLTFUtils.boundsExportOption == GLTFUtils.BoundsExportOption.Default) { float minX = arr[0].x; float minY = arr[0].y; float minZ = arr[0].z; float maxX = arr[0].x; float maxY = arr[0].y; float maxZ = arr[0].z; for (var i = 1; i < count; i++) { var cur = arr[i]; if (cur.x < minX) { minX = cur.x; } if (cur.y < minY) { minY = cur.y; } if (cur.z < minZ) { minZ = cur.z; } if (cur.x > maxX) { maxX = cur.x; } if (cur.y > maxY) { maxY = cur.y; } if (cur.z > maxZ) { maxZ = cur.z; } } accessor.Min = new List { minX, minY, minZ }; accessor.Max = new List { maxX, maxY, maxZ }; } else if (GLTFUtils.boundsExportOption == GLTFUtils.BoundsExportOption.SceneViewerFix) { Bounds totalBounds = new Bounds(); for (var i = 0; i < count; i++) { totalBounds.Encapsulate(arr[i]); } float pivotX = (totalBounds.min.x + totalBounds.max.x) / 2.0f; float pivotY = (totalBounds.min.y + totalBounds.max.y) / 2.0f; float pivotZ = (totalBounds.min.z + totalBounds.max.z) / 2.0f; float minX = totalBounds.min.x; float minY = totalBounds.min.y; float minZ = totalBounds.min.z; float maxX = totalBounds.max.x; float maxY = totalBounds.max.y; float maxZ = totalBounds.max.z; accessor.Min = new List {minX - pivotX, minY - pivotY, minZ - pivotZ}; accessor.Max = new List {maxX - pivotX, maxY - pivotY, maxZ - pivotZ}; } var byteOffset = _bufferWriter.BaseStream.Position; foreach (var vec in arr) { if(switchHandedness) { Vector3 vect = vec.switchHandedness(); _bufferWriter.Write(vect.x); _bufferWriter.Write(vect.y); _bufferWriter.Write(vect.z); } else { _bufferWriter.Write(vec.x); _bufferWriter.Write(vec.y); _bufferWriter.Write(vec.z); } } var byteLength = _bufferWriter.BaseStream.Position - byteOffset; accessor.BufferView = ExportBufferView((int)byteOffset, (int)byteLength); var id = new AccessorId { Id = _root.Accessors.Count, Root = _root }; _root.Accessors.Add(accessor); return id; } private AccessorId ExportAccessorUint(Vector4[] arr) { var count = arr.Length; if (count == 0) { throw new Exception("Accessors can not have a count of 0."); } var accessor = new Accessor(); accessor.ComponentType = GLTFComponentType.UnsignedShort; accessor.Count = count; accessor.Type = GLTFAccessorAttributeType.VEC4; float minX = arr[0].x; float minY = arr[0].y; float minZ = arr[0].z; float minW = arr[0].w; float maxX = arr[0].x; float maxY = arr[0].y; float maxZ = arr[0].z; float maxW = arr[0].w; for (var i = 1; i < count; i++) { var cur = arr[i]; if (cur.x < minX) { minX = cur.x; } if (cur.y < minY) { minY = cur.y; } if (cur.z < minZ) { minZ = cur.z; } if (cur.w < minW) { minW = cur.w; } if (cur.x > maxX) { maxX = cur.x; } if (cur.y > maxY) { maxY = cur.y; } if (cur.z > maxZ) { maxZ = cur.z; } if (cur.w > maxW) { maxW = cur.w; } } accessor.Min = new List { minX, minY, minZ, minW }; accessor.Max = new List { maxX, maxY, maxZ, maxW }; var byteOffset = _bufferWriter.BaseStream.Position; foreach (var vec in arr) { _bufferWriter.Write((ushort)vec.x); _bufferWriter.Write((ushort)vec.y); _bufferWriter.Write((ushort)vec.z); _bufferWriter.Write((ushort)vec.w); } var byteLength = _bufferWriter.BaseStream.Position - byteOffset; accessor.BufferView = ExportBufferView((int)byteOffset, (int)byteLength); var id = new AccessorId { Id = _root.Accessors.Count, Root = _root }; _root.Accessors.Add(accessor); return id; } private AccessorId ExportAccessor(Vector4[] arr, bool switchHandedness=false) { var count = arr.Length; if (count == 0) { throw new Exception("Accessors can not have a count of 0."); } var accessor = new Accessor(); accessor.ComponentType = GLTFComponentType.Float; accessor.Count = count; accessor.Type = GLTFAccessorAttributeType.VEC4; float minX = arr[0].x; float minY = arr[0].y; float minZ = arr[0].z; float minW = arr[0].w; float maxX = arr[0].x; float maxY = arr[0].y; float maxZ = arr[0].z; float maxW = arr[0].w; for (var i = 1; i < count; i++) { var cur = arr[i]; if (cur.x < minX) { minX = cur.x; } if (cur.y < minY) { minY = cur.y; } if (cur.z < minZ) { minZ = cur.z; } if (cur.w < minW) { minW = cur.w; } if (cur.x > maxX) { maxX = cur.x; } if (cur.y > maxY) { maxY = cur.y; } if (cur.z > maxZ) { maxZ = cur.z; } if (cur.w > maxW) { maxW = cur.w; } } accessor.Min = new List { minX, minY, minZ, minW }; accessor.Max = new List { maxX, maxY, maxZ, maxW }; var byteOffset = _bufferWriter.BaseStream.Position; foreach (var vec in arr) { Vector4 vect = switchHandedness ? vec.switchHandedness() : vec; _bufferWriter.Write(vect.x); _bufferWriter.Write(vect.y); _bufferWriter.Write(vect.z); _bufferWriter.Write(vect.w); } var byteLength = _bufferWriter.BaseStream.Position - byteOffset; accessor.BufferView = ExportBufferView((int)byteOffset, (int)byteLength); var id = new AccessorId { Id = _root.Accessors.Count, Root = _root }; _root.Accessors.Add(accessor); return id; } private AccessorId ExportAccessor(UnityEngine.Color[] arr) { var count = arr.Length; if (count == 0) { throw new Exception("Accessors can not have a count of 0."); } var accessor = new Accessor(); accessor.ComponentType = GLTFComponentType.Float; accessor.Count = count; accessor.Type = GLTFAccessorAttributeType.VEC4; float minR = arr[0].r; float minG = arr[0].g; float minB = arr[0].b; float minA = arr[0].a; float maxR = arr[0].r; float maxG = arr[0].g; float maxB = arr[0].b; float maxA = arr[0].a; for (var i = 1; i < count; i++) { var cur = arr[i]; if (cur.r < minR) { minR = cur.r; } if (cur.g < minG) { minG = cur.g; } if (cur.b < minB) { minB = cur.b; } if (cur.a < minA) { minA = cur.a; } if (cur.r > maxR) { maxR = cur.r; } if (cur.g > maxG) { maxG = cur.g; } if (cur.b > maxB) { maxB = cur.b; } if (cur.a > maxA) { maxA = cur.a; } } accessor.Min = new List { minR, minG, minB, minA }; accessor.Max = new List { maxR, maxG, maxB, maxA }; var byteOffset = _bufferWriter.BaseStream.Position; foreach (var color in arr) { _bufferWriter.Write(color.r); _bufferWriter.Write(color.g); _bufferWriter.Write(color.b); _bufferWriter.Write(color.a); } var byteLength = _bufferWriter.BaseStream.Position - byteOffset; accessor.BufferView = ExportBufferView((int)byteOffset, (int)byteLength); var id = new AccessorId { Id = _root.Accessors.Count, Root = _root }; _root.Accessors.Add(accessor); return id; } private AccessorId ExportAccessor(Matrix4x4[] arr, bool switchHandedness = false) { var count = arr.Length; if (count == 0) { throw new Exception("Accessors can not have a count of 0."); } var accessor = new Accessor(); accessor.ComponentType = GLTFComponentType.Float; accessor.Count = count; accessor.Type = GLTFAccessorAttributeType.MAT4; // Dont serialize min/max for matrices var byteOffset = _bufferWriter.BaseStream.Position; foreach (var mat in arr) { Matrix4x4 mamat = switchHandedness ? mat.switchHandedness() : mat; for (int i = 0; i < 4; ++i) { Vector4 col = mamat.GetColumn(i); _bufferWriter.Write(col.x); _bufferWriter.Write(col.y); _bufferWriter.Write(col.z); _bufferWriter.Write(col.w); } } var byteLength = _bufferWriter.BaseStream.Position - byteOffset; accessor.BufferView = ExportBufferView((int)byteOffset, (int)byteLength); var id = new AccessorId { Id = _root.Accessors.Count, Root = _root }; _root.Accessors.Add(accessor); return id; } private BufferViewId ExportBufferView(int byteOffset, int byteLength) { var bufferView = new BufferView { Buffer = _bufferId, ByteOffset = byteOffset, ByteLength = byteLength, }; var id = new BufferViewId { Id = _root.BufferViews.Count, Root = _root }; _root.BufferViews.Add(bufferView); return id; } public MaterialId GetMaterialId(GLTFRoot root, UnityEngine.Material materialObj) { for (var i = 0; i < _materials.Count; i++) { if (_materials[i] == materialObj) { return new MaterialId { Id = i, Root = root }; } } return null; } public TextureId GetTextureId(GLTFRoot root, UnityEngine.Texture textureObj) { for (var i = 0; i < _textures.Count; i++) { if (_textures[i] == textureObj) { return new TextureId { Id = i, Root = root }; } } return null; } public ImageId GetImageId(GLTFRoot root, UnityEngine.Texture imageObj) { for (var i = 0; i < _images.Count; i++) { if (_images[i] == imageObj) { return new ImageId { Id = i, Root = root }; } } return null; } public SamplerId GetSamplerId(GLTFRoot root, UnityEngine.Texture textureObj) { for (var i = 0; i < root.Samplers.Count; i++) { bool filterIsNearest = root.Samplers[i].MinFilter == MinFilterMode.Nearest || root.Samplers[i].MinFilter == MinFilterMode.NearestMipmapNearest || root.Samplers[i].MinFilter == MinFilterMode.LinearMipmapNearest; bool filterIsLinear = root.Samplers[i].MinFilter == MinFilterMode.Linear || root.Samplers[i].MinFilter == MinFilterMode.NearestMipmapLinear; bool filterMatched = textureObj.filterMode == FilterMode.Point && filterIsNearest || textureObj.filterMode == FilterMode.Bilinear && filterIsLinear || textureObj.filterMode == FilterMode.Trilinear && root.Samplers[i].MinFilter == MinFilterMode.LinearMipmapLinear; bool wrapMatched = textureObj.wrapMode == TextureWrapMode.Clamp && root.Samplers[i].WrapS == GLTF.Schema.WrapMode.ClampToEdge || textureObj.wrapMode == TextureWrapMode.Repeat && root.Samplers[i].WrapS != GLTF.Schema.WrapMode.ClampToEdge; if (filterMatched && wrapMatched) { return new SamplerId { Id = i, Root = root }; } } return null; } public enum ROTATION_TYPE { UNKNOWN, QUATERNION, EULER }; private struct TargetCurveSet { public AnimationCurve[] translationCurves; public AnimationCurve[] rotationCurves; //Additional curve types public AnimationCurve[] localEulerAnglesRaw; public AnimationCurve[] m_LocalEuler; public AnimationCurve[] scaleCurves; public ROTATION_TYPE rotationType; public void Init() { translationCurves = new AnimationCurve[3]; rotationCurves = new AnimationCurve[4]; scaleCurves = new AnimationCurve[3]; } } static int bakingFramerate = 30; // FPS static bool bake = true; // Parses Animation/Animator component and generate a glTF animation for the active clip public void exportAnimationFromNode(ref Transform transform/*, ref GLTF.Schema.Animation anim*/) { Animator a = transform.GetComponent(); if (a != null) { AnimationClip[] clips = AnimationUtility.GetAnimationClips(transform.gameObject); for (int i = 0; i < clips.Length; i++) { GLTF.Schema.Animation anim = new GLTF.Schema.Animation(); anim.Name = GLTFEditorExporter.cleanNonAlphanumeric(a.name); convertClipToGLTFAnimation(ref clips[i], ref transform, ref anim); if (anim.Channels.Count > 0 && anim.Samplers.Count > 0) { _root.Animations.Add(anim); } } } UnityEngine.Animation animation = transform.GetComponent(); if (animation != null) { foreach (AnimationState state in animation) { AnimationClip clip = state.clip; GLTF.Schema.Animation anim = new GLTF.Schema.Animation(); anim.Name = GLTFEditorExporter.cleanNonAlphanumeric(state.name); convertClipToGLTFAnimation(ref clip, ref transform, ref anim); if (anim.Channels.Count > 0 && anim.Samplers.Count > 0) { _root.Animations.Add(anim); } } } } private int getTargetIdFromTransform(ref Transform transform) { if (_exportedTransforms.ContainsKey(transform.GetInstanceID())) { return _exportedTransforms[transform.GetInstanceID()]; } else { Debug.Log(transform.name + " " + transform.GetInstanceID()); return 0; } } private AccessorId ExportAccessor() { var id = new AccessorId { Id = 5, Root = _root }; return id; } private void convertClipToGLTFAnimation(ref AnimationClip clip, ref Transform transform, ref GLTF.Schema.Animation animation) { // Generate GLTF.Schema.AnimationChannel and GLTF.Schema.AnimationSampler // 1 channel per node T/R/S, one sampler per node T/R/S // Need to keep a list of nodes to convert to indexes // 1. browse clip, collect all curves and create a TargetCurveSet for each target Dictionary targetCurvesBinding = new Dictionary(); collectClipCurves(clip, ref targetCurvesBinding); // Baking needs all properties, fill missing curves with transform data in 2 keyframes (start, endTime) // where endTime is clip duration // Note: we should avoid creating curves for a property if none of it's components is animated generateMissingCurves(clip.length, ref transform, ref targetCurvesBinding); if (bake) { // Bake animation for all animated nodes foreach (string target in targetCurvesBinding.Keys) { Transform targetTr = target.Length > 0 ? transform.Find(target) : transform; if (targetTr == null || targetTr.GetComponent()) { continue; } // Initialize data // Bake and populate animation data float[] times = null; Vector3[] positions = null; Vector3[] scales = null; Vector4[] rotations = null; bakeCurveSet(targetCurvesBinding[target], clip.length, bakingFramerate, ref times, ref positions, ref rotations, ref scales); int channelTargetId = getTargetIdFromTransform(ref targetTr); AccessorId timeAccessor = ExportAccessor(times); // Create channel AnimationChannel Tchannel = new AnimationChannel(); AnimationChannelTarget TchannelTarget = new AnimationChannelTarget(); TchannelTarget.Path = GLTFAnimationChannelPath.translation; TchannelTarget.Node = new NodeId { Id = channelTargetId, Root = _root }; Tchannel.Target = TchannelTarget; AnimationSampler Tsampler = new AnimationSampler(); Tsampler.Input = timeAccessor; Tsampler.Output = ExportAccessor(positions, true); // Vec3 for translation Tchannel.Sampler = new AnimationSamplerId { Id = animation.Samplers.Count, GLTFAnimation = animation, Root = _root }; animation.Samplers.Add(Tsampler); animation.Channels.Add(Tchannel); // Rotation AnimationChannel Rchannel = new AnimationChannel(); AnimationChannelTarget RchannelTarget = new AnimationChannelTarget(); RchannelTarget.Path = GLTFAnimationChannelPath.rotation; RchannelTarget.Node = new NodeId { Id = channelTargetId, Root = _root }; Rchannel.Target = RchannelTarget; AnimationSampler Rsampler = new AnimationSampler(); Rsampler.Input = timeAccessor; // Float, for time Rsampler.Output = ExportAccessor(rotations, true); // Vec4 for Rchannel.Sampler = new AnimationSamplerId { Id = animation.Samplers.Count, GLTFAnimation = animation, Root = _root }; animation.Samplers.Add(Rsampler); animation.Channels.Add(Rchannel); // Scale AnimationChannel Schannel = new AnimationChannel(); AnimationChannelTarget SchannelTarget = new AnimationChannelTarget(); SchannelTarget.Path = GLTFAnimationChannelPath.scale; SchannelTarget.Node = new NodeId { Id = channelTargetId, Root = _root }; Schannel.Target = SchannelTarget; AnimationSampler Ssampler = new AnimationSampler(); Ssampler.Input = timeAccessor; // Float, for time Ssampler.Output = ExportAccessor(scales); // Vec3 for scale Schannel.Sampler = new AnimationSamplerId { Id = animation.Samplers.Count, GLTFAnimation = animation, Root = _root }; animation.Samplers.Add(Ssampler); animation.Channels.Add(Schannel); } } else { Debug.LogError("Only baked animation is supported for now. Skipping animation"); } } private void collectClipCurves(AnimationClip clip, ref Dictionary targetCurves) { foreach (var binding in UnityEditor.AnimationUtility.GetCurveBindings(clip)) { AnimationCurve curve = AnimationUtility.GetEditorCurve(clip, binding); if (!targetCurves.ContainsKey(binding.path)) { TargetCurveSet curveSet = new TargetCurveSet(); curveSet.Init(); targetCurves.Add(binding.path, curveSet); } TargetCurveSet current = targetCurves[binding.path]; if (binding.propertyName.Contains("m_LocalPosition")) { if (binding.propertyName.Contains(".x")) current.translationCurves[0] = curve; else if (binding.propertyName.Contains(".y")) current.translationCurves[1] = curve; else if (binding.propertyName.Contains(".z")) current.translationCurves[2] = curve; } else if (binding.propertyName.Contains("m_LocalScale")) { if (binding.propertyName.Contains(".x")) current.scaleCurves[0] = curve; else if (binding.propertyName.Contains(".y")) current.scaleCurves[1] = curve; else if (binding.propertyName.Contains(".z")) current.scaleCurves[2] = curve; } else if (binding.propertyName.ToLower().Contains("localrotation")) { current.rotationType = ROTATION_TYPE.QUATERNION; if (binding.propertyName.Contains(".x")) current.rotationCurves[0] = curve; else if (binding.propertyName.Contains(".y")) current.rotationCurves[1] = curve; else if (binding.propertyName.Contains(".z")) current.rotationCurves[2] = curve; else if (binding.propertyName.Contains(".w")) current.rotationCurves[3] = curve; } // Takes into account 'localEuler', 'localEulerAnglesBaked' and 'localEulerAnglesRaw' else if (binding.propertyName.ToLower().Contains("localeuler")) { current.rotationType = ROTATION_TYPE.EULER; if (binding.propertyName.Contains(".x")) current.rotationCurves[0] = curve; else if (binding.propertyName.Contains(".y")) current.rotationCurves[1] = curve; else if (binding.propertyName.Contains(".z")) current.rotationCurves[2] = curve; } targetCurves[binding.path] = current; } } private void generateMissingCurves(float endTime, ref Transform tr, ref Dictionary targetCurvesBinding) { foreach (string target in targetCurvesBinding.Keys) { Transform targetTr = target.Length > 0 ? tr.Find(target) : tr; if (targetTr == null) continue; TargetCurveSet current = targetCurvesBinding[target]; if (current.translationCurves[0] == null) { current.translationCurves[0] = createConstantCurve(targetTr.localPosition.x, endTime); current.translationCurves[1] = createConstantCurve(targetTr.localPosition.y, endTime); current.translationCurves[2] = createConstantCurve(targetTr.localPosition.z, endTime); } if (current.scaleCurves[0] == null) { current.scaleCurves[0] = createConstantCurve(targetTr.localScale.x, endTime); current.scaleCurves[1] = createConstantCurve(targetTr.localScale.y, endTime); current.scaleCurves[2] = createConstantCurve(targetTr.localScale.z, endTime); } if (current.rotationCurves[0] == null) { current.rotationCurves[0] = createConstantCurve(targetTr.localRotation.x, endTime); current.rotationCurves[1] = createConstantCurve(targetTr.localRotation.y, endTime); current.rotationCurves[2] = createConstantCurve(targetTr.localRotation.z, endTime); current.rotationCurves[3] = createConstantCurve(targetTr.localRotation.w, endTime); } } } private AnimationCurve createConstantCurve(float value, float endTime) { // No translation curves, adding them AnimationCurve curve = new AnimationCurve(); curve.AddKey(0, value); curve.AddKey(endTime, value); return curve; } private void bakeCurveSet(TargetCurveSet curveSet, float length, int bakingFramerate, ref float[] times, ref Vector3[] positions, ref Vector4[] rotations, ref Vector3[] scales) { int nbSamples = (int)(length * 30); float deltaTime = length / nbSamples; // Initialize Arrays times = new float[nbSamples]; positions = new Vector3[nbSamples]; scales = new Vector3[nbSamples]; rotations = new Vector4[nbSamples]; // Assuming all the curves exist now for (int i = 0; i < nbSamples; ++i) { float currentTime = i * deltaTime; times[i] = currentTime; positions[i] = new Vector3(curveSet.translationCurves[0].Evaluate(currentTime), curveSet.translationCurves[1].Evaluate(currentTime), curveSet.translationCurves[2].Evaluate(currentTime)); scales[i] = new Vector3(curveSet.scaleCurves[0].Evaluate(currentTime), curveSet.scaleCurves[1].Evaluate(currentTime), curveSet.scaleCurves[2].Evaluate(currentTime)); if (curveSet.rotationType == ROTATION_TYPE.EULER) { Quaternion eulerToQuat = Quaternion.Euler(curveSet.rotationCurves[0].Evaluate(currentTime), curveSet.rotationCurves[1].Evaluate(currentTime), curveSet.rotationCurves[2].Evaluate(currentTime)); rotations[i] = new Vector4(eulerToQuat.x, eulerToQuat.y, eulerToQuat.z, eulerToQuat.w); } else { rotations[i] = new Vector4(curveSet.rotationCurves[0].Evaluate(currentTime), curveSet.rotationCurves[1].Evaluate(currentTime), curveSet.rotationCurves[2].Evaluate(currentTime), curveSet.rotationCurves[3].Evaluate(currentTime)); } } } private void exportSkinFromNode(Transform transform) { PrimKey key = new PrimKey(); UnityEngine.Mesh mesh = getMesh(transform.gameObject); key.Mesh = mesh; key.Material = getMaterial(transform.gameObject); MeshId val; if(!_primOwner.TryGetValue(key, out val)) { Debug.Log("No mesh found for skin"); return; } SkinnedMeshRenderer skin = transform.GetComponent(); GLTF.Schema.Skin gltfSkin = new Skin(); for (int i = 0; i < skin.bones.Length; ++i) { gltfSkin.Joints.Add( new NodeId { Id = _exportedTransforms[skin.bones[i].GetInstanceID()], Root = _root }); } gltfSkin.InverseBindMatrices = ExportAccessor(mesh.bindposes, true); Vector4[] bones = boneWeightToBoneVec4(mesh.boneWeights); Vector4[] weights = boneWeightToWeightVec4(mesh.boneWeights); GLTF.Schema.Mesh gltfMesh = _root.Meshes[val.Id]; foreach(MeshPrimitive prim in gltfMesh.Primitives) { if(!prim.Attributes.ContainsKey("JOINTS_0")) prim.Attributes.Add("JOINTS_0", ExportAccessorUint(bones)); if (!prim.Attributes.ContainsKey("WEIGHTS_0")) prim.Attributes.Add("WEIGHTS_0", ExportAccessor(weights)); } _root.Nodes[_exportedTransforms[transform.GetInstanceID()]].Skin = new SkinId() { Id = _root.Skins.Count, Root = _root }; _root.Skins.Add(gltfSkin); } private Vector4[] boneWeightToBoneVec4(BoneWeight[] bw) { Vector4[] bones = new Vector4[bw.Length]; for (int i = 0; i < bw.Length; ++i) { bones[i] = new Vector4(bw[i].boneIndex0, bw[i].boneIndex1, bw[i].boneIndex2, bw[i].boneIndex3); } return bones; } private Vector4[] boneWeightToWeightVec4(BoneWeight[] bw) { Vector4[] weights = new Vector4[bw.Length]; for (int i = 0; i < bw.Length; ++i) { weights[i] = new Vector4(bw[i].weight0, bw[i].weight1, bw[i].weight2, bw[i].weight3); } return weights; } public static Regex rgx = new Regex("[^a-zA-Z0-9 -_.]"); static public string cleanNonAlphanumeric(string s) { return rgx.Replace(s, ""); } } }