/// <summary> /// Barycentric Parameterization /// Covers barycentric methods which need a fully defined boundary /// A particular method can be chosen by creating an appropriate Laplacian matrix /// See also (Floater 2003) /// </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 BarycentricMapping(TriangleMesh meshin, out TriangleMesh meshout) { /// init an mesh that serves for output of the 2d parametrized mesh meshout = meshin.Copy(); //meshOut = meshIn; /// get lenghts var vertexCount = meshout.Vertices.Count; var boundaryVertices = meshout.Vertices.Where(x => x.OnBoundary).ToList(); /// right hand side (RHS) var bu = new double[vertexCount]; var bv = new double[vertexCount]; var b0 = new double[vertexCount]; // TODO : For geometry images, L mapped edges require splitting. Adaptive length parameterization should be sufficient for crack prediction however FixBoundaryToShape(boundaryVertices, bu, bv); var laplacian = MeshLaplacian.SelectedLaplacian == MeshLaplacian.Type.Harmonic ? MeshLaplacian.CreateBoundedHarmonicLaplacian(meshin, 1d, 0d, true) : MeshLaplacian.SelectedLaplacian == MeshLaplacian.Type.MeanValue ? MeshLaplacian.CreateBoundedMeanLaplacian(meshin, 1d, 0d, true) : MeshLaplacian.CreateBoundedUniformLaplacian(meshin, 1d, 0d, true); var qrSolver = QR.Create(laplacian.Compress()); var success = qrSolver.Solve(bu) && qrSolver.Solve(bv); /// update mesh positions MeshLaplacian.UpdateMesh(meshout, bu, bv, b0, bu, bv); MeshLaplacian.UpdateMesh(meshin, bu, bv); }
/// <summary> /// Computes the QR decomposition for a matrix. /// </summary> /// <param name="matrix">The matrix to factor.</param> /// <returns>The QR decomposition object.</returns> public static QR QR(this Matrix <double> matrix) { return((QR)QR <double> .Create(matrix)); }
/// <summary> /// Computes the QR decomposition for a matrix. /// </summary> /// <param name="matrix">The matrix to factor.</param> /// <param name="method">The type of QR factorization to perform.</param> /// <returns>The QR decomposition object.</returns> public static QR QR(this Matrix <Complex> matrix, QRMethod method = QRMethod.Full) { return((QR)QR <Complex> .Create(matrix, method)); }
/// <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); }
/// <summary> /// Computes the QR decomposition for a matrix. /// </summary> /// <param name="matrix">The matrix to factor.</param> /// <param name="method">The type of QR factorization to perform.</param> /// <returns>The QR decomposition object.</returns> public static QR QR(this Matrix <double> matrix, QRMethod method = QRMethod.Full) { return((QR)QR <double> .Create(matrix, method)); }
/// <summary> /// Computes the QR decomposition for a matrix. /// </summary> /// <param name="matrix">The matrix to factor.</param> /// <returns>The QR decomposition object.</returns> public static QR QR(this Matrix <Complex> matrix) { return((QR)QR <Complex> .Create(matrix)); }
/// <summary> /// Computes the QR decomposition for a matrix. /// </summary> /// <param name="matrix">The matrix to factor.</param> /// <returns>The QR decomposition object.</returns> public static QR QR(this Matrix <float> matrix) { return((QR)QR <float> .Create(matrix)); }