64 lines
20 KiB
C#
64 lines
20 KiB
C#
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;
|
|
|
|
|
|
/// <summary>
|
|
/// The actor this attachment is added to.
|
|
/// </summary>
|
|
public ObiActor actor
|
|
{
|
|
get { return m_Actor; }
|
|
}
|
|
|
|
/// <summary>
|
|
/// The target transform that the <see cref="particleGroup"/> should be attached to.
|
|
/// </summary>
|
|
public Transform target
|
|
{
|
|
get { return m_Target; }
|
|
set
|
|
{
|
|
if (value != m_Target)
|
|
{
|
|
m_Target = value;
|
|
Bind();
|
|
}
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// The particle group that should be attached to the <see cref="target"/>.
|
|
/// </summary>
|
|
public ObiParticleGroup particleGroup
|
|
{
|
|
get
|
|
{
|
|
return m_ParticleGroup;
|
|
}
|
|
|
|
set
|
|
{
|
|
if (value != m_ParticleGroup)
|
|
{
|
|
m_ParticleGroup = value;
|
|
Bind();
|
|
}
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Whether this attachment is currently bound or not.
|
|
/// </summary>
|
|
public bool isBound
|
|
{
|
|
get { return m_Target != null && m_SolverIndices != null && m_PositionOffsets != null; }
|
|
}
|
|
|
|
/// <summary>
|
|
/// Type of attachment, can be either static or dynamic.
|
|
/// </summary>
|
|
public AttachmentType attachmentType
|
|
{
|
|
get { return m_AttachmentType; }
|
|
set
|
|
{
|
|
if (value != m_AttachmentType)
|
|
{
|
|
DisableAttachment(m_AttachmentType);
|
|
m_AttachmentType = value;
|
|
EnableAttachment(m_AttachmentType);
|
|
}
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Should this attachment constraint particle orientations too?
|
|
/// </summary>
|
|
public bool constrainOrientation
|
|
{
|
|
get { return m_ConstrainOrientation; }
|
|
set
|
|
{
|
|
if (value != m_ConstrainOrientation)
|
|
{
|
|
DisableAttachment(m_AttachmentType);
|
|
m_ConstrainOrientation = value;
|
|
EnableAttachment(m_AttachmentType);
|
|
}
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Constraint compliance, in case this attachment is dynamic.
|
|
/// </summary>
|
|
/// 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;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Force thershold above which the attachment should break.
|
|
/// </summary>
|
|
/// 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<ObiActor>();
|
|
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<ObiActor>();
|
|
|
|
// 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<ObiColliderBase>();
|
|
|
|
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<ObiPinConstraintsBatch>;
|
|
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<ObiPinConstraintsBatch>;
|
|
var solverConstraints = solver.GetConstraintsByType(Oni.ConstraintType.Pin) as ObiConstraints<ObiPinConstraintsBatch>;
|
|
|
|
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);
|
|
}
|
|
}
|
|
}
|
|
}
|