public static TripletMatrix CreateBoundedMeanLaplacian(TriangleMesh mesh, double lambda = 0.0, double eye = 0.0, bool normalized = false) { PrecomputeTraits(mesh); var vertexCount = mesh.Vertices.Count; int neighborCount = mesh.Vertices.Aggregate(0, (c, x) => x.VertexCount()); var L = new TripletMatrix(vertexCount, vertexCount, neighborCount, true); foreach (var currentVertex in mesh.Vertices) { if (currentVertex.OnBoundary) { L.Entry(currentVertex.Index, currentVertex.Index, 1d); continue; } // Diagonal entry v(i,i) = eye*1 + λ*L(i,i) var edgeSum = currentVertex.Halfedges.Sum(e => TanWeights(e)); var diagonalVal = eye + GetNormalized(edgeSum, edgeSum, normalized) * lambda; L.Entry(currentVertex.Index, currentVertex.Index, diagonalVal); currentVertex.Halfedges.Apply(e => L.Entry(currentVertex.Index, e.ToVertex.Index, -GetNormalized(TanWeights(e), edgeSum, normalized) * lambda)); } return(L); }
/// </summary> public static TripletMatrix CreateBoundedUniformLaplacian(TriangleMesh mesh, double lambda = 0.0, double eye = 0.0, bool normalized = false) { var vertexCount = mesh.Vertices.Count; int neighborCount = mesh.Vertices.Aggregate(0, (c, x) => x.VertexCount()); var L = new TripletMatrix(vertexCount, vertexCount, neighborCount, true); foreach (var currentVertex in mesh.Vertices) { if (currentVertex.OnBoundary) { L.Entry(currentVertex.Index, currentVertex.Index, 1d); continue; } // Diagonal entry v(i,i) = eye*1 + λ*L(i,i) var diagonalVal = eye + GetNormalized(currentVertex.Vertices.Count(), currentVertex.VertexCount(), normalized) * lambda; L.Entry(currentVertex.Index, currentVertex.Index, diagonalVal); currentVertex.Vertices.Apply(nb => L.Entry(currentVertex.Index, nb.Index, GetNormalized(-1, currentVertex.VertexCount(), normalized) * lambda)); } return(L); }
/// <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> /// The Least-Squares Conformal Mapping method, see (Levy et al. 2002) /// Performs linear mapping with free boundary /// </summary> /// <param name="meshIn">input mesh, after solving its texture coordinates in vertex traits will be adjusted</param> /// <param name="meshOut">an flattened output mesh with only X,Y coordinates set, Z is set to 0</param> private void LSCM(TriangleMesh meshin, out TriangleMesh meshout) { /// copy mesh for output meshout = meshin.Copy(); /// provide uv's for fixed 2 points var b = new double[] { this.P1UV.X, this.P1UV.Y, // u1,v1 this.P2UV.X, this.P2UV.Y, // u2,v2 }; /// get counts int n = meshout.Vertices.Count; int m = meshout.Faces.Count; /// output uv-coordinates var bu = new double[n]; var bv = new double[n]; var b0 = new double[n]; var A1 = new TripletMatrix(2 * m, 2 * n - 4, 6 * 2 * m); var A2 = new TripletMatrix(2 * m, 4, 4 * 2 * m); foreach (var face in meshin.Faces) { var v1_global = face.Vertices.ElementAt(0).Traits.Position; var v2_global = face.Vertices.ElementAt(1).Traits.Position; var v3_global = face.Vertices.ElementAt(2).Traits.Position; var xDir = v2_global - v1_global; var skewedZDir = v3_global - v1_global; var yDir = Vector3.Cross(xDir, skewedZDir); xDir.Normalize(); yDir.Normalize(); var zDir = Vector3.Cross(yDir, xDir); var transform = new Matrix(new[] { xDir.X, xDir.Y, xDir.Z, 0, yDir.X, yDir.Y, yDir.Z, 0, zDir.X, zDir.Y, zDir.Z, 0, 0, 0, 0, 1, }); transform.Transpose(); var v1 = Vector3.Transform(v1_global, transform); var v2 = Vector3.Transform(v2_global, transform); var v3 = Vector3.Transform(v3_global, transform); var areaTriangle = ((double)v1.X * v2.Z - (double)v1.Z * v2.X) + ((double)v2.X * v3.Z - (double)v2.Z * v3.X) + ((double)v3.X * v1.Z - (double)v3.Z * v1.X); var mImaginary = new Vector3(v3.Z - v2.Z, v1.Z - v3.Z, v2.Z - v1.Z) * (float)(1d / areaTriangle); var mReal = new Vector3(v3.X - v2.X, v1.X - v3.X, v2.X - v1.X) * (float)(1d / areaTriangle); var subIndex = 0; // Expected x layout : u1 v1 u2 v2...ui vi ui+2 vi+2...uj vj uj+2 vj+2...un vn foreach (var vertex in face.Vertices) { if (vertex.Index == P1Index || vertex.Index == P2Index) { var entryIndex = vertex.Index == P1Index ? 0 : 1; // -R*MT * u (dx,dy) A2.Entry(2 * face.Index, 2 * entryIndex, -mReal[subIndex]); A2.Entry(2 * face.Index + 1, 2 * entryIndex, -mImaginary[subIndex]); // MT * v (dx,dy) A2.Entry(2 * face.Index, 2 * entryIndex + 1, mImaginary[subIndex]); A2.Entry(2 * face.Index + 1, 2 * entryIndex + 1, -mReal[subIndex]); } else { var entryIndex = AdaptedIndexFor(vertex.Index); // -R*MT * u (dx,dy) A1.Entry(2 * face.Index, 2 * entryIndex, mReal[subIndex]); A1.Entry(2 * face.Index + 1, 2 * entryIndex, mImaginary[subIndex]); // MT * v (dx,dy) A1.Entry(2 * face.Index, 2 * entryIndex + 1, -mImaginary[subIndex]); A1.Entry(2 * face.Index + 1, 2 * entryIndex + 1, mReal[subIndex]); } subIndex++; } } double[] bPrime; A2.Compress().Ax(b, out bPrime); var solver = QR.Create(A1.Compress()); solver.Solve(bPrime); for (var vertIndex = 0; vertIndex < n; vertIndex++) { if (vertIndex == P1Index) { bu[vertIndex] = P1UV[0]; bv[vertIndex] = P1UV[1]; } else if (vertIndex == P2Index) { bu[vertIndex] = P2UV[0]; bv[vertIndex] = P2UV[1]; } else { var adaptedIndex = AdaptedIndexFor(vertIndex); bu[vertIndex] = bPrime[2 * adaptedIndex]; bv[vertIndex] = bPrime[2 * adaptedIndex + 1]; } } /// update mesh positions and uv's MeshLaplacian.UpdateMesh(meshout, bu, bv, b0, bu, bv); MeshLaplacian.UpdateMesh(meshin, bu, bv); }