/// <summary> /// Export to a file asynchronously. /// </summary> /// <remarks> /// This method must be used as a coroutine. Failure to do so whould result in no export whatsoever. In particular, /// for successful export, the returned iterator must be itered over until it's end. /// </remarks> /// <param name="root">The root gameobject of the hierarchy to export.</param> /// <param name="filename">The file name and path to export to.</param> /// <param name="return_callback">The callback that will be called on export completion.</param> /// <param name="progress_callback">The callback that will be called periodically during export to notify current progress.</param> /// <returns>An iterator to use in a coroutine.</returns> public IEnumerator Export(GameObject root, string filename, Module.ExporterSuccessCallback return_callback, Module.ProgressCallback progress_callback = null) { if (isDisposed) { throw new ObjectDisposedException(GetType().FullName); } Module.ProgressCallback progress1 = null; Module.ProgressCallback progress2 = null; if (progress_callback != null) { progress1 = p => progress_callback(p * Importer.unityLoadingPercentage); progress2 = p => progress_callback(p * (1f - Importer.unityLoadingPercentage) + Importer.unityLoadingPercentage); } string extension = Path.GetExtension(filename).Remove(0, 1).ToLower(); Module.IExporter module; if (extensionHandler.TryGetValue(extension, out module)) { return(Export((scene, success) => module.ExportToFile(scene, filename, success, progress2), root, filename, return_callback, progress1)); } else { Debug.LogErrorFormat("Unsupported format with extension '{0}'. No exporter is registered for this format.", extension); } return(null); }
/// <summary> /// Import a file asynchronously from a given byte array as source. /// </summary> /// <remarks> /// This method must be used as a coroutine. Failure to do so whould result in no import whatsoever. In particular, /// for successful import, the returned iterator must be itered over until it's end. /// /// The file name is used mainly to determine wich importer to use based on the extension, /// as well as for logging and error reporting purposes. /// </remarks> /// <param name="filename">The name of the file to import.</param> /// <param name="data">The array containg the data to import.</param> /// <param name="return_callback">The callback that will be called on import completion.</param> /// <param name="progress_callback">The callback that will be called periodically during import to notify current progress.</param> /// <returns>An iterator to use in a coroutine.</returns> public IEnumerator Import(string filename, byte[] data, ReturnCallback return_callback, Module.ProgressCallback progress_callback = null) { if (isDisposed) { throw new ObjectDisposedException(GetType().FullName); } Module.ProgressCallback progress1 = null; Module.ProgressCallback progress2 = null; if (progress_callback != null) { progress1 = p => progress_callback(p * (1f - unityLoadingPercentage)); progress2 = p => progress_callback(p * unityLoadingPercentage + (1f - unityLoadingPercentage)); } string extension = Path.GetExtension(filename).Remove(0, 1).ToLower(); Module.IImporter module; if (extensionHandler.TryGetValue(extension, out module)) { return(Import(result => module.ImportFromBytes(filename, data, result, progress1), filename, return_callback, progress2)); } else { Debug.LogErrorFormat("Unsupported format with extension '{0}'. No importer is registered for this format.", extension); } return(null); }
private static void InitProgress(Module.Import.Assimp.Context context, Module.ProgressCallback callback, aiScene scene) { uint nb_textures = 0; using (aiMaterialArray assimp_materials = scene.Materials) { uint nb_materials = assimp_materials.Size(); for (uint i = 0; i < nb_materials; i++) { using (aiMaterial material = assimp_materials.Get(i)) { foreach (KeyValuePair <string, aiTextureType> pair in Assimp.Convert.textureTypes) { using (aiString texture_name = new aiString()) { if (material.GetTexturePath(pair.Value, 0, texture_name)) { nb_textures++; } } } } } } uint nb_steps = Node.ASSIMP_PROGRESS_FACTOR * CountNodes(scene.mRootNode); nb_steps += Mesh.ASSIMP_PROGRESS_FACTOR * scene.Meshes.Size(); nb_steps += Material.ASSIMP_PROGRESS_FACTOR * scene.Materials.Size(); nb_steps += Texture.ASSIMP_PROGRESS_FACTOR * nb_textures; nb_steps = (uint)(nb_steps / (1f - assimpNativeLoadingPrecentage)); context.progress.Init(nb_steps, callback); }
private static void InitProgress(Module.Export.Assimp.Context context, Module.ProgressCallback callback, Scene scene) { uint nb_steps = Node.ASSIMP_PROGRESS_FACTOR * CountNodes(scene.root_node); nb_steps += Mesh.ASSIMP_PROGRESS_FACTOR * (uint)(scene.meshes != null ? scene.meshes.Length : 0); nb_steps += Material.ASSIMP_PROGRESS_FACTOR * (uint)(scene.materials != null ? scene.materials.Length : 0); nb_steps += Texture.ASSIMP_PROGRESS_FACTOR * (uint)(scene.textures != null ? scene.textures.Length : 0); context.progress.Init(nb_steps, callback); }
private IEnumerator Export(Func <Type.Scene, Module.ExporterSuccessCallback, IEnumerator> exporter, GameObject root, string filename, Module.ExporterSuccessCallback return_callback, Module.ProgressCallback progress_callback = null) { bool success = false; bool waiting; do { lock (this) { if (exporting) { waiting = true; } else { exporting = true; waiting = false; } } if (waiting) { yield return(null); } }while(waiting); if (exporter != null) { int refresh_rate = Screen.currentResolution.refreshRate; float max_frame_duration = 1000.0f * 0.75f * (1.0f / (float)(refresh_rate >= 20 ? refresh_rate : 60)); // In milliseconds. Use only 75% of the available time to avoid missing vsync events // Create timer to break the code that must be executed in unity thread into chunks that will fit into the required target framerate System.Diagnostics.Stopwatch timer = new System.Diagnostics.Stopwatch(); DateTime start = DateTime.Now; Type.Scene scene = null; // Get data from Unity IEnumerator it = Type.Scene.FromUnity(root, s => scene = s, progress_callback); timer.Start(); // Split code executed in unity thread into chunks that allow to maintain targeted framerate, // without loosing unnecessary time by yielding every time possible (because 1 yield <=> 1 frame) while (it.MoveNext()) { if (timer.ElapsedMilliseconds >= max_frame_duration) { yield return(null); timer.Reset(); timer.Start(); } } if (scene != null) { // Export data to final format it = exporter(scene, s => success = s); while (it.MoveNext()) { yield return(it.Current); } DateTime end = DateTime.Now; if (success) { Debug.LogFormat("Export successful: {0}.", end.Subtract(start)); } else { Debug.LogErrorFormat("Export to '{0}' failed.", filename); } } else { Debug.LogErrorFormat("Export to '{0}' failed.", filename); } } else { Debug.LogError("Invalid null exporter."); } // Ready to accept new exports lock (this) { exporting = false; } if (return_callback != null) { return_callback(success); } }
public static IEnumerator FromAssimp(Module.Import.Assimp.Context context, global::Assimp.Importer importer, Func <aiScene> loader, Module.ImporterReturnCallback return_callback, Module.ProgressCallback progress_callback) { if (progress_callback != null) { progress_callback(0.01f); } aiScene scene = null; // Async loading does not work in editor when not in play mode context.threads.AddTask(() => scene = loader()); IEnumerator it = context.threads.WaitForTasksCompletion(); while (it.MoveNext()) { context.progress.Display(); yield return(it.Current); } if (scene != null) { InitProgress(context, progress_callback, scene); context.progress.Update((uint)(assimpNativeLoadingPrecentage * context.progress.TotalSteps)); context.scene = new Scene(); context.scene.assimpTextures = new Dictionary <string, CLARTE.Backport.Tuple <Texture, uint> >(); context.threads.AddTask(() => Mesh.FromAssimp(context, scene)); context.threads.AddTask(() => Material.FromAssimp(context, scene)); context.threads.AddTask(() => context.scene.root_node = Node.FromAssimp(context, scene, scene.mRootNode)); it = context.threads.WaitForTasksCompletion(); while (it.MoveNext()) { context.progress.Display(); yield return(it.Current); } // Assign materials to meshes now that all data is available context.threads.AddTask(() => Node.SetAssimpMeshesMaterials(context.scene, context.scene.root_node)); // Assign textures to final array context.threads.AddTask(() => { context.scene.textures = new Texture[context.scene.assimpTextures.Count]; foreach (KeyValuePair <string, CLARTE.Backport.Tuple <Texture, uint> > pair in context.scene.assimpTextures) { context.scene.textures[pair.Value.Item2] = pair.Value.Item1; } }); // We can safelly dispose of the scene because all tasks must have been completed to reach this point, therefore we do not risque deallocating data used in another thread. // this method can take a few hundred milliseconds, therefore, we do it async and wait for it to complete before allowing new imports. context.threads.AddTask(scene.Dispose); context.threads.AddTask(importer.FreeScene); it = context.threads.WaitForTasksCompletion(); while (it.MoveNext()) { context.progress.Display(); yield return(it.Current); } // Clean up context.scene.assimpTextures = null; if (return_callback != null) { return_callback(context.scene); } context.Clean(); } else { Debug.LogErrorFormat("Failed to open file: {0}{1}{2}.\nThe importer reported the following error: {3}", context.path, System.IO.Path.PathSeparator, context.filename, importer.GetErrorString()); } }
public IEnumerator ToAssimp(Module.Export.Assimp.Context context, string filename, aiPostProcessSteps steps, Module.ExporterSuccessCallback return_callback, Module.ProgressCallback progress_callback) { bool success = false; string extension = System.IO.Path.GetExtension(filename).Remove(0, 1).ToLower(); uint export_format_count = context.exporter.GetExportFormatCount(); bool found_exporter = false; for (uint i = 0; i < export_format_count; i++) { using (aiExportFormatDesc desc = context.exporter.GetExportFormatDescription(i)) { if (extension == desc.fileExtension.ToLower()) { using (aiScene scene = new aiScene()) { InitProgress(context, progress_callback, this); context.scene = scene; // Export nodes IResult nodes_result = context.threads.AddTask(() => { using (aiNode root = root_node.ToAssimp(context, this, null)) { scene.mRootNode = root.Unmanaged(); } }); // Export materials. context.threads.AddTask(() => Material.ToAssimp(context, this)); // We must wait for all the nodes to be processed before exporting meshes because indexes are computed during parsing. while (!nodes_result.Done) { context.progress.Display(); yield return(null); } // Export meshes context.threads.AddTask(() => Mesh.ToAssimp(context, this)); // Wait for all tasks to be completed IEnumerator it = context.threads.WaitForTasksCompletion(); while (it.MoveNext()) { context.progress.Display(); yield return(it.Current); } // Do the final export using Assimp now that we created the complete structure in the C++ DLL. Result <aiReturn> status = context.threads.AddTask(() => context.exporter.Export(scene, desc.id, filename, steps)); // Wait for export to complete while (!status.Done) { context.progress.Display(); yield return(null); } if (progress_callback != null) { progress_callback(1f); } context.Clean(); // Check export status if (status.Success && status.Value == aiReturn.aiReturn_SUCCESS) { success = true; } else { Debug.LogErrorFormat("Failed to export to: {0}. \nThe exporter reported the following error: {1}", filename, context.exporter.GetErrorString()); } } found_exporter = true; break; } } } if (!found_exporter) { Debug.LogErrorFormat("No exporter for format '{0}' was found in Assimp.", extension); } if (return_callback != null) { return_callback(success); } }
public IEnumerator ToUnity(Module.ProgressCallback progress_callback = null) { unityRoot = null; if (root_node != null) { GameObject node_template = new GameObject("Node"); GameObject mesh_template = new GameObject("Mesh"); mesh_template.AddComponent <MeshFilter>(); mesh_template.AddComponent <MeshRenderer>(); unityMapping = new Mapping(); unityReferences = new HashSet <UnityReference>(); uint nb_steps = CountNodes(root_node); nb_steps += (uint)(meshes != null ? meshes.Length : 0); nb_steps += (uint)(materials != null ? materials.Length : 0); nb_steps += (uint)(textures != null ? textures.Length : 0); Utils.Progress progress = new Utils.Progress(); progress.Init(nb_steps, progress_callback); IEnumerator it = root_node.ToUnity(this, null, node_template, mesh_template, progress); while (it.MoveNext()) { progress.Display(); yield return(it.Current); } // Destroy templates if (Application.isPlaying) { UnityEngine.Object.Destroy(node_template); UnityEngine.Object.Destroy(mesh_template); } else { UnityEngine.Object.DestroyImmediate(node_template); UnityEngine.Object.DestroyImmediate(mesh_template); } if (root_node.UnityNodes != null && root_node.UnityNodes.Length > 0) { GameObject[] nodes = root_node.UnityNodes; // This can happen if we have a single original mesh splitted into multiple unity meshes if (nodes.Length > 1) { unityRoot = new GameObject(nodes[0].name); // Regiter an ID for this object unityMapping.Add(unityRoot, unityMapping.GetNewId()); unityRoot.SetActive(false); unityRoot.transform.position = nodes[0].transform.position; unityRoot.transform.rotation = nodes[0].transform.rotation; unityRoot.transform.localScale = nodes[0].transform.localScale; foreach (GameObject go in nodes) { go.transform.parent = unityRoot.transform; go.SetActive(root_node.Active); } } else { unityRoot = nodes[0]; } } // Resolve references foreach (UnityReference reference in unityReferences) { reference.ToUnity(this); } if (unityRoot != null) { unityRoot.SetActive(root_node.Active); } // Clean up unityReferences = null; } }
public static IEnumerator FromUnity(GameObject root, Action <Scene> callback, Module.ProgressCallback progress_callback = null) { if (root != null && callback != null) { Scene scene = new Scene(); scene.unityMapping = new Mapping(); scene.unityMeshes = new Dictionary <UnityEngine.Mesh, int>(); scene.unityMaterials = new Dictionary <UnityEngine.Material, int>(); scene.unityTextures = new Dictionary <Texture2D, int>(); scene.unityComponents = new Dictionary <Component, UnityComponent>(); scene.unityReferences = new HashSet <UnityReference>(); uint nb_nodes = CountNodes(root.transform); Utils.Progress progress = new Utils.Progress(); progress.Init(nb_nodes, progress_callback); // Parse the node hierarchy IEnumerator it = Node.FromUnity(scene, root.transform, n => scene.root_node = n, progress); while (it.MoveNext()) { progress.Display(); yield return(it.Current); } int meshes_count = scene.unityMeshes.Count; int materials_count = scene.unityMaterials.Count; int textures_count = scene.unityTextures.Count; progress.Init((uint)(nb_nodes + meshes_count + materials_count + textures_count), progress_callback); progress.Update(nb_nodes); // Convert the quick lookup meshes dictionary to the final destination array if (meshes_count > 0) { Mesh[] meshes = new Mesh[meshes_count]; foreach (KeyValuePair <UnityEngine.Mesh, int> mesh_pair in scene.unityMeshes) { meshes[mesh_pair.Value] = Mesh.FromUnity(mesh_pair.Key); progress.Update(1); progress.Display(); yield return(null); } scene.meshes = meshes; } // Convert the quick lookup materials dictionary to the final destination array if (materials_count > 0) { Material[] materials = new Material[materials_count]; foreach (KeyValuePair <UnityEngine.Material, int> mat_pair in scene.unityMaterials) { materials[mat_pair.Value] = Material.FromUnity(scene, mat_pair.Key); progress.Update(1); progress.Display(); yield return(null); } scene.materials = materials; } // Convert the quick lookup materials dictionary to the final destination array if (textures_count > 0) { Texture[] textures = new Texture[textures_count]; foreach (KeyValuePair <Texture2D, int> tex_pair in scene.unityTextures) { textures[tex_pair.Value] = Texture.FromUnity(tex_pair.Key); progress.Update(1); progress.Display(); yield return(null); } scene.textures = textures; } // Resolve references foreach (UnityReference reference in scene.unityReferences) { reference.ResolveReference(scene.unityMapping, scene.unityMeshes, scene.unityMaterials, scene.unityTextures); } // Clean up scene.unityMapping = null; scene.unityMeshes = null; scene.unityMaterials = null; scene.unityTextures = null; scene.unityComponents = null; scene.unityReferences = null; // Return the result callback(scene); } }
private IEnumerator Import(Func <Module.ImporterReturnCallback, IEnumerator> importer, string filename, ReturnCallback return_callback, Module.ProgressCallback progress_callback = null) { bool waiting; do { lock (this) { if (importing) { waiting = true; } else { importing = true; waiting = false; } } if (waiting) { yield return(null); } }while(waiting); if (importer != null) { int refresh_rate = Screen.currentResolution.refreshRate; float max_frame_duration = 1000.0f * 0.75f * (1.0f / (float)(refresh_rate >= 20 ? refresh_rate : 60)); // In milliseconds. Use only 75% of the available time to avoid missing vsync events // Create timer to break the code that must be executed in unity thread into chunks that will fit into the required target framerate System.Diagnostics.Stopwatch timer = new System.Diagnostics.Stopwatch(); DateTime start = DateTime.Now; Type.Scene scene = null; // Import data IEnumerator it = importer(s => scene = s); while (it.MoveNext()) { yield return(it.Current); } if (scene != null) { // Convert data to Unity format it = scene.ToUnity(progress_callback); timer.Start(); // Split code executed in unity thread into chunks that allow to maintain targeted framerate, // without loosing unnecessary time by yielding every time possible (because 1 yield <=> 1 frame) while (it.MoveNext()) { if (timer.ElapsedMilliseconds >= max_frame_duration) { yield return(null); timer.Reset(); timer.Start(); } } DateTime end = DateTime.Now; // Add diagnostic info if (scene.UnityRoot != null) { int vertices_loaded = 0; int faces_loaded = 0; foreach (Type.Mesh mesh in scene.meshes) { vertices_loaded += mesh.VerticesCount; faces_loaded += mesh.FacesCount; } scene.UnityRoot.AddComponent <Info>().Init(filename, end.Subtract(start), vertices_loaded, faces_loaded, scene.IdMapping.Id2Go); } if (return_callback != null) { return_callback(scene.UnityRoot); } } else { Debug.LogErrorFormat("Import of '{0}' failed.", filename); } } else { Debug.LogError("Invalid null importer."); } // Ready to accept new imports lock (this) { importing = false; } }