_xiaofang/xiaofang/Assets/Obi/Scripts/Common/Utils/ObiParticleAttachment.cs
杨号敬 bcc74f0465 add
2024-12-18 02:18:45 +08:00

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);
}
}
}
}