using UnityEngine; using Unity.Profiling; using System.Collections; using System.Collections.Generic; namespace Obi { [AddComponentMenu("Physics/Obi/Obi SkinnedCloth", 902)] [RequireComponent(typeof(SkinnedMeshRenderer))] public class ObiSkinnedCloth : ObiClothBase, ITetherConstraintsUser, ISkinConstraintsUser { static ProfilerMarker m_SetTargetSkinPerfMarker = new ProfilerMarker("SetTargetSkin"); static ProfilerMarker m_SortSkinInputsPerfMarker = new ProfilerMarker("SortSkinInputs"); static ProfilerMarker m_SetSkinInputsPerfMarker = new ProfilerMarker("SetSkinInputs"); static ProfilerMarker m_BakeMeshPerfMarker = new ProfilerMarker("BakeMesh"); [SerializeField] protected ObiSkinnedClothBlueprint m_SkinnedClothBlueprint; // tethers [SerializeField] protected bool _tetherConstraintsEnabled = true; [SerializeField] protected float _tetherCompliance = 0; [SerializeField] [Range(0.1f, 2)] protected float _tetherScale = 1; public override ObiActorBlueprint sourceBlueprint { get { return m_SkinnedClothBlueprint; } } public override ObiClothBlueprintBase clothBlueprintBase { get { return m_SkinnedClothBlueprint; } } /// /// Whether this actor's tether constraints are enabled. /// public bool tetherConstraintsEnabled { get { return _tetherConstraintsEnabled; } set { if (value != _tetherConstraintsEnabled) { _tetherConstraintsEnabled = value; SetConstraintsDirty(Oni.ConstraintType.Tether); } } } /// /// Compliance of this actor's tether constraints. /// public float tetherCompliance { get { return _tetherCompliance; } set { _tetherCompliance = value; SetConstraintsDirty(Oni.ConstraintType.Tether); } } /// /// Rest length scaling for this actor's tether constraints. /// public float tetherScale { get { return _tetherScale; } set { _tetherScale = value; SetConstraintsDirty(Oni.ConstraintType.Tether); } } public bool skinConstraintsEnabled { get { return true; } set { } } public ObiSkinnedClothBlueprint skinnedClothBlueprint { get { return m_SkinnedClothBlueprint; } set { if (m_SkinnedClothBlueprint != value) { RemoveFromSolver(); ClearState(); m_SkinnedClothBlueprint = value; AddToSolver(); } } } private SkinnedMeshRenderer skin; [HideInInspector] public List bakedVertices = new List(); [HideInInspector] public List bakedNormals = new List(); [HideInInspector] public List bakedTangents = new List(); [HideInInspector] public Mesh bakedMesh; /**< Unique instance of the shared mesh, gets modified by the simulation*/ private Vector3[] sortedPoints; private Vector3[] sortedNormals; public override void LoadBlueprint(ObiSolver solver) { base.LoadBlueprint(solver); var skinConstraints = GetConstraintsByType(Oni.ConstraintType.Skin) as ObiConstraints; if (skinConstraints != null && skinConstraints.GetBatchCount() > 0) { var batch = skinConstraints.batches[0] as ObiSkinConstraintsBatch; sortedPoints = new Vector3[batch.constraintCount]; sortedNormals = new Vector3[batch.constraintCount]; } } protected override void OnValidate() { base.OnValidate(); SetupRuntimeConstraints(); } private void SetupRuntimeConstraints() { SetConstraintsDirty(Oni.ConstraintType.Distance); SetConstraintsDirty(Oni.ConstraintType.Bending); SetConstraintsDirty(Oni.ConstraintType.Aerodynamics); SetConstraintsDirty(Oni.ConstraintType.Tether); SetSelfCollisions(m_SelfCollisions); SetSimplicesDirty(); UpdateCollisionMaterials(); } protected override void OnEnable() { base.OnEnable(); skin = GetComponent(); bakedMesh = null; CreateBakedMeshIfNeeded(); } protected override void OnDisable() { base.OnDisable(); if (m_SkinnedClothBlueprint != null) skin.sharedMesh = m_SkinnedClothBlueprint.inputMesh; DestroyImmediate(bakedMesh); } public Vector3 GetSkinRadiiBackstop(ObiSkinConstraintsBatch batch, int constraintIndex) { return new Vector3(batch.skinRadiiBackstop[constraintIndex*3], batch.skinRadiiBackstop[constraintIndex * 3+1], batch.skinRadiiBackstop[constraintIndex * 3+2]); } public float GetSkinCompliance(ObiSkinConstraintsBatch batch, int constraintIndex) { return batch.skinCompliance[constraintIndex]; } private void CreateBakedMeshIfNeeded() { if (bakedMesh == null && m_SkinnedClothBlueprint != null) bakedMesh = Instantiate(m_SkinnedClothBlueprint.inputMesh); } private void SetTargetSkin() { using (m_SetTargetSkinPerfMarker.Auto()) { var skinConstraints = GetConstraintsByType(Oni.ConstraintType.Skin) as ObiConstraints; var batch = skinConstraints.batches[0] as ObiSkinConstraintsBatch; var solverSkinConstraints = solver.GetConstraintsByType(Oni.ConstraintType.Skin) as ObiConstraints; var solverBatch = solverSkinConstraints.batches[0] as ObiSkinConstraintsBatch; using (m_SortSkinInputsPerfMarker.Auto()) { int pointCount = bakedVertices.Count; for (int i = 0; i < pointCount; ++i) { int welded = m_SkinnedClothBlueprint.topology.rawToWelded[i]; sortedPoints[welded] = bakedVertices[i]; sortedNormals[welded] = bakedNormals[i]; } } using (m_SetSkinInputsPerfMarker.Auto()) { var skinScale = skin.transform.lossyScale; var invSkinScale = Matrix4x4.Scale(new Vector3(1 / skinScale.x, 1 / skinScale.y, 1 / skinScale.z)); Matrix4x4 skinToSolver = actorLocalToSolverMatrix * invSkinScale; int offset = solverBatchOffsets[(int)Oni.ConstraintType.Skin][0]; for (int i = 0; i < batch.activeConstraintCount; ++i) { int constraintIndex = offset + i; int solverIndex = solverBatch.particleIndices[constraintIndex]; int actorIndex = solver.particleToActor[solverIndex].indexInActor; solverBatch.skinPoints[constraintIndex] = skinToSolver.MultiplyPoint3x4(sortedPoints[actorIndex]); solverBatch.skinNormals[constraintIndex] = skinToSolver.MultiplyVector(sortedNormals[actorIndex]); // Rigidly transform particles with zero skin radius and zero compliance: if (Mathf.Approximately(solverBatch.skinRadiiBackstop[constraintIndex * 3], 0) & Mathf.Approximately(solverBatch.skinCompliance[constraintIndex], 0)) { solver.invMasses[solverIndex] = 0; solver.positions[solverIndex] = solverBatch.skinPoints[constraintIndex]; } } } } } public override void BeginStep(float stepTime) { base.BeginStep(stepTime); CreateBakedMeshIfNeeded(); if (m_SkinnedClothBlueprint != null && bakedMesh != null && isLoaded) { using (m_BakeMeshPerfMarker.Auto()) { // bake skinned vertices/normals: skin.sharedMesh = m_SkinnedClothBlueprint.inputMesh; skin.BakeMesh(bakedMesh); bakedMesh.GetVertices(bakedVertices); bakedMesh.GetNormals(bakedNormals); bakedMesh.GetTangents(bakedTangents); } // update skin constraints / particles: SetTargetSkin(); } } } }