/// <summary> /// Loads the avatar head files from disk into TexturedMesh object (parses .ply file too). /// </summary> /// <param name="avatarCode">Avatar code</param> /// <param name="withBlendshapes">If True, blendshapes will be loaded and added to mesh.</param> /// <param name="detailsLevel">Indicates polygons count in mesh. 0 - highest resolution, 3 - lowest resolution.</param> public static AsyncRequest <TexturedMesh> LoadAvatarHeadFromDiskAsync(string avatarCode, bool withBlendshapes, int detailsLevel) { var request = new AsyncRequest <TexturedMesh> (AvatarSdkMgr.Str(Strings.LoadingAvatar)); AvatarSdkMgr.SpawnCoroutine(LoadAvatarHeadFromDisk(avatarCode, withBlendshapes, detailsLevel, request)); return(request); }
/// <summary> /// Read blendshapes from the avatar directory and add them to 3D head mesh. /// </summary> public static AsyncRequest <Mesh> AddBlendshapesAsync(string avatarId, Mesh mesh, int[] indexMap) { var request = new AsyncRequest <Mesh> (AvatarSdkMgr.Str(Strings.LoadingAnimations)); AvatarSdkMgr.SpawnCoroutine(AddBlendshapes(avatarId, mesh, indexMap, request)); return(request); }
/// <summary> /// Loads the avatar haircut files from disk into TexturedMesh object (parses .ply files too). /// </summary> /// <returns>Async request which gives complete haircut TexturedMesh object eventually.</returns> public static AsyncRequest <TexturedMesh> LoadHaircutFromDiskAsync(string avatarCode, string haircutId) { var request = new AsyncRequest <TexturedMesh> (AvatarSdkMgr.Str(Strings.LoadingHaircut)); AvatarSdkMgr.SpawnCoroutine(LoadHaircutFromDiskFunc(avatarCode, haircutId, request)); return(request); }
/// <summary> /// Loads the mesh with the given details level and converts from .ply format into Unity format. /// </summary> public static AsyncRequest <MeshData> LoadDetailedMeshDataFromDiskAsync(string avatarId, int detailsLevel) { var request = new AsyncRequest <MeshData>(AvatarSdkMgr.Str(Strings.LoadingFiles)); AvatarSdkMgr.SpawnCoroutine(LoadDetailedMeshDataFromDisk(avatarId, detailsLevel, request)); return(request); }
/// <summary> /// Read text file asynchronously /// </summary> public static AsyncRequest <string> ReadFileAsync(string path) { var request = new AsyncRequestThreaded <string>(() => File.ReadAllText(path)); AvatarSdkMgr.SpawnCoroutine(request.Await()); return(request); }
/// <summary> /// Parsing .ply-encoded 3D points (e.g. "haircut point cloud"). /// </summary> public static AsyncRequest <Vector3[]> PlyToPointsAsync(byte[] plyBytes) { var request = new AsyncRequestThreaded <Vector3[]> (() => { Vector3[] points; PlyReader.ReadPointCloudFromPly(plyBytes, out points); return(points); }, AvatarSdkMgr.Str(Strings.ParsingPoints)); AvatarSdkMgr.SpawnCoroutine(request.Await()); return(request); }
/// <summary> /// Loads the avatar haircut points file asynchronously. /// </summary> /// <param name="code">Avatar unique code.</param> /// <param name="haircutId">Unique ID of a haircut.</param> public static AsyncRequest <byte[]> LoadAvatarHaircutPointcloudFileAsync(string code, string haircutId) { try { var filename = AvatarSdkMgr.Storage().GetAvatarHaircutPointCloudFilename(code, haircutId); return(LoadFileAsync(filename)); } catch (Exception ex) { Debug.LogException(ex); var request = new AsyncRequest <byte[]> (); request.SetError(string.Format("Could not load haircut {0} point cloud, reason: {1}", haircutId, ex.Message)); return(request); } }
/// <summary> /// Some of the files involved in avatar generation (e.g. textures) may be large. This function helps to /// work around this by saving file in a separate thread, thus not blocking the main thread. /// </summary> /// <param name="bytes">Binary file content.</param> /// <param name="path">Full absolute path.</param> public static AsyncRequest <string> SaveFileAsync(byte[] bytes, string path) { var request = new AsyncRequestThreaded <string> (() => { Directory.CreateDirectory(Path.GetDirectoryName(path)); File.WriteAllBytes(path, bytes); return(path); }); request.State = AvatarSdkMgr.Str(Strings.SavingFiles); AvatarSdkMgr.SpawnCoroutine(request.Await()); return(request); }
/// <summary> /// Loads the haircut file asynchronously. /// </summary> /// <param name="haircutId">Unique ID of a haircut.</param> /// <param name="file">File type (e.g. haircut texture).</param> public static AsyncRequest <byte[]> LoadHaircutFileAsync(string haircutId, HaircutFile file) { try { var filename = AvatarSdkMgr.Storage().GetHaircutFilename(haircutId, file); return(LoadFileAsync(filename)); } catch (Exception ex) { Debug.LogException(ex); var request = new AsyncRequest <byte[]> (); request.SetError(string.Format("Could not load {0}, reason: {1}", file, ex.Message)); return(request); } }
/// <summary> /// Helper method that automatically generates full path to file from file type and avatar id, and then calls /// SaveFileAsync. /// </summary> /// <param name="bytes">Binary file content.</param> /// <param name="code">Avatar code.</param> /// <param name="file">Avatar file type.</param> public static AsyncRequest <string> SaveAvatarFileAsync(byte[] bytes, string code, AvatarFile file) { try { var filename = AvatarSdkMgr.Storage().GetAvatarFilename(code, file); return(SaveFileAsync(bytes, filename)); } catch (Exception ex) { Debug.LogException(ex); var request = new AsyncRequest <string> (""); request.SetError(string.Format("Could not save {0}, reason: {1}", file, ex.Message)); return(request); } }
/// <summary> /// Same as SaveAvatarFileAsync, but for haircut points, because they are unique for each avatar and should be stored in avatar folder. /// </summary> /// <param name="bytes">Binary file content.</param> /// <param name="code">Avatar unique code.</param> /// <param name="haircutId">Unique ID of a haircut.</param> public static AsyncRequest <string> SaveAvatarHaircutPointCloudZipFileAsync( byte[] bytes, string code, string haircutId ) { try { var filename = AvatarSdkMgr.Storage().GetAvatarHaircutPointCloudZipFilename(code, haircutId); return(SaveFileAsync(bytes, filename)); } catch (Exception ex) { Debug.LogException(ex); var request = new AsyncRequest <string> ("Saving file"); request.SetError(string.Format("Could not save point cloud zip, reason: {0}", ex.Message)); return(request); } }
/// <summary> /// Parsing .ply data asynchronously into Unity mesh data (vertices, triangles, etc.) /// </summary> /// <param name="plyBytes">Binary content of .ply file.</param> public static AsyncRequest <MeshData> PlyToMeshDataAsync(byte[] plyBytes) { var request = new AsyncRequestThreaded <MeshData> (() => { var meshData = new MeshData(); PlyReader.ReadMeshDataFromPly( plyBytes, out meshData.vertices, out meshData.triangles, out meshData.uv, out meshData.indexMap ); return(meshData); }, AvatarSdkMgr.Str(Strings.ParsingMeshData)); AvatarSdkMgr.SpawnCoroutine(request.Await()); return(request); }
/// <summary> /// Converts current haircut mesh from ply to fbx format and saves recolored texture. /// </summary> public static void HaircutPlyToFbx(string avatarId, string haircutId, string fbxFile, Color color, Vector4 tint) { var filenames = AvatarSdkMgr.Storage(); var pointCloudPlyFile = filenames.GetAvatarHaircutPointCloudFilename(avatarId, haircutId); var haircutPlyFile = filenames.GetHaircutFilename(haircutId, HaircutFile.HAIRCUT_MESH_PLY); var srcHaircutTextureFile = filenames.GetHaircutFilename(haircutId, HaircutFile.HAIRCUT_TEXTURE); var dstHaircutTextureFile = Path.Combine(Path.GetDirectoryName(fbxFile), Path.GetFileNameWithoutExtension(fbxFile) + ".png"); var returnCode = CreateMeshConverter().СonvertPlyModelToFbx(pointCloudPlyFile, haircutPlyFile, fbxFile, dstHaircutTextureFile); if (returnCode != 0) { Debug.LogErrorFormat("Unable export haircut to fbx. Error code: {0}", returnCode); return; } RecolorAndSaveTexture(srcHaircutTextureFile, dstHaircutTextureFile, color, tint); }
/// <summary> /// Unzips the file asynchronously. /// </summary> /// <param name="path">Absolute path to zip file.</param> /// <param name="location">Unzip location. If null, then files will be unzipped in the location of .zip file.</param> public static AsyncRequest <string> UnzipFileAsync(string path, string location = null) { if (string.IsNullOrEmpty(location)) { location = Path.GetDirectoryName(path); } AsyncRequest <string> request = null; Func <string> unzipFunc = () => { ZipUtils.Unzip(path, location); return(location); }; // unzip asynchronously in a separate thread request = new AsyncRequestThreaded <string> (() => unzipFunc(), AvatarSdkMgr.Str(Strings.UnzippingFile)); AvatarSdkMgr.SpawnCoroutine(request.Await()); return(request); }
/// <summary> /// Converts avatar mesh from ply to obj format /// </summary> public static void AvatarPlyToObj(string avatarId, AvatarFile avatarMesh, AvatarFile avatarTexture, string objFile) { var plyFile = AvatarSdkMgr.Storage().GetAvatarFilename(avatarId, avatarMesh); var srcTextureFile = AvatarSdkMgr.Storage().GetAvatarFilename(avatarId, avatarTexture); var dstTextureFile = Path.Combine(Path.GetDirectoryName(objFile), Path.GetFileNameWithoutExtension(objFile) + ".jpg"); var returnCode = CreateMeshConverter().ConvertPlyModelToObj(plyFile, null, objFile, dstTextureFile); if (returnCode != 0) { Debug.LogErrorFormat("Unable convert avatar to obj. Error code: {0}", returnCode); return; } if (File.Exists(dstTextureFile)) { File.Delete(dstTextureFile); } File.Copy(srcTextureFile, dstTextureFile); }
public static void ExportAvatarAsFbx(string avatarId, string fbxFile) { var plyFile = AvatarSdkMgr.Storage().GetAvatarFilename(avatarId, AvatarFile.MESH_PLY); var blendshapeDir = AvatarSdkMgr.Storage().GetAvatarSubdirectory(avatarId, AvatarSubdirectory.BLENDSHAPES); // copy texture to destination dir var srcTextureFile = AvatarSdkMgr.Storage().GetAvatarFilename(avatarId, AvatarFile.TEXTURE); var dstTextureFile = Path.Combine(Path.GetDirectoryName(fbxFile), Path.GetFileNameWithoutExtension(fbxFile) + ".jpg"); if (File.Exists(dstTextureFile)) { File.Delete(dstTextureFile); } File.Copy(srcTextureFile, dstTextureFile); var returnCode = CreateMeshConverter().ExportFbxWithBlendshapes(plyFile, dstTextureFile, blendshapeDir, fbxFile); if (returnCode != 0) { Debug.LogErrorFormat("Unable export avatar to fbx. Error code: {0}", returnCode); } }
/// <summary> /// Delete entire avatar directory. /// </summary> public static void DeleteAvatarFiles(string avatarCode) { var path = AvatarSdkMgr.Storage().GetAvatarDirectory(avatarCode); Directory.Delete(path, true); }
/// <summary> /// Delete particular avatar file by type (e.g. zip mesh file after unzip). /// </summary> public static void DeleteAvatarFile(string avatarCode, AvatarFile file) { var path = AvatarSdkMgr.Storage().GetAvatarFilename(avatarCode, file); File.Delete(path); }
/// <summary> /// Read blendshapes from the avatar directory and add them to 3D head mesh. /// </summary> private static IEnumerator AddBlendshapes(string avatarId, Mesh mesh, int[] indexMap, AsyncRequest <Mesh> request) { var blendshapesDirs = AvatarSdkMgr.Storage().GetAvatarBlendshapesDirs(avatarId); var loadBlendshapesRequest = new AsyncRequestThreaded <Dictionary <string, Vector3[]> > ((r) => { var timer = new MeasureTime("Read all blendshapes"); var blendshapes = new Dictionary <string, Vector3[]> (); List <string> blendshapeFiles = new List <string>(); foreach (string dir in blendshapesDirs) { blendshapeFiles.AddRange(Directory.GetFiles(dir)); } var blendshapeReader = new BlendshapeReader(indexMap); for (int i = 0; i < blendshapeFiles.Count; ++i) { var blendshapePath = blendshapeFiles [i]; var filename = Path.GetFileName(blendshapePath); // crude parsing of filenames if (!filename.EndsWith(".bin")) { continue; } var tokens = filename.Split(new [] { ".bin" }, StringSplitOptions.None); if (tokens.Length != 2) { continue; } var blendshapeName = tokens [0]; blendshapes [blendshapeName] = blendshapeReader.ReadVerticesDeltas(blendshapePath); r.Progress = (float)i / blendshapeFiles.Count; } timer.Stop(); return(blendshapes); }, AvatarSdkMgr.Str(Strings.ParsingBlendshapes)); yield return(request.AwaitSubrequest(loadBlendshapesRequest, finalProgress: 0.9f)); if (request.IsError) { yield break; } var addBlendshapesTimer = DateTime.Now; float targetFps = 30.0f; int numBlendshapes = 0, loadedSinceLastPause = 0; var blendshapesDict = loadBlendshapesRequest.Result; foreach (var blendshape in blendshapesDict) { mesh.AddBlendShapeFrame(blendshape.Key, 100.0f, blendshape.Value, null, null); ++numBlendshapes; ++loadedSinceLastPause; if ((DateTime.Now - addBlendshapesTimer).TotalMilliseconds > 1000.0f / targetFps && loadedSinceLastPause >= 5) { // Debug.LogFormat ("Pause after {0} blendshapes to avoid blocking the main thread", numBlendshapes); yield return(null); addBlendshapesTimer = DateTime.Now; loadedSinceLastPause = 0; } } request.Result = mesh; request.IsDone = true; }