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(); } } }
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); } } }