using System; using UnityEngine; namespace Obi { [AddComponentMenu("Physics/Obi/Obi Particle Attachment", 820)] [RequireComponent(typeof(ObiActor))] [ExecuteInEditMode] public class ObiParticleAttachment : MonoBehaviour { public enum AttachmentType { Static, Dynamic } [SerializeField] [HideInInspector] private ObiActor m_Actor; [SerializeField] [HideInInspector] private Transform m_Target; [SerializeField] [HideInInspector] private ObiParticleGroup m_ParticleGroup; [SerializeField] [HideInInspector] private AttachmentType m_AttachmentType = AttachmentType.Static; [SerializeField] [HideInInspector] private bool m_ConstrainOrientation = false; [SerializeField] [HideInInspector] private float m_Compliance = 0; [SerializeField] [HideInInspector] [Delayed] private float m_BreakThreshold = float.PositiveInfinity; // private variables are serialized during script reloading, to keep their value. Must mark them explicitly as non-serialized. [NonSerialized] private ObiPinConstraintsBatch pinBatch; [NonSerialized] private ObiColliderBase attachedCollider; [NonSerialized] private int attachedColliderHandleIndex; [NonSerialized] private int[] m_SolverIndices; [NonSerialized] private Vector3[] m_PositionOffsets = null; [NonSerialized] private Quaternion[] m_OrientationOffsets = null; /// /// The actor this attachment is added to. /// public ObiActor actor { get { return m_Actor; } } /// /// The target transform that the should be attached to. /// public Transform target { get { return m_Target; } set { if (value != m_Target) { m_Target = value; Bind(); } } } /// /// The particle group that should be attached to the . /// public ObiParticleGroup particleGroup { get { return m_ParticleGroup; } set { if (value != m_ParticleGroup) { m_ParticleGroup = value; Bind(); } } } /// /// Whether this attachment is currently bound or not. /// public bool isBound { get { return m_Target != null && m_SolverIndices != null && m_PositionOffsets != null; } } /// /// Type of attachment, can be either static or dynamic. /// public AttachmentType attachmentType { get { return m_AttachmentType; } set { if (value != m_AttachmentType) { DisableAttachment(m_AttachmentType); m_AttachmentType = value; EnableAttachment(m_AttachmentType); } } } /// /// Should this attachment constraint particle orientations too? /// public bool constrainOrientation { get { return m_ConstrainOrientation; } set { if (value != m_ConstrainOrientation) { DisableAttachment(m_AttachmentType); m_ConstrainOrientation = value; EnableAttachment(m_AttachmentType); } } } /// /// Constraint compliance, in case this attachment is dynamic. /// /// High compliance values will increase the attachment's elasticity. public float compliance { get { return m_Compliance; } set { if (!Mathf.Approximately(value, m_Compliance)) { m_Compliance = value; if (m_AttachmentType == AttachmentType.Dynamic && pinBatch != null) { for (int i = 0; i < m_SolverIndices.Length; ++i) pinBatch.stiffnesses[i * 2] = m_Compliance; } } } } /// /// Force thershold above which the attachment should break. /// /// Only affects dynamic attachments, as static attachments do not work with forces. public float breakThreshold { get { return m_BreakThreshold; } set { if (!Mathf.Approximately(value, m_BreakThreshold)) { m_BreakThreshold = value; if (m_AttachmentType == AttachmentType.Dynamic && pinBatch != null) { for (int i = 0; i < m_SolverIndices.Length; ++i) pinBatch.breakThresholds[i] = m_BreakThreshold; } } } } private void OnEnable() { m_Actor = GetComponent(); m_Actor.OnBlueprintLoaded += Actor_OnBlueprintLoaded; m_Actor.OnSimulationStart += Actor_OnSimulate; if (m_Actor.solver != null) Actor_OnBlueprintLoaded(m_Actor, m_Actor.sourceBlueprint); EnableAttachment(m_AttachmentType); } private void OnDisable() { DisableAttachment(m_AttachmentType); m_Actor.OnBlueprintLoaded -= Actor_OnBlueprintLoaded; m_Actor.OnSimulationStart -= Actor_OnSimulate; } private void OnValidate() { m_Actor = GetComponent(); // do not re-bind: simply disable and re-enable the attachment. DisableAttachment(AttachmentType.Static); DisableAttachment(AttachmentType.Dynamic); EnableAttachment(m_AttachmentType); } void Actor_OnBlueprintLoaded(ObiActor act, ObiActorBlueprint blueprint) { Bind(); } void Actor_OnSimulate(ObiActor act, float stepTime, float substepTime) { // Attachments must be updated at the start of the step, before performing any simulation. UpdateAttachment(); // if there's any broken constraint, flag pin constraints as dirty for remerging at the start of the next step. BreakDynamicAttachment(substepTime); } private void Bind() { // Disable attachment. DisableAttachment(m_AttachmentType); if (m_Target != null && m_ParticleGroup != null && m_Actor.isLoaded) { Matrix4x4 bindMatrix = m_Target.worldToLocalMatrix * m_Actor.solver.transform.localToWorldMatrix; m_SolverIndices = new int[m_ParticleGroup.Count]; m_PositionOffsets = new Vector3[m_ParticleGroup.Count]; m_OrientationOffsets = new Quaternion[m_ParticleGroup.Count]; for (int i = 0; i < m_ParticleGroup.Count; ++i) { int particleIndex = m_ParticleGroup.particleIndices[i]; if (particleIndex >= 0 && particleIndex < m_Actor.solverIndices.count) { m_SolverIndices[i] = m_Actor.solverIndices[particleIndex]; m_PositionOffsets[i] = bindMatrix.MultiplyPoint3x4(m_Actor.solver.positions[m_SolverIndices[i]]); } else { Debug.LogError("The particle group \'" + m_ParticleGroup.name + "\' references a particle that does not exist in the actor \'" + m_Actor.name + "\'."); m_SolverIndices = null; m_PositionOffsets = null; m_OrientationOffsets = null; return; } } if (m_Actor.usesOrientedParticles) { Quaternion bindOrientation = bindMatrix.rotation; for (int i = 0; i < m_ParticleGroup.Count; ++i) { int particleIndex = m_ParticleGroup.particleIndices[i]; if (particleIndex >= 0 && particleIndex < m_Actor.solverIndices.count) m_OrientationOffsets[i] = bindOrientation * m_Actor.solver.orientations[m_SolverIndices[i]]; } } } else { m_PositionOffsets = null; m_OrientationOffsets = null; } EnableAttachment(m_AttachmentType); } private void EnableAttachment(AttachmentType type) { if (enabled && m_Actor.isLoaded && isBound) { var solver = m_Actor.solver; switch (type) { case AttachmentType.Dynamic: var pins = m_Actor.GetConstraintsByType(Oni.ConstraintType.Pin) as ObiPinConstraintsData; attachedCollider = m_Target.GetComponent(); if (pins != null && attachedCollider != null && pinBatch == null) { // create a new data batch with all our pin constraints: pinBatch = new ObiPinConstraintsBatch(pins); for (int i = 0; i < m_SolverIndices.Length; ++i) { pinBatch.AddConstraint(m_SolverIndices[i], attachedCollider, m_PositionOffsets[i], m_OrientationOffsets[i], m_Compliance, constrainOrientation ? 0 : 10000, m_BreakThreshold); pinBatch.activeConstraintCount++; } // add the batch to the actor: pins.AddBatch(pinBatch); // store the attached collider's handle: attachedColliderHandleIndex = -1; if (attachedCollider.Handle != null) attachedColliderHandleIndex = attachedCollider.Handle.index; m_Actor.SetConstraintsDirty(Oni.ConstraintType.Pin); } break; case AttachmentType.Static: for (int i = 0; i < m_SolverIndices.Length; ++i) if (m_SolverIndices[i] >= 0 && m_SolverIndices[i] < solver.invMasses.count) solver.invMasses[m_SolverIndices[i]] = 0; if (m_Actor.usesOrientedParticles && m_ConstrainOrientation) { for (int i = 0; i < m_SolverIndices.Length; ++i) if (m_SolverIndices[i] >= 0 && m_SolverIndices[i] < solver.invRotationalMasses.count) solver.invRotationalMasses[m_SolverIndices[i]] = 0; } m_Actor.UpdateParticleProperties(); break; } } } private void DisableAttachment(AttachmentType type) { if (isBound) { switch (type) { case AttachmentType.Dynamic: if (pinBatch != null) { var pins = m_Actor.GetConstraintsByType(Oni.ConstraintType.Pin) as ObiConstraints; if (pins != null) { pins.RemoveBatch(pinBatch); if (actor.isLoaded) m_Actor.SetConstraintsDirty(Oni.ConstraintType.Pin); } attachedCollider = null; pinBatch = null; attachedColliderHandleIndex = -1; } break; case AttachmentType.Static: var solver = m_Actor.solver; var blueprint = m_Actor.sourceBlueprint; for (int i = 0; i < m_SolverIndices.Length; ++i) { int solverIndex = m_SolverIndices[i]; if (solverIndex >= 0 && solverIndex < solver.invMasses.count) solver.invMasses[solverIndex] = blueprint.invMasses[i]; } if (m_Actor.usesOrientedParticles) { for (int i = 0; i < m_SolverIndices.Length; ++i) { int solverIndex = m_SolverIndices[i]; if (solverIndex >= 0 && solverIndex < solver.invRotationalMasses.count) solver.invRotationalMasses[solverIndex] = blueprint.invRotationalMasses[i]; } } m_Actor.UpdateParticleProperties(); break; } } } private void UpdateAttachment() { if (enabled && m_Actor.isLoaded && isBound) { var solver = m_Actor.solver; switch (m_AttachmentType) { case AttachmentType.Dynamic: // in case the handle has been updated/invalidated (for instance, when disabling the target) rebuild constraints: if (attachedCollider != null && attachedCollider.Handle != null && attachedCollider.Handle.index != attachedColliderHandleIndex) { attachedColliderHandleIndex = attachedCollider.Handle.index; m_Actor.SetConstraintsDirty(Oni.ConstraintType.Pin); } break; case AttachmentType.Static: var blueprint = m_Actor.sourceBlueprint; bool targetActive = m_Target.gameObject.activeInHierarchy; // Build the attachment matrix: Matrix4x4 attachmentMatrix = solver.transform.worldToLocalMatrix * m_Target.localToWorldMatrix; // Fix all particles in the group and update their position // Note: skip assignment to startPositions if you want attached particles to be interpolated too. for (int i = 0; i < m_SolverIndices.Length; ++i) { int solverIndex = m_SolverIndices[i]; if (solverIndex >= 0 && solverIndex < solver.invMasses.count) { if (targetActive) { solver.invMasses[solverIndex] = 0; solver.velocities[solverIndex] = Vector3.zero; solver.startPositions[solverIndex] = solver.endPositions[solverIndex] = solver.positions[solverIndex] = attachmentMatrix.MultiplyPoint3x4(m_PositionOffsets[i]); } else solver.invMasses[solverIndex] = blueprint.invMasses[i]; } } if (m_Actor.usesOrientedParticles && m_ConstrainOrientation) { Quaternion attachmentRotation = attachmentMatrix.rotation; for (int i = 0; i < m_SolverIndices.Length; ++i) { int solverIndex = m_SolverIndices[i]; if (solverIndex >= 0 && solverIndex < solver.invRotationalMasses.count) { if (targetActive) { solver.invRotationalMasses[solverIndex] = 0; solver.angularVelocities[solverIndex] = Vector3.zero; solver.startOrientations[solverIndex] = solver.endOrientations[solverIndex] = solver.orientations[solverIndex] = attachmentRotation * m_OrientationOffsets[i]; } else solver.invRotationalMasses[solverIndex] = blueprint.invRotationalMasses[i]; } } } break; } } else if (!isBound && attachedColliderHandleIndex >= 0) { attachedColliderHandleIndex = -1; m_Actor.SetConstraintsDirty(Oni.ConstraintType.Pin); } } private void BreakDynamicAttachment(float substepTime) { if (enabled && m_AttachmentType == AttachmentType.Dynamic && m_Actor.isLoaded && isBound) { var solver = m_Actor.solver; var actorConstraints = m_Actor.GetConstraintsByType(Oni.ConstraintType.Pin) as ObiConstraints; var solverConstraints = solver.GetConstraintsByType(Oni.ConstraintType.Pin) as ObiConstraints; bool dirty = false; if (actorConstraints != null && pinBatch != null && actorConstraints.batchCount <= solverConstraints.batchCount) { int pinBatchIndex = actorConstraints.batches.IndexOf(pinBatch); if (pinBatchIndex >= 0 && pinBatchIndex < actor.solverBatchOffsets[(int)Oni.ConstraintType.Pin].Count) { int offset = actor.solverBatchOffsets[(int)Oni.ConstraintType.Pin][pinBatchIndex]; var solverBatch = solverConstraints.batches[pinBatchIndex]; float sqrTime = substepTime * substepTime; for (int i = 0; i < pinBatch.activeConstraintCount; i++) { // In case the handle has been created/destroyed. if (pinBatch.pinBodies[i] != attachedCollider.Handle) { pinBatch.pinBodies[i] = attachedCollider.Handle; dirty = true; } // in case the constraint has been broken: if (-solverBatch.lambdas[(offset + i) * 4 + 3] / sqrTime > pinBatch.breakThresholds[i]) { pinBatch.DeactivateConstraint(i); dirty = true; } } } } // constraints are recreated at the start of a step. if (dirty) m_Actor.SetConstraintsDirty(Oni.ConstraintType.Pin); } } } }