Beispiel #1
0
        /// <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);
        }
Beispiel #2
0
        /// <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);
        }
Beispiel #3
0
        /// <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);
        }
Beispiel #4
0
        /// <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);
        }
Beispiel #5
0
        /// <summary>
        /// See LoadMeshDataFromDiskAsync.
        /// </summary>
        private static IEnumerator LoadMeshDataFromDisk(string avatarId, AsyncRequest <MeshData> request)
        {
            var meshBytesRequest = LoadAvatarFileAsync(avatarId, AvatarFile.MESH_PLY);

            yield return(request.AwaitSubrequest(meshBytesRequest, finalProgress: 0.5f));

            if (request.IsError)
            {
                yield break;
            }

            var parsePlyTimer   = new MeasureTime("Parse ply");
            var parsePlyRequest = PlyToMeshDataAsync(meshBytesRequest.Result);

            yield return(request.AwaitSubrequest(parsePlyRequest, finalProgress: 1));

            if (request.IsError)
            {
                yield break;
            }
            parsePlyTimer.Stop();

            request.Result = parsePlyRequest.Result;
            request.IsDone = true;
        }
Beispiel #6
0
 /// <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);
     }
 }
Beispiel #7
0
 /// <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);
     }
 }
Beispiel #8
0
 /// <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);
     }
 }
Beispiel #9
0
        /// <summary>
        /// LoadAvatarHeadFromDiskAsync implementation.
        /// </summary>
        private static IEnumerator LoadAvatarHeadFromDisk(
            string avatarId,
            bool withBlendshapes,
            int detailsLevel,
            AsyncRequest <TexturedMesh> request
            )
        {
            // loading two files simultaneously
            var meshDataRequest     = LoadDetailedMeshDataFromDiskAsync(avatarId, detailsLevel);
            var textureBytesRequest = LoadAvatarFileAsync(avatarId, AvatarFile.TEXTURE);

            yield return(request.AwaitSubrequests(0.6f, meshDataRequest, textureBytesRequest));

            if (request.IsError)
            {
                yield break;
            }

            MeshData meshData = meshDataRequest.Result;

            var parseTextureTimer = new MeasureTime("Parse texture data");
            // at this point we have all data we need to generate a textured mesh
            var texturedMesh = new TexturedMesh {
                mesh    = CreateMeshFromMeshData(meshData, "HeadMesh"),
                texture = new Texture2D(0, 0)
            };

            // This actually blocks the main thread for a few frames, which is bad for VR.
            // To optimize: load jpg/png texture in C++ code in a separate thread and only SetPixels here in Unity. Should be faster.
            texturedMesh.texture.LoadImage(textureBytesRequest.Result);
            parseTextureTimer.Stop();

            if (withBlendshapes)
            {
                // adding blendshapes...
                using (new MeasureTime("Add blendshapes")) {
                    var addBlendshapesRequest = AddBlendshapesAsync(avatarId, texturedMesh.mesh, meshData.indexMap);
                    yield return(request.AwaitSubrequest(addBlendshapesRequest, 1.0f));

                    if (addBlendshapesRequest.IsError)
                    {
                        Debug.LogError("Could not add blendshapes!");
                    }
                }
            }

            request.Result = texturedMesh;
            request.IsDone = true;
        }
Beispiel #10
0
 /// <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);
     }
 }
Beispiel #11
0
        /// <summary>
        /// LoadHaircutFromDiskAsync implementation.
        /// </summary>
        private static IEnumerator LoadHaircutFromDiskFunc(
            string avatarCode, string haircutId, AsyncRequest <TexturedMesh> request
            )
        {
            var loadingTime = Time.realtimeSinceStartup;

            // start three async request in parallel
            var haircutTexture = LoadHaircutFileAsync(haircutId, HaircutFile.HAIRCUT_TEXTURE);
            var haircutMesh    = LoadHaircutFileAsync(haircutId, HaircutFile.HAIRCUT_MESH_PLY);
            var haircutPoints  = LoadAvatarHaircutPointcloudFileAsync(avatarCode, haircutId);

            // wait until mesh and points load
            yield return(request.AwaitSubrequests(0.4f, haircutMesh, haircutPoints));

            if (request.IsError)
            {
                yield break;
            }

            // we can start another two subrequests, now parsing the ply files
            var parseHaircutPly    = PlyToMeshDataAsync(haircutMesh.Result);
            var parseHaircutPoints = PlyToPointsAsync(haircutPoints.Result);

            // await everything else we need for the haircut
            yield return(request.AwaitSubrequests(0.95f, parseHaircutPly, parseHaircutPoints, haircutTexture));

            if (request.IsError)
            {
                yield break;
            }

            // now we have all data we need to generate a textured mesh
            var haircutMeshData = ReplacePointCoords(parseHaircutPly.Result, parseHaircutPoints.Result);

            var texturedMesh = new TexturedMesh();

            texturedMesh.mesh    = CreateMeshFromMeshData(haircutMeshData, "HaircutMesh");
            texturedMesh.texture = new Texture2D(0, 0);
            texturedMesh.texture.LoadImage(haircutTexture.Result);

            request.Result = texturedMesh;
            request.IsDone = true;

            Debug.LogFormat("Took {0} seconds to load a haircut", Time.realtimeSinceStartup - loadingTime);
        }
