private void UpdateBordersMesh() { if (bordersMesh == null || halfEdge.InputMesh == null || !halfEdge.Initialized) { return; } bordersMesh.Clear(); Vector3[] vertices = new Vector3[halfEdge.BorderEdgeCount * 2]; int[] edgeIndices = new int[halfEdge.BorderEdgeCount * 2]; int borderIndex = 0; for (int i = 0; i < halfEdge.heHalfEdges.Length; i++) { if (halfEdge.heHalfEdges[i].face == -1) { Oni.Vertex v1 = halfEdge.heVertices[halfEdge.heHalfEdges[i].endVertex]; Oni.Vertex v2 = halfEdge.heVertices[halfEdge.GetHalfEdgeStartVertex(halfEdge.heHalfEdges[i])]; vertices[borderIndex * 2] = v1.position; vertices[borderIndex * 2 + 1] = v2.position; edgeIndices[borderIndex * 2] = borderIndex * 2; edgeIndices[borderIndex * 2 + 1] = borderIndex * 2 + 1; borderIndex++; } } bordersMesh.vertices = vertices; bordersMesh.SetIndices(edgeIndices, UnityEngine.MeshTopology.Lines, 0); }
public IEnumerable <Oni.Vertex> GetNeighbourVerticesEnumerator(Oni.Vertex vertex) { Oni.HalfEdge startEdge = heHalfEdges[vertex.halfEdge]; Oni.HalfEdge edge = startEdge; do { yield return(heVertices[edge.endVertex]); edge = heHalfEdges[edge.pair]; edge = heHalfEdges[edge.nextHalfEdge]; } while (edge.index != startEdge.index); }
public IEnumerable <Oni.HalfEdge> GetNeighbourEdgesEnumerator(Oni.Vertex vertex) { Oni.HalfEdge startEdge = heHalfEdges[vertex.halfEdge]; Oni.HalfEdge edge = startEdge; do { edge = heHalfEdges[edge.pair]; yield return(edge); edge = heHalfEdges[edge.nextHalfEdge]; yield return(edge); } while (edge.index != startEdge.index); }
public IEnumerable <Oni.Face> GetNeighbourFacesEnumerator(Oni.Vertex vertex) { Oni.HalfEdge startEdge = heHalfEdges[vertex.halfEdge]; Oni.HalfEdge edge = startEdge; do { edge = heHalfEdges[edge.pair]; if (edge.face > -1) { yield return(heFaces[edge.face]); } edge = heHalfEdges[edge.nextHalfEdge]; } while (edge.index != startEdge.index); }
/** * Deactivates all fixed particles that are attached to fixed particles only, and all the constraints * affecting them. */ public void Optimize() { // Iterate over all particles and get those fixed ones that are only linked to fixed particles. for (int i = 0; i < topology.heVertices.Length; ++i) { Oni.Vertex vertex = topology.heVertices[i]; if (invMasses[i] > 0) { continue; } active[i] = false; foreach (Oni.Vertex n in topology.GetNeighbourVerticesEnumerator(vertex)) { // If at least one neighbour particle is not fixed, then the particle we are considering for optimization should not be removed. if (invMasses[n.index] > 0) { active[i] = true; break; } } // Deactivate all constraints involving this inactive particle: if (!active[i]) { // for each constraint type: foreach (ObiBatchedConstraints constraint in constraints) { // for each constraint batch (usually only one) if (constraint != null) { foreach (ObiConstraintBatch batch in constraint.GetBatches()) { // deactivate constraints that affect the particle: List <int> affectedConstraints = batch.GetConstraintsInvolvingParticle(i); foreach (int j in affectedConstraints) { batch.DeactivateConstraint(j); } batch.SetActiveConstraints(); } } } } } PushDataToSolver(ParticleData.ACTIVE_STATUS); }
public bool IsParticleFacingCamera(Camera cam, int particleIndex) { if (cam == null) { return(false); } Oni.Vertex vertex = cloth.topology.heVertices[particleIndex]; Vector3 camToParticle = cam.transform.position - wsPositions[particleIndex]; foreach (int index in cloth.topology.visualVertexBuffer[particleIndex]) { if (Vector3.Dot(cloth.transform.TransformVector(cloth.MeshNormals[index]), camToParticle) > 0) { return(true); } } return(false); }
/** * Generates the particle based physical representation of the cloth mesh. This is the initialization method for the cloth object * and should not be called directly once the object has been created. */ public override IEnumerator GeneratePhysicRepresentationForMesh() { initialized = false; initializing = false; if (sharedTopology == null) { Debug.LogError("No ObiMeshTopology provided. Cannot initialize physical representation."); yield break; } else if (!sharedTopology.Initialized) { Debug.LogError("The provided ObiMeshTopology contains no data. Cannot initialize physical representation."); yield break; } initializing = true; RemoveFromSolver(null); ResetTopology(); maxVertexValency = 0; pooledParticles = (int)((topology.heFaces.Length * 3 - topology.heVertices.Length) * tearCapacity); usedParticles = topology.heVertices.Length; int totalParticles = usedParticles + pooledParticles; active = new bool[totalParticles]; positions = new Vector3[totalParticles]; restPositions = new Vector4[totalParticles]; velocities = new Vector3[totalParticles]; vorticities = new Vector3[totalParticles]; invMasses = new float[totalParticles]; solidRadii = new float[totalParticles]; phases = new int[totalParticles]; areaContribution = new float[totalParticles]; tearResistance = new float[totalParticles]; deformableTriangles = new int[topology.heFaces.Length * 3]; // Create a particle for each vertex, and gather per-vertex data (area, valency) for (int i = 0; i < topology.heVertices.Length; i++) { Oni.Vertex vertex = topology.heVertices[i]; // Get the particle's area contribution. areaContribution[i] = 0; foreach (Oni.Face face in topology.GetNeighbourFacesEnumerator(vertex)) { areaContribution[i] += topology.GetFaceArea(face) / 3; } // Calculate particle's valency: int valency = 0; foreach (Oni.HalfEdge edge in topology.GetNeighbourEdgesEnumerator(vertex)) { valency++; } maxVertexValency = Mathf.Max(maxVertexValency, valency); // Get the shortest neighbour edge, particle radius will be half of its length. float minEdgeLength = Single.MaxValue; foreach (Oni.HalfEdge edge in topology.GetNeighbourEdgesEnumerator(vertex)) { minEdgeLength = Mathf.Min(minEdgeLength, Vector3.Distance(topology.heVertices[topology.GetHalfEdgeStartVertex(edge)].position, topology.heVertices[edge.endVertex].position)); } active[i] = true; tearResistance[i] = 1; invMasses[i] = (areaContribution[i] > 0) ? (1.0f / (0.05f * areaContribution[i])) : 0; positions[i] = vertex.position; restPositions[i] = positions[i]; restPositions[i][3] = 0; // activate rest position. solidRadii[i] = minEdgeLength * 0.5f; phases[i] = Oni.MakePhase(gameObject.layer, selfCollisions?Oni.ParticlePhase.SelfCollide:0); if (i % 500 == 0) { yield return(new CoroutineJob.ProgressInfo("ObiCloth: generating particles...", i / (float)topology.heVertices.Length)); } } // Initialize basic data for pooled particles: for (int i = topology.heVertices.Length; i < pooledParticles; i++) { active[i] = false; tearResistance[i] = 1; invMasses[i] = 1.0f / 0.05f; solidRadii[i] = 0.1f; phases[i] = Oni.MakePhase(gameObject.layer, selfCollisions?Oni.ParticlePhase.SelfCollide:0); if (i % 100 == 0) { yield return(new CoroutineJob.ProgressInfo("ObiRope: generating pooled particles...", i / (float)pooledParticles)); } } // Generate deformable triangles: for (int i = 0; i < topology.heFaces.Length; i++) { Oni.Face face = topology.heFaces[i]; Oni.HalfEdge e1 = topology.heHalfEdges[face.halfEdge]; Oni.HalfEdge e2 = topology.heHalfEdges[e1.nextHalfEdge]; Oni.HalfEdge e3 = topology.heHalfEdges[e2.nextHalfEdge]; deformableTriangles[i * 3] = e1.endVertex; deformableTriangles[i * 3 + 1] = e2.endVertex; deformableTriangles[i * 3 + 2] = e3.endVertex; if (i % 500 == 0) { yield return(new CoroutineJob.ProgressInfo("ObiCloth: generating deformable geometry...", i / (float)topology.heFaces.Length)); } } List <ObiMeshTopology.HEEdge> edges = topology.GetEdgeList(); DistanceConstraints.Clear(); ObiDistanceConstraintBatch distanceBatch = new ObiDistanceConstraintBatch(false, false); DistanceConstraints.AddBatch(distanceBatch); // Initialize constraint-halfedge map for cloth tearing purposes: TODO: reset on awake!!! distanceConstraintMap = new int[topology.heHalfEdges.Length]; for (int i = 0; i < distanceConstraintMap.Length; i++) { distanceConstraintMap[i] = -1; } // Create distance springs: for (int i = 0; i < edges.Count; i++) { distanceConstraintMap[edges[i].halfEdgeIndex] = i; Oni.HalfEdge hedge = topology.heHalfEdges[edges[i].halfEdgeIndex]; Oni.Vertex startVertex = topology.heVertices[topology.GetHalfEdgeStartVertex(hedge)]; Oni.Vertex endVertex = topology.heVertices[hedge.endVertex]; distanceBatch.AddConstraint(topology.GetHalfEdgeStartVertex(hedge), hedge.endVertex, Vector3.Distance(startVertex.position, endVertex.position), 1, 1); if (i % 500 == 0) { yield return(new CoroutineJob.ProgressInfo("ObiCloth: generating structural constraints...", i / (float)topology.heHalfEdges.Length)); } } // Create aerodynamic constraints: AerodynamicConstraints.Clear(); ObiAerodynamicConstraintBatch aeroBatch = new ObiAerodynamicConstraintBatch(false, false); AerodynamicConstraints.AddBatch(aeroBatch); for (int i = 0; i < topology.heVertices.Length; i++) { aeroBatch.AddConstraint(i, areaContribution[i], AerodynamicConstraints.dragCoefficient, AerodynamicConstraints.liftCoefficient); if (i % 500 == 0) { yield return(new CoroutineJob.ProgressInfo("ObiCloth: generating aerodynamic constraints...", i / (float)topology.heFaces.Length)); } } BendingConstraints.Clear(); ObiBendConstraintBatch bendBatch = new ObiBendConstraintBatch(false, false); BendingConstraints.AddBatch(bendBatch); bendConstraintOffsets = new int[topology.heVertices.Length + 1]; Dictionary <int, int> cons = new Dictionary <int, int>(); for (int i = 0; i < topology.heVertices.Length; i++) { Oni.Vertex vertex = topology.heVertices[i]; bendConstraintOffsets[i] = bendBatch.ConstraintCount; foreach (Oni.Vertex n1 in topology.GetNeighbourVerticesEnumerator(vertex)) { float cosBest = 0; Oni.Vertex vBest = n1; foreach (Oni.Vertex n2 in topology.GetNeighbourVerticesEnumerator(vertex)) { float cos = Vector3.Dot((n1.position - vertex.position).normalized, (n2.position - vertex.position).normalized); if (cos < cosBest) { cosBest = cos; vBest = n2; } } if (!cons.ContainsKey(vBest.index) || cons[vBest.index] != n1.index) { cons[n1.index] = vBest.index; float[] restPos = new float[] { n1.position[0], n1.position[1], n1.position[2], vBest.position[0], vBest.position[1], vBest.position[2], vertex.position[0], vertex.position[1], vertex.position[2] }; float restBend = Oni.BendingConstraintRest(restPos); bendBatch.AddConstraint(n1.index, vBest.index, vertex.index, restBend, 0, 1); } } if (i % 500 == 0) { yield return(new CoroutineJob.ProgressInfo("ObiCloth: adding bend constraints...", i / (float)sharedTopology.heVertices.Length)); } } bendConstraintOffsets[topology.heVertices.Length] = bendBatch.ConstraintCount; //Initialize pin constraints: PinConstraints.Clear(); ObiPinConstraintBatch pinBatch = new ObiPinConstraintBatch(false, false); PinConstraints.AddBatch(pinBatch); AddToSolver(null); initializing = false; initialized = true; InitializeWithRegularMesh(); pooledVertices = (int)((topology.heFaces.Length * 3 - sharedMesh.vertexCount) * tearCapacity); }
/** * Calculates angle-weighted normals for the input mesh, taking into account shared vertices. */ /*public Vector3[] AngleWeightedNormals(){ * * if (input == null) return null; * * Vector3[] normals = input.normals; * Vector3[] vertices = input.vertices; * * for(int i = 0; i < normals.Length; i++) * normals[i] = Vector3.zero; * * int i1,i2,i3; * Vector3 e1, e2; * foreach(HEFace face in heFaces){ * * HEVertex hv1 = heVertices[heHalfEdges[face.edges[0]].endVertex]; * HEVertex hv2 = heVertices[heHalfEdges[face.edges[1]].endVertex]; * HEVertex hv3 = heVertices[heHalfEdges[face.edges[2]].endVertex]; * * i1 = hv1.physicalIDs[0]; * i2 = hv2.physicalIDs[0]; * i3 = hv3.physicalIDs[0]; * * e1 = vertices[i2]-vertices[i1]; * e2 = vertices[i3]-vertices[i1]; * foreach(int pi in hv1.physicalIDs) * normals[pi] += Vector3.Cross(e1,e2) * Mathf.Acos(Vector3.Dot(e1.normalized,e2.normalized)); * * e1 = vertices[i3]-vertices[i2]; * e2 = vertices[i1]-vertices[i2]; * foreach(int pi in hv2.physicalIDs) * normals[pi] += Vector3.Cross(e1,e2) * Mathf.Acos(Vector3.Dot(e1.normalized,e2.normalized)); * * e1 = vertices[i1]-vertices[i3]; * e2 = vertices[i2]-vertices[i3]; * foreach(int pi in hv3.physicalIDs) * normals[pi] += Vector3.Cross(e1,e2) * Mathf.Acos(Vector3.Dot(e1.normalized,e2.normalized)); * * } * * for(int i = 0; i < normals.Length; i++) * normals[i].Normalize(); * * return normals; * }*/ /** * Splits a vertex in two along a plane. Returns true if the vertex can be split, false otherwise. * \param vertex the vertex to split. * \param splitPlane plane to split the vertex at. * \param newVertex the newly created vertex after the split operation has been performed. * \param vertices new mesh vertices list after the split operation. * \param updatedEdges indices of half-edges that need some kind of constraint update. */ public bool SplitVertex(Oni.Vertex vertex, Plane splitPlane, MeshBuffer meshBuffer, Vector4[] positions, List <int> particleIndices, out Oni.Vertex newVertex, out HashSet <int> updatedEdges, out HashSet <int> addedEdges) { // initialize return values: updatedEdges = new HashSet <int>(); addedEdges = new HashSet <int>(); newVertex = new Oni.Vertex(); // initialize face lists for each side of the split plane. List <Oni.Face> side1Faces = new List <Oni.Face>(); List <Oni.Face> side2Faces = new List <Oni.Face>(); HashSet <int> side2Edges = new HashSet <int>(); // classify adjacent faces depending on which side of the cut plane they reside in: foreach (Oni.Face face in GetNeighbourFacesEnumerator(vertex)) { Oni.HalfEdge e1 = heHalfEdges[face.halfEdge]; Oni.HalfEdge e2 = heHalfEdges[e1.nextHalfEdge]; Oni.HalfEdge e3 = heHalfEdges[e2.nextHalfEdge]; // Skip this face if it doesnt contain the splitted vertex. // This can happen because edge pair links are not updated, and so a vertex in the cut stil "sees" // the faces at the other side like neighbour faces. if (e1.endVertex != vertex.index && e2.endVertex != vertex.index && e3.endVertex != vertex.index) { continue; } // Average positions to get the center of the face: Vector3 faceCenter = (positions[particleIndices[e1.endVertex]] + positions[particleIndices[e2.endVertex]] + positions[particleIndices[e3.endVertex]]) / 3.0f; if (splitPlane.GetSide(faceCenter)) { side1Faces.Add(face); } else { side2Faces.Add(face); side2Edges.Add(e1.index); side2Edges.Add(e2.index); side2Edges.Add(e3.index); } } // If the vertex cant be split, return false. if (side1Faces.Count == 0 || side2Faces.Count == 0) { return(false); } // create a new vertex: newVertex = new Oni.Vertex(vertex.position, heVertices.Count, vertex.halfEdge); // add a new vertex to the mesh too, if needed. if (meshBuffer != null) { visualVertexBuffer.Add(new List <int>() { meshBuffer.vertexCount }); meshBuffer.AddVertex(visualVertexBuffer[vertex.index][0]); } // rearrange edges at side 1: foreach (Oni.Face face in side1Faces) { // find half edges that start or end at the split vertex: int[] faceEdges = GetFaceEdges(face); Oni.HalfEdge edgeIn = heHalfEdges[Array.Find <int>(faceEdges, e => heHalfEdges[e].endVertex == vertex.index)]; Oni.HalfEdge edgeOut = heHalfEdges[Array.Find <int>(faceEdges, e => this.GetHalfEdgeStartVertex(heHalfEdges[e]) == vertex.index)]; // Edges whose pair is on the other side of the cut and share the same vertices, will spawn a new constraint. if (side2Edges.Contains(edgeIn.pair) && GetHalfEdgeStartVertex(edgeIn) == heHalfEdges[edgeIn.pair].endVertex) { addedEdges.Add(Mathf.Max(edgeIn.index, edgeIn.pair)); } if (side2Edges.Contains(edgeOut.pair) && GetHalfEdgeStartVertex(heHalfEdges[edgeOut.pair]) == edgeOut.endVertex) { addedEdges.Add(Mathf.Max(edgeOut.index, edgeOut.pair)); } // Constraints for these edges should be updated. (There's no guarantee the constraint exists!). updatedEdges.Add(edgeIn.index); updatedEdges.Add(edgeIn.pair); updatedEdges.Add(edgeOut.index); updatedEdges.Add(edgeOut.pair); // stitch in half edge to new vertex edgeIn.endVertex = newVertex.index; newVertex.halfEdge = edgeOut.index; heHalfEdges[edgeIn.index] = edgeIn; heHalfEdges[edgeOut.index] = edgeOut; // update mesh triangle buffer to point at new vertex where needed: if (meshBuffer != null) { if (meshBuffer.triangles[face.index * 3] == visualVertexBuffer[vertex.index][0]) { meshBuffer.triangles[face.index * 3] = meshBuffer.vertexCount - 1; } if (meshBuffer.triangles[face.index * 3 + 1] == visualVertexBuffer[vertex.index][0]) { meshBuffer.triangles[face.index * 3 + 1] = meshBuffer.vertexCount - 1; } if (meshBuffer.triangles[face.index * 3 + 2] == visualVertexBuffer[vertex.index][0]) { meshBuffer.triangles[face.index * 3 + 2] = meshBuffer.vertexCount - 1; } } } // Add the nex vertex to the half-edge. heVertices.Add(newVertex); meshInfo.closed = false; return(true); }
private List <HashSet <int> > GenerateIslands(IEnumerable <int> particles, bool onlyFixed) { List <HashSet <int> > islands = new List <HashSet <int> >(); // Partition fixed particles into islands: foreach (int i in particles) { Oni.Vertex vertex = topology.heVertices[i]; if ((onlyFixed && invMasses[i] > 0) || !active[i]) { continue; } int assignedIsland = -1; // keep a list of islands to merge with ours: List <int> mergeableIslands = new List <int>(); // See if any of our neighbors is part of an island: foreach (Oni.Vertex n in topology.GetNeighbourVerticesEnumerator(vertex)) { if (!active[n.index]) { continue; } for (int k = 0; k < islands.Count; ++k) { if (islands[k].Contains(n.index)) { // if we are not in an island yet, pick this one: if (assignedIsland < 0) { assignedIsland = k; islands[k].Add(i); } // if we already are in an island, we will merge this newfound island with ours: else if (assignedIsland != k && !mergeableIslands.Contains(k)) { mergeableIslands.Add(k); } } } } // merge islands with the assigned one: foreach (int merge in mergeableIslands) { islands[assignedIsland].UnionWith(islands[merge]); } // remove merged islands: mergeableIslands.Sort(); mergeableIslands.Reverse(); foreach (int merge in mergeableIslands) { islands.RemoveAt(merge); } // If no adjacent particle is in an island, create a new one: if (assignedIsland < 0) { islands.Add(new HashSet <int>() { i }); } } return(islands); }
/** * Generates the particle based physical representation of the cloth mesh. This is the initialization method for the cloth object * and should not be called directly once the object has been created. */ protected override IEnumerator Initialize() { initialized = false; initializing = false; if (sharedTopology == null) { Debug.LogError("No ObiMeshTopology provided. Cannot initialize physical representation."); yield break; } else if (!sharedTopology.Initialized) { Debug.LogError("The provided ObiMeshTopology contains no data. Cannot initialize physical representation."); yield break; } initializing = true; RemoveFromSolver(null); GameObject.DestroyImmediate(topology); topology = GameObject.Instantiate(sharedTopology); active = new bool[topology.heVertices.Length]; positions = new Vector3[topology.heVertices.Length]; restPositions = new Vector4[topology.heVertices.Length]; velocities = new Vector3[topology.heVertices.Length]; invMasses = new float[topology.heVertices.Length]; principalRadii = new Vector3[topology.heVertices.Length]; phases = new int[topology.heVertices.Length]; areaContribution = new float[topology.heVertices.Length]; deformableTriangles = new int[topology.heFaces.Length * 3]; initialScaleMatrix.SetTRS(Vector3.zero, Quaternion.identity, transform.lossyScale); // Create a particle for each vertex: for (int i = 0; i < topology.heVertices.Length; i++) { Oni.Vertex vertex = topology.heVertices[i]; // Get the particle's area contribution. areaContribution[i] = 0; foreach (Oni.Face face in topology.GetNeighbourFacesEnumerator(vertex)) { areaContribution[i] += topology.GetFaceArea(face) / 3; } // Get the shortest neighbour edge, particle radius will be half of its length. float minEdgeLength = Single.MaxValue; foreach (Oni.HalfEdge edge in topology.GetNeighbourEdgesEnumerator(vertex)) { // vertices at each end of the edge: Vector3 v1 = initialScaleMatrix * topology.heVertices[topology.GetHalfEdgeStartVertex(edge)].position; Vector3 v2 = initialScaleMatrix * topology.heVertices[edge.endVertex].position; minEdgeLength = Mathf.Min(minEdgeLength, Vector3.Distance(v1, v2)); } active[i] = true; invMasses[i] = (skinnedMeshRenderer == null && areaContribution[i] > 0) ? (1.0f / (DEFAULT_PARTICLE_MASS * areaContribution[i])) : 0; positions[i] = initialScaleMatrix * vertex.position; restPositions[i] = positions[i]; restPositions[i][3] = 1; // activate rest position. principalRadii[i] = Vector3.one * minEdgeLength * 0.5f; phases[i] = Oni.MakePhase(1, selfCollisions?Oni.ParticlePhase.SelfCollide:0); if (i % 500 == 0) { yield return(new CoroutineJob.ProgressInfo("ObiCloth: generating particles...", i / (float)topology.heVertices.Length)); } } // Generate deformable triangles: for (int i = 0; i < topology.heFaces.Length; i++) { Oni.Face face = topology.heFaces[i]; Oni.HalfEdge e1 = topology.heHalfEdges[face.halfEdge]; Oni.HalfEdge e2 = topology.heHalfEdges[e1.nextHalfEdge]; Oni.HalfEdge e3 = topology.heHalfEdges[e2.nextHalfEdge]; deformableTriangles[i * 3] = e1.endVertex; deformableTriangles[i * 3 + 1] = e2.endVertex; deformableTriangles[i * 3 + 2] = e3.endVertex; if (i % 500 == 0) { yield return(new CoroutineJob.ProgressInfo("ObiCloth: generating deformable geometry...", i / (float)topology.heFaces.Length)); } } List <ObiMeshTopology.HEEdge> edges = topology.GetEdgeList(); DistanceConstraints.Clear(); ObiDistanceConstraintBatch distanceBatch = new ObiDistanceConstraintBatch(true, false); DistanceConstraints.AddBatch(distanceBatch); // Create distance springs: for (int i = 0; i < edges.Count; i++) { Oni.HalfEdge hedge = topology.heHalfEdges[edges[i].halfEdgeIndex]; Oni.Vertex startVertex = topology.heVertices[topology.GetHalfEdgeStartVertex(hedge)]; Oni.Vertex endVertex = topology.heVertices[hedge.endVertex]; distanceBatch.AddConstraint(topology.GetHalfEdgeStartVertex(hedge), hedge.endVertex, Vector3.Distance(initialScaleMatrix * startVertex.position, initialScaleMatrix * endVertex.position), 1, 1); if (i % 500 == 0) { yield return(new CoroutineJob.ProgressInfo("ObiCloth: generating structural constraints...", i / (float)topology.heHalfEdges.Length)); } } // Cook distance constraints, for better cache and SIMD use: distanceBatch.Cook(); // Create aerodynamic constraints: AerodynamicConstraints.Clear(); ObiAerodynamicConstraintBatch aeroBatch = new ObiAerodynamicConstraintBatch(false, false); AerodynamicConstraints.AddBatch(aeroBatch); for (int i = 0; i < topology.heVertices.Length; i++) { aeroBatch.AddConstraint(i, areaContribution[i], AerodynamicConstraints.dragCoefficient, AerodynamicConstraints.liftCoefficient); if (i % 500 == 0) { yield return(new CoroutineJob.ProgressInfo("ObiCloth: generating aerodynamic constraints...", i / (float)topology.heFaces.Length)); } } //Create skin constraints (if needed) if (skinnedMeshRenderer != null) { SkinConstraints.Clear(); ObiSkinConstraintBatch skinBatch = new ObiSkinConstraintBatch(true, false); SkinConstraints.AddBatch(skinBatch); for (int i = 0; i < topology.heVertices.Length; ++i) { skinBatch.AddConstraint(i, initialScaleMatrix * topology.heVertices[i].position, Vector3.up, 0.05f, 0.1f, 0, 1); if (i % 500 == 0) { yield return(new CoroutineJob.ProgressInfo("ObiCloth: generating skin constraints...", i / (float)topology.heVertices.Length)); } } for (int i = 0; i < topology.normals.Length; ++i) { skinBatch.skinNormals[topology.visualMap[i]] = topology.normals[i]; } skinBatch.Cook(); } //Create pressure constraints if the mesh is closed: VolumeConstraints.Clear(); if (topology.IsClosed) { ObiVolumeConstraintBatch volumeBatch = new ObiVolumeConstraintBatch(false, false); VolumeConstraints.AddBatch(volumeBatch); float avgInitialScale = (initialScaleMatrix.m00 + initialScaleMatrix.m11 + initialScaleMatrix.m22) * 0.33f; int[] triangleIndices = new int[topology.heFaces.Length * 3]; for (int i = 0; i < topology.heFaces.Length; i++) { Oni.Face face = topology.heFaces[i]; Oni.HalfEdge e1 = topology.heHalfEdges[face.halfEdge]; Oni.HalfEdge e2 = topology.heHalfEdges[e1.nextHalfEdge]; Oni.HalfEdge e3 = topology.heHalfEdges[e2.nextHalfEdge]; triangleIndices[i * 3] = e1.endVertex; triangleIndices[i * 3 + 1] = e2.endVertex; triangleIndices[i * 3 + 2] = e3.endVertex; if (i % 500 == 0) { yield return(new CoroutineJob.ProgressInfo("ObiCloth: generating volume constraints...", i / (float)topology.heFaces.Length)); } } volumeBatch.AddConstraint(triangleIndices, topology.MeshVolume * avgInitialScale, 1, 1); } //Create bending constraints: BendingConstraints.Clear(); ObiBendConstraintBatch bendBatch = new ObiBendConstraintBatch(true, false); BendingConstraints.AddBatch(bendBatch); Dictionary <int, int> cons = new Dictionary <int, int>(); for (int i = 0; i < topology.heVertices.Length; i++) { Oni.Vertex vertex = topology.heVertices[i]; foreach (Oni.Vertex n1 in topology.GetNeighbourVerticesEnumerator(vertex)) { float cosBest = 0; Oni.Vertex vBest = n1; foreach (Oni.Vertex n2 in topology.GetNeighbourVerticesEnumerator(vertex)) { float cos = Vector3.Dot((n1.position - vertex.position).normalized, (n2.position - vertex.position).normalized); if (cos < cosBest) { cosBest = cos; vBest = n2; } } if (!cons.ContainsKey(vBest.index) || cons[vBest.index] != n1.index) { cons[n1.index] = vBest.index; Vector3 n1Pos = initialScaleMatrix * n1.position; Vector3 bestPos = initialScaleMatrix * vBest.position; Vector3 vertexPos = initialScaleMatrix * vertex.position; float[] bendRestPositions = new float[] { n1Pos[0], n1Pos[1], n1Pos[2], bestPos[0], bestPos[1], bestPos[2], vertexPos[0], vertexPos[1], vertexPos[2] }; float restBend = Oni.BendingConstraintRest(bendRestPositions); bendBatch.AddConstraint(n1.index, vBest.index, vertex.index, restBend, 0, 1); } } if (i % 500 == 0) { yield return(new CoroutineJob.ProgressInfo("ObiCloth: adding bend constraints...", i / (float)sharedTopology.heVertices.Length)); } } bendBatch.Cook(); // Initialize tether constraints: TetherConstraints.Clear(); // Initialize pin constraints: PinConstraints.Clear(); ObiPinConstraintBatch pinBatch = new ObiPinConstraintBatch(false, false); PinConstraints.AddBatch(pinBatch); initializing = false; initialized = true; if (skinnedMeshRenderer == null) { InitializeWithRegularMesh(); } else { InitializeWithSkinnedMesh(); } }
/** * Automatically generates tether constraints for the cloth. * Partitions fixed particles into "islands", then generates up to maxTethers constraints for each * particle, linking it to the closest point in each island. */ public override bool GenerateTethers(int maxTethers) { if (!Initialized) { return(false); } TetherConstraints.Clear(); if (maxTethers > 0) { ObiTetherConstraintBatch tetherBatch = new ObiTetherConstraintBatch(true, false); TetherConstraints.AddBatch(tetherBatch); List <HashSet <int> > islands = new List <HashSet <int> >(); // Partition fixed particles into islands: for (int i = 0; i < topology.heVertices.Length; i++) { Oni.Vertex vertex = topology.heVertices[i]; if (invMasses[i] > 0 || !active[i]) { continue; } int assignedIsland = -1; // keep a list of islands to merge with ours: List <int> mergeableIslands = new List <int>(); // See if any of our neighbors is part of an island: foreach (Oni.Vertex n in topology.GetNeighbourVerticesEnumerator(vertex)) { if (!active[n.index]) { continue; } for (int k = 0; k < islands.Count; ++k) { if (islands[k].Contains(n.index)) { // if we are not in an island yet, pick this one: if (assignedIsland < 0) { assignedIsland = k; islands[k].Add(i); } // if we already are in an island, we will merge this newfound island with ours: else if (assignedIsland != k && !mergeableIslands.Contains(k)) { mergeableIslands.Add(k); } } } } // merge islands with the assigned one: foreach (int merge in mergeableIslands) { islands[assignedIsland].UnionWith(islands[merge]); } // remove merged islands: mergeableIslands.Sort(); mergeableIslands.Reverse(); foreach (int merge in mergeableIslands) { islands.RemoveAt(merge); } // If no adjacent particle is in an island, create a new one: if (assignedIsland < 0) { islands.Add(new HashSet <int>() { i }); } } // Generate tether constraints: for (int i = 0; i < invMasses.Length; ++i) { if (invMasses[i] == 0 || !active[i]) { continue; } List <KeyValuePair <float, int> > tethers = new List <KeyValuePair <float, int> >(islands.Count * maxTethers); // Find the closest particle in each island, and add it to tethers. foreach (HashSet <int> island in islands) { int closest = -1; float minDistance = Mathf.Infinity; foreach (int j in island) { float distance = (topology.heVertices[i].position - topology.heVertices[j].position).sqrMagnitude; if (distance < minDistance) { minDistance = distance; closest = j; } } if (closest >= 0) { tethers.Add(new KeyValuePair <float, int>(minDistance, closest)); } } // Sort tether indices by distance: tethers.Sort( delegate(KeyValuePair <float, int> x, KeyValuePair <float, int> y) { return(x.Key.CompareTo(y.Key)); } ); // Create constraints for "maxTethers" closest anchor particles: for (int k = 0; k < Mathf.Min(maxTethers, tethers.Count); ++k) { tetherBatch.AddConstraint(i, tethers[k].Value, Mathf.Sqrt(tethers[k].Key), TetherConstraints.tetherScale, TetherConstraints.stiffness); } } tetherBatch.Cook(); } return(true); }