private void UpdateTornDistanceConstraints(HashSet <int> updatedHalfEdges) { var distanceConstraints = GetConstraintsByType(Oni.ConstraintType.Distance) as ObiConstraints <ObiDistanceConstraintsBatch>; foreach (int halfEdgeIndex in updatedHalfEdges) { HalfEdgeMesh.HalfEdge e = m_TearableBlueprintInstance.topology.halfEdges[halfEdgeIndex]; Vector2Int constraintDescriptor = m_TearableClothBlueprint.distanceConstraintMap[halfEdgeIndex]; // skip edges with no associated constraint (border half-edges) if (constraintDescriptor.x > -1) { // get batch and index of the constraint: var batch = distanceConstraints.batches[constraintDescriptor.x] as ObiDistanceConstraintsBatch; int index = batch.GetConstraintIndex(constraintDescriptor.y); // update constraint particle indices: batch.particleIndices[index * 2] = m_TearableBlueprintInstance.topology.GetHalfEdgeStartVertex(e); batch.particleIndices[index * 2 + 1] = e.endVertex; // make sure the constraint is active, in case it is a newly added one. batch.ActivateConstraint(index); } // update deformable triangles: if (e.indexInFace > -1) { m_TearableBlueprintInstance.deformableTriangles[e.face * 3 + e.indexInFace] = e.endVertex; } } }
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; } }
public void SwapVertices(int index1, int index2) { vertices.Swap(index1, index2); restNormals.Swap(index1, index2); restOrientations.Swap(index1, index2); for (int i = 0; i < halfEdges.Count; ++i) { HalfEdgeMesh.HalfEdge halfEdge = halfEdges[i]; if (halfEdge.endVertex == index1) { halfEdge.endVertex = index2; halfEdges[i] = halfEdge; } else if (halfEdge.endVertex == index2) { halfEdge.endVertex = index1; halfEdges[i] = halfEdge; } } for (int i = 0; i < borderEdges.Count; ++i) { HalfEdgeMesh.HalfEdge halfEdge = borderEdges[i]; if (halfEdge.endVertex == index1) { halfEdge.endVertex = index2; borderEdges[i] = halfEdge; } else if (halfEdge.endVertex == index2) { halfEdge.endVertex = index1; borderEdges[i] = halfEdge; } } for (int i = 0; i < rawToWelded.Count; ++i) { if (rawToWelded[i] == index1) { rawToWelded[i] = index2; } else if (rawToWelded[i] == index2) { rawToWelded[i] = index1; } } }
protected virtual IEnumerator CreateVolumeConstraints() { //Create pressure constraints if the mesh is closed: if (topology.closed) { volumeConstraintsData = new ObiVolumeConstraintsData(); ObiVolumeConstraintsBatch volumeBatch = new ObiVolumeConstraintsBatch(); volumeConstraintsData.AddBatch(volumeBatch); float avgInitialScale = (scale.x + scale.y + scale.z) * 0.33f; int[] triangleIndices = new int[topology.faces.Count * 3]; for (int i = 0; i < topology.faces.Count; i++) { HalfEdgeMesh.Face face = topology.faces[i]; HalfEdgeMesh.HalfEdge e1 = topology.halfEdges[face.halfEdge]; HalfEdgeMesh.HalfEdge e2 = topology.halfEdges[e1.nextHalfEdge]; HalfEdgeMesh.HalfEdge e3 = topology.halfEdges[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.faces.Count)); } } volumeBatch.AddConstraint(triangleIndices, topology.volume * avgInitialScale); // Set initial amount of active constraints: for (int i = 0; i < volumeConstraintsData.batches.Count; ++i) { volumeConstraintsData.batches[i].activeConstraintCount = volumeConstraintsData.batches[i].constraintCount; } // last triangle is the triangle count: volumeBatch.firstTriangle.Add(volumeBatch.particleIndices.count / 3); } }
protected virtual IEnumerator CreateSimplices() { triangles = new int[topology.faces.Count * 3]; restNormals = new Vector3[topology.vertices.Count]; // Generate deformable triangles: for (int i = 0; i < topology.faces.Count; i++) { HalfEdgeMesh.Face face = topology.faces[i]; HalfEdgeMesh.HalfEdge e1 = topology.halfEdges[face.halfEdge]; HalfEdgeMesh.HalfEdge e2 = topology.halfEdges[e1.nextHalfEdge]; HalfEdgeMesh.HalfEdge e3 = topology.halfEdges[e2.nextHalfEdge]; triangles[i * 3] = e1.endVertex; triangles[i * 3 + 1] = e2.endVertex; triangles[i * 3 + 2] = e3.endVertex; Vector3 v1 = positions[e1.endVertex]; Vector3 v2 = positions[e2.endVertex]; Vector3 v3 = positions[e3.endVertex]; Vector3 n = Vector3.Cross(v2 - v1, v3 - v1); restNormals[e1.endVertex] += n; restNormals[e2.endVertex] += n; restNormals[e3.endVertex] += n; if (i % 500 == 0) { yield return(new CoroutineJob.ProgressInfo("ObiCloth: generating deformable geometry...", i / (float)topology.faces.Count)); } } for (int i = 0; i < restNormals.Length; ++i) { restNormals[i].Normalize(); } }
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 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); }