private IEnumerator CreateInitialDistanceConstraints(List <int> edges) { List <int> particleIndices = new List <int>(); List <int> constraintIndices = new List <int>(); for (int i = 0; i < edges.Count; i++) { HalfEdgeMesh.HalfEdge hedge = topology.halfEdges[edges[i]]; // ignore borders: if (hedge.face < 0) { continue; } particleIndices.Add(topology.GetHalfEdgeStartVertex(hedge)); particleIndices.Add(hedge.endVertex); constraintIndices.Add(constraintIndices.Count * 2); if (i % 500 == 0) { yield return(new CoroutineJob.ProgressInfo("ObiCloth: generating structural constraints...", i / (float)topology.halfEdges.Count)); } } constraintIndices.Add(constraintIndices.Count * 2); int[] constraintColors = GraphColoring.Colorize(particleIndices.ToArray(), constraintIndices.ToArray()); for (int i = 0; i < constraintColors.Length; ++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; } }
/** * 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 void GenerateTethers(bool[] selected) { tetherConstraintsData = new ObiTetherConstraintsData(); // generate disjoint groups of particles (islands) List <HashSet <int> > islands = GenerateIslands(System.Linq.Enumerable.Range(0, topology.vertices.Count), null); // generate tethers for each one: List <int> particleIndices = new List <int>(); foreach (HashSet <int> island in islands) { GenerateTethersForIsland(island, particleIndices, selected, 4); } // for tethers, it's easy to use the optimal amount of colors analytically. if (particleIndices.Count > 0) { int color = 0; int lastParticle = particleIndices[0]; for (int i = 0; i < particleIndices.Count; i += 2) { if (particleIndices[i] != lastParticle) { lastParticle = particleIndices[i]; color = 0; } // Add a new batch if needed: if (color >= tetherConstraintsData.GetBatchCount()) { tetherConstraintsData.AddBatch(new ObiTetherConstraintsBatch()); } HalfEdgeMesh.Vertex startVertex = topology.vertices[particleIndices[i]]; HalfEdgeMesh.Vertex endVertex = topology.vertices[particleIndices[i + 1]]; tetherConstraintsData.batches[color].AddConstraint(new Vector2Int(particleIndices[i], particleIndices[i + 1]), Vector3.Distance(Vector3.Scale(scale, startVertex.position), Vector3.Scale(scale, endVertex.position)), 1); color++; } } // Set initial amount of active constraints: for (int i = 0; i < tetherConstraintsData.batches.Count; ++i) { tetherConstraintsData.batches[i].activeConstraintCount = tetherConstraintsData.batches[i].constraintCount; } }
private void ClassifyFaces(HalfEdgeMesh.Vertex vertex, Plane plane, List <HalfEdgeMesh.Face> side1, List <HalfEdgeMesh.Face> side2) { foreach (HalfEdgeMesh.Face face in m_TearableBlueprintInstance.topology.GetNeighbourFacesEnumerator(vertex)) { HalfEdgeMesh.HalfEdge e1 = m_TearableBlueprintInstance.topology.halfEdges[face.halfEdge]; HalfEdgeMesh.HalfEdge e2 = m_TearableBlueprintInstance.topology.halfEdges[e1.nextHalfEdge]; HalfEdgeMesh.HalfEdge e3 = m_TearableBlueprintInstance.topology.halfEdges[e2.nextHalfEdge]; // Skip this face if it doesn't contain the vertex being split. // This can happen because edge pair links are not updated in a vertex split operation, // so split vertices still "see" faces at the other side of the cut as adjacent. if (e1.endVertex != vertex.index && e2.endVertex != vertex.index && e3.endVertex != vertex.index) { continue; } // calculate actual face center from deformed vertex positions: Vector3 faceCenter = (m_Solver.positions[solverIndices[e1.endVertex]] + m_Solver.positions[solverIndices[e2.endVertex]] + m_Solver.positions[solverIndices[e3.endVertex]]) * 0.33f; if (plane.GetSide(faceCenter)) { side1.Add(face); } else { side2.Add(face); } } }
private List <HashSet <int> > GenerateIslands(IEnumerable <int> particles, Func <int, bool> condition) { List <HashSet <int> > islands = new List <HashSet <int> >(); // Partition fixed particles into islands: foreach (int i in particles) { HalfEdgeMesh.Vertex vertex = topology.vertices[i]; if (condition != null && !condition(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 (HalfEdgeMesh.Vertex n in topology.GetNeighbourVerticesEnumerator(vertex)) { 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); }
protected virtual IEnumerator CreateBendingConstraints() { bendConstraintsData = new ObiBendConstraintsData(); List <int> particleIndices = new List <int>(); List <int> constraintIndices = new List <int>(); Dictionary <int, int> cons = new Dictionary <int, int>(); for (int i = 0; i < topology.vertices.Count; i++) { HalfEdgeMesh.Vertex vertex = topology.vertices[i]; foreach (HalfEdgeMesh.Vertex n1 in topology.GetNeighbourVerticesEnumerator(vertex)) { float cosBest = 0; HalfEdgeMesh.Vertex vBest = n1; foreach (HalfEdgeMesh.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; particleIndices.Add(n1.index); particleIndices.Add(vBest.index); particleIndices.Add(vertex.index); constraintIndices.Add(constraintIndices.Count * 3); } } if (i % 500 == 0) { yield return(new CoroutineJob.ProgressInfo("ObiCloth: adding bend constraints...", i / (float)topology.vertices.Count)); } } constraintIndices.Add(constraintIndices.Count * 3); int[] constraintColors = GraphColoring.Colorize(particleIndices.ToArray(), constraintIndices.ToArray()); for (int i = 0; i < constraintColors.Length; ++i) { int color = constraintColors[i]; int cIndex = constraintIndices[i]; // Add a new batch if needed: if (color >= bendConstraintsData.GetBatchCount()) { bendConstraintsData.AddBatch(new ObiBendConstraintsBatch()); } HalfEdgeMesh.Vertex n1 = topology.vertices[particleIndices[cIndex]]; HalfEdgeMesh.Vertex vBest = topology.vertices[particleIndices[cIndex + 1]]; HalfEdgeMesh.Vertex vertex = topology.vertices[particleIndices[cIndex + 2]]; Vector3 n1Pos = Vector3.Scale(scale, n1.position); Vector3 bestPos = Vector3.Scale(scale, vBest.position); Vector3 vertexPos = Vector3.Scale(scale, vertex.position); float restBend = ObiUtils.RestBendingConstraint(n1Pos, bestPos, vertexPos); bendConstraintsData.batches[color].AddConstraint(new Vector3Int(particleIndices[cIndex], particleIndices[cIndex + 1], particleIndices[cIndex + 2]), restBend); } // Set initial amount of active constraints: for (int i = 0; i < bendConstraintsData.batches.Count; ++i) { bendConstraintsData.batches[i].activeConstraintCount = bendConstraintsData.batches[i].constraintCount; } }
protected override IEnumerator Initialize() { 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(); positions = new Vector3[topology.vertices.Count]; restPositions = new Vector4[topology.vertices.Count]; velocities = new Vector3[topology.vertices.Count]; invMasses = new float[topology.vertices.Count]; principalRadii = new Vector3[topology.vertices.Count]; phases = new int[topology.vertices.Count]; colors = new Color[topology.vertices.Count]; areaContribution = new float[topology.vertices.Count]; // 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)); } invMasses[i] = (/*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; phases[i] = ObiUtils.MakePhase(1, /*selfCollisions ? Oni.ParticlePhase.SelfCollide : 0*/ 0); colors[i] = Color.white; if (i % 500 == 0) { yield return(new CoroutineJob.ProgressInfo("ObiCloth: generating particles...", i / (float)topology.vertices.Count)); } } // Deformable triangles: 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); } // Create volume constraints: IEnumerator vc = CreateVolumeConstraints(); while (vc.MoveNext()) { yield return(vc.Current); } }
private bool SplitTopologyAtVertex(int vertexIndex, Plane plane, List <HalfEdgeMesh.Face> updatedFaces, HashSet <int> updatedEdgeIndices) { if (vertexIndex < 0 || vertexIndex >= m_TearableBlueprintInstance.topology.vertices.Count) { return(false); } updatedFaces.Clear(); updatedEdgeIndices.Clear(); HalfEdgeMesh.Vertex vertex = m_TearableBlueprintInstance.topology.vertices[vertexIndex]; // classify adjacent faces depending on which side of the plane they're at: var otherSide = new List <HalfEdgeMesh.Face>(); ClassifyFaces(vertex, plane, updatedFaces, otherSide); // guard against pathological case in which all particles are in one side of the plane: if (otherSide.Count == 0 || updatedFaces.Count == 0) { return(false); } // create a new vertex: var newVertex = new HalfEdgeMesh.Vertex(); newVertex.position = vertex.position; newVertex.index = m_TearableBlueprintInstance.topology.vertices.Count; newVertex.halfEdge = vertex.halfEdge; // rearrange edges at the updated side: foreach (HalfEdgeMesh.Face face in updatedFaces) { // find half edges that start and end at the split vertex: HalfEdgeMesh.HalfEdge e1 = m_TearableBlueprintInstance.topology.halfEdges[face.halfEdge]; HalfEdgeMesh.HalfEdge e2 = m_TearableBlueprintInstance.topology.halfEdges[e1.nextHalfEdge]; HalfEdgeMesh.HalfEdge e3 = m_TearableBlueprintInstance.topology.halfEdges[e2.nextHalfEdge]; var in_ = e1; var out_ = e2; if (e1.endVertex == vertex.index) { in_ = e1; } else if (m_TearableBlueprintInstance.topology.GetHalfEdgeStartVertex(e1) == vertex.index) { out_ = e1; } if (e2.endVertex == vertex.index) { in_ = e2; } else if (m_TearableBlueprintInstance.topology.GetHalfEdgeStartVertex(e2) == vertex.index) { out_ = e2; } if (e3.endVertex == vertex.index) { in_ = e3; } else if (m_TearableBlueprintInstance.topology.GetHalfEdgeStartVertex(e3) == vertex.index) { out_ = e3; } // stitch edges to new vertex: in_.endVertex = newVertex.index; m_TearableBlueprintInstance.topology.halfEdges[in_.index] = in_; newVertex.halfEdge = out_.index; // store edges to be updated: updatedEdgeIndices.UnionWith(new int[] { in_.index, in_.pair, out_.index, out_.pair }); } // add new vertex: m_TearableBlueprintInstance.topology.vertices.Add(newVertex); m_TearableBlueprintInstance.topology.restNormals.Add(m_TearableBlueprintInstance.topology.restNormals[vertexIndex]); m_TearableBlueprintInstance.topology.restOrientations.Add(m_TearableBlueprintInstance.topology.restOrientations[vertexIndex]); //TODO: update mesh info. (mesh cannot be closed now) return(true); }