using System; using System.Collections; using System.Collections.Generic; using System.IO; using UnityEngine; using UnityEngine.Networking; using System.Threading.Tasks; using static BrainFailProductions.PolyFewRuntime.PolyfewRuntime; #if UNITY_EDITOR using UnityEditor; #endif /// /// Asynchronous importer and loader /// namespace BrainFailProductions.PolyFew.AsImpL { /// /// Abstract loader to be used as a base class for specific loaders. /// public abstract class Loader : MonoBehaviour { /// /// Total loading progress, for all the models currently loading. /// public static LoadingProgress totalProgress = new LoadingProgress(); /// /// Options to define how the model will be loaded and imported. /// public ImportOptions buildOptions; public ReferencedNumeric individualProgress = new ReferencedNumeric(0); #if UNITY_EDITOR /// /// Alternative texture path: if not null textures will be loaded from here. /// public string altTexPath = null; #endif // raw subdivision in percentages of the loading phases (empirically computed loading a large sample OBJ file) // TODO: refine or change this method protected static float LOAD_PHASE_PERC = 8f; protected static float TEXTURE_PHASE_PERC = 1f; protected static float MATERIAL_PHASE_PERC = 1f; protected static float BUILD_PHASE_PERC = 90f; protected static Dictionary loadedModels = new Dictionary(); protected static Dictionary instanceCount = new Dictionary(); protected DataSet dataSet = new DataSet(); protected ObjectBuilder objectBuilder = new ObjectBuilder(); protected List materialData; protected SingleLoadingProgress objLoadingProgress = new SingleLoadingProgress(); protected Stats loadStats; private Texture2D loadedTexture = null; /// /// Load the file assuming its vertical axis is Z instead of Y /// public bool ConvertVertAxis { get { return buildOptions != null ? buildOptions.zUp : false; } set { if (buildOptions == null) { buildOptions = new ImportOptions(); } buildOptions.zUp = value; } } /// /// Rescaling for the model (1 = no rescaling) /// public float Scaling { get { return buildOptions != null ? buildOptions.modelScaling : 1f; } set { if (buildOptions == null) { buildOptions = new ImportOptions(); } buildOptions.modelScaling = value; } } /// /// Check if a material library is defined for this model /// protected abstract bool HasMaterialLibrary { get; } #if UNITY_EDITOR /// /// Import data as assets in the project (Editor only) /// public bool ImportingAssets { get { return !string.IsNullOrEmpty(altTexPath); } } #endif /// /// Event triggered when an object is created. /// public event Action ModelCreated; /// /// Event triggered when an object is successfully loaded. /// public event Action ModelLoaded; /// /// Event triggered if failed to load an object /// public event Action ModelError; /// /// Get a previusly loaded model by its absolute path /// /// absolute path used to load the model /// The game object previously loaded public static GameObject GetModelByPath(string absolutePath) { if (loadedModels.ContainsKey(absolutePath)) { return loadedModels[absolutePath]; } return null; } /// /// Load a model. /// /// name of the GameObject, if empty use file name /// absolute file path /// Transform to which attach the loaded object (null=scene) /// You can use StartCoroutine( loader.Load(...) ) public async Task Load(string objName, string objAbsolutePath, Transform parentObj, string texturesFolderPath = "", string materialsFolderPath = "") { string fileName = Path.GetFileName(objAbsolutePath); string fileNameNoExt = Path.GetFileNameWithoutExtension(objAbsolutePath); string name = objName; if (name == null || name == "") objName = fileNameNoExt; totalProgress.singleProgress.Add(objLoadingProgress); objLoadingProgress.fileName = fileName; objLoadingProgress.error = false; objLoadingProgress.message = "Loading " + fileName + "..."; //Debug.LogFormat("Loading {0}\n from: {1}...", objName, absolutePath); await Task.Yield(); //yield return null; // TODO: implementation of a caching mechanism for models downloaded from an URL /* // if the model was already loaded duplicate the existing object if (buildOptions != null && buildOptions.reuseLoaded && loadedModels.ContainsKey(absolutePath) && loadedModels[absolutePath] != null) { Debug.LogFormat("File {0} already loaded, creating instance.", absolutePath); instanceCount[absolutePath]++; if (name == null || name == "") objName = objName + "_" + instanceCount[absolutePath]; objLoadingProgress.message = "Instantiating " + objName + "..."; while (loadedModels[absolutePath] == null) { await Task.Yield(); //yield return null; } GameObject newObj = Instantiate(loadedModels[absolutePath]); //yield return newObj; OnCreated(newObj, absolutePath); newObj.name = objName; if (parentObj != null) newObj.transform.parent = parentObj.transform; totalProgress.singleProgress.Remove(objLoadingProgress); OnLoaded(newObj, absolutePath); return; //yield break; } */ loadedModels[objAbsolutePath] = null; // define a key for the dictionary instanceCount[objAbsolutePath] = 0; // define a key for the dictionary float lastTime = Time.realtimeSinceStartup; float startTime = lastTime; await LoadModelFile(objAbsolutePath, texturesFolderPath, materialsFolderPath); loadStats.modelParseTime = Time.realtimeSinceStartup - lastTime; if (objLoadingProgress.error) { OnLoadFailed(objAbsolutePath); return null; } lastTime = Time.realtimeSinceStartup; if (HasMaterialLibrary) { await LoadMaterialLibrary(objAbsolutePath, materialsFolderPath); } loadStats.materialsParseTime = Time.realtimeSinceStartup - lastTime; lastTime = Time.realtimeSinceStartup; await Build(objAbsolutePath, objName, parentObj, texturesFolderPath); loadStats.buildTime = Time.realtimeSinceStartup - lastTime; loadStats.totalTime = Time.realtimeSinceStartup - startTime; /* Debug.Log("Done: " + objName + "\n Loaded in " + loadStats.totalTime + " seconds" + "\n Model data parsed in " + loadStats.modelParseTime + " seconds" + "\n Material data parsed in " + loadStats.materialsParseTime + " seconds" + "\n Game objects built in " + loadStats.buildTime + " seconds" + "\n textures: " + loadStats.buildStats.texturesTime + " seconds" + "\n materials: " + loadStats.buildStats.materialsTime + " seconds" + "\n objects: " + loadStats.buildStats.objectsTime + " seconds" ); */ totalProgress.singleProgress.Remove(objLoadingProgress); OnLoaded(loadedModels[objAbsolutePath], objAbsolutePath); return loadedModels[objAbsolutePath]; } public async Task LoadFromNetwork(string objURL, string diffuseTexURL, string bumpTexURL, string specularTexURL, string opacityTexURL, string materialURL, string objName) { string fileName = objName + ".obj"; #pragma warning disable string fileNameNoExt = objName; totalProgress.singleProgress.Add(objLoadingProgress); objLoadingProgress.fileName = fileName; objLoadingProgress.error = false; objLoadingProgress.message = "Loading " + fileName + "..."; //Debug.LogFormat("Loading {0}\n from: {1}...", objName, absolutePath); await Task.Yield(); loadedModels[objURL] = null; // define a key for the dictionary instanceCount[objURL] = 0; // define a key for the dictionary float lastTime = Time.realtimeSinceStartup; float startTime = lastTime; // base model here try { await LoadModelFileNetworked(objURL); } catch (Exception ex) { throw ex; } loadStats.modelParseTime = Time.realtimeSinceStartup - lastTime; if (objLoadingProgress.error) { OnLoadFailed(objURL); return null; } lastTime = Time.realtimeSinceStartup; if (HasMaterialLibrary) { // materials here try { await LoadMaterialLibrary(materialURL); } catch(Exception ex) { throw ex; } } else { ObjectImporter.activeDownloads -= 1; } loadStats.materialsParseTime = Time.realtimeSinceStartup - lastTime; lastTime = Time.realtimeSinceStartup; try { await NetworkedBuild(null, objName, objURL, diffuseTexURL, bumpTexURL, specularTexURL, opacityTexURL); } catch (Exception ex) { throw ex; } loadStats.buildTime = Time.realtimeSinceStartup - lastTime; loadStats.totalTime = Time.realtimeSinceStartup - startTime; /* Debug.Log("Done: " + objName + "\n Loaded in " + loadStats.totalTime + " seconds" + "\n Model data parsed in " + loadStats.modelParseTime + " seconds" + "\n Material data parsed in " + loadStats.materialsParseTime + " seconds" + "\n Game objects built in " + loadStats.buildTime + " seconds" + "\n textures: " + loadStats.buildStats.texturesTime + " seconds" + "\n materials: " + loadStats.buildStats.materialsTime + " seconds" + "\n objects: " + loadStats.buildStats.objectsTime + " seconds" ); */ totalProgress.singleProgress.Remove(objLoadingProgress); OnLoaded(loadedModels[objURL], objURL); return loadedModels[objURL]; } public IEnumerator LoadFromNetworkWebGL(string objURL, string diffuseTexURL, string bumpTexURL, string specularTexURL, string opacityTexURL, string materialURL, string objName, Action OnSuccess, Action OnError) { string fileName = objName + ".obj"; #pragma warning disable string fileNameNoExt = objName; totalProgress.singleProgress.Add(objLoadingProgress); objLoadingProgress.fileName = fileName; objLoadingProgress.error = false; objLoadingProgress.message = "Loading " + fileName + "..."; //Debug.LogFormat("Loading {0}\n from: {1}...", objName, absolutePath); loadedModels[objURL] = null; // define a key for the dictionary instanceCount[objURL] = 0; // define a key for the dictionary float lastTime = Time.realtimeSinceStartup; float startTime = lastTime; // base model here yield return StartCoroutine(LoadModelFileNetworkedWebGL(objURL, OnError)); if(ObjectImporter.isException) { yield return null; } loadStats.modelParseTime = Time.realtimeSinceStartup - lastTime; if (objLoadingProgress.error) { OnLoadFailed(objURL); OnError(new Exception("Load failed due to unknown reasons.")); yield return null; } lastTime = Time.realtimeSinceStartup; if (HasMaterialLibrary) { // materials here yield return StartCoroutine(LoadMaterialLibraryWebGL(materialURL)); } else { ObjectImporter.activeDownloads -= 1; } if (ObjectImporter.isException) { yield return null; } loadStats.materialsParseTime = Time.realtimeSinceStartup - lastTime; lastTime = Time.realtimeSinceStartup; yield return StartCoroutine(NetworkedBuildWebGL(null, objName, objURL, diffuseTexURL, bumpTexURL, specularTexURL, opacityTexURL)); if (ObjectImporter.isException) { yield return null; } loadStats.buildTime = Time.realtimeSinceStartup - lastTime; loadStats.totalTime = Time.realtimeSinceStartup - startTime; /* Debug.Log("Done: " + objName + "\n Loaded in " + loadStats.totalTime + " seconds" + "\n Model data parsed in " + loadStats.modelParseTime + " seconds" + "\n Material data parsed in " + loadStats.materialsParseTime + " seconds" + "\n Game objects built in " + loadStats.buildTime + " seconds" + "\n textures: " + loadStats.buildStats.texturesTime + " seconds" + "\n materials: " + loadStats.buildStats.materialsTime + " seconds" + "\n objects: " + loadStats.buildStats.objectsTime + " seconds" ); */ totalProgress.singleProgress.Remove(objLoadingProgress); OnLoaded(loadedModels[objURL], objURL); OnSuccess(loadedModels[objURL]); } /// /// Parse the model to get a list of the paths of all used textures /// /// absolute path of the model /// List of paths of the textures referenced by the model public abstract string[] ParseTexturePaths(string absolutePath); /// /// Load the main model file /// /// absolute file path /// This is called by Load() method protected abstract Task LoadModelFile(string absolutePath, string texturesFolderPath = "", string materialsFolderPath = ""); protected abstract Task LoadModelFileNetworked(string objURL); protected abstract IEnumerator LoadModelFileNetworkedWebGL(string objURL, Action OnError); /// /// Load the material library from the given path. /// /// absolute file path /// This is called by Load() method protected abstract Task LoadMaterialLibrary(string absolutePath, string materialsFolderPath = ""); protected abstract Task LoadMaterialLibrary(string materialURL); protected abstract IEnumerator LoadMaterialLibraryWebGL(string materialURL); /// /// Build the game objects from data set, materials and textures. /// /// absolute file path /// Name of the main game object (model root) /// transform to which the model root will be attached (if null it will be a root aobject) /// This is called by Load() method protected async Task Build(string absolutePath, string objName, Transform parentTransform, string texturesFolderPath = "") { float prevTime = Time.realtimeSinceStartup; if (materialData != null) { //string basePath = GetDirName(absolutePath); string basePath = Path.GetDirectoryName(absolutePath); objLoadingProgress.message = "Loading textures..."; int count = 0; foreach (MaterialData mtl in materialData) { objLoadingProgress.percentage = LOAD_PHASE_PERC + TEXTURE_PHASE_PERC * count / materialData.Count; count++; if (mtl.diffuseTexPath != null) { #if UNITY_EDITOR if (ImportingAssets) { mtl.diffuseTex = LoadAssetTexture(mtl.diffuseTexPath); } else #endif { await LoadMaterialTexture(basePath, mtl.diffuseTexPath, texturesFolderPath); mtl.diffuseTex = loadedTexture; } } if (mtl.bumpTexPath != null) { #if UNITY_EDITOR if (ImportingAssets) { mtl.bumpTex = LoadAssetTexture(mtl.bumpTexPath); } else #endif { await LoadMaterialTexture(basePath, mtl.bumpTexPath, texturesFolderPath); mtl.bumpTex = loadedTexture; } } if (mtl.specularTexPath != null) { #if UNITY_EDITOR if (ImportingAssets) { mtl.specularTex = LoadAssetTexture(mtl.specularTexPath); } else #endif { await LoadMaterialTexture(basePath, mtl.specularTexPath, texturesFolderPath); mtl.specularTex = loadedTexture; } } if (mtl.opacityTexPath != null) { #if UNITY_EDITOR if (ImportingAssets) { mtl.opacityTex = LoadAssetTexture(mtl.opacityTexPath); } else #endif { await LoadMaterialTexture(basePath, mtl.opacityTexPath, texturesFolderPath); mtl.opacityTex = loadedTexture; } } } } loadStats.buildStats.texturesTime = Time.realtimeSinceStartup - prevTime; prevTime = Time.realtimeSinceStartup; ObjectBuilder.ProgressInfo info = new ObjectBuilder.ProgressInfo(); objLoadingProgress.message = "Loading materials..."; //yield return null; #if UNITY_EDITOR objectBuilder.alternativeTexPath = altTexPath; #endif objectBuilder.buildOptions = buildOptions; bool hasColors = dataSet.colorList.Count > 0; bool hasMaterials = materialData != null; objectBuilder.InitBuildMaterials(materialData, hasColors); float objInitPerc = objLoadingProgress.percentage; if (hasMaterials) { while (objectBuilder.BuildMaterials(info)) { objLoadingProgress.percentage = objInitPerc + MATERIAL_PHASE_PERC * objectBuilder.NumImportedMaterials / materialData.Count; } loadStats.buildStats.materialsTime = Time.realtimeSinceStartup - prevTime; prevTime = Time.realtimeSinceStartup; } objLoadingProgress.message = "Building scene objects..."; GameObject newObj = new GameObject(objName); if (buildOptions.hideWhileLoading) { newObj.SetActive(false); } if (parentTransform != null) newObj.transform.SetParent(parentTransform.transform, false); OnCreated(newObj, absolutePath); ////newObj.transform.localScale = Vector3.one * Scaling; float initProgress = objLoadingProgress.percentage; objectBuilder.StartBuildObjectAsync(dataSet, newObj); while (objectBuilder.BuildObjectAsync(ref info)) { objLoadingProgress.message = "Building scene objects... " + (info.objectsLoaded + info.groupsLoaded) + "/" + (dataSet.objectList.Count + info.numGroups); objLoadingProgress.percentage = initProgress + BUILD_PHASE_PERC * (info.objectsLoaded / dataSet.objectList.Count + (float)info.groupsLoaded / info.numGroups); } objLoadingProgress.percentage = 100.0f; loadedModels[absolutePath] = newObj; loadStats.buildStats.objectsTime = Time.realtimeSinceStartup - prevTime; } protected async Task NetworkedBuild(Transform parentTransform, string objName, string objURL, string diffuseTexURL, string bumpTexURL, string specularTexURL, string opacityTexURL) { float prevTime = Time.realtimeSinceStartup; if (materialData != null) { objLoadingProgress.message = "Loading textures..."; int count = 0; foreach (MaterialData mtl in materialData) { objLoadingProgress.percentage = LOAD_PHASE_PERC + TEXTURE_PHASE_PERC * count / materialData.Count; count++; if (mtl.diffuseTexPath != null) { #if UNITY_EDITOR if (ImportingAssets) { mtl.diffuseTex = LoadAssetTexture(mtl.diffuseTexPath); } else #endif { if(!string.IsNullOrWhiteSpace(diffuseTexURL)) { try { await LoadMaterialTexture(diffuseTexURL); } catch (Exception exc) { throw exc; } } else { ObjectImporter.activeDownloads -= 1; } mtl.diffuseTex = loadedTexture; } } else { ObjectImporter.activeDownloads -= 1; } ObjectImporter.downloadProgress.Value = (individualProgress.Value / ObjectImporter.activeDownloads) * 100f; if (mtl.bumpTexPath != null) { #if UNITY_EDITOR if (ImportingAssets) { mtl.bumpTex = LoadAssetTexture(mtl.bumpTexPath); } else #endif { if (!string.IsNullOrWhiteSpace(bumpTexURL)) { try { await LoadMaterialTexture(bumpTexURL); } catch (Exception exc) { throw exc; } } else { ObjectImporter.activeDownloads -= 1; } mtl.bumpTex = loadedTexture; } } else { ObjectImporter.activeDownloads -= 1; } ObjectImporter.downloadProgress.Value = (individualProgress.Value / ObjectImporter.activeDownloads) * 100f; if (mtl.specularTexPath != null) { #if UNITY_EDITOR if (ImportingAssets) { mtl.specularTex = LoadAssetTexture(mtl.specularTexPath); } else #endif { if (!string.IsNullOrWhiteSpace(specularTexURL)) { try { await LoadMaterialTexture(specularTexURL); } catch (Exception exc) { throw exc; } } else { ObjectImporter.activeDownloads -= 1; } mtl.specularTex = loadedTexture; } } else { ObjectImporter.activeDownloads -= 1; } ObjectImporter.downloadProgress.Value = (individualProgress.Value / ObjectImporter.activeDownloads) * 100f; if (mtl.opacityTexPath != null) { #if UNITY_EDITOR if (ImportingAssets) { mtl.opacityTex = LoadAssetTexture(mtl.opacityTexPath); } else #endif { if (!string.IsNullOrWhiteSpace(opacityTexURL)) { try { await LoadMaterialTexture(opacityTexURL); } catch (Exception exc) { throw exc; } } else { ObjectImporter.activeDownloads -= 1; } mtl.opacityTex = loadedTexture; } } else { ObjectImporter.activeDownloads -= 1; } ObjectImporter.downloadProgress.Value = (individualProgress.Value / ObjectImporter.activeDownloads) * 100f; } } loadStats.buildStats.texturesTime = Time.realtimeSinceStartup - prevTime; prevTime = Time.realtimeSinceStartup; ObjectBuilder.ProgressInfo info = new ObjectBuilder.ProgressInfo(); objLoadingProgress.message = "Loading materials..."; //yield return null; #if UNITY_EDITOR objectBuilder.alternativeTexPath = altTexPath; #endif objectBuilder.buildOptions = buildOptions; bool hasColors = dataSet.colorList.Count > 0; bool hasMaterials = materialData != null; objectBuilder.InitBuildMaterials(materialData, hasColors); float objInitPerc = objLoadingProgress.percentage; if (hasMaterials) { while (objectBuilder.BuildMaterials(info)) { objLoadingProgress.percentage = objInitPerc + MATERIAL_PHASE_PERC * objectBuilder.NumImportedMaterials / materialData.Count; await Task.Delay(0); } loadStats.buildStats.materialsTime = Time.realtimeSinceStartup - prevTime; prevTime = Time.realtimeSinceStartup; } objLoadingProgress.message = "Building scene objects..."; GameObject newObj = new GameObject(objName); if (buildOptions.hideWhileLoading) { newObj.SetActive(false); } if (parentTransform != null) newObj.transform.SetParent(parentTransform.transform, false); OnCreated(newObj, objURL); ////newObj.transform.localScale = Vector3.one * Scaling; float initProgress = objLoadingProgress.percentage; objectBuilder.StartBuildObjectAsync(dataSet, newObj); while (objectBuilder.BuildObjectAsync(ref info)) { objLoadingProgress.message = "Building scene objects... " + (info.objectsLoaded + info.groupsLoaded) + "/" + (dataSet.objectList.Count + info.numGroups); objLoadingProgress.percentage = initProgress + BUILD_PHASE_PERC * (info.objectsLoaded / dataSet.objectList.Count + (float)info.groupsLoaded / info.numGroups); await Task.Delay(0); } objLoadingProgress.percentage = 100.0f; loadedModels[objURL] = newObj; loadStats.buildStats.objectsTime = Time.realtimeSinceStartup - prevTime; } protected IEnumerator NetworkedBuildWebGL(Transform parentTransform, string objName, string objURL, string diffuseTexURL, string bumpTexURL, string specularTexURL, string opacityTexURL) { float prevTime = Time.realtimeSinceStartup; if (materialData != null) { objLoadingProgress.message = "Loading textures..."; int count = 0; foreach (MaterialData mtl in materialData) { objLoadingProgress.percentage = LOAD_PHASE_PERC + TEXTURE_PHASE_PERC * count / materialData.Count; count++; if (mtl.diffuseTexPath != null) { #if UNITY_EDITOR if (ImportingAssets) { mtl.diffuseTex = LoadAssetTexture(mtl.diffuseTexPath); } else #endif { if (!string.IsNullOrWhiteSpace(diffuseTexURL)) { yield return StartCoroutine(LoadMaterialTextureWebGL(diffuseTexURL)); } else { ObjectImporter.activeDownloads -= 1; } mtl.diffuseTex = loadedTexture; } } else { ObjectImporter.activeDownloads -= 1; } ObjectImporter.downloadProgress.Value = (individualProgress.Value / ObjectImporter.activeDownloads) * 100f; if (mtl.bumpTexPath != null) { #if UNITY_EDITOR if (ImportingAssets) { mtl.bumpTex = LoadAssetTexture(mtl.bumpTexPath); } else #endif { if (!string.IsNullOrWhiteSpace(bumpTexURL)) { yield return StartCoroutine(LoadMaterialTextureWebGL(bumpTexURL)); } else { ObjectImporter.activeDownloads -= 1; } mtl.bumpTex = loadedTexture; } } else { ObjectImporter.activeDownloads -= 1; } ObjectImporter.downloadProgress.Value = (individualProgress.Value / ObjectImporter.activeDownloads) * 100f; if (mtl.specularTexPath != null) { #if UNITY_EDITOR if (ImportingAssets) { mtl.specularTex = LoadAssetTexture(mtl.specularTexPath); } else #endif { if (!string.IsNullOrWhiteSpace(specularTexURL)) { yield return StartCoroutine(LoadMaterialTextureWebGL(specularTexURL)); } else { ObjectImporter.activeDownloads -= 1; } mtl.specularTex = loadedTexture; } } else { ObjectImporter.activeDownloads -= 1; } ObjectImporter.downloadProgress.Value = (individualProgress.Value / ObjectImporter.activeDownloads) * 100f; if (mtl.opacityTexPath != null) { #if UNITY_EDITOR if (ImportingAssets) { mtl.opacityTex = LoadAssetTexture(mtl.opacityTexPath); } else #endif { if (!string.IsNullOrWhiteSpace(opacityTexURL)) { yield return StartCoroutine(LoadMaterialTextureWebGL(opacityTexURL)); } else { ObjectImporter.activeDownloads -= 1; } mtl.opacityTex = loadedTexture; } } else { ObjectImporter.activeDownloads -= 1; } ObjectImporter.downloadProgress.Value = (individualProgress.Value / ObjectImporter.activeDownloads) * 100f; } } loadStats.buildStats.texturesTime = Time.realtimeSinceStartup - prevTime; prevTime = Time.realtimeSinceStartup; ObjectBuilder.ProgressInfo info = new ObjectBuilder.ProgressInfo(); objLoadingProgress.message = "Loading materials..."; //yield return null; #if UNITY_EDITOR objectBuilder.alternativeTexPath = altTexPath; #endif objectBuilder.buildOptions = buildOptions; bool hasColors = dataSet.colorList.Count > 0; bool hasMaterials = materialData != null; objectBuilder.InitBuildMaterials(materialData, hasColors); float objInitPerc = objLoadingProgress.percentage; if (hasMaterials) { while (objectBuilder.BuildMaterials(info)) { objLoadingProgress.percentage = objInitPerc + MATERIAL_PHASE_PERC * objectBuilder.NumImportedMaterials / materialData.Count; } loadStats.buildStats.materialsTime = Time.realtimeSinceStartup - prevTime; prevTime = Time.realtimeSinceStartup; } objLoadingProgress.message = "Building scene objects..."; GameObject newObj = new GameObject(objName); if (buildOptions.hideWhileLoading) { newObj.SetActive(false); } if (parentTransform != null) newObj.transform.SetParent(parentTransform.transform, false); OnCreated(newObj, objURL); ////newObj.transform.localScale = Vector3.one * Scaling; float initProgress = objLoadingProgress.percentage; objectBuilder.StartBuildObjectAsync(dataSet, newObj); while (objectBuilder.BuildObjectAsync(ref info)) { objLoadingProgress.message = "Building scene objects... " + (info.objectsLoaded + info.groupsLoaded) + "/" + (dataSet.objectList.Count + info.numGroups); objLoadingProgress.percentage = initProgress + BUILD_PHASE_PERC * (info.objectsLoaded / dataSet.objectList.Count + (float)info.groupsLoaded / info.numGroups); } objLoadingProgress.percentage = 100.0f; loadedModels[objURL] = newObj; loadStats.buildStats.objectsTime = Time.realtimeSinceStartup - prevTime; } /// /// Get the directory name of the given path, appending the final slash if eeded. /// /// the absolute path /// the directory name ending with `/` protected string GetDirName(string absolutePath) { string basePath; if (absolutePath.Contains("//")) { basePath = absolutePath.Remove(absolutePath.LastIndexOf('/') + 1); } else { string dirName = Path.GetDirectoryName(absolutePath); basePath = string.IsNullOrEmpty(dirName) ? "" : dirName; if (!basePath.EndsWith("/")) { basePath += "/"; } } return basePath; } protected virtual void OnLoaded(GameObject obj, string absolutePath) { if (obj == null) { if (ModelError != null) { ModelError(absolutePath); } } else { if (buildOptions != null) { obj.transform.localPosition = buildOptions.localPosition; obj.transform.localRotation = Quaternion.Euler(buildOptions.localEulerAngles); ; obj.transform.localScale = buildOptions.localScale; if (buildOptions.inheritLayer) { obj.layer = obj.transform.parent.gameObject.layer; MeshRenderer[] mrs = obj.transform.GetComponentsInChildren(true); for (int i = 0; i < mrs.Length; i++) { mrs[i].gameObject.layer = obj.transform.parent.gameObject.layer; } } } if (buildOptions.hideWhileLoading) { obj.SetActive(true); } if (ModelLoaded != null) { ModelLoaded(obj, absolutePath); } } } protected virtual void OnCreated(GameObject obj, string absolutePath) { if (obj == null) { if (ModelError != null) { ModelError(absolutePath); } } else { if (ModelCreated != null) { ModelCreated(obj, absolutePath); } } } protected virtual void OnLoadFailed(string absolutePath) { if (ModelError != null) { ModelError(absolutePath); } } #if UNITY_EDITOR /// /// Load a texture from the asset database /// /// texture path inside the asset database /// the loaded texture or null on error private Texture2D LoadAssetTexture(string texturePath) { FileInfo textFileInfo = new FileInfo(texturePath); string texpath = altTexPath + textFileInfo.Name; texpath = texpath.Replace("//", "/"); Debug.LogFormat("Loading texture asset '{0}'", texpath); return AssetDatabase.LoadAssetAtPath(texpath); } #endif /// /// Convert a texture path to a texture URL and update the progress message /// /// base texture path /// relative texture path /// URL of the texture private string GetTextureUrl(string basePath, string texturePath) { string texPath = texturePath.Replace("\\", "/").Replace("//", "/"); if (!Path.IsPathRooted(texPath)) { texPath = basePath + texturePath; } if (!texPath.Contains("//")) { texPath = "file:///" + texPath; } objLoadingProgress.message = "Loading textures...\n" + texPath; return texPath; } private async Task LoadMaterialTexture(string basePath, string path, string texturesFolderPath="") { loadedTexture = null; //string texPath = GetTextureUrl(basePath, path); string textPath = string.IsNullOrWhiteSpace(texturesFolderPath) ? basePath + path : (texturesFolderPath + "\\" + path); textPath = Path.GetFullPath(textPath); //WWW loader = new WWW(texPath); //yield return loader; if (File.Exists(textPath)) { byte[] result; using (FileStream stream = File.Open(textPath, FileMode.Open)) { result = new byte[stream.Length]; await stream.ReadAsync(result, 0, (int)stream.Length); } if (result.Length > 0) { Texture2D tex = new Texture2D(1, 1); tex.LoadImage(result); loadedTexture = tex; } } else { Debug.LogWarning("Failed to load texture at path " + textPath + " BasePath " + basePath + " path " + path); } } private async Task LoadMaterialTexture(string textureURL) { loadedTexture = null; bool isWorking = true; byte[] downloadedBytes = null; float oldProgress = individualProgress.Value; try { StartCoroutine(DownloadFile(textureURL, individualProgress, (bytes) => { isWorking = false; downloadedBytes = bytes; //loadedText = Encoding.UTF8.GetString(bytes); }, (error) => { ObjectImporter.activeDownloads -= 1; isWorking = false; Debug.LogWarning("Failed to load the associated texture file." + error); })); } catch (Exception exc) { ObjectImporter.activeDownloads -= 1; individualProgress.Value = oldProgress; ObjectImporter.downloadProgress.Value = (individualProgress.Value / ObjectImporter.activeDownloads) * 100f; isWorking = false; throw exc; } while (isWorking) { ObjectImporter.downloadProgress.Value = (individualProgress.Value / ObjectImporter.activeDownloads) * 100f; await Task.Delay(3); } ObjectImporter.downloadProgress.Value = (individualProgress.Value / ObjectImporter.activeDownloads) * 100f; if (downloadedBytes != null && downloadedBytes.Length > 0) { Texture2D tex = new Texture2D(1, 1); tex.LoadImage(downloadedBytes); loadedTexture = tex; } else { Debug.LogWarning("Failed to load texture."); } } private IEnumerator LoadMaterialTextureWebGL(string textureURL) { loadedTexture = null; bool isWorking = true; float oldProgress = individualProgress.Value; try { StartCoroutine(DownloadTexFileWebGL(textureURL, individualProgress, (texture) => { isWorking = false; loadedTexture = texture; //loadedText = Encoding.UTF8.GetString(bytes); }, (error) => { ObjectImporter.activeDownloads -= 1; isWorking = false; Debug.LogWarning("Failed to load the associated texture file." + error); })); } catch (Exception exc) { ObjectImporter.activeDownloads -= 1; individualProgress.Value = oldProgress; ObjectImporter.downloadProgress.Value = (individualProgress.Value / ObjectImporter.activeDownloads) * 100f; isWorking = false; throw exc; } while (isWorking) { yield return new WaitForSeconds(0.1f); ObjectImporter.downloadProgress.Value = (individualProgress.Value / ObjectImporter.activeDownloads) * 100f; } ObjectImporter.downloadProgress.Value = (individualProgress.Value / ObjectImporter.activeDownloads) * 100f; if (loadedTexture == null) { Debug.LogWarning("Failed to load texture."); } } /// /// Load a texture from the URL got from the parameter. /// #if UNITY_2018_3_OR_NEWER private Texture2D LoadTexture(UnityWebRequest loader) #else private Texture2D LoadTexture(WWW loader) #endif { string ext = Path.GetExtension(loader.url).ToLower(); Texture2D tex = null; // TODO: add support for more formats (bmp, gif, dds, ...) if (ext == ".tga") { tex = TextureLoader.LoadTextureFromUrl(loader.url); //tex = TgaLoader.LoadTGA(new MemoryStream(loader.bytes)); } else if (ext == ".png" || ext == ".jpg" || ext == ".jpeg") { #if UNITY_2018_3_OR_NEWER tex = DownloadHandlerTexture.GetContent(loader); #else tex = loader.texture; #endif } else { Debug.LogWarning("Unsupported texture format: " + ext); } if (tex == null) { Debug.LogErrorFormat("Failed to load texture {0}", loader.url); } else { //tex.alphaIsTransparency = true; //tex.filterMode = FilterMode.Trilinear; } return tex; } protected struct BuildStats { public float texturesTime; public float materialsTime; public float objectsTime; } protected struct Stats { public float modelParseTime; public float materialsParseTime; public float buildTime; public BuildStats buildStats; public float totalTime; } public IEnumerator DownloadFile(string url, ReferencedNumeric downloadProgress, Action DownloadComplete, Action OnError) { //Debug.Log("Into the DownloadFile Coroutine"); WWW www = null; float oldProgress = downloadProgress.Value; try { www = new WWW(url); } catch (Exception ex) { downloadProgress.Value = oldProgress; OnError(ex.ToString()); } Coroutine progress = StartCoroutine(GetProgress(www, downloadProgress)); yield return www; if (!string.IsNullOrWhiteSpace(www.error)) { downloadProgress.Value = oldProgress; OnError(www.error); } else { if (www.bytes == null || www.bytes.Length == 0) { if (string.IsNullOrWhiteSpace(www.error)) { downloadProgress.Value = oldProgress; OnError("No bytes downloaded. The file might be empty."); } else { downloadProgress.Value = oldProgress; OnError(www.error); } } else { if (string.IsNullOrWhiteSpace(www.error)) { //Debug.Log("Calling Download Complete"); downloadProgress.Value = oldProgress + 1; DownloadComplete(www.bytes); } else { downloadProgress.Value = oldProgress; OnError(www.error); } } } StopCoroutine(progress); www.Dispose(); //Debug.Log("End of File Download Coroutine."); } private IEnumerator GetProgress(WWW www, ReferencedNumeric downloadProgress) { float oldProgress = downloadProgress.Value; if (www != null && downloadProgress != null) { while (!www.isDone && string.IsNullOrWhiteSpace(www.error)) { yield return new WaitForSeconds(0.1f); downloadProgress.Value = oldProgress + www.progress; } if(www.isDone && string.IsNullOrWhiteSpace(www.error)) { downloadProgress.Value = oldProgress + www.progress; Debug.Log("Progress " + www.progress); } } } public IEnumerator DownloadFileWebGL(string url, ReferencedNumeric downloadProgress, Action DownloadComplete, Action OnError) { WWW www = null; float oldProgress = downloadProgress.Value; try { www = new WWW(url); } catch (Exception ex) { downloadProgress.Value = oldProgress; OnError(ex.ToString()); } Coroutine progress = StartCoroutine(GetProgress(www, downloadProgress)); yield return www; if (!string.IsNullOrWhiteSpace(www.error)) { downloadProgress.Value = oldProgress; OnError(www.error); } else { if (www.bytes == null || www.bytes.Length == 0) { if (string.IsNullOrWhiteSpace(www.error)) { downloadProgress.Value = oldProgress; OnError("No bytes downloaded. The file might be empty."); } else { downloadProgress.Value = oldProgress; OnError(www.error); } } else { if (string.IsNullOrWhiteSpace(www.error)) { downloadProgress.Value = oldProgress + 1; DownloadComplete(www.text); } else { downloadProgress.Value = oldProgress; OnError(www.error); } } } try { StopCoroutine(progress); } catch (Exception ex) { } www.Dispose(); } public IEnumerator DownloadTexFileWebGL(string url, ReferencedNumeric downloadProgress, Action DownloadComplete, Action OnError) { WWW www = null; float oldProgress = downloadProgress.Value; try { www = new WWW(url); } catch (Exception ex) { downloadProgress.Value = oldProgress; OnError(ex.ToString()); } Coroutine progress = StartCoroutine(GetProgress(www, downloadProgress)); yield return www; if (!string.IsNullOrWhiteSpace(www.error)) { downloadProgress.Value = oldProgress; OnError(www.error); } else { if (www.bytes == null || www.bytes.Length == 0) { if (string.IsNullOrWhiteSpace(www.error)) { downloadProgress.Value = oldProgress; OnError("No bytes downloaded. The file might be empty."); } else { downloadProgress.Value = oldProgress; OnError(www.error); } } else { if (string.IsNullOrWhiteSpace(www.error)) { downloadProgress.Value = oldProgress + 1; DownloadComplete(www.texture); } else { downloadProgress.Value = oldProgress; OnError(www.error); } } } StopCoroutine(progress); www.Dispose(); } } }