Example #1
0
        public static int[] ComputeLinearlyIndependentBlendShapeIndices(UnityEngine.Mesh mesh, int[] blendShapeIndices)
        {
            var numShapes = blendShapeIndices.Length;
            var outShapes = new int[numShapes];

            var numVertices  = mesh.vertexCount;
            var numEquations = 3 * numVertices;
            var numVariables = 0;

            var tmpPositions = new UnityEngine.Vector3[numVertices];
            var tmpTangents  = new UnityEngine.Vector3[numVertices];
            var tmpNormals   = new UnityEngine.Vector3[numVertices];

            var A = new double[numEquations, 1];            // start with an empty column
            var _ = new double[numEquations];

            {
                for (int j = 0; j != numShapes; j++)
                {
                    int k = blendShapeIndices[j];

                    if (EditorUtilityProxy.DisplayCancelableProgressBar("Filter linearly independent blend shapes", "Processing shape " + k + " (" + mesh.GetBlendShapeName(k) + ")", j / (float)numShapes))
                    {
                        outShapes = null;
                        break;                        // user cancelled
                    }
                    mesh.GetBlendShapeFrameVertices(k, 0, tmpPositions, tmpNormals, tmpTangents);

                    for (int i = 0; i != numVertices; i++)
                    {
                        _[i * 3 + 0] = tmpPositions[i].x;
                        _[i * 3 + 1] = tmpPositions[i].y;
                        _[i * 3 + 2] = tmpPositions[i].z;
                    }

                    A.SetColumn(numVariables, _);                    // write to empty column

                    var rank = Matrix.Rank(A.TransposeAndDot(A));
                    if (rank == numVariables + 1)
                    {
                        outShapes[numVariables++] = k;
                        A = A.Concatenate(_);                        // grow by one column
                    }
                    else
                    {
                        UnityEngine.Debug.LogWarning("shape " + k + " (" + mesh.GetBlendShapeName(k) + ") did NOT increase rank => skip");
                    }
                }

                EditorUtilityProxy.ClearProgressBar();
            }

            if (outShapes != null)
            {
                Array.Resize(ref outShapes, numVariables);
            }

            return(outShapes);
        }
        public void BuildFrom(MeshAdjacency meshAdjacency, int[] constraintIndices)
        {
            vertexCount = meshAdjacency.vertexCount;

            this.constraintIndices = constraintIndices.Clone() as int[];
            this.constraintWeight  = 1.0;

            // count unconstrained laplacian non-zero fields
            int nzmax = vertexCount;

            for (int i = 0; i != vertexCount; i++)
            {
                nzmax += meshAdjacency.vertexVertices.lists[i].size;
            }

            // build Ls
            EditorUtilityProxy.DisplayProgressBar("MeshLaplacian", "build Ls", 0.0f);
            var Ls_storage = new CoordinateStorage <double>(vertexCount, vertexCount, nzmax);

            for (int i = 0; i != vertexCount; i++)            // D
            {
                //TODO proper fix
                //Ls_storage.At(i, i, meshAdjacency.vertexVertices.lists[i].size);
                Ls_storage.At(i, i, Mathf.Max(1, meshAdjacency.vertexVertices.lists[i].size));
            }
            for (int i = 0; i != vertexCount; i++)            // A
            {
                foreach (var j in meshAdjacency.vertexVertices[i])
                {
                    Ls_storage.At(i, j, -1.0);
                }
            }
            Ls = Converter.ToCompressedColumnStorage(Ls_storage) as SparseMatrix;

            // build Lc
            EditorUtilityProxy.DisplayProgressBar("MeshLaplacian", "build Lc", 0.0f);
            var Lc_storage = new CoordinateStorage <double>(vertexCount + constraintIndices.Length, vertexCount, nzmax + constraintIndices.Length);

            for (int i = 0; i != vertexCount; i++)
            {
                //TODO proper fix
                //Lc_storage.At(i, i, meshAdjacency.vertexVertices.lists[i].size);
                Lc_storage.At(i, i, Mathf.Max(1, meshAdjacency.vertexVertices.lists[i].size));
            }
            for (int i = 0; i != vertexCount; i++)
            {
                foreach (var j in meshAdjacency.vertexVertices[i])
                {
                    Lc_storage.At(i, j, -1.0);
                }
            }
            for (int i = 0; i != constraintIndices.Length; i++)
            {
                Lc_storage.At(vertexCount + i, constraintIndices[i], constraintWeight);
            }
            Lc = Converter.ToCompressedColumnStorage(Lc_storage) as SparseMatrix;

            // build LcT
            EditorUtilityProxy.DisplayProgressBar("MeshLaplacian", "build LcT", 0.0f);
            LcT = Lc.Transpose() as SparseMatrix;

            // build LcT_Lc
            EditorUtilityProxy.DisplayProgressBar("MeshLaplacian", "build LcT_Lc", 0.0f);
            LcT_Lc = LcT.Multiply(Lc) as SparseMatrix;

            // build LcT_Lc_chol
            EditorUtilityProxy.DisplayProgressBar("MeshLaplacian", "build LcT_Lc_chol", 0.0f);
            LcT_Lc_chol = SparseCholesky.Create(LcT_Lc, ColumnOrdering.MinimumDegreeAtPlusA);

            // done
            EditorUtilityProxy.ClearProgressBar();
        }
        public void BuildFrom(MeshAdjacency meshAdjacency, int[] roiIndices, int roiBoundaryLevels, int[] roiConstraintIndices = null)
        {
            unsafe
            {
                using (var visited = new UnsafeArrayBool(meshAdjacency.vertexCount))
                    using (var visitedBoundary = new UnsafeArrayBool(meshAdjacency.vertexCount))
                        using (var visitor = new UnsafeBFS(meshAdjacency.vertexCount))
                        {
                            // find boundary
                            visited.Clear(false);
                            visitedBoundary.Clear(false);
                            visitor.Clear();

                            int visitedCount         = 0;
                            int visitedBoundaryCount = 0;

                            foreach (int i in roiIndices)
                            {
                                visited.val[i] = true;
                                visitedCount++;
                                visitor.Ignore(i);
                            }

                            foreach (int i in roiIndices)
                            {
                                foreach (var j in meshAdjacency.vertexVertices[i])
                                {
                                    visitor.Insert(j);
                                }
                            }

                            // step boundary
                            while (visitor.MoveNext())
                            {
                                int i = visitor.position;

                                visited.val[i] = true;
                                visitedCount++;
                                visitedBoundary.val[i] = true;
                                visitedBoundaryCount++;

                                if (visitor.depth < roiBoundaryLevels)
                                {
                                    foreach (var j in meshAdjacency.vertexVertices[i])
                                    {
                                        visitor.Insert(j);
                                    }
                                }
                            }

                            // add constraints
                            if (roiConstraintIndices != null)
                            {
                                foreach (int i in roiConstraintIndices)
                                {
                                    if (visited.val[i])
                                    {
                                        if (visitedBoundary.val[i] == false)
                                        {
                                            visitedBoundary.val[i] = true;
                                            visitedBoundaryCount++;
                                        }
                                    }
                                    else
                                    {
                                        Debug.LogWarning("ignoring user constraint outside ROI: vertex " + i);
                                    }
                                }
                            }

                            // build translations
                            internalCount = 0;
                            externalCount = meshAdjacency.vertexCount;

                            internalFromExternal = new int[externalCount];
                            externalFromInternal = new int[visitedCount];

                            for (int i = 0; i != meshAdjacency.vertexCount; i++)
                            {
                                if (visited.val[i])
                                {
                                    int internalIndex = internalCount++;
                                    externalFromInternal[internalIndex] = i;
                                    internalFromExternal[i]             = internalIndex;
                                }
                                else
                                {
                                    internalFromExternal[i] = -1;
                                }
                            }

                            // find constraint indices
                            constraintIndices = new int[visitedBoundaryCount];
                            constraintWeight  = 1.0;

                            int constraintCount = 0;
                            for (int i = 0; i != internalCount; i++)
                            {
                                if (visitedBoundary.val[externalFromInternal[i]])
                                {
                                    constraintIndices[constraintCount++] = i;
                                }
                            }

                            // count unconstrained laplacian non-zero fields
                            int nzmax = internalCount;
                            for (int i = 0; i != internalCount; i++)
                            {
                                nzmax += InternalValence(meshAdjacency, i);
                            }

                            // build Ls
                            EditorUtilityProxy.DisplayProgressBar("MeshLaplacian", "build Ls", 0.0f);
                            var Ls_storage = new CoordinateStorage <double>(internalCount, internalCount, nzmax);
                            for (int i = 0; i != internalCount; i++)            // D
                            {
                                //TODO proper fix
                                //Ls_storage.At(i, i, InternalValence(meshAdjacency, i));
                                Ls_storage.At(i, i, Mathf.Max(1, InternalValence(meshAdjacency, i)));
                            }
                            for (int i = 0; i != internalCount; i++)            // A
                            {
                                foreach (var k in meshAdjacency.vertexVertices[externalFromInternal[i]])
                                {
                                    int j = internalFromExternal[k];
                                    if (j != -1)
                                    {
                                        Ls_storage.At(i, j, -1.0);
                                    }
                                }
                            }
                            Ls = Converter.ToCompressedColumnStorage(Ls_storage) as SparseMatrix;

                            // build Lc
                            EditorUtilityProxy.DisplayProgressBar("MeshLaplacian", "build Lc", 0.0f);
                            var Lc_storage = new CoordinateStorage <double>(internalCount + constraintCount, internalCount, nzmax + constraintCount);
                            for (int i = 0; i != internalCount; i++)
                            {
                                //TODO proper fix
                                //Lc_storage.At(i, i, InternalValence(meshAdjacency, i));
                                Lc_storage.At(i, i, Mathf.Max(1, InternalValence(meshAdjacency, i)));
                            }
                            for (int i = 0; i != internalCount; i++)
                            {
                                foreach (var k in meshAdjacency.vertexVertices[externalFromInternal[i]])
                                {
                                    int j = internalFromExternal[k];
                                    if (j != -1)
                                    {
                                        Lc_storage.At(i, j, -1.0);
                                    }
                                }
                            }
                            for (int i = 0; i != constraintIndices.Length; i++)
                            {
                                Lc_storage.At(internalCount + i, constraintIndices[i], constraintWeight);
                            }
                            Lc = Converter.ToCompressedColumnStorage(Lc_storage) as SparseMatrix;

                            // build LcT
                            EditorUtilityProxy.DisplayProgressBar("MeshLaplacian", "build LcT", 0.0f);
                            LcT = Lc.Transpose() as SparseMatrix;

                            // build LcT_Lc
                            EditorUtilityProxy.DisplayProgressBar("MeshLaplacian", "build LcT_Lc", 0.0f);
                            LcT_Lc = LcT.Multiply(Lc) as SparseMatrix;

                            // build LcT_Lc_chol
                            EditorUtilityProxy.DisplayProgressBar("MeshLaplacian", "build LcT_Lc_chol", 0.0f);
                            LcT_Lc_chol = SparseCholesky.Create(LcT_Lc, ColumnOrdering.MinimumDegreeAtPlusA);

                            // done
                            EditorUtilityProxy.ClearProgressBar();
                        }
            }
        }