Beispiel #12
0
        /// <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);
        }
Beispiel #13
0
        /// <summary>
        /// Helper function that allows implementation of the "main" request as a sequence of async subrequests.
        /// Usage: yield return request.AwaitSubrequest(subrequest, finalProgress: 0.5f)
        /// </summary>
        /// <param name="subrequest">Subrequest - the async operation that is a part of "main" request.</param>
        /// <param name="finalProgress">Progress of main operation by the end of this sub-operation.</param>
        public IEnumerator AwaitSubrequests(float finalProgress, params AsyncRequest[] subrequests)
        {
            if (IsDone || IsError)
            {
                Debug.LogError("Cannot start subrequest for request that is already finished!");
                yield break;
            }

            if (subrequests.Length == 0)
            {
                Debug.LogError("Cannot await on empty list of subrequests");
                yield break;
            }

            if (finalProgress < Progress)
            {
                Debug.LogWarningFormat(
                    "Cannot not rollback progress from {0} to {1}, progress can only move forward",
                    Progress, finalProgress
                    );
            }

            float initialProgress            = Progress;
            float progressShare              = Math.Max(0, finalProgress - initialProgress);
            float progressSharePerSubrequest = progressShare / subrequests.Length;

            while (!IsDone)
            {
                float newProgress          = initialProgress;
                int   numUnfinished        = subrequests.Length;
                bool  currentSubrequestSet = false;
                foreach (var subrequest in subrequests)
                {
                    if (subrequest.IsError)
                    {
                        SetError(string.Format("{0} failed, reason: {1}", State, subrequest.ErrorMessage));
                        Debug.LogWarning(ErrorMessage);
                        break;
                    }
                    else
                    {
                        newProgress += progressSharePerSubrequest * subrequest.Progress;
                        if (newProgress > 1.0001f)
                        {
                            Debug.LogWarningFormat("Progress for {0} is more than 1 ({1})!", State, newProgress);
                            newProgress = 0.99f;
                        }

                        if (subrequest.IsDone)
                        {
                            --numUnfinished;
                        }
                        else if (!currentSubrequestSet)
                        {
                            CurrentSubrequest    = subrequest;
                            currentSubrequestSet = true;
                        }
                    }
                }

                if (!IsDone)
                {
                    Progress = newProgress;
                }

                if (numUnfinished == 0)
                {
                    break;
                }

                yield return(null);
            }
        }
Beispiel #14
0
 /// <summary>
 /// Tiny wrapper around AwaitSubrequests for trivial case.
 /// </summary>
 public IEnumerator AwaitSubrequest(AsyncRequest request, float finalProgress)
 {
     return(AwaitSubrequests(finalProgress, request));
 }
Beispiel #15
0
        /// <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;
        }
Beispiel #16
0
        /// <summary>
        /// Loads a mesh with the given level of details.
        /// It takes faces and UV-coordinates from the template model, points coordinates from the avatar's model and merges them into a single model.
        /// </summary>
        private static IEnumerator LoadDetailedMeshDataFromDisk(string avatarId, int detailsLevel, AsyncRequest <MeshData> request)
        {
            if (detailsLevel < 0)
            {
                Debug.LogWarningFormat("Invalid details level parameter: {0}. Will be used value 0 (highest resolution).", detailsLevel);
                detailsLevel = 0;
            }

            if (detailsLevel > 4)
            {
                Debug.LogWarningFormat("Invalid details level parameter: {0}. Will be used value 3 (lowest resolution).", detailsLevel);
                detailsLevel = 4;
            }

            if (detailsLevel == 0)
            {
                yield return(LoadMeshDataFromDisk(avatarId, request));
            }
            else
            {
                var meshBytesRequest = LoadAvatarFileAsync(avatarId, AvatarFile.MESH_PLY);
                yield return(request.AwaitSubrequest(meshBytesRequest, finalProgress: 0.3f));

                if (request.IsError)
                {
                    yield break;
                }

                string headTemplateFileName = string.Format("template_heads/head_lod_{0}", detailsLevel);
                var    headTemplateRequest  = Resources.LoadAsync(headTemplateFileName);
                yield return(headTemplateRequest);

                TextAsset templateHeadAsset = headTemplateRequest.asset as TextAsset;
                if (templateHeadAsset == null)
                {
                    Debug.LogError("Unable to load template head!");
                    yield break;
                }

                var meshRequest   = PlyToMeshDataAsync(templateHeadAsset.bytes);
                var pointsRequest = PlyToPointsAsync(meshBytesRequest.Result);
                yield return(request.AwaitSubrequests(0.95f, meshRequest, pointsRequest));

                request.Result = ReplacePointCoords(meshRequest.Result, pointsRequest.Result);
                request.IsDone = true;
            }
        }