using System.Collections; using System.Collections.Generic; using UnityEngine; using System; namespace Obi { [CreateAssetMenu(fileName = "tearable cloth blueprint", menuName = "Obi/Tearable Cloth Blueprint", order = 121)] public class ObiTearableClothBlueprint : ObiClothBlueprint { [Tooltip("Amount of memory preallocated to create extra particles and mesh data when tearing the cloth. 0 means no extra memory will be allocated, and the cloth will not be tearable. 1 means all cloth triangles will be fully tearable.")] [Range(0, 1)] public float tearCapacity = 0.5f; [HideInInspector][SerializeField] private int pooledParticles = 0; [HideInInspector] public float[] tearResistance; /**< Per-particle tear resistance.*/ [HideInInspector][SerializeField] public Vector2Int[] distanceConstraintMap; /** constraintHalfEdgeMap[half-edge index] = distance constraint index, or -1 if there's no constraint. Each initial constraint is the lower-index of each pair of half-edges. When a half-edge is split during tearing, one of the two half-edges gets its constraint updated and the other gets a new constraint.*/ public int PooledParticles{ get { return pooledParticles; } } public override bool usesTethers { get { return false; } } protected override IEnumerator Initialize() { /** * Have a map for half-edge->constraint. * Initially create all constraints and pre-cook them. * Constraints at each side of the same edge, are in different batches. */ if (inputMesh == null || !inputMesh.isReadable) { // TODO: return an error in the coroutine. Debug.LogError("The input mesh is null, or not readable."); yield break; } ClearParticleGroups(); topology = new HalfEdgeMesh(); topology.inputMesh = inputMesh; topology.Generate(); pooledParticles = (int)((topology.faces.Count * 3 - topology.vertices.Count) * tearCapacity); int totalParticles = topology.vertices.Count + pooledParticles; positions = new Vector3[totalParticles]; restPositions = new Vector4[totalParticles]; velocities = new Vector3[totalParticles]; invMasses = new float[totalParticles]; principalRadii = new Vector3[totalParticles]; filters = new int[totalParticles]; colors = new Color[totalParticles]; areaContribution = new float[totalParticles]; tearResistance = new float[totalParticles]; // Create a particle for each vertex: m_ActiveParticleCount = topology.vertices.Count; for (int i = 0; i < topology.vertices.Count; i++) { HalfEdgeMesh.Vertex vertex = topology.vertices[i]; // Get the particle's area contribution. areaContribution[i] = 0; foreach (HalfEdgeMesh.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 (HalfEdgeMesh.HalfEdge edge in topology.GetNeighbourEdgesEnumerator(vertex)) { // vertices at each end of the edge: Vector3 v1 = Vector3.Scale(scale, topology.vertices[topology.GetHalfEdgeStartVertex(edge)].position); Vector3 v2 = Vector3.Scale(scale, topology.vertices[edge.endVertex].position); minEdgeLength = Mathf.Min(minEdgeLength, Vector3.Distance(v1, v2)); } tearResistance[i] = 1; invMasses[i] = 1;//(/*skinnedMeshRenderer == null &&*/ areaContribution[i] > 0) ? (1.0f / (DEFAULT_PARTICLE_MASS * areaContribution[i])) : 0; positions[i] = Vector3.Scale(scale,vertex.position); restPositions[i] = positions[i]; restPositions[i][3] = 1; // activate rest position. principalRadii[i] = Vector3.one * minEdgeLength * 0.5f; filters[i] = ObiUtils.MakeFilter(ObiUtils.CollideWithEverything, 1); colors[i] = Color.white; if (i % 500 == 0) yield return new CoroutineJob.ProgressInfo("ObiCloth: generating particles...", i / (float)topology.vertices.Count); } colorizer = new GraphColoring(totalParticles); IEnumerator dt = GenerateDeformableTriangles(); while (dt.MoveNext()) yield return dt.Current; //Create distance constraints: IEnumerator dc = CreateDistanceConstraints(); while (dc.MoveNext()) yield return dc.Current; // Create aerodynamic constraints: IEnumerator ac = CreateAerodynamicConstraints(); while (ac.MoveNext()) yield return ac.Current; //Create bending constraints: IEnumerator bc = CreateBendingConstraints(); while (bc.MoveNext()) yield return bc.Current; } protected override IEnumerator CreateDistanceConstraints() { // prepare an array that maps from half edge index to distanceConstraintMap = new Vector2Int[topology.halfEdges.Count]; for (int i = 0; i < distanceConstraintMap.Length; i++) distanceConstraintMap[i] = new Vector2Int(-1,-1); //Create distance constraints, one for each half-edge. distanceConstraintsData = new ObiDistanceConstraintsData(); List edges = topology.GetEdgeList(); IEnumerator dc = CreateInitialDistanceConstraints(edges); while (dc.MoveNext()) yield return dc.Current; dc = CreatePooledDistanceConstraints(edges); while (dc.MoveNext()) yield return dc.Current; } private IEnumerator CreateInitialDistanceConstraints(List edges) { colorizer.Clear(); for (int i = 0; i < edges.Count; i++) { HalfEdgeMesh.HalfEdge hedge = topology.halfEdges[edges[i]]; // ignore borders: if (hedge.face < 0) continue; colorizer.AddConstraint(new[] { topology.GetHalfEdgeStartVertex(hedge), hedge.endVertex }); if (i % 500 == 0) yield return new CoroutineJob.ProgressInfo("ObiCloth: generating structural constraints...", i / (float)edges.Count); } List constraintColors = new List(); var colorize = colorizer.Colorize("ObiCloth: colorizing distance constraints...", constraintColors); while (colorize.MoveNext()) yield return colorize.Current; var particleIndices = colorizer.particleIndices; var constraintIndices = colorizer.constraintIndices; for (int i = 0; i < constraintColors.Count; ++i) { int color = constraintColors[i]; int cIndex = constraintIndices[i]; // Add a new batch if needed: if (color >= distanceConstraintsData.GetBatchCount()) distanceConstraintsData.AddBatch(new ObiDistanceConstraintsBatch()); HalfEdgeMesh.HalfEdge hedge = topology.halfEdges[edges[i]]; HalfEdgeMesh.Vertex startVertex = topology.vertices[topology.GetHalfEdgeStartVertex(hedge)]; HalfEdgeMesh.Vertex endVertex = topology.vertices[hedge.endVertex]; distanceConstraintsData.batches[color].AddConstraint(new Vector2Int(particleIndices[cIndex], particleIndices[cIndex + 1]), Vector3.Distance(Vector3.Scale(scale,startVertex.position), Vector3.Scale(scale, endVertex.position))); distanceConstraintMap[hedge.index] = new Vector2Int(color, distanceConstraintsData.batches[color].constraintCount - 1); } // Set initial amount of active constraints: for (int i = 0; i < distanceConstraintsData.batches.Count; ++i) { distanceConstraintsData.batches[i].activeConstraintCount = distanceConstraintsData.batches[i].constraintCount; } } private IEnumerator CreatePooledDistanceConstraints(List edges) { colorizer.Clear(); for (int i = 0; i < edges.Count; i++) { HalfEdgeMesh.HalfEdge hedge = topology.halfEdges[topology.halfEdges[edges[i]].pair]; // ignore borders: if (hedge.face < 0) continue; colorizer.AddConstraint(new[] { topology.GetHalfEdgeStartVertex(hedge), hedge.endVertex }); if (i % 500 == 0) yield return new CoroutineJob.ProgressInfo("ObiCloth: generating structural constraints...", i / (float)edges.Count); } List constraintColors = new List(); var colorize = colorizer.Colorize("ObiCloth: colorizing distance constraints...", constraintColors); while (colorize.MoveNext()) yield return colorize.Current; var particleIndices = colorizer.particleIndices; var constraintIndices = colorizer.constraintIndices; int batchCount = distanceConstraintsData.batches.Count; int j = 0; for (int i = 0; i < edges.Count; i++) { HalfEdgeMesh.HalfEdge hedge = topology.halfEdges[topology.halfEdges[edges[i]].pair]; if (hedge.face < 0) continue; int color = batchCount + constraintColors[j]; int cIndex = constraintIndices[j]; // Add a new batch if needed: if (color >= distanceConstraintsData.GetBatchCount()) distanceConstraintsData.AddBatch(new ObiDistanceConstraintsBatch()); HalfEdgeMesh.Vertex startVertex = topology.vertices[topology.GetHalfEdgeStartVertex(hedge)]; HalfEdgeMesh.Vertex endVertex = topology.vertices[hedge.endVertex]; distanceConstraintsData.batches[color].AddConstraint(new Vector2Int(particleIndices[cIndex], particleIndices[cIndex + 1]), Vector3.Distance(Vector3.Scale(scale, startVertex.position), Vector3.Scale(scale,endVertex.position))); distanceConstraintMap[hedge.index] = new Vector2Int(color, distanceConstraintsData.batches[color].constraintCount - 1); ++j; } } } }