/// <summary> /// Download main texture into memory. Can be used right away to create Unity texture. /// </summary> public virtual AsyncWebRequest <byte[]> DownloadTextureBytesAsync(AvatarData avatar) { var r = AvatarDataRequestAsync(avatar.texture); r.State = AvatarSdkMgr.Str(Strings.DownloadingHeadTexture); return(r); }
/// <summary> /// Download all avatar files, unzip and save to disk. /// </summary> /// <param name="connection">Connection session.</param> /// <param name="avatar">Avatar to download.</param> /// <param name="withHaircutPointClouds">If set to true, download all haircut point clouds too.</param> /// <param name="withBlendshapes">If set to true, download blendshapes too.</param> public AsyncRequest DownloadAndSaveAvatarModelAsync(AvatarData avatar, bool withHaircutPointClouds, bool withBlendshapes) { var request = new AsyncRequest <AvatarData>(AvatarSdkMgr.Str(Strings.DownloadingAvatar)); AvatarSdkMgr.SpawnCoroutine(DownloadAndSaveAvatarModel(avatar, withHaircutPointClouds, withBlendshapes, request)); return(request); }
/// <summary> /// Edit avatar name/description on the server. /// </summary> public virtual AsyncWebRequest EditAvatarAsync(AvatarData avatar, string name = null, string description = null) { var request = new AsyncWebRequest(AvatarSdkMgr.Str(Strings.EditingAvatar)); byte[] requestBodyData = null; using (var requestBody = new MultipartBody()) { requestBody.WriteTextField("name", name); requestBody.WriteTextField("description", description); requestBody.WriteFooter(); requestBodyData = requestBody.GetRequestBodyData(); Func <UnityWebRequest> webRequestFactory = () => { var webRequest = UnityWebRequest.Post(avatar.url, " "); webRequest.chunkedTransfer = false; webRequest.method = "PATCH"; webRequest.uploadHandler = new UploadHandlerRaw(requestBodyData); webRequest.SetRequestHeader( "Content-Type", string.Format("multipart/form-data; boundary=\"{0}\"", requestBody.Boundary) ); SetAuthHeaders(webRequest); return(webRequest); }; Debug.LogFormat("Uploading photo..."); AvatarSdkMgr.SpawnCoroutine(AwaitJsonWebRequest(webRequestFactory, request)); return(request); } }
/// <summary> /// Get list of all textures for avatar. /// </summary> public virtual AsyncWebRequest <TextureData[]> GetTexturesAsync(AvatarData avatar) { var r = AvatarJsonArrayRequest <TextureData> (GetUrl("avatars", avatar.code, "textures")); r.State = AvatarSdkMgr.Str(Strings.RequestingTextureInfo); return(r); }
/// <summary> /// Downloading coordinates of the vertices of the head model. This can be used to save download time, because faces and UV are always the same. /// </summary> public virtual AsyncWebRequest <byte[]> DownloadPointCloudZipAsync(AvatarData avatar) { var r = AvatarDataRequestAsync(GetUrl("avatars", avatar.code, "pointcloud")); r.State = AvatarSdkMgr.Str(Strings.DownloadingHeadMesh); return(r); }
/// <summary> /// Get list of all haircuts for avatar. /// </summary> public virtual AsyncWebRequest <AvatarHaircutData[]> GetHaircutsAsync(AvatarData avatar) { var r = AvatarJsonArrayRequest <AvatarHaircutData> (avatar.haircuts); r.State = AvatarSdkMgr.Str(Strings.RequestingHaircutInfo); return(r); }
/// <summary> /// Wait until avatar is calculated. Report progress through the async request object. /// This function will return error (request.IsError == true) only if calculations failed on server or /// avatar has been deleted from the server. In all other cases it will continue to poll status. /// </summary> public virtual AsyncRequest <AvatarData> AwaitAvatarCalculationsAsync(AvatarData avatar) { var request = new AsyncRequest <AvatarData> (AvatarSdkMgr.Str(Strings.StartingCalculations)); AvatarSdkMgr.SpawnCoroutine(AwaitAvatarCalculationsLoop(avatar, request)); return(request); }
/// <summary> /// Download mesh zip file into memory. /// </summary> /// <param name="levelOfDetails">Level of mesh details. 0 - highest resolution, 7 - lowest resolution</param> /// <returns></returns> public virtual AsyncWebRequest <byte[]> DownloadMeshZipAsync(AvatarData avatar, int levelOfDetails = 0) { var url = UrlWithParams(avatar.mesh, "lod", levelOfDetails.ToString()); var r = AvatarDataRequestAsync(url); r.State = AvatarSdkMgr.Str(Strings.DownloadingHeadMesh); return(r); }
/// <summary> /// Download all avatar files, unzip and save to disk. /// </summary> /// <param name="connection">Connection session.</param> /// <param name="avatar">Avatar to download.</param> /// <param name="withHaircutPointClouds">If set to true, download all haircut point clouds too.</param> /// <param name="withBlendshapes">If set to true, download blendshapes too.</param> public static AsyncRequest <AvatarData> DownloadAndSaveAvatarModelAsync( Connection connection, AvatarData avatar, bool withHaircutPointClouds, bool withBlendshapes ) { Debug.LogWarning("This method is obsolete. Use CloudAvatarProvider instead."); var request = new AsyncRequest <AvatarData> (AvatarSdkMgr.Str(Strings.DownloadingAvatar)); AvatarSdkMgr.SpawnCoroutine(DownloadAndSaveAvatarModel(connection, avatar, withHaircutPointClouds, withBlendshapes, request)); return(request); }
/// <summary> /// Download thumbnail with the specified resolution. /// </summary> /// <param name="avatar">Avatar data</param> /// <returns></returns> public virtual AsyncWebRequest <byte[]> DownloadAvatarThumbnailBytesAsync(AvatarData avatar, int maxW, int maxH) { var param = new Dictionary <string, string> { { "max_w", maxW.ToString() }, { "max_h", maxH.ToString() }, }; var url = UrlWithParams(avatar.thumbnail, param); var r = AvatarDataRequestAsync(url); r.State = AvatarSdkMgr.Str(Strings.DownloadingThumbnail); return(r); }
/// <summary> /// Delete avatar record on the server (does not delete local files). /// </summary> public virtual AsyncWebRequest DeleteAvatarAsync(AvatarData avatar) { var request = new AsyncWebRequest(AvatarSdkMgr.Str(Strings.DeletingAvatarOnServer)); Func <UnityWebRequest> webRequestFactory = () => { var webRequest = UnityWebRequest.Delete(avatar.url); SetAuthHeaders(webRequest); webRequest.downloadHandler = new DownloadHandlerBuffer(); return(webRequest); }; AvatarSdkMgr.SpawnCoroutine(AwaitWebRequest(webRequestFactory, request)); return(request); }
/// <summary> /// Downloads zip archive with all the blendshapes. /// </summary> /// <param name="format">Format of blendshapes inside the zip file. Use BIN if you don't know which one to choose.</param> /// <param name="levelOfDetails">Level of mesh details. 0 - highest resolution, 7 - lowest resolution</param> public virtual AsyncWebRequest <byte[]> DownloadBlendshapesZipAsync(AvatarData avatar, BlendshapesFormat format = BlendshapesFormat.BIN, int levelOfDetails = 0) { var fmtToStr = new Dictionary <BlendshapesFormat, string> { { BlendshapesFormat.BIN, "bin" }, { BlendshapesFormat.FBX, "fbx" }, { BlendshapesFormat.PLY, "ply" }, }; string url = string.Format("{0}?fmt={1}&lod={2}", avatar.blendshapes, fmtToStr [format], levelOfDetails); var r = AvatarDataRequestAsync(url); r.State = AvatarSdkMgr.Str(Strings.DownloadingBlendshapes); return(r); }
/// <summary> /// AwaitAvatarCalculationsAsync implementation. /// </summary> private IEnumerator AwaitAvatarCalculationsLoop(AvatarData avatar, AsyncRequest <AvatarData> request) { while (!Strings.FinalStates.Contains(avatar.status)) { yield return(new WaitForSeconds(4)); var avatarStatusRequest = GetAvatarAsync(avatar.code); yield return(avatarStatusRequest); if (avatarStatusRequest.Status.Value == (long)StatusCode.Code.NOT_FOUND) { Debug.LogWarning("404 error most likely means that avatar was deleted from the server"); request.SetError(string.Format("Avatar status response: {0}", avatarStatusRequest.ErrorMessage)); yield break; } if (avatarStatusRequest.Status.Value == (long)StatusCode.Code.TOO_MANY_REQUESTS_THROTTLING) { Debug.LogWarning("Too many requests!"); yield return(new WaitForSeconds(10)); } if (avatarStatusRequest.IsError) { Debug.LogWarningFormat("Status polling error: {0}", avatarStatusRequest.ErrorMessage); // Most likely this is a temporary issue. Keep polling. continue; } avatar = avatarStatusRequest.Result; Debug.LogFormat("Status: {0}, progress: {1}%", avatar.status, avatar.progress); request.State = AvatarSdkMgr.Str(avatar.status); if (avatar.status == Strings.Computing) { request.Progress = (float)avatar.progress / 100; } } if (Strings.GoodFinalStates.Contains(avatar.status)) { request.Result = avatar; request.IsDone = true; } else { request.SetError(string.Format("Avatar calculations failed, status: {0}", avatar.status)); } }
/// <summary> /// GenerateAndSaveAvatarAsync implementation. /// </summary> private static IEnumerator GenerateAndSaveAvatarFunc( Connection connection, string name, string description, byte[] photoBytes, bool withHaircutPointClouds, bool withBlendshapes, bool forcePowerOfTwoTexture, AsyncRequest <AvatarData> request ) { // uploading photo and registering new avatar on the server var createAvatar = connection.CreateAvatarWithPhotoAsync(name, description, photoBytes, forcePowerOfTwoTexture); // Wait until async request is completed (without blocking the main thread). // Instead of using AwaitSubrequest we could just use `yield return createAvatar;` // AwaitSubrequest is a helper function that allows to track progress on composite // requests automatically. It also provides info for the caller about current subrequest // (and it's progress) and propagetes error from subrequest to the parent request. // finalProgress is a value between 0 and 1, a desired progress of parent request when given // subrequest is completed. yield return(request.AwaitSubrequest(createAvatar, finalProgress: 0.19f)); // must check whether request was successful before proceeding if (request.IsError) { yield break; } // Result field contains, well, result of the request. In this case it's an AvatarData object. AvatarData avatar = createAvatar.Result; // save photo for later use var savePhoto = CoreTools.SaveAvatarFileAsync(photoBytes, avatar.code, AvatarFile.PHOTO); yield return(request.AwaitSubrequest(savePhoto, finalProgress: 0.2f)); // again, must check for the error, there's no point in proceeding otherwise if (request.IsError) { yield break; } // Server starts calculating 3D shape and texture after photo has been uploaded. // Now we must wait until calculations finish. var awaitCalculations = connection.AwaitAvatarCalculationsAsync(avatar); yield return(request.AwaitSubrequest(awaitCalculations, finalProgress: 0.95f)); if (request.IsError) { yield break; } // calculations finished, update avatar info from the latest result avatar = awaitCalculations.Result; // download, save and unzip all files var downloadAndSave = DownloadAndSaveAvatarModelAsync(connection, avatar, withHaircutPointClouds, withBlendshapes); yield return(request.AwaitSubrequest(downloadAndSave, finalProgress: 1)); if (request.IsError) { yield break; } // At this point we have all avatar files stored in the filesystem ready for being loaded and displayed. // Our job is considered done here. request.Result = avatar; request.IsDone = true; }
/// <summary> /// DownloadAndSaveAvatarModelAsync implementation. /// </summary> private static IEnumerator DownloadAndSaveAvatarModel( Connection connection, AvatarData avatar, bool withHaircutPointClouds, bool withBlendshapes, AsyncRequest <AvatarData> request ) { // By initializing multiple requests at the same time (without yield between them) we're // starting them in parallel. In this particular case we're downloading multiple files at the same time, // which is usually a bit faster than sequential download. var meshZip = connection.DownloadMeshZipAsync(avatar); var textureRequest = connection.DownloadTextureBytesAsync(avatar); var download = new List <AsyncRequest> { meshZip, textureRequest }; AsyncWebRequest <byte[]> allHaircutPointCloudsZip = null, blendshapesZip = null; #if BLENDSHAPES_IN_PLY_OR_FBX // just a sample of how to get blendshapes in a different format AsyncWebRequest <byte[]> blendshapesZipFbx = null, blendshapesZipPly = null; #endif if (withHaircutPointClouds) { allHaircutPointCloudsZip = connection.DownloadAllHaircutPointCloudsZipAsync(avatar); download.Add(allHaircutPointCloudsZip); } if (withBlendshapes) { blendshapesZip = connection.DownloadBlendshapesZipAsync(avatar); download.Add(blendshapesZip); #if BLENDSHAPES_IN_PLY_OR_FBX // just a sample of how to get blendshapes in a different format blendshapesZipFbx = connection.DownloadBlendshapesZipAsync(avatar, BlendshapesFormat.FBX); download.Add(blendshapesZipFbx); blendshapesZipPly = connection.DownloadBlendshapesZipAsync(avatar, BlendshapesFormat.PLY); download.Add(blendshapesZipPly); #endif } // continue execution when all requests finish yield return(request.AwaitSubrequests(0.9f, download.ToArray())); // return if any of the requests failed if (request.IsError) { yield break; } // save all the results to disk, also in parallel var saveMeshZip = CoreTools.SaveAvatarFileAsync(meshZip.Result, avatar.code, AvatarFile.MESH_ZIP); var saveTexture = CoreTools.SaveAvatarFileAsync(textureRequest.Result, avatar.code, AvatarFile.TEXTURE); var save = new List <AsyncRequest> () { saveMeshZip, saveTexture }; AsyncRequest <string> saveHaircutPointsZip = null, saveBlendshapesZip = null; if (allHaircutPointCloudsZip != null) { saveHaircutPointsZip = CoreTools.SaveAvatarFileAsync(allHaircutPointCloudsZip.Result, avatar.code, AvatarFile.ALL_HAIRCUT_POINTS_ZIP); save.Add(saveHaircutPointsZip); } if (blendshapesZip != null) { saveBlendshapesZip = CoreTools.SaveAvatarFileAsync(blendshapesZip.Result, avatar.code, AvatarFile.BLENDSHAPES_ZIP); save.Add(saveBlendshapesZip); } #if BLENDSHAPES_IN_PLY_OR_FBX // just a sample of how to get blendshapes in a different format if (blendshapesZipFbx != null) { var saveBlendshapesZipFbx = CoreTools.SaveAvatarFileAsync(blendshapesZipFbx.Result, avatar.code, AvatarFile.BLENDSHAPES_FBX_ZIP); save.Add(saveBlendshapesZipFbx); } if (blendshapesZipPly != null) { var saveBlendshapesZipPly = CoreTools.SaveAvatarFileAsync(blendshapesZipPly.Result, avatar.code, AvatarFile.BLENDSHAPES_PLY_ZIP); save.Add(saveBlendshapesZipPly); } #endif yield return(request.AwaitSubrequests(0.95f, save.ToArray())); if (request.IsError) { yield break; } var unzipMesh = CoreTools.UnzipFileAsync(saveMeshZip.Result); var unzip = new List <AsyncRequest> () { unzipMesh }; AsyncRequest <string> unzipHaircutPoints = null, unzipBlendshapes = null; if (saveHaircutPointsZip != null) { unzipHaircutPoints = CoreTools.UnzipFileAsync(saveHaircutPointsZip.Result); unzip.Add(unzipHaircutPoints); } if (saveBlendshapesZip != null) { unzipBlendshapes = UnzipBlendshapes(saveBlendshapesZip.Result, avatar.code); unzip.Add(unzipBlendshapes); } yield return(request.AwaitSubrequests(0.99f, unzip.ToArray())); if (request.IsError) { yield break; } // delete all .zip files we don't need anymore try { foreach (var fileToDelete in new AvatarFile[] { AvatarFile.MESH_ZIP, AvatarFile.ALL_HAIRCUT_POINTS_ZIP, AvatarFile.BLENDSHAPES_ZIP }) { CoreTools.DeleteAvatarFile(avatar.code, fileToDelete); } } catch (Exception ex) { // error here is not critical, we can just ignore it Debug.LogException(ex); } request.Result = avatar; request.IsDone = true; }
/// <summary> /// Downloads zip archive with point clouds for all haircuts. It is recommended to use this request /// for less overall download time (instead of downloading all individual haircuts separately). /// </summary> public virtual AsyncWebRequest <byte[]> DownloadAllHaircutPointCloudsZipAsync(AvatarData avatar) { string url = string.Format("{0}pointclouds/", avatar.haircuts); var r = AvatarDataRequestAsync(url); r.State = AvatarSdkMgr.Str(Strings.DownloadingAllHaircutPointClouds); return(r); }