/// <summary> /// Checks if the asset has the contents of the format to import, fetching them if need be; then imports /// the asset. /// </summary> /// <param name="asset">The asset who's format is being imported.</param> /// <param name="format">The format to import.</param> /// <param name="options">The import options for this asset.</param> /// <param name="callback">The callback to call when this is finished.</param> private void FetchAndImportFormat(PolyAsset asset, PolyFormat format, PolyImportOptions options, PolyApi.ImportCallback callback = null) { if (format.root.contents != null) { // If asset already has the gltf package, proceed directly to importing it. ImportFormat(asset, format, options, callback); } else { // Otherwise, first fetch the package and then import the model. FetchFormatFiles(asset, format.formatType, (PolyAsset resultAsset, PolyStatus status) => { PolyFormat fetchedFormat = resultAsset.GetFormatIfExists(format.formatType); if (fetchedFormat != null) { ImportFormat(asset, fetchedFormat, options, callback); } else { if (callback != null) { callback(asset, new PolyStatusOr <PolyImportResult>( PolyStatus.Error("Could not fetch format files for asset"))); } } }); } }
private void UnpackPackageToFolder(PolyFormat package, string destFolder, string mainFileName) { // First write the resources, then the main file, so that when the main file is imported // all the necessary resources are already in place. // Maintain a mapping of original file names to their corresponding hash. StringBuilder fileMapSb = new StringBuilder(); foreach (PolyFile file in package.resources) { // In order to avoid having to replicate the original directory structure of the // asset (which might be incompatible with our file system, or even maliciously constructed), // we replace the original path of each resource file with the MD5 hash of the path. // That maintains uniqueness of paths and flattens the structure so that every resource // can live in the same directory. string path = Path.Combine(destFolder, PolyInternalUtils.ConvertFilePathToHash(file.relativePath)); if (file.contents != null) { File.WriteAllBytes(path, file.contents); fileMapSb.AppendFormat("{0} -> {1}\n", file.relativePath, PolyInternalUtils.ConvertFilePathToHash(file.relativePath)); } } // Lastly, write the main file. File.WriteAllBytes(Path.Combine(destFolder, mainFileName), package.root.contents); // Write the file mapping. File.WriteAllText(Path.Combine(destFolder, "FileNameMapping.txt"), fileMapSb.ToString()); }
/// <summary> /// Processes the result of fetching an individual file. /// </summary> /// <param name="state">Indicates the state of the ongoing fetch operation (as set up in FetchObj).</param> /// <param name="index">If ROOT_FILE_INDEX, then this is a result for the main file; else this is a result for /// the resource file with that index.</param> /// <param name="status">The status indicating if the download succeed</param> /// <param name="data">The data that was downloaded.</param> private void ProcessFileFetchResult(FetchOperationState state, int index, PolyStatus status, byte[] data) { if (state.pendingFiles == 0) { // Another request for this format failed, so we ignore any further responses. return; } if (!status.ok) { // This request failed, so we set pendingFiles to 0 so we ignore any further responses, and callback with // an error message. state.pendingFiles = 0; state.completionCallback(state.asset, PolyStatus.Error(status, "Failed to fetch file #{0}", index)); return; } PolyFormat package = state.packageBeingFetched; PolyFile file = index == ROOT_FILE_INDEX ? package.root : package.resources[index]; file.contents = data; --state.pendingFiles; if (state.progressCallback != null) { state.progressCallback(state.asset, 1.0f - ((float)state.pendingFiles / state.totalFiles)); } if (state.pendingFiles <= 0) { // All files done, call callback indicating success. state.completionCallback(state.asset, PolyStatus.Success()); } }
/// <summary> /// Imports the given format of the given asset, asynchronously in a background thread. /// Calls the supplied callback when done. /// </summary> public void ImportAsync(PolyAsset asset, PolyFormat format, PolyImportOptions options, AsyncImportCallback callback = null) { ImportOperation operation = new ImportOperation(); operation.instance = this; operation.asset = asset; operation.format = format; operation.options = options; operation.callback = callback; operation.status = PolyStatus.Success(); operation.loader = new FormatLoader(format); if (Application.isPlaying) { Task.Run(() => BackgroundThreadProc(operation)); //ThreadPool.QueueUserWorkItem(new WaitCallback(BackgroundThreadProc), operation); } else { // If we are in the editor, don't do this in a background thread. Do it directly // here on the main thread. BackgroundThreadProc(operation); Update(); } }
/// <summary> /// Parses a single asset. /// </summary> public static PolyStatus ParseAsset(JObject asset, out PolyAsset polyAsset) { polyAsset = new PolyAsset(); if (asset["visibility"] == null) { return(PolyStatus.Error("Asset has no visibility set.")); } polyAsset.name = asset["name"].ToString(); polyAsset.authorName = asset["authorName"].ToString(); if (asset["thumbnail"] != null) { var thumbnailElements = asset["thumbnail"].ToObject <JObject>(); //IJEnumerable<JToken> thumbnailElements = asset["thumbnail"].AsJEnumerable(); polyAsset.thumbnail = new PolyFile(thumbnailElements["relativePath"].ToString(), thumbnailElements["url"].ToString(), thumbnailElements["contentType"].ToString()); } if (asset["formats"] == null) { Debug.LogError("No formats found"); } else { foreach (var format in asset["formats"].ToObject <List <JObject> >()) //foreach (JToken format in asset["formats"]) { PolyFormat newFormat = ParseAssetsPackage(format); newFormat.formatType = ParsePolyFormatType(format["formatType"]); if (newFormat.formatType == PolyFormatType.UNKNOWN) { PtDebug.Log("Did not recognize format type: " + format["formatType"].ToString()); } polyAsset.formats.Add(newFormat); } } polyAsset.displayName = asset["displayName"].ToString(); polyAsset.createTime = DateTime.Parse(asset["createTime"].ToString()); polyAsset.updateTime = DateTime.Parse(asset["updateTime"].ToString()); polyAsset.visibility = ParsePolyVisibility(asset["visibility"]); polyAsset.license = ParsePolyAssetLicense(asset["license"]); if (asset["isCurated"] != null) { polyAsset.isCurated = bool.Parse(asset["isCurated"].ToString()); } return(PolyStatus.Success()); }
/// <summary> /// Imports the relevant format and corrects that the designated glTF format if need be. /// </summary> private void ImportFormat(PolyAsset asset, PolyFormat format, PolyImportOptions options, PolyApi.ImportCallback callback) { asyncImporter.ImportAsync(asset, format, options, (PolyStatus status, GameObject root, IEnumerable meshCreator) => { if (!status.ok) { // Failed. callback(asset, new PolyStatusOr <PolyImportResult>(status)); return; } PolyImportResult result = new PolyImportResult(root); result.mainThreadThrottler = meshCreator; callback(asset, new PolyStatusOr <PolyImportResult>(result)); }); }
public void FetchFormatFiles(PolyAsset asset, PolyFormatType formatType, PolyApi.FetchFormatFilesCallback completionCallback, FetchProgressCallback progressCallback = null) { PolyUtils.AssertNotNull(asset, "Asset can't be null."); PolyUtils.AssertNotNull(formatType, "formatType can't be null."); PolyFormat packageToFetch = asset.GetFormatIfExists(formatType); if (packageToFetch == null) { if (completionCallback != null) { completionCallback(asset, PolyStatus.Error("Format type not present in asset")); } return; } PolyUtils.AssertNotNull(packageToFetch.root, "packageToFetch.root can't be null."); PolyUtils.AssertNotNull(packageToFetch.root.url, "packageToFetch.root.url can't be null."); PolyUtils.AssertNotNull(packageToFetch.resources, "packageToFetch.resources can't be null."); string accessToken = GetAccessToken(); FetchOperationState state = new FetchOperationState(); state.asset = asset; state.completionCallback = completionCallback; state.progressCallback = progressCallback; state.packageBeingFetched = packageToFetch; // Indicates how many files are pending download (1 for main file + 1 for each resource). state.totalFiles = state.pendingFiles = 1 + packageToFetch.resources.Count; // Note that the callbacks are asynchronous so they may complete in any order. What we do know is that they // will all be called on the main thread, so they won't be called concurrently. long maxCacheAge = asset.IsMutable ? MUTABLE_ASSET_MAX_CACHE_AGE : IMMUTABLE_ASSET_MAX_CACHE_AGE; PolyClientUtils.GetRawFileBytes(packageToFetch.root.url, accessToken, maxCacheAge, (PolyStatus status, byte[] data) => { ProcessFileFetchResult(state, ROOT_FILE_INDEX, status, data); }); for (int i = 0; i < packageToFetch.resources.Count; i++) { int thisIndex = i; // copy of variable, for closure below. PolyClientUtils.GetRawFileBytes(packageToFetch.resources[i].url, accessToken, maxCacheAge, (status, data) => { ProcessFileFetchResult(state, thisIndex, status, data); }); } }
/// <summary> /// Starts downloading and importing the given asset (in the background). When done, the asset will /// be saved to the user's Assets folder. /// </summary> /// <param name="asset">The asset to download and import.</param> /// <param name="ptAssetLocalPath">Path to the PtAsset that should be created (or replaced).</param> /// <param name="options">Import options.</param> public void StartDownloadAndImport(PolyAsset asset, string ptAssetLocalPath, EditTimeImportOptions options) { if (!assetsBeingDownloaded.Add(asset)) { return; } PtDebug.LogFormat("ABM: starting to fetch asset {0} ({1}) -> {2}", asset.name, asset.displayName, ptAssetLocalPath); // Prefer glTF1 to glTF2. // It used to be that no Poly assets had both formats, so the ordering did not matter. // Blocks assets now have both glTF1 and glTF2. PT does not understand the glTF2 version, // so the ordering matters a great deal. PolyFormat glTF2format = asset.GetFormatIfExists(PolyFormatType.GLTF_2); PolyFormat glTFformat = asset.GetFormatIfExists(PolyFormatType.GLTF); PolyMainInternal.FetchProgressCallback progressCallback = (PolyAsset assetBeingFetched, float progress) => { EditorUtility.DisplayProgressBar(DOWNLOAD_PROGRESS_TITLE, DOWNLOAD_PROGRESS_TEXT, progress); }; if (glTFformat != null) { EditorUtility.DisplayProgressBar(DOWNLOAD_PROGRESS_TITLE, DOWNLOAD_PROGRESS_TEXT, 0.0f); PolyMainInternal.Instance.FetchFormatFiles(asset, PolyFormatType.GLTF, (PolyAsset resultAsset, PolyStatus status) => { EditorUtility.ClearProgressBar(); OnFetchFinished(status, resultAsset, /*isGltf2*/ false, ptAssetLocalPath, options); }, progressCallback); } else if (glTF2format != null) { EditorUtility.DisplayProgressBar(DOWNLOAD_PROGRESS_TITLE, DOWNLOAD_PROGRESS_TEXT, 0.0f); PolyMainInternal.Instance.FetchFormatFiles(asset, PolyFormatType.GLTF_2, (PolyAsset resultAsset, PolyStatus status) => { EditorUtility.ClearProgressBar(); OnFetchFinished(status, resultAsset, /*isGltf2*/ true, ptAssetLocalPath, options); }, progressCallback); } else { Debug.LogError("Asset not in GLTF_2 or GLTF format. Can't import."); PtAnalytics.SendEvent(PtAnalytics.Action.IMPORT_FAILED, "Unsupported format"); } }
public void Import(PolyAsset asset, PolyImportOptions options, PolyApi.ImportCallback callback = null) { PolyFormat gltfFormat = asset.GetFormatIfExists(PolyFormatType.GLTF); PolyFormat gltf2Format = asset.GetFormatIfExists(PolyFormatType.GLTF_2); if (gltf2Format != null && gltfFormat == null) { FetchAndImportFormat(asset, gltf2Format, options, callback); } else if (gltfFormat != null) { FetchAndImportFormat(asset, gltfFormat, options, callback); } else { callback(asset, new PolyStatusOr <PolyImportResult>( PolyStatus.Error("Neither glTF or glTF_2 format was present in asset"))); } }
private static PolyFormat ParseAssetsPackage(JToken token) { PolyFormat package = new PolyFormat(); package.root = new PolyFile(token["root"]["relativePath"].ToString(), token["root"]["url"].ToString(), token["root"]["contentType"].ToString()); // Get the supporting files (resources). // Supporting files (including MTL files) are listed under /resource: package.resources = new List <PolyFile>(); if (token["resources"] != null) { var resourceTags = token["resources"].ToObject <List <JObject> >(); //IJEnumerable<JToken> resourceTags = token["resources"].AsJEnumerable(); if (resourceTags != null) { foreach (var resourceTag in resourceTags) { if (resourceTag["url"] != null) { package.resources.Add(new PolyFile( resourceTag["relativePath"].ToString(), resourceTag["url"].ToString(), resourceTag["contentType"].ToString())); } } } } // Get the format complexity if (token["formatComplexity"] != null) { package.formatComplexity = new PolyFormatComplexity(); if (token["formatComplexity"]["triangleCount"] != null) { package.formatComplexity.triangleCount = int.Parse(token["formatComplexity"]["triangleCount"].ToString()); } if (token["formatComplexity"]["lodHint"] != null) { package.formatComplexity.lodHint = int.Parse(token["formatComplexity"]["lodHint"].ToString()); } } return(package); }
public FormatLoader(PolyFormat format) { this.format = format; }
public abstract void Dereference(IUriLoader uriLoader = null, PolyFormat gltfFormat = null);
/// Map glTFid values (ie, string names) names to the objects they refer to public override void Dereference(IUriLoader uriLoader = null, PolyFormat gltfFormat = null) { // "dereference" all the names scenePtr = scenes[scene]; foreach (var pair in buffers) { pair.Value.gltfId = pair.Key; Gltf1Buffer buffer = pair.Value; if (uriLoader != null) { Debug.Assert(buffer.type == "arraybuffer"); buffer.data = uriLoader.Load(buffer.uri); } else if (gltfFormat != null) { // Runtime import case; the uris refer to resource files in the PolyFormat. Debug.Assert(buffer.type == "arraybuffer"); foreach (PolyFile resource in gltfFormat.resources) { if (resource.relativePath == buffer.uri) { buffer.data = new Reader(resource.contents); break; } } } } foreach (var pair in accessors) { pair.Value.gltfId = pair.Key; pair.Value.bufferViewPtr = bufferViews[pair.Value.bufferView]; } foreach (var pair in bufferViews) { pair.Value.gltfId = pair.Key; pair.Value.bufferPtr = buffers[pair.Value.buffer]; } foreach (var pair in meshes) { pair.Value.gltfId = pair.Key; foreach (var prim in pair.Value.primitives) { prim.attributePtrs = prim.attributes.ToDictionary( elt => elt.Key, elt => accessors[elt.Value]); prim.indicesPtr = accessors[prim.indices]; prim.materialPtr = materials[prim.material]; } } if (shaders != null) { foreach (var pair in shaders) { pair.Value.gltfId = pair.Key; } } if (programs != null) { foreach (var pair in programs) { pair.Value.gltfId = pair.Key; var program = pair.Value; if (program.vertexShader != null) { program.vertexShaderPtr = shaders[program.vertexShader]; } if (program.fragmentShader != null) { program.fragmentShaderPtr = shaders[program.fragmentShader]; } } } if (techniques != null) { foreach (var pair in techniques) { pair.Value.gltfId = pair.Key; var technique = pair.Value; if (technique.program != null) { technique.programPtr = programs[technique.program]; } } } if (images != null) { foreach (var pair in images) { pair.Value.gltfId = pair.Key; } foreach (var pair in textures) { pair.Value.gltfId = pair.Key; var texture = pair.Value; if (texture.source != null) { texture.sourcePtr = images[texture.source]; } } } if (materials != null) { foreach (var pair in materials) { pair.Value.gltfId = pair.Key; var material = pair.Value; if (material.technique != null) { material.techniquePtr = techniques[material.technique]; } if (material.values != null) { if (material.values.BaseColorTex != null) { material.values.BaseColorTexPtr = textures[material.values.BaseColorTex]; } } } } foreach (var pair in nodes) { pair.Value.gltfId = pair.Key; var node = pair.Value; if (node.meshes != null) { node.meshPtrs = node.meshes.Select(id => meshes[id]).ToList(); } if (node.children != null) { node.childPtrs = node.children.Select(id => nodes[id]).ToList(); } } foreach (var pair in scenes) { pair.Value.gltfId = pair.Key; var scene2 = pair.Value; if (scene2.nodes != null) { scene2.nodePtrs = scene2.nodes.Select(name => nodes[name]).ToList(); } } }
/// Map gltfIndex values (ie, int indices) names to the objects they refer to public override void Dereference(IUriLoader uriLoader = null, PolyFormat gltfFormat = null) { // "dereference" all the indices scenePtr = scenes[scene]; for (int i = 0; i < buffers.Count; i++) { Gltf2Buffer buffer = buffers[i]; buffer.gltfIndex = i; if (uriLoader != null) { buffer.data = uriLoader.Load(buffer.uri); } else if (gltfFormat != null) { // Runtime import case; the uris refer to resource files in the PolyFormat. foreach (PolyFile resource in gltfFormat.resources) { if (resource.relativePath == buffer.uri) { buffer.data = new Reader(resource.contents); break; } } } } for (int i = 0; i < accessors.Count; i++) { accessors[i].gltfIndex = i; accessors[i].bufferViewPtr = bufferViews[accessors[i].bufferView]; } for (int i = 0; i < bufferViews.Count; i++) { bufferViews[i].gltfIndex = i; bufferViews[i].bufferPtr = buffers[bufferViews[i].buffer]; } for (int i = 0; i < meshes.Count; i++) { meshes[i].gltfIndex = i; foreach (var prim in meshes[i].primitives) { prim.attributePtrs = prim.attributes.ToDictionary( elt => elt.Key, elt => accessors[elt.Value]); prim.indicesPtr = accessors[prim.indices]; prim.materialPtr = materials[prim.material]; } } if (images != null) { for (int i = 0; i < images.Count; i++) { images[i].gltfIndex = i; } } if (textures != null) { for (int i = 0; i < textures.Count; i++) { textures[i].gltfIndex = i; textures[i].sourcePtr = images[textures[i].source]; } } for (int i = 0; i < materials.Count; i++) { Gltf2Material mat = materials[i]; mat.gltfIndex = i; DereferenceTextureInfo(mat.emissiveTexture); DereferenceTextureInfo(mat.normalTexture); if (mat.pbrMetallicRoughness != null) { DereferenceTextureInfo(mat.pbrMetallicRoughness.baseColorTexture); DereferenceTextureInfo(mat.pbrMetallicRoughness.metallicRoughnessTexture); } } for (int i = 0; i < nodes.Count; i++) { nodes[i].gltfIndex = i; Gltf2Node node = nodes[i]; if (node.mesh >= 0) { node.meshPtr = meshes[node.mesh]; } if (node.children != null) { node.childPtrs = node.children.Select(id => nodes[id]).ToList(); } } for (int i = 0; i < scenes.Count; i++) { scenes[i].gltfIndex = i; var thisScene = scenes[i]; if (thisScene.nodes != null) { thisScene.nodePtrs = thisScene.nodes.Select(index => nodes[index]).ToList(); } } }