using Cinemachine.Utility;
using System;
using UnityEngine;
namespace Cinemachine
{
///
/// Property applied to CinemachineImpulseManager Channels.
/// Used for custom drawing in the inspector.
///
public sealed class CinemachineImpulseDefinitionPropertyAttribute : PropertyAttribute {}
///
/// Definition of an impulse signal that gets propagated to listeners.
///
/// Here you provide a Raw Signal source, and define an envelope for time-scaling
/// it to craft the complete Impulse signal shape. Also, you provide here parameters
/// that define how the signal dissipates with spatial distance from the source location.
/// Finally, you specify the Impulse Channel on which the signal will be sent.
///
/// An API method is provided here to take these parameters, create an Impulse Event,
/// and broadcast it on the channel.
///
/// When creating a custom Impulse Source class, you will have an instance of this class
/// as a field in your custom class. Be sure also to include the
/// [CinemachineImpulseDefinition] attribute on the field, to get the right
/// property drawer for it.
///
[DocumentationSorting(DocumentationSortingAttribute.Level.API)]
[Serializable]
public class CinemachineImpulseDefinition
{
///
/// Impulse events generated here will appear on the channels included in the mask.
///
[CinemachineImpulseChannelProperty]
[Tooltip("Impulse events generated here will appear on the channels included in the mask.")]
public int m_ImpulseChannel = 1;
///
/// Defines the signal that will be generated.
///
[Header("Signal Shape")]
[Tooltip("Defines the signal that will be generated.")]
[CinemachineEmbeddedAssetProperty(true)]
public SignalSourceAsset m_RawSignal = null;
///
/// Gain to apply to the amplitudes defined in the signal source asset.
///
[Tooltip("Gain to apply to the amplitudes defined in the signal source. 1 is normal. Setting this to 0 completely mutes the signal.")]
public float m_AmplitudeGain = 1f;
///
/// Scale factor to apply to the time axis.
///
[Tooltip("Scale factor to apply to the time axis. 1 is normal. Larger magnitudes will make the signal progress more rapidly.")]
public float m_FrequencyGain = 1f;
/// How to fit the signal into the envelope time
public enum RepeatMode
{
/// Time-stretch the signal to fit the envelope
Stretch,
/// Loop the signal in time to fill the envelope
Loop
}
/// How to fit the signal into the envelope time
[Tooltip("How to fit the signal into the envelope time")]
public RepeatMode m_RepeatMode = RepeatMode.Stretch;
/// Randomize the signal start time
[Tooltip("Randomize the signal start time")]
public bool m_Randomize = true;
///
/// This defines the time-envelope of the signal.
/// The raw signal will be time-scaled to fit in the envelope.
///
[Tooltip("This defines the time-envelope of the signal. The raw signal will be time-scaled to fit in the envelope.")]
[CinemachineImpulseEnvelopeProperty]
public CinemachineImpulseManager.EnvelopeDefinition m_TimeEnvelope
= CinemachineImpulseManager.EnvelopeDefinition.Default();
///
/// The signal will have full amplitude in this radius surrounding the impact point.
/// Beyond that it will dissipate with distance.
///
[Header("Spatial Range")]
[Tooltip("The signal will have full amplitude in this radius surrounding the impact point. Beyond that it will dissipate with distance.")]
public float m_ImpactRadius = 100;
/// How the signal direction behaves as the listener moves away from the origin.
[Tooltip("How the signal direction behaves as the listener moves away from the origin.")]
public CinemachineImpulseManager.ImpulseEvent.DirectionMode m_DirectionMode
= CinemachineImpulseManager.ImpulseEvent.DirectionMode.Fixed;
///
/// This defines how the signal will dissipate with distance beyond the impact radius.
///
[Tooltip("This defines how the signal will dissipate with distance beyond the impact radius.")]
public CinemachineImpulseManager.ImpulseEvent.DissipationMode m_DissipationMode
= CinemachineImpulseManager.ImpulseEvent.DissipationMode.ExponentialDecay;
///
/// At this distance beyond the impact radius, the signal will have dissipated to zero.
///
[Tooltip("At this distance beyond the impact radius, the signal will have dissipated to zero.")]
public float m_DissipationDistance = 1000;
///
/// The speed (m/s) at which the impulse propagates through space. High speeds
/// allow listeners to react instantaneously, while slower speeds allow listeners in the
/// scene to react as if to a wave spreading from the source.
///
[Tooltip("The speed (m/s) at which the impulse propagates through space. High speeds "
+ "allow listeners to react instantaneously, while slower speeds allow listeners in the "
+ "scene to react as if to a wave spreading from the source.")]
public float m_PropagationSpeed = 343; // speed of sound
/// Call this from your behaviour's OnValidate to validate the fields here
public void OnValidate()
{
m_ImpactRadius = Mathf.Max(0, m_ImpactRadius);
m_DissipationDistance = Mathf.Max(0, m_DissipationDistance);
m_TimeEnvelope.Validate();
m_PropagationSpeed = Mathf.Max(1, m_PropagationSpeed);
}
/// Generate an impulse event at a location in space,
/// and broadcast it on the appropriate impulse channel
/// Event originates at this position in world space
/// This direction is considered to be "down" for the purposes of the
/// event signal, and the magnitude of the signal will be scaled according to the
/// length of this vector
public void CreateEvent(Vector3 position, Vector3 velocity)
{
CreateAndReturnEvent(position, velocity);
}
/// Generate an impulse event at a location in space,
/// and broadcast it on the appropriate impulse channel
/// Event originates at this position in world space
/// This direction is considered to be "down" for the purposes of the
/// event signal, and the magnitude of the signal will be scaled according to the
/// length of this vector
/// Returns the created event, so that the caller can modify it dynamically
public CinemachineImpulseManager.ImpulseEvent CreateAndReturnEvent(Vector3 position, Vector3 velocity)
{
if (m_RawSignal == null || Mathf.Abs(m_TimeEnvelope.Duration) < UnityVectorExtensions.Epsilon)
return null;
CinemachineImpulseManager.ImpulseEvent e
= CinemachineImpulseManager.Instance.NewImpulseEvent();
e.m_Envelope = m_TimeEnvelope;
// Scale the time-envelope decay as the root of the amplitude scale
e.m_Envelope = m_TimeEnvelope;
if (m_TimeEnvelope.m_ScaleWithImpact)
e.m_Envelope.m_DecayTime *= Mathf.Sqrt(velocity.magnitude);
e.m_SignalSource = new SignalSource(this, velocity);
e.m_Position = position;
e.m_Radius = m_ImpactRadius;
e.m_Channel = m_ImpulseChannel;
e.m_DirectionMode = m_DirectionMode;
e.m_DissipationMode = m_DissipationMode;
e.m_DissipationDistance = m_DissipationDistance;
e.m_PropagationSpeed = m_PropagationSpeed;
CinemachineImpulseManager.Instance.AddImpulseEvent(e);
return e;
}
// Wrap the raw signal to handle gain, RepeatMode, randomization, and velocity
class SignalSource : ISignalSource6D
{
CinemachineImpulseDefinition m_Def;
Vector3 m_Velocity;
float m_StartTimeOffset = 0;
public SignalSource(CinemachineImpulseDefinition def, Vector3 velocity)
{
m_Def = def;
m_Velocity = velocity;
if (m_Def.m_Randomize && m_Def.m_RawSignal.SignalDuration <= 0)
m_StartTimeOffset = UnityEngine.Random.Range(-1000f, 1000f);
}
public float SignalDuration { get { return m_Def.m_RawSignal.SignalDuration; } }
public void GetSignal(float timeSinceSignalStart, out Vector3 pos, out Quaternion rot)
{
float time = m_StartTimeOffset + timeSinceSignalStart * m_Def.m_FrequencyGain;
// Do we have to fit the signal into the envelope?
float signalDuration = SignalDuration;
if (signalDuration > 0)
{
if (m_Def.m_RepeatMode == RepeatMode.Loop)
time %= signalDuration;
else if (m_Def.m_TimeEnvelope.Duration > UnityVectorExtensions.Epsilon)
time *= m_Def.m_TimeEnvelope.Duration / signalDuration; // stretch
}
m_Def.m_RawSignal.GetSignal(time, out pos, out rot);
float gain = m_Velocity.magnitude;
Vector3 dir = m_Velocity.normalized;
gain *= m_Def.m_AmplitudeGain;
pos *= gain;
pos = Quaternion.FromToRotation(Vector3.down, m_Velocity) * pos;
rot = Quaternion.SlerpUnclamped(Quaternion.identity, rot, gain);
}
}
}
}