using System; using System.Collections; using System.Collections.Generic; using System.IO; using System.Text.RegularExpressions; using GLTF; using GLTF.Schema; using UnityEngine; using UnityGLTF.Cache; using UnityGLTF.Extensions; using UnityEditor; namespace UnityGLTF { /// /// Editor windows to load a GLTF scene in editor /// /// public class GLTFEditorImporter { public bool _useGLTFMaterial = false; bool _isDone = false; // Import paths and options private string _projectDirectoryPath; private string _gltfDirectoryPath; private string _glTFPath = ""; private bool _addToCurrentScene; // GLTF data private byte[] _glTFData; protected GLTFRoot _root; AssetManager _assetManager; private int _nbParsedNodes; private GameObject _sceneObject; public UnityEngine.Material defaultMaterial; protected AssetCache _assetCache; private TaskManager _taskManager; private bool _userStopped = false; private string _currentSampleName = ""; private List _assetsToRemove; Dictionary _importedObjects; Dictionary>_skinIndexToGameObjects; // Import Progress public enum IMPORT_STEP { READ_FILE, BUFFER, IMAGE, TEXTURE, MATERIAL, MESH, NODE, ANIMATION, SKIN } public delegate void RefreshWindow(); public delegate void ProgressCallback(IMPORT_STEP step, int current, int total); private RefreshWindow _finishCallback; private ProgressCallback _progressCallback; protected const string Base64StringInitializer = "^data:[a-z-]+/[a-z-]+;base64,"; /// /// Constructor /// public GLTFEditorImporter() { Initialize(); } /// /// Constructors setting the delegate function to call after each iteration /// /// The function to call after each iteration (usually Repaint()) public GLTFEditorImporter(ProgressCallback progressCallback, RefreshWindow finish=null) { _progressCallback = progressCallback; _finishCallback = finish; Initialize(); } /// /// Initializes all the structures and objects /// public void Initialize() { _importedObjects = new Dictionary(); _skinIndexToGameObjects = new Dictionary>(); _isDone = true; _taskManager = new TaskManager(); _assetsToRemove = new List(); defaultMaterial = new UnityEngine.Material(Shader.Find("Standard")); } /// /// Setup importer for an import /// /// Absolute path to the glTF file to import /// Path in current project where to import the model /// Name of the model prefab to create public void setupForPath(string gltfPath, string importPath, string modelName, bool addScene=false) { _glTFPath = gltfPath; _gltfDirectoryPath = Path.GetDirectoryName(_glTFPath); _currentSampleName = modelName.Length > 0 ? modelName : "GLTFScene"; _projectDirectoryPath = importPath; _assetManager = new AssetManager(_projectDirectoryPath, _currentSampleName); _importedObjects.Clear(); _addToCurrentScene = addScene; _skinIndexToGameObjects.Clear(); } public void Update() { if(!_isDone) { if (_userStopped) { _userStopped = false; Clear(); _isDone = true; } else { if (_taskManager != null && _taskManager.play()) { // Do stuff } else { _isDone = true; finishImport(); } } } } public void Load(bool useGLTFMaterial=false) { _isDone = false; _userStopped = false; _useGLTFMaterial = useGLTFMaterial; LoadFile(); LoadGLTFScene(); } // Private private void checkValidity() { if (_taskManager == null) { _taskManager = new TaskManager(); } } private void LoadFile(int sceneIndex = -1) { _glTFData = File.ReadAllBytes(_glTFPath); _root = GLTFParser.ParseJson(_glTFData); } private void LoadGLTFScene(int sceneIndex = -1) { Scene scene; if (sceneIndex >= 0 && sceneIndex < _root.Scenes.Count) { scene = _root.Scenes[sceneIndex]; } else { scene = _root.GetDefaultScene(); } if (scene == null) { throw new Exception("No default scene in gltf file."); } _assetCache = new AssetCache( _root.Images != null ? _root.Images.Count : 0, _root.Textures != null ? _root.Textures.Count : 0, _root.Materials != null ? _root.Materials.Count : 0, _root.Buffers != null ? _root.Buffers.Count : 0, _root.Meshes != null ? _root.Meshes.Count : 0 ); // Load dependencies LoadBuffersEnum(); if (_root.Images != null) LoadImagesEnum(); if (_root.Textures != null) SetupTexturesEnum(); if (_root.Materials != null) LoadMaterialsEnum(); LoadMeshesEnum(); LoadSceneEnum(); if (_root.Animations != null && _root.Animations.Count > 0) LoadAnimationsEnum(); if (_root.Skins != null && _root.Skins.Count > 0) LoadSkinsEnum(); } private void LoadBuffersEnum() { _taskManager.addTask(LoadBuffers()); } private void LoadImagesEnum() { _taskManager.addTask(LoadImages()); } private void SetupTexturesEnum() { _taskManager.addTask(SetupTextures()); } private void LoadMaterialsEnum() { _taskManager.addTask(LoadMaterials()); } private void LoadMeshesEnum() { _taskManager.addTask(LoadMeshes()); } private void LoadSceneEnum() { _taskManager.addTask(LoadScene()); } private void LoadAnimationsEnum() { _taskManager.addTask(LoadAnimations()); } private void LoadSkinsEnum() { _taskManager.addTask(LoadSkins()); } private IEnumerator LoadBuffers() { if (_root.Buffers != null) { // todo add fuzzing to verify that buffers are before uri setProgress(IMPORT_STEP.BUFFER, 0, _root.Buffers.Count); for (int i = 0; i < _root.Buffers.Count; ++i) { GLTF.Schema.Buffer buffer = _root.Buffers[i]; if (buffer.Uri != null) { LoadBuffer(_gltfDirectoryPath, buffer, i); } else //null buffer uri indicates GLB buffer loading { byte[] glbBuffer; GLTFParser.ExtractBinaryChunk(_glTFData, i, out glbBuffer); _assetCache.BufferCache[i] = glbBuffer; } setProgress(IMPORT_STEP.BUFFER, (i + 1), _root.Buffers.Count); yield return null; } } } protected virtual void LoadBuffer(string sourceUri, GLTF.Schema.Buffer buffer, int bufferIndex) { if (buffer.Uri != null) { byte[] bufferData = null; var uri = buffer.Uri; var bufferPath = Path.Combine(sourceUri, uri); bufferData = File.ReadAllBytes(bufferPath); _assetCache.BufferCache[bufferIndex] = bufferData; } } private IEnumerator LoadImages() { for (int i = 0; i < _root.Images.Count; ++i) { Image image = _root.Images[i]; LoadImage(_gltfDirectoryPath, image, i); setProgress(IMPORT_STEP.IMAGE, (i + 1), _root.Images.Count); yield return null; } } private void LoadImage(string rootPath, Image image, int imageID) { if (_assetCache.ImageCache[imageID] == null) { if (image.Uri != null) { // Is base64 uri ? var uri = image.Uri; Regex regex = new Regex(Base64StringInitializer); Match match = regex.Match(uri); if (match.Success) { var base64Data = uri.Substring(match.Length); var textureData = Convert.FromBase64String(base64Data); _assetManager.registerImageFromData(textureData, imageID); } else if(File.Exists(Path.Combine(rootPath, uri))) // File is a real file { string imagePath = Path.Combine(rootPath, uri); _assetManager.copyAndRegisterImageInProject(imagePath, imageID); } else { Debug.Log("Image not found / Unknown image buffer"); } } else { var bufferView = image.BufferView.Value; var buffer = bufferView.Buffer.Value; var data = new byte[bufferView.ByteLength]; var bufferContents = _assetCache.BufferCache[bufferView.Buffer.Id]; System.Buffer.BlockCopy(bufferContents, bufferView.ByteOffset, data, 0, data.Length); _assetManager.registerImageFromData(data, imageID); } } } private IEnumerator SetupTextures() { for(int i = 0; i < _root.Textures.Count; ++i) { SetupTexture(_root.Textures[i], i); setProgress(IMPORT_STEP.TEXTURE, (i + 1), _root.Textures.Count); yield return null; } } private void SetupTexture(GLTF.Schema.Texture def, int textureIndex) { Texture2D source = _assetManager.getOrCreateTexture(def.Source.Id, textureIndex); // Default values var desiredFilterMode = FilterMode.Bilinear; var desiredWrapMode = UnityEngine.TextureWrapMode.Repeat; if (def.Sampler != null) { var sampler = def.Sampler.Value; switch (sampler.MinFilter) { case MinFilterMode.Nearest: desiredFilterMode = FilterMode.Point; break; case MinFilterMode.Linear: default: desiredFilterMode = FilterMode.Bilinear; break; } switch (sampler.WrapS) { case GLTF.Schema.WrapMode.ClampToEdge: desiredWrapMode = UnityEngine.TextureWrapMode.Clamp; break; case GLTF.Schema.WrapMode.Repeat: default: desiredWrapMode = UnityEngine.TextureWrapMode.Repeat; break; } } source.filterMode = desiredFilterMode; source.wrapMode = desiredWrapMode; _assetManager.registerTexture(source); } private IEnumerator LoadMaterials() { for(int i = 0; i < _root.Materials.Count; ++i) { CreateUnityMaterial(_root.Materials[i], i); setProgress(IMPORT_STEP.MATERIAL, (i + 1), _root.Materials.Count); yield return null; } } protected virtual void CreateUnityMaterial(GLTF.Schema.Material def, int materialIndex) { Extension specularGlossinessExtension = null; bool isSpecularPBR = def.Extensions != null && def.Extensions.TryGetValue("KHR_materials_pbrSpecularGlossiness", out specularGlossinessExtension); Shader shader = isSpecularPBR ? Shader.Find("Standard (Specular setup)") : Shader.Find("Standard"); var material = new UnityEngine.Material(shader); material.hideFlags = HideFlags.DontUnloadUnusedAsset; // Avoid material to be deleted while being built material.name = def.Name; //Transparency if (def.AlphaMode == AlphaMode.MASK) { GLTFUtils.SetupMaterialWithBlendMode(material, GLTFUtils.BlendMode.Cutout); material.SetFloat("_Mode", 1); material.SetFloat("_Cutoff", (float)def.AlphaCutoff); } else if (def.AlphaMode == AlphaMode.BLEND) { GLTFUtils.SetupMaterialWithBlendMode(material, GLTFUtils.BlendMode.Fade); material.SetFloat("_Mode", 3); } if (def.NormalTexture != null) { var texture = def.NormalTexture.Index.Id; Texture2D normalTexture = getTexture(texture) as Texture2D; //Automatically set it to normal map TextureImporter im = AssetImporter.GetAtPath(AssetDatabase.GetAssetPath(normalTexture)) as TextureImporter; im.textureType = TextureImporterType.NormalMap; im.SaveAndReimport(); material.SetTexture("_BumpMap", getTexture(texture)); material.SetFloat("_BumpScale", (float)def.NormalTexture.Scale); } if (def.EmissiveTexture != null) { material.EnableKeyword("EMISSION_MAP_ON"); var texture = def.EmissiveTexture.Index.Id; material.SetTexture("_EmissionMap", getTexture(texture)); material.SetInt("_EmissionUV", def.EmissiveTexture.TexCoord); } // PBR channels if (specularGlossinessExtension != null) { KHR_materials_pbrSpecularGlossinessExtension pbr = (KHR_materials_pbrSpecularGlossinessExtension)specularGlossinessExtension; material.SetColor("_Color", pbr.DiffuseFactor.ToUnityColor().gamma); if (pbr.DiffuseTexture != null) { var texture = pbr.DiffuseTexture.Index.Id; material.SetTexture("_MainTex", getTexture(texture)); } if (pbr.SpecularGlossinessTexture != null) { var texture = pbr.SpecularGlossinessTexture.Index.Id; material.SetTexture("_SpecGlossMap", getTexture(texture)); material.SetFloat("_GlossMapScale", (float)pbr.GlossinessFactor); material.SetFloat("_Glossiness", (float)pbr.GlossinessFactor); } else { material.SetFloat("_Glossiness", (float)pbr.GlossinessFactor); } Vector3 specularVec3 = pbr.SpecularFactor.ToUnityVector3(); material.SetColor("_SpecColor", new Color(specularVec3.x, specularVec3.y, specularVec3.z, 1.0f)); if (def.OcclusionTexture != null) { var texture = def.OcclusionTexture.Index.Id; material.SetFloat("_OcclusionStrength", (float)def.OcclusionTexture.Strength); material.SetTexture("_OcclusionMap", getTexture(texture)); } GLTFUtils.SetMaterialKeywords(material, GLTFUtils.WorkflowMode.Specular); } else if (def.PbrMetallicRoughness != null) { var pbr = def.PbrMetallicRoughness; material.SetColor("_Color", pbr.BaseColorFactor.ToUnityColor().gamma); if (pbr.BaseColorTexture != null) { var texture = pbr.BaseColorTexture.Index.Id; material.SetTexture("_MainTex", getTexture(texture)); } material.SetFloat("_Metallic", (float)pbr.MetallicFactor); material.SetFloat("_Glossiness", 1.0f - (float)pbr.RoughnessFactor); if (pbr.MetallicRoughnessTexture != null) { var texture = pbr.MetallicRoughnessTexture.Index.Id; UnityEngine.Texture2D inputTexture = getTexture(texture) as Texture2D; List splitTextures = splitMetalRoughTexture(inputTexture, def.OcclusionTexture != null, (float)pbr.MetallicFactor, (float)pbr.RoughnessFactor); material.SetTexture("_MetallicGlossMap", splitTextures[0]); if (def.OcclusionTexture != null) { material.SetFloat("_OcclusionStrength", (float)def.OcclusionTexture.Strength); material.SetTexture("_OcclusionMap", splitTextures[1]); } } GLTFUtils.SetMaterialKeywords(material, GLTFUtils.WorkflowMode.Metallic); } material.SetColor("_EmissionColor", def.EmissiveFactor.ToUnityColor().gamma); material = _assetManager.saveMaterial(material, materialIndex); _assetManager._parsedMaterials.Add(material); material.hideFlags = HideFlags.None; } public List splitMetalRoughTexture(Texture2D inputTexture, bool hasOcclusion, float metallicFactor, float roughnessFactor) { string inputTexturePath = AssetDatabase.GetAssetPath(inputTexture); if (!_assetsToRemove.Contains(inputTexturePath)) { _assetsToRemove.Add(inputTexturePath); } List outputs = new List(); #if true int width = inputTexture.width; int height = inputTexture.height; Color[] occlusion = new Color[width * height]; Color[] metalRough = new Color[width * height]; Color[] textureColors = new Color[width * height]; GLTFUtils.getPixelsFromTexture(ref inputTexture, out textureColors); for (int i = 0; i < height; ++i) { for (int j = 0; j < width; ++j) { float occ = textureColors[i * width + j].r; float rough = textureColors[i * width + j].g; float met = textureColors[i * width + j].b; occlusion[i * width + j] = new Color(occ, occ, occ, 1.0f); metalRough[i * width + j] = new Color(met * metallicFactor, met * metallicFactor, met * metallicFactor, (1.0f - rough) * roughnessFactor); } } Texture2D metalRoughTexture = new Texture2D(width, height, TextureFormat.ARGB32, true); metalRoughTexture.name = Path.GetFileNameWithoutExtension(inputTexturePath) + "_metal"; metalRoughTexture.SetPixels(metalRough); metalRoughTexture.Apply(); outputs.Add(_assetManager.saveTexture(metalRoughTexture)); if (hasOcclusion) { Texture2D occlusionTexture = new Texture2D(width, height); occlusionTexture.name = Path.GetFileNameWithoutExtension(inputTexturePath) + "_occlusion"; occlusionTexture.SetPixels(occlusion); occlusionTexture.Apply(); outputs.Add(_assetManager.saveTexture(occlusionTexture)); } // Delete original texture AssetDatabase.Refresh(); #else string inputTextureName = Path.GetFileNameWithoutExtension(inputTexturePath); string metalRoughPath = Path.Combine(_assetManager.getImportTextureDir(), inputTextureName + "_metal.png"); GLTFTextureUtils.extractMetalRough(inputTexture, metalRoughPath); outputs.Add(AssetDatabase.LoadAssetAtPath(GLTFUtils.getPathProjectFromAbsolute(metalRoughPath))); if (hasOcclusion) { string occlusionPath = Path.Combine(_assetManager.getImportTextureDir(), inputTextureName + "_occlusion.png"); GLTFTextureUtils.extractOcclusion(inputTexture, occlusionPath); outputs.Add(AssetDatabase.LoadAssetAtPath(GLTFUtils.getPathProjectFromAbsolute(occlusionPath))); } #endif return outputs; } private Texture2D getTexture(int index) { return _assetManager.getTexture(index); } private UnityEngine.Material getMaterial(int index) { return _assetManager.getMaterial(index); } private IEnumerator LoadMeshes() { for(int i = 0; i < _root.Meshes.Count; ++i) { CreateMeshObject(_root.Meshes[i], i); setProgress(IMPORT_STEP.MESH, (i + 1), _root.Meshes.Count); yield return null; } } protected virtual void CreateMeshObject(GLTF.Schema.Mesh mesh, int meshId) { for (int i = 0; i < mesh.Primitives.Count; ++i) { var primitive = mesh.Primitives[i]; CreateMeshPrimitive(primitive, mesh.Name, meshId, i); // Converted to mesh } } protected virtual void CreateMeshPrimitive(MeshPrimitive primitive, string meshName, int meshID, int primitiveIndex) { var meshAttributes = BuildMeshAttributes(primitive, meshID, primitiveIndex); var vertexCount = primitive.Attributes[SemanticProperties.POSITION].Value.Count; UnityEngine.Mesh mesh = new UnityEngine.Mesh { vertices = primitive.Attributes.ContainsKey(SemanticProperties.POSITION) ? meshAttributes[SemanticProperties.POSITION].AccessorContent.AsVertices.ToUnityVector3() : null, normals = primitive.Attributes.ContainsKey(SemanticProperties.NORMAL) ? meshAttributes[SemanticProperties.NORMAL].AccessorContent.AsNormals.ToUnityVector3() : null, uv = primitive.Attributes.ContainsKey(SemanticProperties.TexCoord(0)) ? meshAttributes[SemanticProperties.TexCoord(0)].AccessorContent.AsTexcoords.ToUnityVector2() : null, uv2 = primitive.Attributes.ContainsKey(SemanticProperties.TexCoord(1)) ? meshAttributes[SemanticProperties.TexCoord(1)].AccessorContent.AsTexcoords.ToUnityVector2() : null, uv3 = primitive.Attributes.ContainsKey(SemanticProperties.TexCoord(2)) ? meshAttributes[SemanticProperties.TexCoord(2)].AccessorContent.AsTexcoords.ToUnityVector2() : null, uv4 = primitive.Attributes.ContainsKey(SemanticProperties.TexCoord(3)) ? meshAttributes[SemanticProperties.TexCoord(3)].AccessorContent.AsTexcoords.ToUnityVector2() : null, colors = primitive.Attributes.ContainsKey(SemanticProperties.Color(0)) ? meshAttributes[SemanticProperties.Color(0)].AccessorContent.AsColors.ToUnityColor() : null, triangles = primitive.Indices != null ? meshAttributes[SemanticProperties.INDICES].AccessorContent.AsTriangles : MeshPrimitive.GenerateTriangles(vertexCount), tangents = primitive.Attributes.ContainsKey(SemanticProperties.TANGENT) ? meshAttributes[SemanticProperties.TANGENT].AccessorContent.AsTangents.ToUnityVector4(true) : null }; if (primitive.Attributes.ContainsKey(SemanticProperties.JOINT) && primitive.Attributes.ContainsKey(SemanticProperties.WEIGHT)) { Vector4[] bones = new Vector4[1]; Vector4[] weights = new Vector4[1]; LoadSkinnedMeshAttributes(meshID, primitiveIndex, ref bones, ref weights); if(bones.Length != mesh.vertices.Length || weights.Length != mesh.vertices.Length) { Debug.LogError("Not enough skinning data (bones:" + bones.Length + " weights:" + weights.Length + " verts:" + mesh.vertices.Length + ")"); return; } BoneWeight[] bws = new BoneWeight[mesh.vertices.Length]; int maxBonesIndex = 0; for (int i = 0; i < bws.Length; ++i) { // Unity seems expects the the sum of weights to be 1. float[] normalizedWeights = GLTFUtils.normalizeBoneWeights(weights[i]); bws[i].boneIndex0 = (int)bones[i].x; bws[i].weight0 = normalizedWeights[0]; bws[i].boneIndex1 = (int)bones[i].y; bws[i].weight1 = normalizedWeights[1]; bws[i].boneIndex2 = (int)bones[i].z; bws[i].weight2 = normalizedWeights[2]; bws[i].boneIndex3 = (int)bones[i].w; bws[i].weight3 = normalizedWeights[3]; maxBonesIndex = (int)Mathf.Max(maxBonesIndex, bones[i].x, bones[i].y, bones[i].z, bones[i].w); } mesh.boneWeights = bws; // initialize inverseBindMatrix array with identity matrix in order to output a valid mesh object Matrix4x4[] bindposes = new Matrix4x4[maxBonesIndex]; for(int j=0; j < maxBonesIndex; ++j) { bindposes[j] = Matrix4x4.TRS(Vector3.zero, Quaternion.identity, Vector3.one); } mesh.bindposes = bindposes; } if(primitive.Targets != null && primitive.Targets.Count > 0) { for (int b = 0; b < primitive.Targets.Count; ++b) { Vector3[] deltaVertices = new Vector3[primitive.Targets[b]["POSITION"].Value.Count]; Vector3[] deltaNormals = new Vector3[primitive.Targets[b]["POSITION"].Value.Count]; Vector3[] deltaTangents = new Vector3[primitive.Targets[b]["POSITION"].Value.Count]; if(primitive.Targets[b].ContainsKey("POSITION")) { NumericArray num = new NumericArray(); deltaVertices = primitive.Targets[b]["POSITION"].Value.AsVector3Array(ref num, _assetCache.BufferCache[0], false).ToUnityVector3(true); } if (primitive.Targets[b].ContainsKey("NORMAL")) { NumericArray num = new NumericArray(); deltaNormals = primitive.Targets[b]["NORMAL"].Value.AsVector3Array(ref num, _assetCache.BufferCache[0], true).ToUnityVector3(true); } //if (primitive.Targets[b].ContainsKey("TANGENT")) //{ // deltaTangents = primitive.Targets[b]["TANGENT"].Value.AsVector3Array(ref num, _assetCache.BufferCache[0], true).ToUnityVector3(true); //} mesh.AddBlendShapeFrame(GLTFUtils.buildBlendShapeName(meshID, b), 1.0f, deltaVertices, deltaNormals, deltaTangents); } } mesh.RecalculateBounds(); mesh.RecalculateTangents(); mesh = _assetManager.saveMesh(mesh, meshName + "_" + meshID + "_" + primitiveIndex); UnityEngine.Material material = primitive.Material != null && primitive.Material.Id >= 0 ? getMaterial(primitive.Material.Id) : defaultMaterial; _assetManager.addPrimitiveMeshData(meshID, primitiveIndex, mesh, material); } protected virtual Dictionary BuildMeshAttributes(MeshPrimitive primitive, int meshID, int primitiveIndex) { Dictionary attributeAccessors = new Dictionary(primitive.Attributes.Count + 1); foreach (var attributePair in primitive.Attributes) { AttributeAccessor AttributeAccessor = new AttributeAccessor() { AccessorId = attributePair.Value, Buffer = _assetCache.BufferCache[attributePair.Value.Value.BufferView.Value.Buffer.Id] }; attributeAccessors[attributePair.Key] = AttributeAccessor; } if (primitive.Indices != null) { AttributeAccessor indexBuilder = new AttributeAccessor() { AccessorId = primitive.Indices, Buffer = _assetCache.BufferCache[primitive.Indices.Value.BufferView.Value.Buffer.Id] }; attributeAccessors[SemanticProperties.INDICES] = indexBuilder; } GLTFHelpers.BuildMeshAttributes(ref attributeAccessors); return attributeAccessors; } private IEnumerator LoadScene(int sceneIndex = -1) { Scene scene; _nbParsedNodes = 0; if (sceneIndex >= 0 && sceneIndex < _root.Scenes.Count) { scene = _root.Scenes[sceneIndex]; } else { scene = _root.GetDefaultScene(); } if (scene == null) { throw new Exception("No default scene in gltf file."); } _sceneObject = createGameObject(_currentSampleName); foreach (var node in scene.Nodes) { var nodeObj = CreateNode(node.Value, node.Id); nodeObj.transform.SetParent(_sceneObject.transform, false); } yield return null; } private IEnumerator LoadAnimations() { for (int i = 0; i < _root.Animations.Count; ++i) { AnimationClip clip = new AnimationClip(); clip.wrapMode = UnityEngine.WrapMode.Loop; LoadAnimation(_root.Animations[i], i, clip); setProgress(IMPORT_STEP.ANIMATION, (i + 1), _root.Animations.Count); _assetManager.saveAnimationClip(clip); yield return null; } } private void LoadAnimation(GLTF.Schema.Animation gltfAnimation, int index, AnimationClip clip) { clip.name = gltfAnimation.Name != null && gltfAnimation.Name.Length > 0 ? gltfAnimation.Name : "GLTFAnimation_" + index; for(int i=0; i < gltfAnimation.Channels.Count; ++i) { AnimationChannel channel = gltfAnimation.Channels[i]; addGLTFChannelDataToClip(gltfAnimation.Channels[i], clip); } clip.EnsureQuaternionContinuity(); } private void addGLTFChannelDataToClip(GLTF.Schema.AnimationChannel channel, AnimationClip clip) { int animatedNodeIndex = channel.Target.Node.Id; if (!_importedObjects.ContainsKey(animatedNodeIndex)) { Debug.Log("Node '" + animatedNodeIndex + "' found for animation, aborting."); } Transform animatedNode = _importedObjects[animatedNodeIndex].transform; string nodePath = AnimationUtility.CalculateTransformPath(animatedNode, _sceneObject.transform); bool isStepInterpolation = channel.Sampler.Value.Interpolation != InterpolationType.LINEAR; byte[] timeBufferData = _assetCache.BufferCache[channel.Sampler.Value.Output.Value.BufferView.Value.Buffer.Id]; float[] times = GLTFHelpers.ParseKeyframeTimes(channel.Sampler.Value.Input.Value, timeBufferData); if (channel.Target.Path == GLTFAnimationChannelPath.translation || channel.Target.Path == GLTFAnimationChannelPath.scale) { byte[] bufferData = _assetCache.BufferCache[channel.Sampler.Value.Output.Value.BufferView.Value.Buffer.Id]; GLTF.Math.Vector3[] keyValues = GLTFHelpers.ParseVector3Keyframes(channel.Sampler.Value.Output.Value, bufferData); if (keyValues == null) return; Vector3[] values = keyValues.ToUnityVector3(); AnimationCurve[] vector3Curves = GLTFUtils.createCurvesFromArrays(times, values, isStepInterpolation, channel.Target.Path == GLTFAnimationChannelPath.translation); if (channel.Target.Path == GLTFAnimationChannelPath.translation) GLTFUtils.addTranslationCurvesToClip(vector3Curves, nodePath, ref clip); else GLTFUtils.addScaleCurvesToClip(vector3Curves, nodePath, ref clip); } else if (channel.Target.Path == GLTFAnimationChannelPath.rotation) { byte[] bufferData = _assetCache.BufferCache[channel.Sampler.Value.Output.Value.BufferView.Value.Buffer.Id]; Vector4[] values = GLTFHelpers.ParseRotationKeyframes(channel.Sampler.Value.Output.Value, bufferData).ToUnityVector4(); AnimationCurve[] rotationCurves = GLTFUtils.createCurvesFromArrays(times, values, isStepInterpolation); GLTFUtils.addRotationCurvesToClip(rotationCurves, nodePath, ref clip); } else if(channel.Target.Path == GLTFAnimationChannelPath.weights) { List morphTargets = new List(); int meshIndex = _root.Nodes[animatedNodeIndex].Mesh.Id; for(int i=0; i< _root.Meshes[meshIndex].Primitives[0].Targets.Count; ++i) { morphTargets.Add(GLTFUtils.buildBlendShapeName(meshIndex, i)); } byte[] bufferData = _assetCache.BufferCache[channel.Sampler.Value.Output.Value.BufferView.Value.Buffer.Id]; float[] values = GLTFHelpers.ParseKeyframeTimes(channel.Sampler.Value.Output.Value, bufferData); AnimationCurve[] morphCurves = GLTFUtils.buildMorphAnimationCurves(times, values, morphTargets.Count); GLTFUtils.addMorphAnimationCurvesToClip(morphCurves, nodePath, morphTargets.ToArray(), ref clip); } else { Debug.Log("Unsupported animation channel target: " + channel.Target.Path); } } private void setProgress(IMPORT_STEP step, int current, int total) { if (_progressCallback != null) _progressCallback(step, current, total); } private IEnumerator LoadSkins() { setProgress(IMPORT_STEP.SKIN, 0, _root.Skins.Count); for (int i = 0; i < _root.Skins.Count; ++i) { LoadSkin(_root.Skins[i], i); setProgress(IMPORT_STEP.SKIN, (i + 1), _root.Skins.Count); yield return null; } } private void LoadSkin(GLTF.Schema.Skin skin, int index) { Transform[] boneList = new Transform[skin.Joints.Count]; for (int i = 0; i < skin.Joints.Count; ++i) { boneList[i] = _importedObjects[skin.Joints[i].Id].transform; } foreach (SkinnedMeshRenderer skinMesh in _skinIndexToGameObjects[index]) { skinMesh.bones = boneList; } } private void BuildSkinnedMesh(GameObject nodeObj, GLTF.Schema.Skin skin, int meshIndex, int primitiveIndex) { if(skin.InverseBindMatrices.Value.Count == 0) return; SkinnedMeshRenderer skinMesh = nodeObj.AddComponent(); skinMesh.sharedMesh = _assetManager.getMesh(meshIndex, primitiveIndex); skinMesh.sharedMaterial = _assetManager.getMaterial(meshIndex, primitiveIndex); byte[] bufferData = _assetCache.BufferCache[skin.InverseBindMatrices.Value.BufferView.Value.Buffer.Id]; NumericArray content = new NumericArray(); List bindPoseMatrices = new List(); GLTF.Math.Matrix4x4[] inverseBindMatrices = skin.InverseBindMatrices.Value.AsMatrixArray(ref content, bufferData); foreach (GLTF.Math.Matrix4x4 mat in inverseBindMatrices) { bindPoseMatrices.Add(mat.ToUnityMatrix().switchHandedness()); } skinMesh.sharedMesh.bindposes = bindPoseMatrices.ToArray(); if(skin.Skeleton != null && _importedObjects.ContainsKey(skin.Skeleton.Id)) skinMesh.rootBone = skin.Skeleton == null ? _importedObjects[skin.Skeleton.Id].transform : null; } protected virtual void LoadSkinnedMeshAttributes(int meshIndex, int primitiveIndex, ref Vector4[] boneIndexes, ref Vector4[] weights) { GLTF.Schema.MeshPrimitive prim = _root.Meshes[meshIndex].Primitives[primitiveIndex]; if (!prim.Attributes.ContainsKey(SemanticProperties.JOINT) || !prim.Attributes.ContainsKey(SemanticProperties.WEIGHT)) return; parseAttribute(ref prim, SemanticProperties.JOINT, ref boneIndexes); parseAttribute(ref prim, SemanticProperties.WEIGHT, ref weights); foreach(Vector4 wei in weights) { wei.Normalize(); } } private void parseAttribute(ref GLTF.Schema.MeshPrimitive prim, string property, ref Vector4[] values) { byte[] bufferData = _assetCache.BufferCache[prim.Attributes[property].Value.BufferView.Value.Buffer.Id]; NumericArray num = new NumericArray(); GLTF.Math.Vector4[] gltfValues = prim.Attributes[property].Value.AsVector4Array(ref num, bufferData); values = new Vector4[gltfValues.Length]; for (int i = 0; i < gltfValues.Length; ++i) { values[i] = gltfValues[i].ToUnityVector4(); } } protected virtual GameObject CreateNode(Node node, int index) { var nodeObj = createGameObject(node.Name != null && node.Name.Length > 0 ? node.Name : "GLTFNode_" + index); _nbParsedNodes++; setProgress(IMPORT_STEP.NODE, _nbParsedNodes, _root.Nodes.Count); Vector3 position; Quaternion rotation; Vector3 scale; node.GetUnityTRSProperties(out position, out rotation, out scale); nodeObj.transform.localPosition = position; nodeObj.transform.localRotation = rotation; nodeObj.transform.localScale = scale; bool isSkinned = node.Skin != null && isValidSkin(node.Skin.Id); bool hasMorphOnly = node.Skin == null && node.Mesh != null && node.Mesh.Value.Weights != null && node.Mesh.Value.Weights.Count != 0; if (node.Mesh != null) { if (isSkinned) // Mesh is skinned (it can also have morph) { if (!_skinIndexToGameObjects.ContainsKey(node.Skin.Id)) _skinIndexToGameObjects[node.Skin.Id] = new List(); BuildSkinnedMesh(nodeObj, node.Skin.Value, node.Mesh.Id, 0); _skinIndexToGameObjects[node.Skin.Id].Add(nodeObj.GetComponent()); } else if (hasMorphOnly) { SkinnedMeshRenderer smr = nodeObj.AddComponent(); smr.sharedMesh = _assetManager.getMesh(node.Mesh.Id, 0); smr.sharedMaterial = _assetManager.getMaterial(node.Mesh.Id, 0); } else { // If several primitive, create several nodes and add them as child of this current Node MeshFilter meshFilter = nodeObj.AddComponent(); meshFilter.sharedMesh = _assetManager.getMesh(node.Mesh.Id, 0); MeshRenderer meshRenderer = nodeObj.AddComponent(); meshRenderer.material = _assetManager.getMaterial(node.Mesh.Id, 0); } for(int i = 1; i < _assetManager._parsedMeshData[node.Mesh.Id].Count; ++i) { GameObject go = createGameObject(node.Name ?? "GLTFNode_" + i); if (isSkinned) { BuildSkinnedMesh(go, node.Skin.Value, node.Mesh.Id, i); _skinIndexToGameObjects[node.Skin.Id].Add(go.GetComponent()); } else if (hasMorphOnly) { SkinnedMeshRenderer smr = go.AddComponent(); smr.sharedMesh = _assetManager.getMesh(node.Mesh.Id, i); smr.sharedMaterial = _assetManager.getMaterial(node.Mesh.Id, i); } else { MeshFilter mf = go.AddComponent(); mf.sharedMesh = _assetManager.getMesh(node.Mesh.Id, i); MeshRenderer mr = go.AddComponent(); mr.material = _assetManager.getMaterial(node.Mesh.Id, i); } go.transform.SetParent(nodeObj.transform, false); } } if (node.Children != null) { foreach (var child in node.Children) { var childObj = CreateNode(child.Value, child.Id); childObj.transform.SetParent(nodeObj.transform, false); } } _importedObjects.Add(index, nodeObj); return nodeObj; } private GameObject createGameObject(string name) { name = GLTFUtils.cleanName(name); return _assetManager.createGameObject(name); } private bool isValidSkin(int skinIndex) { if (skinIndex >= _root.Skins.Count) return false; Skin glTFSkin = _root.Skins[skinIndex]; return glTFSkin.Joints.Count > 0 && glTFSkin.Joints.Count == glTFSkin.InverseBindMatrices.Value.Count; } private void finishImport() { GameObject prefab = _assetManager.savePrefab(_sceneObject, _projectDirectoryPath, _addToCurrentScene); if (_finishCallback != null) _finishCallback(); Clear(); if (_addToCurrentScene == true) { // Select and focus imported object _sceneObject = PrefabUtility.InstantiatePrefab(prefab) as GameObject; GameObject[] obj = new GameObject[1]; obj[0] = _sceneObject; Selection.objects = obj; #if UNITY_2017 EditorApplication.ExecuteMenuItem("Edit/Frame Selected"); #endif } } /// /// Call this to abort current import /// public void abortImport() { if (!_isDone) { _userStopped = true; } } /// /// Cleans all generated files and structures /// /// public void softClean() { if (_assetManager != null) _assetManager.softClean(); _taskManager.clear(); Resources.UnloadUnusedAssets(); } public void Clear() { if (_assetManager != null) _assetManager.softClean(); _taskManager.clear(); Resources.UnloadUnusedAssets(); } private void cleanObjects() { foreach (GameObject ob in _importedObjects.Values) { GameObject.DestroyImmediate(ob); } GameObject.DestroyImmediate(_sceneObject); _sceneObject = null; _assetManager.softClean(); } public void OnDestroy() { Clear(); } } } class TaskManager { List _tasks; IEnumerator _current = null; public TaskManager() { _tasks = new List(); } public void addTask(IEnumerator task) { _tasks.Add(task); } public void clear() { _tasks.Clear(); } public bool play() { if(_tasks.Count > 0) { if (_current == null || !_current.MoveNext()) { _current = _tasks[0]; _tasks.RemoveAt(0); } } if (_current != null) _current.MoveNext(); if (_current != null && !_current.MoveNext() && _tasks.Count == 0) return false; return true; } }