private SlaveVertex BindToFace(int slaveIndex, MasterFace triangle, Vector3 position, Vector3 normalPoint, Vector3 tangentPoint) { return(new SlaveVertex(slaveIndex, triangle.index, FindSkinBarycentricCoords(triangle, position, 24, 0.001f), FindSkinBarycentricCoords(triangle, normalPoint, 16, 0.02f), FindSkinBarycentricCoords(triangle, tangentPoint, 16, 0.02f))); }
private float GetFaceMappingError(MasterFace triangle, SlaveVertex vertex, Vector3 normal) { // initialize the error with barycentric coords error (larger the further away we are from the triangle's limits): float bary_error = GetBarycentricError(vertex.position.barycentricCoords) * barycentricWeight; float error = bary_error; // calculate deviation of point normal from face normal, use it to weight normal barycentric error: float normal_deviation = Mathf.Clamp01(0.5f * (1.0f - Mathf.Abs(Vector3.Dot(triangle.faceNormal, normal)))); error += normal_deviation * GetBarycentricError(vertex.normal.barycentricCoords) * normalAlignmentWeight; // make height relative to triangle size, and calculate error weight based on barycentric error: float height_val = vertex.position.height / triangle.size; float height_w = 0.3f + 2.5f * bary_error; error += height_w * Mathf.Abs(height_val) * elevationWeight; return(error); }
private bool BindToFace(int slaveIndex, MasterFace triangle, Vector3 position, Vector3 normalPoint, Vector3 tangentPoint, out SlaveVertex skinning) { skinning = SlaveVertex.empty; BarycentricPoint posBary; if (FindSkinBarycentricCoords(triangle, position, 24, 0.001f, out posBary)) { BarycentricPoint normBary; BarycentricPoint tangentBary; FindSkinBarycentricCoords(triangle, normalPoint, 16, 0.005f, out normBary); FindSkinBarycentricCoords(triangle, tangentPoint, 16, 0.005f, out tangentBary); skinning = new SlaveVertex(slaveIndex, triangle.index, posBary, normBary, tangentBary); return(true); } return(false); }
public IEnumerator Bind() { Clear(); if (master == null || slave == null) { yield break; } Vector3[] slavePositions = slave.vertices; Vector3[] slaveNormals = slave.normals; Vector4[] slaveTangents = slave.tangents; Matrix4x4 s2world = m_SlaveTransform.GetMatrix4X4(); Matrix4x4 s2worldNormal = s2world.inverse.transpose; // count active triangles normals: int activeDeformableTriangles = 0; for (int i = 0; i < master.deformableTriangles.Length; i += 3) { int t1 = master.deformableTriangles[i]; int t2 = master.deformableTriangles[i + 1]; int t3 = master.deformableTriangles[i + 2]; if (t1 >= master.activeParticleCount || t2 >= master.activeParticleCount || t3 >= master.activeParticleCount) { continue; } activeDeformableTriangles++; } // generate master triangle info: MasterFace[] masterFaces = new MasterFace[activeDeformableTriangles]; int count = 0; for (int i = 0; i < master.deformableTriangles.Length; i += 3) { MasterFace face = new MasterFace(); int t1 = master.deformableTriangles[i]; int t2 = master.deformableTriangles[i + 1]; int t3 = master.deformableTriangles[i + 2]; if (t1 >= master.activeParticleCount || t2 >= master.activeParticleCount || t3 >= master.activeParticleCount) { continue; } face.p1 = master.positions[t1]; face.p2 = master.positions[t2]; face.p3 = master.positions[t3]; face.n1 = master.restNormals[t1]; face.n2 = master.restNormals[t2]; face.n3 = master.restNormals[t3]; face.master = m_MasterChannels[t1] | m_MasterChannels[t2] | m_MasterChannels[t3]; face.size = ((face.p1 - face.p2).magnitude + (face.p1 - face.p3).magnitude + (face.p2 - face.p3).magnitude) / 3.0f; face.faceNormal = Vector3.Cross(face.p2 - face.p1, face.p3 - face.p1).normalized; face.index = i; face.CacheBarycentricData(); masterFaces[count++] = face; if (i % 10 == 0) { yield return(new CoroutineJob.ProgressInfo("Generating master faces...", count / (float)masterFaces.Length)); } } // for each slave vertex, find the best fitting master triangle: for (int i = 0; i < slavePositions.Length; ++i) { // if vertex slave channel is deactivated, don´t skin it. if (m_SlaveChannels[i] == 0) { continue; } // initialize best triangle error and index: float bestError = float.MaxValue; SlaveVertex bestSkinning = SlaveVertex.empty; Vector3 worldPos = s2world.MultiplyPoint3x4(slavePositions[i]); Vector3 worldNormalDir = s2worldNormal.MultiplyVector(slaveNormals[i]).normalized; Vector3 worldNormalPoint = worldPos + worldNormalDir * 0.05f; Vector3 worldTangentPoint = worldPos + s2worldNormal.MultiplyVector(new Vector3(slaveTangents[i].x, slaveTangents[i].y, slaveTangents[i].z).normalized) * 0.05f; // find the best fitting master face: for (int j = 0; j < masterFaces.Length; ++j) { MasterFace face = masterFaces[j]; // if the triangle master channel and the target slave channel do not overlap, skip it. if ((face.master & m_SlaveChannels[i]) == 0) { continue; } // calculate skinning data for this face: SlaveVertex skinning = BindToFace(i, face, worldPos, worldNormalPoint, worldTangentPoint); // calculate mapping error for this triangle: float error = GetFaceMappingError(face, skinning, worldNormalDir); // if the error is less than the best, update it. if (error < bestError) { bestError = error; bestSkinning = skinning; } } // skin target vertex to the best source triangle found, if any: if (!bestSkinning.isEmpty) { skinnedVertices.Add(bestSkinning); } if (i % 5 == 0) { yield return(new CoroutineJob.ProgressInfo("Skinning slave vertices (" + i + "/" + slavePositions.Length + ")...", i / (float)slavePositions.Length)); } } bound = true; #if UNITY_EDITOR EditorUtility.SetDirty(this); #endif }
/** * We need to find the barycentric coordinates of point such that the interpolated normal at that point passes trough our target position. * * X * \ / / * \------/--/ * * This is necessary to ensure curvature changes in the surface affect skinned points away from the face plane. * To do so, we use an iterative method similar to Newton´s method for root finding: * * - Project the point on the triangle using an initial normal. * - Get interpolated normal at projection. * - Intersect line from point and interpolated normal with triangle, to find a new projection. * - Repeat. */ BarycentricPoint FindSkinBarycentricCoords(MasterFace triangle, Vector3 position, int max_iterations, float min_convergence) { BarycentricPoint barycentricPoint = BarycentricPoint.zero; // start at center of triangle: Vector3 trusted_bary = Vector3.one / 3.0f; Vector3 temp_normal = ObiUtils.BarycentricInterpolation(triangle.n1, triangle.n2, triangle.n3, trusted_bary); int it = 0; float trust = 1.0f; float convergence = float.MaxValue; while (it++ < max_iterations) { Vector3 point; if (!Obi.ObiUtils.LinePlaneIntersection(triangle.p1, triangle.faceNormal, position, temp_normal, out point)) { break; } // get bary coords at intersection: Vector3 bary = Vector3.zero; if (!triangle.BarycentricCoords(point, ref bary)) { break; } // calculate error: Vector3 error = bary - trusted_bary; // distance from current estimation to last trusted estimation. convergence = Vector3.Dot(error, error); // get a single convergence value. // weighted sum of bary coords: trusted_bary = (1.0f - trust) * trusted_bary + trust * bary; // update normal temp_normal = ObiUtils.BarycentricInterpolation(triangle.n1, triangle.n2, triangle.n3, trusted_bary); if (convergence < min_convergence) { break; } trust *= 0.8f; } Vector3 pos_on_tri = trusted_bary[0] * triangle.p1 + trusted_bary[1] * triangle.p2 + trusted_bary[2] * triangle.p3; float height = Vector3.Dot(position - pos_on_tri, temp_normal); barycentricPoint.barycentricCoords = trusted_bary; barycentricPoint.height = height; return(barycentricPoint); }