public void AddBatch(ObiBendConstraintBatch batch) { if (batch != null && batch.GetConstraintType() == GetConstraintType()) { batches.Add(batch); } }
/** * Generates the particle based physical representation of the bone hierarcht. This is the initialization method for the actor * and should not be called directly once the object has been created. */ protected override IEnumerator Initialize() { initialized = false; initializing = true; RemoveFromSolver(null); // get a list of bones in preorder: bones = new List <Transform>(); foreach (Transform bone in EnumerateBonesBreadthFirst()) { bones.Add(bone); } parentIndices = new int[bones.Count]; active = new bool[bones.Count]; positions = new Vector3[bones.Count]; velocities = new Vector3[bones.Count]; invMasses = new float[bones.Count]; principalRadii = new Vector3[bones.Count]; phases = new int[bones.Count]; restPositions = new Vector4[bones.Count]; restOrientations = new Quaternion[bones.Count]; frozen = new bool[bones.Count]; DistanceConstraints.Clear(); ObiDistanceConstraintBatch distanceBatch = new ObiDistanceConstraintBatch(false, false, MIN_YOUNG_MODULUS, MAX_YOUNG_MODULUS); DistanceConstraints.AddBatch(distanceBatch); BendingConstraints.Clear(); ObiBendConstraintBatch bendingBatch = new ObiBendConstraintBatch(false, false, MIN_YOUNG_MODULUS, MAX_YOUNG_MODULUS); BendingConstraints.AddBatch(bendingBatch); SkinConstraints.Clear(); ObiSkinConstraintBatch skinBatch = new ObiSkinConstraintBatch(true, false, MIN_YOUNG_MODULUS, MAX_YOUNG_MODULUS); SkinConstraints.AddBatch(skinBatch); for (int i = 0; i < bones.Count; ++i) { active[i] = true; invMasses[i] = 1.0f / DEFAULT_PARTICLE_MASS; positions[i] = transform.InverseTransformPoint(bones[i].position); restPositions[i] = positions[i]; restPositions[i][3] = 1; restOrientations[i] = Quaternion.identity; principalRadii[i] = new Vector3(particleRadius, particleRadius, particleRadius); frozen[i] = false; phases[i] = Oni.MakePhase(1, selfCollisions?Oni.ParticlePhase.SelfCollide:0); parentIndices[i] = -1; if (bones[i].parent != null) { parentIndices[i] = bones.IndexOf(bones[i].parent); } skinBatch.AddConstraint(i, positions[i], Vector3.up, 0.05f, 0, 0, 1); foreach (Transform child in bones[i]) { int childIndex = bones.IndexOf(child); if (childIndex >= 0) { // add distance constraint between the bone and its child. distanceBatch.AddConstraint(i, childIndex, Vector3.Distance(bones[i].position, child.position), 1, 1); if (parentIndices[i] >= 0) { Transform parent = bones[parentIndices[i]]; float[] bendRestPositions = new float[] { parent.position[0], parent.position[1], parent.position[2], child.position[0], child.position[1], child.position[2], bones[i].position[0], bones[i].position[1], bones[i].position[2] }; float restBend = Oni.BendingConstraintRest(bendRestPositions); // add bend constraint between the bone, its parent and its child. bendingBatch.AddConstraint(parentIndices[i], childIndex, i, restBend, 0, 0); } } } if (i % 10 == 0) { yield return(new CoroutineJob.ProgressInfo("ObiBone: generating particles...", i / (float)bones.Count)); } } skinBatch.Cook(); initializing = false; initialized = true; }
/** * Tears a cloth distance constraint, affecting both the physical representation of the cloth and its mesh. */ public void Tear(int constraintIndex) { if (topology == null) { return; } // don't allow splitting if there are no free particles left in the pool. if (usedParticles >= sharedMesh.vertexCount + pooledVertices) { return; } // get involved constraint batches: ObiDistanceConstraintBatch distanceBatch = (ObiDistanceConstraintBatch)DistanceConstraints.GetBatches()[0]; ObiBendConstraintBatch bendingBatch = (ObiBendConstraintBatch)BendingConstraints.GetBatches()[0]; // get particle indices at both ends of the constraint: int splitIndex = distanceBatch.springIndices[constraintIndex * 2]; int intactIndex = distanceBatch.springIndices[constraintIndex * 2 + 1]; // we will split the particle with higher mass, so swap them if needed. if (invMasses[splitIndex] > invMasses[intactIndex]) { int aux = splitIndex; splitIndex = intactIndex; intactIndex = aux; } // Calculate the splitting plane in local space: Vector3 v1 = transform.worldToLocalMatrix.MultiplyPoint3x4(solver.renderablePositions[particleIndices[splitIndex]]); Vector3 v2 = transform.worldToLocalMatrix.MultiplyPoint3x4(solver.renderablePositions[particleIndices[intactIndex]]); Vector3 normal = (v2 - v1).normalized; int numUpdatedHalfEdges = maxVertexValency; int[] updatedHalfEdges = new int[numUpdatedHalfEdges]; // Try to split the vertex at that particle. // If we cannot not split the higher mass particle, try the other one. If that fails too, we cannot tear this edge. if (invMasses[splitIndex] == 0 || !Oni.TearDeformableMeshAtVertex(deformableMesh, splitIndex, ref v1, ref normal, updatedHalfEdges, ref numUpdatedHalfEdges)) { // Try to split the other particle: int aux = splitIndex; splitIndex = intactIndex; intactIndex = aux; v1 = transform.worldToLocalMatrix.MultiplyPoint3x4(solver.renderablePositions[particleIndices[splitIndex]]); v2 = transform.worldToLocalMatrix.MultiplyPoint3x4(solver.renderablePositions[particleIndices[intactIndex]]); normal = (v2 - v1).normalized; if (invMasses[splitIndex] == 0 || !Oni.TearDeformableMeshAtVertex(deformableMesh, splitIndex, ref v1, ref normal, updatedHalfEdges, ref numUpdatedHalfEdges)) { return; } } // identify weak points around the cut: int weakPt1 = -1; int weakPt2 = -1; float weakestValue = float.MaxValue; float secondWeakestValue = float.MaxValue; foreach (Oni.Vertex v in topology.GetNeighbourVerticesEnumerator(topology.heVertices[splitIndex])) { Vector3 neighbour = transform.worldToLocalMatrix.MultiplyPoint3x4(solver.renderablePositions[particleIndices[v.index]]); float weakness = Mathf.Abs(Vector3.Dot(normal, (neighbour - v1).normalized)); if (weakness < weakestValue) { secondWeakestValue = weakestValue; weakestValue = weakness; weakPt2 = weakPt1; weakPt1 = v.index; } else if (weakness < secondWeakestValue) { secondWeakestValue = weakness; weakPt2 = v.index; } } // reduce tear resistance at the weak spots of the cut, to encourage coherent tear formation. if (weakPt1 >= 0) { tearResistance[weakPt1] *= 1 - tearDebilitation; } if (weakPt2 >= 0) { tearResistance[weakPt2] *= 1 - tearDebilitation; } topology.UpdateVertexCount(); // halve the mass and radius of the original particle: invMasses[splitIndex] *= 2; solidRadii[splitIndex] *= 0.5f; // copy the new particle data in the actor and solver arrays: positions[usedParticles] = positions[splitIndex]; velocities[usedParticles] = velocities[splitIndex]; active[usedParticles] = active[splitIndex]; invMasses[usedParticles] = invMasses[splitIndex]; solidRadii[usedParticles] = solidRadii[splitIndex]; phases[usedParticles] = phases[splitIndex]; areaContribution[usedParticles] = areaContribution[splitIndex]; tearResistance[usedParticles] = tearResistance[splitIndex]; restPositions[usedParticles] = positions[splitIndex]; restPositions[usedParticles][3] = 0; // activate rest position. solver.activeParticles.Add(particleIndices[usedParticles]); // update solver particle data: Vector4[] velocity = { Vector4.zero }; Oni.GetParticleVelocities(solver.OniSolver, velocity, 1, particleIndices[splitIndex]); Oni.SetParticleVelocities(solver.OniSolver, velocity, 1, particleIndices[usedParticles]); Vector4[] position = { Vector4.zero }; Oni.GetParticlePositions(solver.OniSolver, position, 1, particleIndices[splitIndex]); Oni.SetParticlePositions(solver.OniSolver, position, 1, particleIndices[usedParticles]); Oni.SetParticleInverseMasses(solver.OniSolver, new float[] { invMasses[splitIndex] }, 1, particleIndices[usedParticles]); Oni.SetParticleSolidRadii(solver.OniSolver, new float[] { solidRadii[splitIndex] }, 1, particleIndices[usedParticles]); Oni.SetParticlePhases(solver.OniSolver, new int[] { phases[splitIndex] }, 1, particleIndices[usedParticles]); usedParticles++; // update distance constraints: for (int i = 0; i < numUpdatedHalfEdges; ++i) { int halfEdgeIndex = updatedHalfEdges[i]; Oni.HalfEdge e = topology.heHalfEdges[halfEdgeIndex]; // find start and end vertex indices for this edge: int startVertex = topology.GetHalfEdgeStartVertex(topology.heHalfEdges[halfEdgeIndex]); int endVertex = topology.heHalfEdges[halfEdgeIndex].endVertex; if (distanceConstraintMap[halfEdgeIndex] > -1) // update existing edge { distanceBatch.springIndices[distanceConstraintMap[halfEdgeIndex] * 2] = startVertex; distanceBatch.springIndices[distanceConstraintMap[halfEdgeIndex] * 2 + 1] = endVertex; } else if (topology.IsSplit(halfEdgeIndex)) // new edge { int pairConstraintIndex = distanceConstraintMap[topology.heHalfEdges[halfEdgeIndex].pair]; // update constraint-edge map: distanceConstraintMap[halfEdgeIndex] = distanceBatch.restLengths.Count; // add the new constraint: distanceBatch.AddConstraint(startVertex, endVertex, distanceBatch.restLengths[pairConstraintIndex], distanceBatch.stiffnesses[pairConstraintIndex].x, distanceBatch.stiffnesses[pairConstraintIndex].y); } // update deformable triangles: if (e.indexInFace > -1) { deformableTriangles[e.face * 3 + e.indexInFace] = e.endVertex; } } if (splitIndex < bendConstraintOffsets.Length - 1) { // deactivate bend constraints that contain the split vertex... // ...at the center: for (int i = bendConstraintOffsets[splitIndex]; i < bendConstraintOffsets[splitIndex + 1]; ++i) { bendingBatch.DeactivateConstraint(i); } // ...at one end: foreach (Oni.Vertex v in topology.GetNeighbourVerticesEnumerator(topology.heVertices[splitIndex])) { if (v.index < bendConstraintOffsets.Length - 1) { for (int i = bendConstraintOffsets[v.index]; i < bendConstraintOffsets[v.index + 1]; ++i) { if (bendingBatch.bendingIndices[i * 3] == splitIndex || bendingBatch.bendingIndices[i * 3 + 1] == splitIndex) { bendingBatch.DeactivateConstraint(i); } } } } } // Create new aerodynamic constraint: /*aerodynamicConstraints.AddConstraint(true,newParticle[0], * Vector3.up, * aerodynamicConstraints.windVector, * areaContribution[newParticle[0]], * aerodynamicConstraints.dragCoefficient, * aerodynamicConstraints.liftCoefficient);*/ }
/** * 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); }
public void RemoveBatch(ObiBendConstraintBatch batch) { batches.Remove(batch); }
private int AddParticles(int amount) { // get constraint batches: ObiDistanceConstraintBatch distanceBatch = rope.DistanceConstraints.GetFirstBatch(); ObiBendConstraintBatch bendingBatch = rope.BendingConstraints.GetFirstBatch(); amount = Mathf.Min(amount, rope.PooledParticles); // if no particles can be added, just return. if (amount == 0) { return(0); } // find current constraint and hot constraint: int constraint = rope.GetConstraintIndexAtNormalizedCoordinate(normalizedCoord); rope.DistanceConstraints.RemoveFromSolver(null); rope.BendingConstraints.RemoveFromSolver(null); // find indices of first N inactive particles. we'll need them to create new rope. // the first and last particle indices in this array will be the ones in the current constraint. int[] newParticleIndices = new int[amount + 2]; for (int i = 0, j = 0; i < amount && j < rope.TotalParticles; ++j) { if (!rope.active[j]) { newParticleIndices[i + 1] = j; rope.active[j] = true; rope.invMasses[j] = 1.0f / ObiRope.DEFAULT_PARTICLE_MASS; ++i; } } // TODO: closed curves have a different amount of bend constraints! Vector4[] zeroVelocity = new Vector4[] { Vector4.zero }; Vector4[] refPosition1 = new Vector4[1]; Vector4[] refPosition2 = new Vector4[1]; if (direction) { // fill first and last indices of the new particles array with the ones in the current constraint: newParticleIndices[0] = distanceBatch.springIndices[constraint * 2]; newParticleIndices[newParticleIndices.Length - 1] = distanceBatch.springIndices[constraint * 2 + 1]; // update normalized coord: normalizedCoord = constraint / (float)(distanceBatch.ConstraintCount + amount); Oni.GetParticlePositions(rope.Solver.OniSolver, refPosition1, 1, rope.particleIndices[newParticleIndices[0]]); Oni.GetParticlePositions(rope.Solver.OniSolver, refPosition2, 1, rope.particleIndices[newParticleIndices[newParticleIndices.Length - 1]]); // update constraints: distanceBatch.SetParticleIndex(constraint, newParticleIndices[newParticleIndices.Length - 2], ObiDistanceConstraintBatch.DistanceIndexType.First, rope.Closed); bendingBatch.SetParticleIndex(constraint, newParticleIndices[newParticleIndices.Length - 2], ObiBendConstraintBatch.BendIndexType.First, rope.Closed); bendingBatch.SetParticleIndex(constraint - 1, newParticleIndices[1], ObiBendConstraintBatch.BendIndexType.Second, rope.Closed); // add constraints and particles: for (int i = 1; i < newParticleIndices.Length - 1; ++i) { Vector4[] pos = new Vector4[] { refPosition1[0] + (refPosition2[0] - refPosition1[0]) * i / (float)(newParticleIndices.Length - 1) * 0.5f }; Oni.SetParticlePositions(rope.Solver.OniSolver, pos, 1, rope.particleIndices[newParticleIndices[i]]); Oni.SetParticleVelocities(rope.Solver.OniSolver, zeroVelocity, 1, rope.particleIndices[newParticleIndices[i]]); int newConstraintIndex = constraint + i - 1; distanceBatch.InsertConstraint(newConstraintIndex, newParticleIndices[i - 1], newParticleIndices[i], rope.InterparticleDistance, 0, 0); bendingBatch.InsertConstraint(newConstraintIndex, newParticleIndices[i - 1], newParticleIndices[i + 1], newParticleIndices[i], 0, 0, 0); } } else { // fill first and last indices of the new particles array with the ones in the current constraint: newParticleIndices[0] = distanceBatch.springIndices[constraint * 2 + 1]; newParticleIndices[newParticleIndices.Length - 1] = distanceBatch.springIndices[constraint * 2]; // update normalized coord: normalizedCoord = (constraint + amount) / (float)(distanceBatch.ConstraintCount + amount); Oni.GetParticlePositions(rope.Solver.OniSolver, refPosition1, 1, rope.particleIndices[newParticleIndices[0]]); Oni.GetParticlePositions(rope.Solver.OniSolver, refPosition2, 1, rope.particleIndices[newParticleIndices[newParticleIndices.Length - 1]]); // update constraints: distanceBatch.SetParticleIndex(constraint, newParticleIndices[newParticleIndices.Length - 2], ObiDistanceConstraintBatch.DistanceIndexType.Second, rope.Closed); bendingBatch.SetParticleIndex(constraint, newParticleIndices[1], ObiBendConstraintBatch.BendIndexType.First, rope.Closed); bendingBatch.SetParticleIndex(constraint - 1, newParticleIndices[newParticleIndices.Length - 2], ObiBendConstraintBatch.BendIndexType.Second, rope.Closed); // add constraints and particles: for (int i = 1; i < newParticleIndices.Length - 1; ++i) { Vector4[] pos = new Vector4[] { refPosition1[0] + (refPosition2[0] - refPosition1[0]) * i / (float)(newParticleIndices.Length - 1) * 0.5f }; Oni.SetParticlePositions(rope.Solver.OniSolver, pos, 1, rope.particleIndices[newParticleIndices[i]]); Oni.SetParticleVelocities(rope.Solver.OniSolver, zeroVelocity, 1, rope.particleIndices[newParticleIndices[i]]); distanceBatch.InsertConstraint(constraint + 1, newParticleIndices[i], newParticleIndices[i - 1], rope.InterparticleDistance, 0, 0); bendingBatch.InsertConstraint(constraint, newParticleIndices[i + 1], newParticleIndices[i - 1], newParticleIndices[i], 0, 0, 0); } } rope.DistanceConstraints.AddToSolver(null); rope.BendingConstraints.AddToSolver(null); rope.PushDataToSolver(ParticleData.ACTIVE_STATUS); rope.UsedParticles += amount; rope.RegenerateRestPositions(); return(amount); }
/** * Removes a certain amount of particles and constraints from the rope, at the point and direction specified: */ private int RemoveParticles(int amount) { // get constraint batches: ObiDistanceConstraintBatch distanceBatch = rope.DistanceConstraints.GetFirstBatch(); ObiBendConstraintBatch bendingBatch = rope.BendingConstraints.GetFirstBatch(); amount = Mathf.Min(amount, rope.UsedParticles - 2); // find current constraint and hot constraint: int constraint = Mathf.Max(rope.GetConstraintIndexAtNormalizedCoordinate(normalizedCoord), firstConstraintId); int hotConstraint = FindHotConstraint(distanceBatch, constraint, amount); amount = Mathf.Min(amount, Mathf.Abs(hotConstraint - constraint)); // if no particles can be removed, just return. if (amount == 0) { return(0); } rope.DistanceConstraints.RemoveFromSolver(null); rope.BendingConstraints.RemoveFromSolver(null); if (direction) { // update normalized coord: normalizedCoord = constraint / (float)(distanceBatch.ConstraintCount - amount); // update constraints: distanceBatch.SetParticleIndex(constraint, distanceBatch.springIndices[hotConstraint * 2 + 1], ObiDistanceConstraintBatch.DistanceIndexType.Second, rope.Closed); bendingBatch.SetParticleIndex(constraint - 1, distanceBatch.springIndices[hotConstraint * 2 + 1], ObiBendConstraintBatch.BendIndexType.Second, rope.Closed); bendingBatch.SetParticleIndex(hotConstraint, distanceBatch.springIndices[constraint * 2], ObiBendConstraintBatch.BendIndexType.First, rope.Closed); // remove constraints and particles: for (int i = constraint + amount; i > constraint; --i) { rope.active[distanceBatch.springIndices[i * 2]] = false; distanceBatch.RemoveConstraint(i); bendingBatch.RemoveConstraint(i - 1); } } else { // update normalized coord: normalizedCoord = (constraint - amount) / (float)(distanceBatch.ConstraintCount - amount); // update constraint: distanceBatch.SetParticleIndex(constraint, distanceBatch.springIndices[hotConstraint * 2], ObiDistanceConstraintBatch.DistanceIndexType.First, rope.Closed); bendingBatch.SetParticleIndex(constraint, distanceBatch.springIndices[hotConstraint * 2], ObiBendConstraintBatch.BendIndexType.First, rope.Closed); bendingBatch.SetParticleIndex(hotConstraint - 1, distanceBatch.springIndices[constraint * 2 + 1], ObiBendConstraintBatch.BendIndexType.Second, rope.Closed); // remove constraints and particles: for (int i = constraint - 1; i >= constraint - amount; --i) { rope.active[distanceBatch.springIndices[i * 2 + 1]] = false; distanceBatch.RemoveConstraint(i); bendingBatch.RemoveConstraint(i); } } rope.DistanceConstraints.AddToSolver(null); rope.BendingConstraints.AddToSolver(null); rope.PushDataToSolver(ParticleData.ACTIVE_STATUS); rope.UsedParticles -= amount; rope.RegenerateRestPositions(); return(amount); }
/** * Generates the particle based physical representation of the rope. This is the initialization method for the rope object * and should not be called directly once the object has been created. */ protected override IEnumerator Initialize() { initialized = false; initializing = true; interParticleDistance = -1; RemoveFromSolver(null); if (ropePath == null) { Debug.LogError("Cannot initialize rope. There's no ropePath present. Please provide a spline to define the shape of the rope"); yield break; } ropePath.RecalculateSplineLenght(0.00001f, 7); closed = ropePath.closed; restLength = ropePath.Length; usedParticles = Mathf.CeilToInt(restLength / thickness * resolution) + (closed ? 0:1); totalParticles = usedParticles + pooledParticles; //allocate extra particles to allow for lenght change and tearing. active = new bool[totalParticles]; positions = new Vector3[totalParticles]; velocities = new Vector3[totalParticles]; invMasses = new float[totalParticles]; principalRadii = new Vector3[totalParticles]; phases = new int[totalParticles]; restPositions = new Vector4[totalParticles]; tearResistance = new float[totalParticles]; colors = new Color[totalParticles]; int numSegments = usedParticles - (closed ? 0:1); if (numSegments > 0) { interParticleDistance = restLength / (float)numSegments; } else { interParticleDistance = 0; } float radius = interParticleDistance * resolution; for (int i = 0; i < usedParticles; i++) { active[i] = true; invMasses[i] = 1.0f / DEFAULT_PARTICLE_MASS; float mu = ropePath.GetMuAtLenght(interParticleDistance * i); positions[i] = transform.InverseTransformPoint(ropePath.transform.TransformPoint(ropePath.GetPositionAt(mu))); principalRadii[i] = Vector3.one * radius; phases[i] = Oni.MakePhase(1, selfCollisions?Oni.ParticlePhase.SelfCollide:0); tearResistance[i] = 1; colors[i] = Color.white; if (i % 100 == 0) { yield return(new CoroutineJob.ProgressInfo("ObiRope: generating particles...", i / (float)usedParticles)); } } // Initialize basic data for pooled particles: for (int i = usedParticles; i < totalParticles; i++) { active[i] = false; invMasses[i] = 1.0f / DEFAULT_PARTICLE_MASS; principalRadii[i] = Vector3.one * radius; phases[i] = Oni.MakePhase(1, selfCollisions?Oni.ParticlePhase.SelfCollide:0); tearResistance[i] = 1; colors[i] = Color.white; if (i % 100 == 0) { yield return(new CoroutineJob.ProgressInfo("ObiRope: generating particles...", i / (float)usedParticles)); } } DistanceConstraints.Clear(); ObiDistanceConstraintBatch distanceBatch = new ObiDistanceConstraintBatch(false, false, MIN_YOUNG_MODULUS, MAX_YOUNG_MODULUS); DistanceConstraints.AddBatch(distanceBatch); for (int i = 0; i < numSegments; i++) { distanceBatch.AddConstraint(i, (i + 1) % (ropePath.closed ? usedParticles:usedParticles + 1), interParticleDistance, 1, 1); if (i % 500 == 0) { yield return(new CoroutineJob.ProgressInfo("ObiRope: generating structural constraints...", i / (float)numSegments)); } } BendingConstraints.Clear(); ObiBendConstraintBatch bendingBatch = new ObiBendConstraintBatch(false, false, MIN_YOUNG_MODULUS, MAX_YOUNG_MODULUS); BendingConstraints.AddBatch(bendingBatch); for (int i = 0; i < usedParticles - (closed?0:2); i++) { // rope bending constraints always try to keep it completely straight: bendingBatch.AddConstraint(i, (i + 2) % usedParticles, (i + 1) % usedParticles, 0, 0, 1); if (i % 500 == 0) { yield return(new CoroutineJob.ProgressInfo("ObiRope: adding bend constraints...", i / (float)usedParticles)); } } // Initialize tether constraints: TetherConstraints.Clear(); // Initialize pin constraints: PinConstraints.Clear(); ObiPinConstraintBatch pinBatch = new ObiPinConstraintBatch(false, false, 0, MAX_YOUNG_MODULUS); PinConstraints.AddBatch(pinBatch); initializing = false; initialized = true; RegenerateRestPositions(); }
public void Tear(int constraintIndex) { // don't allow splitting if there are no free particles left in the pool. if (usedParticles >= totalParticles) { return; } // get involved constraint batches: ObiDistanceConstraintBatch distanceBatch = (ObiDistanceConstraintBatch)DistanceConstraints.GetFirstBatch(); ObiBendConstraintBatch bendingBatch = (ObiBendConstraintBatch)BendingConstraints.GetFirstBatch(); // get particle indices at both ends of the constraint: int splitIndex = distanceBatch.springIndices[constraintIndex * 2]; int intactIndex = distanceBatch.springIndices[constraintIndex * 2 + 1]; // see if the rope is continuous at the split index and the intact index: bool continuousAtSplit = (constraintIndex < distanceBatch.ConstraintCount - 1 && distanceBatch.springIndices[(constraintIndex + 1) * 2] == splitIndex) || (constraintIndex > 0 && distanceBatch.springIndices[(constraintIndex - 1) * 2 + 1] == splitIndex); bool continuousAtIntact = (constraintIndex < distanceBatch.ConstraintCount - 1 && distanceBatch.springIndices[(constraintIndex + 1) * 2] == intactIndex) || (constraintIndex > 0 && distanceBatch.springIndices[(constraintIndex - 1) * 2 + 1] == intactIndex); // we will split the particle with higher mass, so swap them if needed (and possible). Also make sure that the rope hasnt been cut there yet: if ((invMasses[splitIndex] > invMasses[intactIndex] || invMasses[splitIndex] == 0) && continuousAtIntact) { int aux = splitIndex; splitIndex = intactIndex; intactIndex = aux; } // see if we are able to proceed with the cut: if (invMasses[splitIndex] == 0 || !continuousAtSplit) { return; } // halve the mass of the teared particle: invMasses[splitIndex] *= 2; // copy the new particle data in the actor and solver arrays: positions[usedParticles] = positions[splitIndex]; velocities[usedParticles] = velocities[splitIndex]; active[usedParticles] = active[splitIndex]; invMasses[usedParticles] = invMasses[splitIndex]; principalRadii[usedParticles] = principalRadii[splitIndex]; phases[usedParticles] = phases[splitIndex]; if (colors != null && colors.Length > 0) { colors[usedParticles] = colors[splitIndex]; } tearResistance[usedParticles] = tearResistance[splitIndex]; restPositions[usedParticles] = positions[splitIndex]; restPositions[usedParticles][3] = 1; // activate rest position. // update solver particle data: solver.velocities[particleIndices[usedParticles]] = solver.velocities[particleIndices[splitIndex]]; solver.startPositions[particleIndices[usedParticles]] = solver.positions [particleIndices[usedParticles]] = solver.positions [particleIndices[splitIndex]]; solver.invMasses [particleIndices[usedParticles]] = solver.invMasses [particleIndices[splitIndex]] = invMasses[splitIndex]; solver.principalRadii[particleIndices[usedParticles]] = solver.principalRadii[particleIndices[splitIndex]] = principalRadii[splitIndex]; solver.phases [particleIndices[usedParticles]] = solver.phases [particleIndices[splitIndex]]; // Update bending constraints: for (int i = 0; i < bendingBatch.ConstraintCount; ++i) { // disable the bending constraint centered at the split particle: if (bendingBatch.bendingIndices[i * 3 + 2] == splitIndex) { bendingBatch.DeactivateConstraint(i); } // update the one that bridges the cut: else if (!DoesBendConstraintSpanDistanceConstraint(distanceBatch, bendingBatch, constraintIndex, i)) { // if the bend constraint does not involve the split distance constraint, // update the end that references the split vertex: if (bendingBatch.bendingIndices[i * 3] == splitIndex) { bendingBatch.bendingIndices[i * 3] = usedParticles; } else if (bendingBatch.bendingIndices[i * 3 + 1] == splitIndex) { bendingBatch.bendingIndices[i * 3 + 1] = usedParticles; } } } // Update distance constraints at both ends of the cut: if (constraintIndex < distanceBatch.ConstraintCount - 1) { if (distanceBatch.springIndices[(constraintIndex + 1) * 2] == splitIndex) { distanceBatch.springIndices[(constraintIndex + 1) * 2] = usedParticles; } if (distanceBatch.springIndices[(constraintIndex + 1) * 2 + 1] == splitIndex) { distanceBatch.springIndices[(constraintIndex + 1) * 2 + 1] = usedParticles; } } if (constraintIndex > 0) { if (distanceBatch.springIndices[(constraintIndex - 1) * 2] == splitIndex) { distanceBatch.springIndices[(constraintIndex - 1) * 2] = usedParticles; } if (distanceBatch.springIndices[(constraintIndex - 1) * 2 + 1] == splitIndex) { distanceBatch.springIndices[(constraintIndex - 1) * 2 + 1] = usedParticles; } } usedParticles++; pooledParticles--; }
/** * Returns whether a bend constraint affects the two particles referenced by a given distance constraint: */ public bool DoesBendConstraintSpanDistanceConstraint(ObiDistanceConstraintBatch dbatch, ObiBendConstraintBatch bbatch, int d, int b) { return((bbatch.bendingIndices[b * 3 + 2] == dbatch.springIndices[d * 2] && bbatch.bendingIndices[b * 3 + 1] == dbatch.springIndices[d * 2 + 1]) || (bbatch.bendingIndices[b * 3 + 1] == dbatch.springIndices[d * 2] && bbatch.bendingIndices[b * 3 + 2] == dbatch.springIndices[d * 2 + 1]) || (bbatch.bendingIndices[b * 3 + 2] == dbatch.springIndices[d * 2] && bbatch.bendingIndices[b * 3] == dbatch.springIndices[d * 2 + 1]) || (bbatch.bendingIndices[b * 3] == dbatch.springIndices[d * 2] && bbatch.bendingIndices[b * 3 + 2] == dbatch.springIndices[d * 2 + 1])); }
/** * 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(); } }