Files
HauntedBloodlines/Assets/Obi/Scripts/Cloth/Blueprints/ObiTearableClothBlueprint.cs
2025-05-29 22:31:40 +03:00

263 lines
11 KiB
C#

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 <batch, constraintId>
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<int> 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<int> 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<int> constraintColors = new List<int>();
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<int> 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<int> constraintColors = new List<int>();
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;
}
}
}
}