Example #4
0
        public static void FitFramesToBlendShapes(SkinDeformation[] frames, UnityEngine.Mesh mesh, int[] blendShapeIndices, Method fittingMethod, Param fittingParam)
        {
            // Ax = b
            // x* = (A^T A)^-1 A^T b
            int numShapes    = mesh.blendShapeCount;
            int numVertices  = mesh.vertexCount;
            int numEquations = 0;
            int numVariables = blendShapeIndices.Length;

            var meshBuffers = null as MeshBuffers;
            var meshEdges   = null as MeshEdges;

            switch (fittingParam)
            {
            case Param.DeltaPosition:
                numEquations = 3 * numVertices;
                break;

            case Param.OutputEdgeLength:
            case Param.OutputEdgeCurvature:
                meshBuffers  = new MeshBuffers(mesh);
                meshEdges    = new MeshEdges(mesh.triangles);
                numEquations = meshEdges.edges.Length;
                break;
            }

            var tmpPositions = new UnityEngine.Vector3[numVertices];
            var tmpTangents  = new UnityEngine.Vector3[numVertices];
            var tmpNormals   = new UnityEngine.Vector3[numVertices];

            var edgeLengths    = null as float[];
            var edgeCurvatures = null as float[];

            // prepare A
            var A = new double[numEquations, numVariables];
            var _ = new double[numEquations];
            {
                for (int j = 0; j != numVariables; j++)
                {
                    int k = blendShapeIndices[j];

                    EditorUtilityProxy.DisplayProgressBar("Building 'A'", "Processing shape " + k + " (" + mesh.GetBlendShapeName(k) + ")", j / (float)numVariables);
                    mesh.GetBlendShapeFrameVertices(k, 0, tmpPositions, tmpNormals, tmpTangents);

                    switch (fittingParam)
                    {
                    case Param.DeltaPosition:
                        for (int i = 0; i != numVertices; i++)
                        {
                            _[i * 3 + 0] = tmpPositions[i].x;
                            _[i * 3 + 1] = tmpPositions[i].y;
                            _[i * 3 + 2] = tmpPositions[i].z;
                        }
                        break;

                    case Param.OutputEdgeLength:
                        for (int i = 0; i != numVertices; i++)
                        {
                            tmpPositions[i] += meshBuffers.vertexPositions[i];
                        }
                        meshEdges.ComputeLengths(ref edgeLengths, tmpPositions);
                        for (int i = 0; i != numEquations; i++)
                        {
                            _[i] = edgeLengths[i];
                        }
                        break;

                    case Param.OutputEdgeCurvature:
                        for (int i = 0; i != numVertices; i++)
                        {
                            tmpPositions[i] += meshBuffers.vertexPositions[i];
                            tmpNormals[i]   += meshBuffers.vertexNormals[i];
                            tmpNormals[i].Normalize();
                        }
                        meshEdges.ComputeCurvatures(ref edgeCurvatures, tmpPositions, tmpNormals);
                        for (int i = 0; i != numEquations; i++)
                        {
                            _[i] = edgeCurvatures[i];
                        }
                        break;
                    }

                    A.SetColumn(j, _);
                }
            }

            // prepare (A^T A)^-1 A^T for LLS
            var At_A_inv_At = null as double[, ];

            if (fittingMethod == Method.LinearLeastSquares)
            {
                EditorUtilityProxy.DisplayProgressBar("Computing A^T", "Processing ...", 0.0f);
                var At = A.Transpose();
                EditorUtilityProxy.DisplayProgressBar("Computing (A^T A)", "Processing ...", 0.25f);
                var At_A = At.Dot(A);
                EditorUtilityProxy.DisplayProgressBar("Computing (A^T A)^-1", "Processing ...", 0.5f);
                var At_A_inv = At_A.Inverse();
                EditorUtilityProxy.DisplayProgressBar("Computing (A^T A)^-1 A^T", "Processing ...", 0.75f);
                At_A_inv_At = At_A_inv.Dot(At);
            }

            // prepare A[][] for NNLS
            var A_jagged = null as double[][];

            if (fittingMethod == Method.NonNegativeLeastSquares)
            {
                A_jagged = MakeJagged(A);
            }

            // prepare shared job data
            sharedJobData.frames            = frames;
            sharedJobData.blendShapeIndices = blendShapeIndices;

            sharedJobData.meshBuffers = meshBuffers;
            sharedJobData.meshEdges   = meshEdges;

            sharedJobData.numEquations = numEquations;
            sharedJobData.numVariables = numVariables;
            sharedJobData.numVertices  = numVertices;

            sharedJobData.fittingMethod = fittingMethod;
            sharedJobData.fittingParam  = fittingParam;

            sharedJobData.At_A_inv_At = At_A_inv_At;
            sharedJobData.A_jagged    = A_jagged;

            // prepare jobs
            var jobs       = new FrameFittingJob[frames.Length];
            var jobHandles = new JobHandle[frames.Length];

            for (int k = 0; k != jobs.Length; k++)
            {
                jobs[k].frameIndex = k;
            }

            // execute jobs
            for (int k = 0; k != jobs.Length; k++)
            {
                jobHandles[k] = jobs[k].Schedule();
            }

            // wait until done
            var progressTime0        = DateTime.Now;
            var progressNumCompleted = -1;

            while (true)
            {
                int numCompleted = 0;
                for (int i = 0; i != jobs.Length; i++)
                {
                    numCompleted += (jobHandles[i].IsCompleted ? 1 : 0);
                }

                if (numCompleted == jobs.Length)
                {
                    break;
                }

                if (numCompleted > progressNumCompleted)
                {
                    var progressVal = numCompleted / (float)frames.Length;
                    var progressMsg = "Processing frames ... Completed " + numCompleted + " / " + frames.Length;
                    if (progressVal > 0.0f)
                    {
                        var timeElapsed = DateTime.Now - progressTime0;
                        var timeArrival = TimeSpan.FromMilliseconds(timeElapsed.TotalMilliseconds / progressVal);
                        var timeRemains = timeArrival - timeElapsed;

                        progressMsg += " ... Est. time " + string.Format("{0}m{1:D2}s", (int)UnityEngine.Mathf.Floor((float)timeRemains.TotalMinutes), timeRemains.Seconds);
                    }

                    switch (sharedJobData.fittingMethod)
                    {
                    case Method.LinearLeastSquares:
                        EditorUtilityProxy.DisplayProgressBar("Computing x* = (A^T A)^-1 A^T b", progressMsg, progressVal);
                        break;

                    case Method.NonNegativeLeastSquares:
                        EditorUtilityProxy.DisplayProgressBar("Computing x* = NonNegativeLeastSquares(A, b)", progressMsg, progressVal);
                        break;
                    }

                    progressNumCompleted = numCompleted;
                }
                else
                {
                    System.Threading.Thread.Sleep(1);
                }
            }
        }