/// <summary> /// Expand matrix to full storage (symmetric matrix is expected to store upper part only). /// </summary> public static SparseMatrix Expand(this SparseMatrix A) { if (A.Any((i, j, a) => i > j && a != 0.0)) { throw new Exception("Expected matrix to be upper."); } // Transpose A. var T = A.Transpose(); // Remove diagonal. T.Keep((i, j, a) => i != j); return((SparseMatrix)A.Add(T)); }
/// <summary> /// The Direct Conformal Parameterization (DCP) method, see (Desbrun et al. 2002) /// </summary> /// <param name="meshin"></param> /// <param name="meshout"></param> private void DCP(TriangleMesh meshin, out TriangleMesh meshout) { MeshLaplacian.PrecomputeTraits(meshin); /// copy the mesh meshout = meshin.Copy(); /// counters var vertexCount = meshout.Vertices.Count; /// output uv-coordinates var bu = new double[vertexCount]; var bv = new double[vertexCount]; var b0 = new double[vertexCount]; // A * x = b var x = new double[vertexCount * 2 + 4]; var M_A = new TripletMatrix(2 * (vertexCount + 2), 2 * vertexCount, 4 * (vertexCount + 1) * vertexCount); var M_X = new TripletMatrix(2 * (vertexCount + 2), 2 * vertexCount, 4 * (vertexCount + 1) * vertexCount); foreach (var vertex in meshin.Vertices.Where(v => !v.OnBoundary)) { var angleWeightSum = 0d; var areaWeightSum = 0d; foreach (var halfEdge in vertex.Halfedges) { // cot(alpha) + cot(beta) var angleWeight = halfEdge.Previous.Traits.Cotan + halfEdge.Opposite.Previous.Traits.Cotan; // cot(gamma) + cot(delta) var areaWeight = (halfEdge.Next.Traits.Cotan + halfEdge.Opposite.Traits.Cotan); areaWeight /= (halfEdge.FromVertex.Traits.Position - halfEdge.ToVertex.Traits.Position).LengthSquared(); M_A.Entry(vertex.Index * 2, halfEdge.ToVertex.Index * 2, angleWeight); M_A.Entry(vertex.Index * 2 + 1, halfEdge.ToVertex.Index * 2 + 1, angleWeight); M_X.Entry(vertex.Index * 2, halfEdge.ToVertex.Index * 2, areaWeight); M_X.Entry(vertex.Index * 2 + 1, halfEdge.ToVertex.Index * 2 + 1, areaWeight); angleWeightSum += angleWeight; areaWeightSum += areaWeight; } M_A.Entry(vertex.Index * 2, vertex.Index * 2, -angleWeightSum); M_A.Entry(vertex.Index * 2 + 1, vertex.Index * 2 + 1, -angleWeightSum); M_X.Entry(vertex.Index * 2, vertex.Index * 2, -areaWeightSum); M_X.Entry(vertex.Index * 2 + 1, vertex.Index * 2 + 1, -areaWeightSum); } // Free boundary foreach (var vertex in meshin.Vertices.Where(v => v.OnBoundary)) { var weightSum = 0d; // Inner edges foreach (var halfEdge in vertex.Halfedges.Where(he => !he.Edge.OnBoundary)) { // cot(alpha) + cot(beta) var borderWeight = halfEdge.Previous.Traits.Cotan + halfEdge.Opposite.Previous.Traits.Cotan; M_A.Entry(vertex.Index * 2, halfEdge.ToVertex.Index * 2, -borderWeight); M_A.Entry(vertex.Index * 2 + 1, halfEdge.ToVertex.Index * 2 + 1, -borderWeight); M_X.Entry(vertex.Index * 2, halfEdge.ToVertex.Index * 2, -borderWeight); M_X.Entry(vertex.Index * 2 + 1, halfEdge.ToVertex.Index * 2 + 1, -borderWeight); weightSum += borderWeight; } // Boundary edges foreach (var halfEdge in vertex.Halfedges.Where(he => he.Edge.OnBoundary)) { // Last edge if (halfEdge.OnBoundary) { var borderWeight = halfEdge.Opposite.Previous.Traits.Cotan; // Weight edge by cotan once and substract the rotated uv vector M_A.Entry(vertex.Index * 2, halfEdge.ToVertex.Index * 2, -borderWeight); M_A.Entry(vertex.Index * 2, halfEdge.ToVertex.Index * 2 + 1, -1); M_A.Entry(vertex.Index * 2 + 1, halfEdge.ToVertex.Index * 2 + 1, -borderWeight); M_A.Entry(vertex.Index * 2 + 1, halfEdge.ToVertex.Index * 2, 1); M_X.Entry(vertex.Index * 2, halfEdge.ToVertex.Index * 2, -borderWeight); M_X.Entry(vertex.Index * 2, halfEdge.ToVertex.Index * 2 + 1, -1); M_X.Entry(vertex.Index * 2 + 1, halfEdge.ToVertex.Index * 2 + 1, -borderWeight); M_X.Entry(vertex.Index * 2 + 1, halfEdge.ToVertex.Index * 2, 1); weightSum += borderWeight; } // First edge else { // cot(alpha) + cot(beta) var borderWeight = halfEdge.Previous.Traits.Cotan; // Weight edge by cotan once and substract the rotated uv vector M_A.Entry(vertex.Index * 2, halfEdge.ToVertex.Index * 2, -borderWeight); M_A.Entry(vertex.Index * 2, halfEdge.ToVertex.Index * 2 + 1, 1); M_A.Entry(vertex.Index * 2 + 1, halfEdge.ToVertex.Index * 2 + 1, -borderWeight); M_A.Entry(vertex.Index * 2 + 1, halfEdge.ToVertex.Index * 2, -1); M_X.Entry(vertex.Index * 2, halfEdge.ToVertex.Index * 2, -borderWeight); M_X.Entry(vertex.Index * 2, halfEdge.ToVertex.Index * 2 + 1, 1); M_X.Entry(vertex.Index * 2 + 1, halfEdge.ToVertex.Index * 2 + 1, -borderWeight); M_X.Entry(vertex.Index * 2 + 1, halfEdge.ToVertex.Index * 2, -1); weightSum += borderWeight; } } M_A.Entry(vertex.Index * 2, vertex.Index * 2, weightSum); M_A.Entry(vertex.Index * 2 + 1, vertex.Index * 2 + 1, weightSum); M_X.Entry(vertex.Index * 2, vertex.Index * 2, weightSum); M_X.Entry(vertex.Index * 2 + 1, vertex.Index * 2 + 1, weightSum); } // Fixed vertices // M^n M_A.Entry(2 * vertexCount, 2 * P1Index, 1); M_A.Entry(2 * vertexCount + 1, 2 * P1Index + 1, 1); M_X.Entry(2 * vertexCount, 2 * P1Index, 1); M_X.Entry(2 * vertexCount + 1, 2 * P1Index + 1, 1); M_A.Entry(2 * vertexCount + 2, 2 * P2Index, 1); M_A.Entry(2 * vertexCount + 3, 2 * P2Index + 1, 1); M_X.Entry(2 * vertexCount + 2, 2 * P2Index, 1); M_X.Entry(2 * vertexCount + 3, 2 * P2Index + 1, 1); // M^n transp M_A.Entry(2 * P1Index, 2 * vertexCount, 1); M_A.Entry(2 * P1Index + 1, 2 * vertexCount + 1, 1); M_X.Entry(2 * P1Index, 2 * vertexCount, 1); M_X.Entry(2 * P1Index + 1, 2 * vertexCount + 1, 1); M_A.Entry(2 * P2Index, 2 * vertexCount + 2, 1); M_A.Entry(2 * P2Index + 1, 2 * vertexCount + 3, 1); M_X.Entry(2 * P2Index, 2 * vertexCount + 2, 1); M_X.Entry(2 * P2Index + 1, 2 * vertexCount + 3, 1); // b^n x[2 * vertexCount] = P1UV.X; x[2 * vertexCount + 1] = P1UV.Y; x[2 * vertexCount + 2] = P2UV.X; x[2 * vertexCount + 3] = P2UV.Y; var matrix = SparseMatrix.Add(M_A.Compress(), M_X.Compress(), AngleToAreaRatio, 1 - AngleToAreaRatio); var solver = QR.Create(matrix); solver.Solve(x); for (var vertexIndex = 0; vertexIndex < vertexCount; vertexIndex++) { bu[vertexIndex] = x[2 * vertexIndex]; bv[vertexIndex] = x[2 * vertexIndex + 1]; } /// update mesh positions and uv's MeshLaplacian.UpdateMesh(meshout, bu, bv, b0, bu, bv); MeshLaplacian.UpdateMesh(meshin, bu, bv); }
/// <summary> /// Minimum degree ordering of A+A' (if A is symmetric) or A'A. /// </summary> /// <param name="order">0:natural, 1:Chol, 2:LU, 3:QR</param> /// <param name="A">column-compressed matrix</param> /// <returns>amd(A+A') if A is symmetric, or amd(A'A) otherwise, null on /// error or for natural ordering</returns> public static int[] AMD(int order, SparseMatrix A) { SparseMatrix C, A2, AT; int[] Cp, Ci, P, W, nv, next, head, elen, degree, w, hhead, ATp, ATi; int d, dk, dext, lemax = 0, e, elenk, eln, i, j, k, k1, k2, k3, jlast, ln, dense, nzmax, mindeg = 0, nvi, nvj, nvk, mark, wnvi, cnz, nel = 0, p, p1, p2, p3, p4, pj, pk, pk1, pk2, pn, q, n, m; bool ok; int h; // Construct matrix C if (order <= 0 || order > 3) { return(null); // check } AT = (SparseMatrix)A.Transpose(false); // compute A' if (AT == null) { return(null); } m = A.m; n = A.n; dense = Math.Max(16, 10 * (int)Math.Sqrt(n)); // find dense threshold dense = Math.Min(n - 2, dense); if (order == 1 && n == m) { C = SparseMatrix.Add(A, AT, 0, 0); // C = A+A' } else if (order == 2) { ATp = AT.p; // drop dense columns from AT ATi = AT.i; for (p2 = 0, j = 0; j < m; j++) { p = ATp[j]; // column j of AT starts here ATp[j] = p2; // new column j starts here if (ATp[j + 1] - p > dense) { continue; // skip dense col j } for (; p < ATp[j + 1]; p++) { ATi[p2++] = ATi[p]; } } ATp[m] = p2; // finalize AT A2 = (SparseMatrix)AT.Transpose(false); // A2 = AT' C = A2 != null?SparseMatrix.Multiply(AT, A2) : null; // C=A'*A with no dense rows } else { C = SparseMatrix.Multiply(AT, A); // C=A'*A } if (C == null) { return(null); } C.Keep(KeepOffDiag, null); // drop diagonal entries Cp = C.p; cnz = Cp[n]; // add elbow room to C if (!C.Resize(cnz + cnz / 5 + 2 * n)) { return(null); } P = new int[n + 1]; // allocate result W = new int[n + 1]; // get workspace w = new int[n + 1]; degree = new int[n + 1]; elen = new int[n + 1]; // Initialized to 0's // Initialize quotient graph for (k = 0; k < n; k++) { W[k] = Cp[k + 1] - Cp[k]; } W[n] = 0; nzmax = C.nzmax; Ci = C.i; for (i = 0; i <= n; i++) { P[i] = -1; w[i] = 1; // node i is alive degree[i] = W[i]; // degree of node i } next = new int[n + 1]; hhead = new int[n + 1]; head = new int[n + 1]; nv = new int[n + 1]; Array.Copy(P, next, n + 1); Array.Copy(P, head, n + 1); // degree list i is empty Array.Copy(P, hhead, n + 1); // hash list i is empty Array.Copy(w, nv, n + 1); // node i is just one node mark = Clear(0, 0, w, n); // clear w elen[n] = -2; // n is a dead element Cp[n] = -1; // n is a root of assembly tree w[n] = 0; // n is a dead element // Initialize degree lists for (i = 0; i < n; i++) { d = degree[i]; if (d == 0) // node i is empty { elen[i] = -2; // element i is dead nel++; Cp[i] = -1; // i is a root of assembly tree w[i] = 0; } else if (d > dense) // node i is dense { nv[i] = 0; // absorb i into element n elen[i] = -1; // node i is dead nel++; Cp[i] = FLIP(n); nv[n]++; } else { if (head[d] != -1) { P[head[d]] = i; } next[i] = head[d]; // put node i in degree list d head[d] = i; } } while (nel < n) // while (selecting pivots) do { // Select node of minimum approximate degree for (k = -1; mindeg < n && (k = head[mindeg]) == -1; mindeg++) { ; } if (next[k] != -1) { P[next[k]] = -1; } head[mindeg] = next[k]; // remove k from degree list elenk = elen[k]; // elenk = |Ek| nvk = nv[k]; // # of nodes k represents nel += nvk; // nv[k] nodes of A eliminated // Garbage collection if (elenk > 0 && cnz + mindeg >= nzmax) { for (j = 0; j < n; j++) { if ((p = Cp[j]) >= 0) // j is a live node or element { Cp[j] = Ci[p]; // save first entry of object Ci[p] = FLIP(j); // first entry is now CS_FLIP(j) } } for (q = 0, p = 0; p < cnz;) // scan all of memory { if ((j = FLIP(Ci[p++])) >= 0) // found object j { Ci[q] = Cp[j]; // restore first entry of object Cp[j] = q++; // new pointer to object j for (k3 = 0; k3 < W[j] - 1; k3++) { Ci[q++] = Ci[p++]; } } } cnz = q; // Ci [cnz...nzmax-1] now free } // Construct new element dk = 0; nv[k] = -nvk; // flag k as in Lk p = Cp[k]; pk1 = (elenk == 0) ? p : cnz; // do in place if elen[k] == 0 pk2 = pk1; for (k1 = 1; k1 <= elenk + 1; k1++) { if (k1 > elenk) { e = k; // search the nodes in k pj = p; // list of nodes starts at Ci[pj]*/ ln = W[k] - elenk; // length of list of nodes in k } else { e = Ci[p++]; // search the nodes in e pj = Cp[e]; ln = W[e]; // length of list of nodes in e } for (k2 = 1; k2 <= ln; k2++) { i = Ci[pj++]; if ((nvi = nv[i]) <= 0) { continue; // node i dead, or seen } dk += nvi; // degree[Lk] += size of node i nv[i] = -nvi; // negate nv[i] to denote i in Lk Ci[pk2++] = i; // place i in Lk if (next[i] != -1) { P[next[i]] = P[i]; } if (P[i] != -1) // remove i from degree list { next[P[i]] = next[i]; } else { head[degree[i]] = next[i]; } } if (e != k) { Cp[e] = FLIP(k); // absorb e into k w[e] = 0; // e is now a dead element } } if (elenk != 0) { cnz = pk2; // Ci [cnz...nzmax] is free } degree[k] = dk; // external degree of k - |Lk\i| Cp[k] = pk1; // element k is in Ci[pk1..pk2-1] W[k] = pk2 - pk1; elen[k] = -2; // k is now an element // Find set differences mark = Clear(mark, lemax, w, n); // clear w if necessary for (pk = pk1; pk < pk2; pk++) // scan 1: find |Le\Lk| { i = Ci[pk]; if ((eln = elen[i]) <= 0) { continue; // skip if elen[i] empty } nvi = -nv[i]; // nv [i] was negated wnvi = mark - nvi; for (p = Cp[i]; p <= Cp[i] + eln - 1; p++) // scan Ei { e = Ci[p]; if (w[e] >= mark) { w[e] -= nvi; // decrement |Le\Lk| } else if (w[e] != 0) // ensure e is a live element { w[e] = degree[e] + wnvi; // 1st time e seen in scan 1 } } } // Degree update for (pk = pk1; pk < pk2; pk++) // scan2: degree update { i = Ci[pk]; // consider node i in Lk p1 = Cp[i]; p2 = p1 + elen[i] - 1; pn = p1; for (h = 0, d = 0, p = p1; p <= p2; p++) // scan Ei { e = Ci[p]; if (w[e] != 0) // e is an unabsorbed element { dext = w[e] - mark; // dext = |Le\Lk| if (dext > 0) { d += dext; // sum up the set differences Ci[pn++] = e; // keep e in Ei h += e; // compute the hash of node i } else { Cp[e] = FLIP(k); // aggressive absorb. e.k w[e] = 0; // e is a dead element } } } elen[i] = pn - p1 + 1; // elen[i] = |Ei| p3 = pn; p4 = p1 + W[i]; for (p = p2 + 1; p < p4; p++) // prune edges in Ai { j = Ci[p]; if ((nvj = nv[j]) <= 0) { continue; // node j dead or in Lk } d += nvj; // degree(i) += |j| Ci[pn++] = j; // place j in node list of i h += j; // compute hash for node i } if (d == 0) // check for mass elimination { Cp[i] = FLIP(k); // absorb i into k nvi = -nv[i]; dk -= nvi; // |Lk| -= |i| nvk += nvi; // |k| += nv[i] nel += nvi; nv[i] = 0; elen[i] = -1; // node i is dead } else { degree[i] = Math.Min(degree[i], d); // update degree(i) Ci[pn] = Ci[p3]; // move first node to end Ci[p3] = Ci[p1]; // move 1st el. to end of Ei Ci[p1] = k; // add k as 1st element in of Ei W[i] = pn - p1 + 1; // new len of adj. list of node i h = ((h < 0) ? (-h) : h) % n; // finalize hash of i next[i] = hhead[h]; // place i in hash bucket hhead[h] = i; P[i] = h; // save hash of i in last[i] } } // scan2 is done degree[k] = dk; // finalize |Lk| lemax = Math.Max(lemax, dk); mark = Clear(mark + lemax, lemax, w, n); // clear w // Supernode detection for (pk = pk1; pk < pk2; pk++) { i = Ci[pk]; if (nv[i] >= 0) { continue; // skip if i is dead } h = P[i]; // scan hash bucket of node i i = hhead[h]; hhead[h] = -1; // hash bucket will be empty for (; i != -1 && next[i] != -1; i = next[i], mark++) { ln = W[i]; eln = elen[i]; for (p = Cp[i] + 1; p <= Cp[i] + ln - 1; p++) { w[Ci[p]] = mark; } jlast = i; for (j = next[i]; j != -1;) // compare i with all j { ok = (W[j] == ln) && (elen[j] == eln); for (p = Cp[j] + 1; ok && p <= Cp[j] + ln - 1; p++) { if (w[Ci[p]] != mark) { ok = false; // compare i and j } } if (ok) // i and j are identical { Cp[j] = FLIP(i); // absorb j into i nv[i] += nv[j]; nv[j] = 0; elen[j] = -1; // node j is dead j = next[j]; // delete j from hash bucket next[jlast] = j; } else { jlast = j; // j and i are different j = next[j]; } } } } // Finalize new element for (p = pk1, pk = pk1; pk < pk2; pk++) // finalize Lk { i = Ci[pk]; if ((nvi = -nv[i]) <= 0) { continue; // skip if i is dead } nv[i] = nvi; // restore nv[i] d = degree[i] + dk - nvi; // compute external degree(i) d = Math.Min(d, n - nel - nvi); if (head[d] != -1) { P[head[d]] = i; } next[i] = head[d]; // put i back in degree list P[i] = -1; head[d] = i; mindeg = Math.Min(mindeg, d); // find new minimum degree degree[i] = d; Ci[p++] = i; // place i in Lk } nv[k] = nvk; // # nodes absorbed into k if ((W[k] = p - pk1) == 0) // length of adj list of element k { Cp[k] = -1; // k is a root of the tree w[k] = 0; // k is now a dead element } if (elenk != 0) { cnz = p; // free unused space in Lk } } // Postordering for (i = 0; i < n; i++) { Cp[i] = FLIP(Cp[i]); // fix assembly tree } for (j = 0; j <= n; j++) { head[j] = -1; } for (j = n; j >= 0; j--) // place unordered nodes in lists { if (nv[j] > 0) { continue; // skip if j is an element } next[j] = head[Cp[j]]; // place j in list of its parent head[Cp[j]] = j; } for (e = n; e >= 0; e--) // place elements in lists { if (nv[e] <= 0) { continue; // skip unless e is an element } if (Cp[e] != -1) { next[e] = head[Cp[e]]; // place e in list of its parent head[Cp[e]] = e; } } for (k = 0, i = 0; i <= n; i++) // postorder the assembly tree { if (Cp[i] == -1) { k = Common.TreeDepthFirstSearch(i, k, head, next, P, w); } } return(P); }