BITFALL/Assets/Plugins/Trails FX/Scripts/TrailEffect.cs

1421 lines
58 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

using System;
using System.Collections.Generic;
using UnityEngine;
namespace TrailsFX {
public enum TrailStyle {
Color,
TextureStamp,
Clone,
Outline,
SpaceDistortion,
Dash,
Custom
}
public enum ColorSequence {
Fixed,
Cycle,
PingPong,
Random,
FixedRandom
}
public enum PositionChangeRelative {
World,
OtherGameObject
}
public static class TrailStyleProperties {
public static bool supportsColor(this TrailStyle s) {
return s != TrailStyle.SpaceDistortion;
}
}
[ExecuteInEditMode]
[HelpURL("https://kronnect.com/support")]
[DefaultExecutionOrder(100)]
public partial class TrailEffect : MonoBehaviour {
#region Public Properties
public TrailEffectProfile profile;
[Tooltip("If enabled, settings will be synced with profile.")]
public bool profileSync;
public Transform target;
[SerializeField]
bool _active = true;
public bool active { get { return _active; } set { _active = value; if (!_active) wasInactive = true; } }
[Tooltip("By default, trails are not generated if the renderer is not visibile. This option ignores renderer visibility.")]
public bool ignoreVisibility;
public bool executeInEditMode;
public int ignoreFrames;
[Tooltip("The duration of this trail.")]
public float duration = 0.5f;
public bool continuous;
[Tooltip("Use max steps to create a smooth trail if trigger condition is satisfied.")]
public bool smooth;
public bool checkWorldPosition;
public float minDistance = 0.1f;
public PositionChangeRelative worldPositionRelativeOption = PositionChangeRelative.World;
public Transform worldPositionRelativeTransform;
public bool checkScreenPosition = true;
public int minPixelDistance = 10;
public int stepsBufferSize = 1023;
public int maxStepsPerFrame = 12;
public bool checkTime;
public float timeInterval = 1f;
public bool checkCollisions;
public bool orientToSurface = true;
public bool ground;
public float surfaceOffset = 0.05f;
public LayerMask collisionLayerMask = -1;
[Tooltip("Optional mask texture to be applied to the effect. Uses the red channel as an alpha (transparency) multiplier.")]
public Texture2D mask;
public bool drawBehind = true;
public UnityEngine.Rendering.CullMode cullMode = UnityEngine.Rendering.CullMode.Back;
public int subMeshMask = -1;
[GradientUsage(hdr: true)]
public Gradient colorOverTime;
public bool colorRamp;
public Texture2D colorRampTexture;
public Transform colorRampStart, colorRampEnd;
public bool fadeOut = true;
public ColorSequence colorSequence = ColorSequence.Fixed;
[ColorUsage(showAlpha: true, hdr: true)]
public Color color = Color.white;
public float colorCycleDuration = 3f;
public bool colorCycleLoop = true;
public float pingPongSpeed = 1f;
[GradientUsage(hdr: true)]
public Gradient colorStartPalette;
public Camera cam;
public TrailStyle effect = TrailStyle.Color;
public Material customMaterial;
public Texture2D texture;
public Vector3 scale = Vector3.one, scaleStartRandomMin = Vector3.one, scaleStartRandomMax = Vector3.one;
public AnimationCurve scaleOverTime;
[Tooltip("Ignores object scale when calculating trail scale.")]
public bool ignoreTransformScale;
[Tooltip("Applies an uniform scale to x/y/z axis.")]
public bool scaleUniform;
[Tooltip("If set, trail will be parented to this gameobject")]
public Transform parent;
public Vector3 localPositionRandomMin, localPositionRandomMax;
public float laserBandWidth = 0.1f, laserIntensity = 20f, laserFlash = 0.2f;
[ColorUsage(showAlpha: true, hdr: true)]
public Color trailTint = new Color(0f, 0, 0.1f);
[Tooltip("Fades out effects based on distance to camera")]
public bool cameraDistanceFade;
[Tooltip("The closest distance particles can get to the camera before they fade from the cameras view.")]
public float cameraDistanceFadeNear;
[Tooltip("The farthest distance particles can get away from the camera before they fade from the cameras view.")]
public float cameraDistanceFadeFar = 1000;
[Tooltip("Add trails only during these animation states. Optionally include start and end time, example: Attack or Attack(0.1-1.5)")]
public string animationStates;
[Tooltip("The animator component. If not specified, first animator component found in children or parent will be used.")]
public Animator animator;
public Transform lookTarget;
public bool lookToCamera = true;
[Range(0, 1)]
public float textureCutOff = 0.25f;
[Range(0, 1)]
public float normalThreshold = 0.3f;
public bool useLastAnimationState;
public int maxBatches = 50;
public int meshPoolSize = 256;
[Tooltip("Interpolate vertices to provide a smoother effect.")]
public bool interpolate;
#endregion
static Color colorTransparent = new Color(0, 0, 0, 0);
const int MAX_BATCH_INSTANCES = 1023; // max number of instances submitted to GPU in a batch. This limit is defined by Unity.
const int BAKED_GRADIENTS_LENGTH = 256; // number of baked values for the gradients
struct SnapshotTransform {
public Matrix4x4 matrix, parentMatrix;
public float time;
public int meshIndex;
public Color color;
public Vector3 rampStartPos;
public Vector3 rampEndPos;
}
public struct SnapshotIndex {
public float t;
public int index;
}
static class ShaderParams {
public static int ColorArray = Shader.PropertyToID("_Colors");
public static int SubFrameKeyds = Shader.PropertyToID("_SubFrameKeys");
public static int ColorRamp = Shader.PropertyToID("_ColorRamp");
public static int CutOff = Shader.PropertyToID("_CutOff");
public static int NormalThreshold = Shader.PropertyToID("_NormalThreshold");
public static int AdditiveTint = Shader.PropertyToID("_AdditiveTint");
public static int LaserData = Shader.PropertyToID("_LaserData");
public static int Cull = Shader.PropertyToID("_Cull");
public static int RampStartPositions = Shader.PropertyToID("_RampStartPos");
public static int RampEndPositions = Shader.PropertyToID("_RampEndPos");
public static int MaskTex = Shader.PropertyToID("_MaskTex");
public static int ParentMatricesArray = Shader.PropertyToID("_ParentMatrices");
public static int PivotMatrix = Shader.PropertyToID("_PivotMatrix");
public const string SKW_MASK = "TRAIL_MASK";
public const string SKW_ALPHACLIP = "TRAIL_ALPHACLIP";
public const string SKW_INTERPOLATE = "TRAIL_INTERPOLATE";
public const string SKW_COLOR_RAMP = "TRAIL_COLOR_RAMP";
public const string SKW_LOCAL = "TRAIL_LOCAL";
}
struct AnimationStatesInfo {
public int hash;
public float startTime;
public float endTime;
}
SnapshotTransform[] trail;
SnapshotIndex[] sortIndices;
int trailIndex;
Mesh[] meshPool;
int meshPoolIndex;
readonly List<Vector3> prevBakedMeshVertices = new List<Vector3>();
float[] subFrameKeys;
Material trailMask, trailClearMask;
Material[] trailMaterial;
Renderer theRenderer;
Vector3 lastCornerMinPos, lastCornerMaxPos, lastPosition, lastRandomizedPosition, lastRelativePosition;
Quaternion lastRotation;
float lastIntervalTimeCheck;
MaterialPropertyBlock properties;
Matrix4x4[] matrices;
Matrix4x4[] parentMatrices;
Vector4[] colors;
Vector4[] rampStartPositions, rampEndPositions;
Vector3 rampLastStartPosition, rampLastEndPosition;
static int globalRenderQueue = 3100;
int renderQueue;
SkinnedMeshRenderer skinnedMeshRenderer;
[NonSerialized]
public bool isSkinned;
int bakeTime;
int batchNumber;
static Mesh quadMesh;
Dictionary<string, Material> effectMaterials;
bool orient;
Vector3 groundNormal;
int startFrameCount;
float startTime;
float smoothDuration;
bool isLimitedToAnimationStates;
AnimationStatesInfo[] stateHashes;
bool supportsGPUInstancing;
MaterialPropertyBlock propertyBlock;
Color colorRandomAtStart;
Color[] bakedColorOverTime, bakedColorStartPalette;
float[] bakedScaleOverTime;
bool wasInactive;
bool interpolating;
bool usingColorRamp;
static readonly char[] commaSeparator = new char[] { ',' };
static readonly char[] dashSeparator = new char[] { '-' };
bool hasParent;
Vector3 parentPosition, lastParentPosition;
Quaternion parentRotation, lastParentRotation;
void OnEnable() {
CheckEditorSettings();
// setup materials
renderQueue = globalRenderQueue;
globalRenderQueue += maxBatches + 2;
if (globalRenderQueue > 3500) {
globalRenderQueue = 3100;
}
if (trailMask == null) {
trailMask = new Material(Shader.Find("TrailsFX/Mask"));
trailMask.hideFlags = HideFlags.DontSave;
}
trailMask.renderQueue = renderQueue;
if (trailClearMask == null) {
trailClearMask = Instantiate(Resources.Load<Material>("TrailsFX/TrailClearMask"));
trailClearMask.hideFlags = HideFlags.DontSave;
}
if (properties == null) {
properties = new MaterialPropertyBlock();
} else {
properties.Clear();
}
supportsGPUInstancing = SystemInfo.supportsInstancing;
if (!supportsGPUInstancing) {
if (propertyBlock == null) {
propertyBlock = new MaterialPropertyBlock();
} else {
propertyBlock.Clear();
}
}
if (profileSync && profile != null) {
profile.Load(this);
}
Clear();
}
void DestroyMaterial(Material mat) {
if (mat != null) {
DestroyImmediate(mat);
}
}
void OnDestroy() {
DestroyMaterial(trailMask);
DestroyMaterial(trailClearMask);
if (trailMaterial != null) {
for (int k = 0; k < trailMaterial.Length; k++) {
DestroyMaterial(trailMaterial[k]);
}
}
if (effectMaterials != null) {
foreach (KeyValuePair<string, Material> kvp in effectMaterials) {
DestroyMaterial(kvp.Value);
}
}
if (isSkinned && meshPool != null) {
for (int k = 0; k < meshPool.Length; k++) {
if (meshPool[k] != null) {
DestroyImmediate(meshPool[k]);
}
}
}
}
void OnValidate() {
CheckEditorSettings();
}
void Start() {
startFrameCount = Time.frameCount;
if (executeInEditMode || Application.isPlaying) {
UpdateMaterialProperties();
}
colorRandomAtStart = bakedColorStartPalette[UnityEngine.Random.Range(0, BAKED_GRADIENTS_LENGTH)];
}
#if UNITY_EDITOR
private void OnDisable() {
UnityEditor.EditorApplication.update -= ExecuteInEditor;
}
void ExecuteInEditor() {
UnityEditor.EditorUtility.SetDirty(this);
}
#endif
void LateUpdate() {
if (!executeInEditMode && !Application.isPlaying)
return;
if (trail == null)
return;
if (cam == null) {
cam = Camera.main;
if (cam == null) {
cam = FindObjectOfType<Camera>();
if (cam == null)
return;
}
}
AddSnapshot();
RenderTrail();
}
void OnCollisionEnter(Collision collision) {
if (!checkCollisions || !_active)
return;
if (((1 << collision.gameObject.layer) & collisionLayerMask) == 0)
return;
Quaternion rotation;
ContactPoint contact = collision.contacts[0];
Vector3 pos = contact.point;
pos += contact.normal * surfaceOffset;
if (orientToSurface) {
rotation = Quaternion.LookRotation(-contact.normal);
} else {
if (lookTarget != null) {
rotation = Quaternion.LookRotation(pos - lookTarget.transform.position);
} else if (lookToCamera) {
Camera camera = cam;
if (camera == null) {
camera = Camera.main;
}
if (camera != null) {
rotation = Quaternion.LookRotation(pos - camera.transform.position);
} else {
rotation = target.rotation;
}
} else {
rotation = target.rotation;
}
}
AddSnapshot(pos, rotation);
}
public void CheckEditorSettings() {
if (target == null) {
target = transform;
}
if (colorOverTime == null) {
colorOverTime = new Gradient();
GradientColorKey[] colorKeys = new GradientColorKey[2];
colorKeys[0].color = Color.yellow;
colorKeys[0].time = 0f;
colorKeys[1].color = Color.yellow;
colorKeys[1].time = 1f;
GradientAlphaKey[] alphaKeys = new GradientAlphaKey[2];
alphaKeys[0].alpha = 1f;
alphaKeys[0].time = 0f;
alphaKeys[1].alpha = 1f;
alphaKeys[1].time = 10f;
colorOverTime.SetKeys(colorKeys, alphaKeys);
}
if (scaleOverTime == null) {
scaleOverTime = new AnimationCurve();
Keyframe[] keys = new Keyframe[2];
keys[0].value = 1f;
keys[1].time = 0;
keys[1].value = 1f;
keys[1].time = 1;
scaleOverTime.keys = keys;
}
if (colorStartPalette == null) {
colorStartPalette = new Gradient();
GradientColorKey[] colorKeys = new GradientColorKey[3];
colorKeys[0].color = Color.red;
colorKeys[0].time = 0f;
colorKeys[1].color = Color.green;
colorKeys[1].time = 0.5f;
colorKeys[2].color = Color.blue;
colorKeys[2].time = 1f;
colorStartPalette.colorKeys = colorKeys;
}
}
/// <summary>
/// Clears current trail and restarts cycle
/// </summary>
public void Clear() {
UpdateMaterialProperties();
if (theRenderer != null) {
StoreCurrentPositions();
}
lastRandomizedPosition = GetRandomizedPosition();
lastRotation = GetRotation();
meshPoolIndex = 0;
prevBakedMeshVertices.Clear();
if (trail != null) {
for (int k = 0; k < trail.Length; k++) {
trail[k].time = float.MinValue;
}
}
trailIndex = -1;
startFrameCount = Time.frameCount;
startTime = Time.time;
CopyRampPositions();
}
/// <summary>
/// Restarts current trail cycle but keeps existing trail
/// </summary>
public void Restart() {
startFrameCount = Time.frameCount;
startTime = Time.time;
}
void CopyRampPositions() {
if (colorRampStart != null) rampLastStartPosition = colorRampStart.position;
if (colorRampEnd != null) rampLastEndPosition = colorRampEnd.position;
}
public void UpdateMaterialProperties() {
#if UNITY_EDITOR
UnityEditor.EditorApplication.update -= ExecuteInEditor;
if (executeInEditMode) {
UnityEditor.EditorApplication.update += ExecuteInEditor;
}
#endif
CheckEditorSettings();
if (bakedColorOverTime == null || bakedColorOverTime.Length == 0) {
bakedColorOverTime = new Color[BAKED_GRADIENTS_LENGTH];
}
if (bakedScaleOverTime == null || bakedScaleOverTime.Length == 0) {
bakedScaleOverTime = new float[BAKED_GRADIENTS_LENGTH];
}
if (bakedColorStartPalette == null || bakedColorStartPalette.Length == 0) {
bakedColorStartPalette = new Color[BAKED_GRADIENTS_LENGTH];
}
for (int k = 0; k < BAKED_GRADIENTS_LENGTH; k++) {
float t = (float)k / BAKED_GRADIENTS_LENGTH;
bakedColorOverTime[k] = colorOverTime.Evaluate(t);
bakedScaleOverTime[k] = scaleOverTime.Evaluate(t);
bakedColorStartPalette[k] = colorStartPalette.Evaluate(t);
}
groundNormal = Vector3.up;
skinnedMeshRenderer = null;
theRenderer = target.GetComponentInChildren<Renderer>();
if (theRenderer == null) {
trail = null;
if (Application.isPlaying) {
enabled = false;
}
return;
}
isLimitedToAnimationStates = false;
if (!string.IsNullOrEmpty(animationStates)) {
if (animator == null) {
animator = target.GetComponentInChildren<Animator>();
if (animator == null) {
animator = target.GetComponentInParent<Animator>();
}
}
isLimitedToAnimationStates = animator != null;
if (isLimitedToAnimationStates) {
string[] names = animationStates.Split(commaSeparator, StringSplitOptions.RemoveEmptyEntries);
int hashCount = names.Length;
stateHashes = new AnimationStatesInfo[hashCount];
for (int k = 0; k < hashCount; k++) {
string name = null;
float startTime = 0, endTime = 0;
string data = names[k].Trim();
int par0 = data.IndexOf("(");
int par1 = data.IndexOf(")");
if (par1 > par0 && par0 > 0) {
name = data.Substring(0, par0).Trim();
string interval = data.Substring(par0 + 1, par1 - par0 - 1);
string[] times = interval.Split(dashSeparator, StringSplitOptions.RemoveEmptyEntries);
if (times.Length == 2) {
float.TryParse(times[0], System.Globalization.NumberStyles.Any, System.Globalization.CultureInfo.InvariantCulture, out startTime);
float.TryParse(times[1], System.Globalization.NumberStyles.Any, System.Globalization.CultureInfo.InvariantCulture, out endTime);
}
} else {
name = data;
}
stateHashes[k].hash = Animator.StringToHash(name);
stateHashes[k].startTime = startTime;
stateHashes[k].endTime = endTime;
}
}
}
isSkinned = theRenderer is SkinnedMeshRenderer;
if (isSkinned) {
skinnedMeshRenderer = (SkinnedMeshRenderer)theRenderer;
int poolSize = useLastAnimationState ? 1 : meshPoolSize;
if (meshPool == null || meshPool.Length != poolSize) {
meshPool = new Mesh[meshPoolSize];
}
int meshPoolLength = meshPool.Length;
for (int k = 0; k < meshPoolLength; k++) {
if (meshPool[k] == null) {
meshPool[k] = new Mesh();
meshPool[k].hideFlags = HideFlags.DontSave;
}
}
} else {
MeshCollider mc = theRenderer.GetComponent<MeshCollider>();
if (meshPool == null || meshPool.Length != 1) {
meshPool = new Mesh[1];
}
if (mc != null) {
meshPool[0] = mc.sharedMesh;
} else {
MeshFilter mf = theRenderer.GetComponent<MeshFilter>();
if (mf != null) {
meshPool[0] = mf.sharedMesh;
}
}
}
// Runtime only setup
if (!executeInEditMode && !Application.isPlaying)
return;
orient = false;
if (trailMask == null) return;
trailMask.DisableKeyword(ShaderParams.SKW_ALPHACLIP);
trailMask.mainTexture = null;
trailMask.SetInt(ShaderParams.Cull, (int)cullMode);
Material trailMat = null;
switch (effect) {
case TrailStyle.Color:
trailMat = GetEffectMaterial("TrailEffectColor");
break;
case TrailStyle.TextureStamp:
trailMat = GetEffectMaterial("TrailEffectTextureStamp");
if (quadMesh == null) {
quadMesh = BuildQuadMesh();
}
orient = (ground && orientToSurface) || lookToCamera || lookTarget != null;
break;
case TrailStyle.Clone:
trailMat = GetEffectMaterial("TrailEffectClone");
break;
case TrailStyle.Outline:
trailMat = GetEffectMaterial("TrailEffectOutline");
break;
case TrailStyle.SpaceDistortion:
trailMat = GetEffectMaterial("TrailEffectDistort");
break;
case TrailStyle.Dash:
trailMat = GetEffectMaterial("TrailEffectLaser");
break;
case TrailStyle.Custom:
trailMat = customMaterial;
break;
}
if (trailMat == null) {
trail = null;
enabled = false;
return;
}
trailMat.SetInt(ShaderParams.Cull, (int)cullMode);
interpolating = isSkinned && interpolate && !useLastAnimationState;
usingColorRamp = colorRamp && colorRampTexture != null && colorRampStart != null && colorRampEnd != null;
if (trailMaterial == null || trailMaterial.Length != maxBatches) {
if (trailMaterial != null) {
for (int k = 0; k < trailMaterial.Length; k++) {
DestroyMaterial(trailMaterial[k]);
}
}
trailMaterial = new Material[maxBatches];
}
for (int k = 0; k < trailMaterial.Length; k++) {
if (trailMaterial[k] == null || trailMaterial[k].shader != trailMat.shader) {
trailMaterial[k] = Instantiate(trailMat);
trailMaterial[k].hideFlags = HideFlags.DontSave;
}
SetMaterialProperties(trailMaterial[k]);
trailMaterial[k].renderQueue = renderQueue + k + 1;
}
trailClearMask.renderQueue = renderQueue + maxBatches + 1;
trailClearMask.SetInt(ShaderParams.Cull, (int)cullMode);
if (trail == null || trail.Length != stepsBufferSize) {
trail = new SnapshotTransform[stepsBufferSize];
for (int k = 0; k < trail.Length; k++) {
trail[k].time = float.MinValue;
}
trailIndex = -1;
}
if (sortIndices == null || sortIndices.Length != stepsBufferSize) {
sortIndices = new SnapshotIndex[stepsBufferSize];
}
if (matrices == null || matrices.Length != MAX_BATCH_INSTANCES) {
matrices = new Matrix4x4[MAX_BATCH_INSTANCES];
}
if (parentMatrices == null || parentMatrices.Length != MAX_BATCH_INSTANCES) {
parentMatrices = new Matrix4x4[MAX_BATCH_INSTANCES];
}
if (colors == null || colors.Length != MAX_BATCH_INSTANCES) {
colors = new Vector4[MAX_BATCH_INSTANCES];
}
if (subFrameKeys == null || subFrameKeys.Length != MAX_BATCH_INSTANCES) {
subFrameKeys = new float[MAX_BATCH_INSTANCES];
}
if (rampStartPositions == null || rampStartPositions.Length != MAX_BATCH_INSTANCES) {
rampStartPositions = new Vector4[MAX_BATCH_INSTANCES];
}
if (rampEndPositions == null || rampEndPositions.Length != MAX_BATCH_INSTANCES) {
rampEndPositions = new Vector4[MAX_BATCH_INSTANCES];
}
StoreCurrentPositions();
}
/// <summary>
/// Loads and applies a different profile
/// </summary>
public void SetProfile(TrailEffectProfile profile) {
if (profile != null) {
profile.Load(this);
}
}
void SetMaterialProperties(Material trailMat) {
switch (effect) {
case TrailStyle.Color:
trailMat.SetTexture(ShaderParams.ColorRamp, colorRampTexture);
break;
case TrailStyle.TextureStamp:
trailMat.renderQueue = renderQueue + 1;
trailMat.mainTexture = texture;
trailMat.SetFloat(ShaderParams.CutOff, textureCutOff);
trailMask.mainTexture = texture;
trailMask.SetFloat(ShaderParams.CutOff, textureCutOff);
trailMask.EnableKeyword(ShaderParams.SKW_ALPHACLIP);
break;
case TrailStyle.Clone:
Material origMat = theRenderer.sharedMaterial;
if (origMat != null) {
trailMat.mainTexture = origMat.mainTexture;
trailMat.mainTextureScale = origMat.mainTextureScale;
trailMat.mainTextureOffset = origMat.mainTextureOffset;
trailMat.SetFloat(ShaderParams.CutOff, textureCutOff);
if (textureCutOff > 0) {
trailMat.EnableKeyword(ShaderParams.SKW_ALPHACLIP);
} else {
trailMat.DisableKeyword(ShaderParams.SKW_ALPHACLIP);
}
}
break;
case TrailStyle.Outline:
trailMat.SetFloat(ShaderParams.NormalThreshold, normalThreshold);
break;
case TrailStyle.SpaceDistortion:
trailMat.SetColor(ShaderParams.AdditiveTint, trailTint);
break;
case TrailStyle.Dash:
trailMat.SetVector(ShaderParams.LaserData, new Vector3(laserBandWidth, laserIntensity, laserFlash));
break;
}
if (mask != null) {
trailMat.SetTexture(ShaderParams.MaskTex, mask);
trailMat.EnableKeyword(ShaderParams.SKW_MASK);
} else {
trailMat.DisableKeyword(ShaderParams.SKW_MASK);
}
if (interpolating) {
trailMat.EnableKeyword(ShaderParams.SKW_INTERPOLATE);
} else {
trailMat.DisableKeyword(ShaderParams.SKW_INTERPOLATE);
}
if (usingColorRamp) {
trailMat.EnableKeyword(ShaderParams.SKW_COLOR_RAMP);
} else {
trailMat.DisableKeyword(ShaderParams.SKW_COLOR_RAMP);
}
hasParent = parent != null;
if (hasParent) {
trailMat.EnableKeyword(ShaderParams.SKW_LOCAL);
} else {
trailMat.DisableKeyword(ShaderParams.SKW_LOCAL);
}
}
Material GetEffectMaterial(string materialName) {
if (effectMaterials == null) {
effectMaterials = new Dictionary<string, Material>();
}
Material mat;
if (!effectMaterials.TryGetValue(materialName, out mat)) {
mat = Resources.Load<Material>("TrailsFX/" + materialName);
if (mat == null) {
Debug.LogError("Could not find trail material " + materialName);
return null;
}
mat = Instantiate(mat);
mat.hideFlags = HideFlags.DontSave;
effectMaterials[materialName] = mat;
}
return mat;
}
Mesh BuildQuadMesh() {
Mesh mesh = new Mesh();
mesh.name = "TrailQuadMesh";
// Setup vertices
Vector3[] newVertices = new Vector3[4];
float halfHeight = 0.5f;
float halfWidth = 0.5f;
newVertices[0] = new Vector3(-halfWidth, -halfHeight, 0);
newVertices[1] = new Vector3(-halfWidth, halfHeight, 0);
newVertices[2] = new Vector3(halfWidth, -halfHeight, 0);
newVertices[3] = new Vector3(halfWidth, halfHeight, 0);
// Setup UVs
Vector2[] newUVs = new Vector2[newVertices.Length];
newUVs[0] = new Vector2(0, 0);
newUVs[1] = new Vector2(0, 1);
newUVs[2] = new Vector2(1, 0);
newUVs[3] = new Vector2(1, 1);
// Setup triangles
int[] newTriangles = new int[] { 0, 1, 2, 3, 2, 1 };
// Setup normals
Vector3[] newNormals = new Vector3[newVertices.Length];
for (int i = 0; i < newNormals.Length; i++) {
newNormals[i] = Vector3.forward;
}
// Create quad
mesh.vertices = newVertices;
mesh.uv = newUVs;
mesh.triangles = newTriangles;
mesh.normals = newNormals;
mesh.RecalculateBounds();
return mesh;
}
Vector3 GetSnapshotScale() {
Vector3 objectScale = ignoreTransformScale ? Vector3.one : target.lossyScale;
if (scaleUniform) {
float t = UnityEngine.Random.Range(scaleStartRandomMin.x, scaleStartRandomMax.x);
if (isSkinned) {
return new Vector3(t * scale.x, t * scale.y, t * scale.z);
} else {
return new Vector3(objectScale.x * scale.x * t, objectScale.y * scale.y * t, objectScale.z * scale.z * t);
}
} else {
if (isSkinned) {
return new Vector3(scale.x * UnityEngine.Random.Range(scaleStartRandomMin.x, scaleStartRandomMax.x),
scale.y * UnityEngine.Random.Range(scaleStartRandomMin.y, scaleStartRandomMax.y),
scale.z * UnityEngine.Random.Range(scaleStartRandomMin.z, scaleStartRandomMax.z));
} else {
return new Vector3(objectScale.x * scale.x * UnityEngine.Random.Range(scaleStartRandomMin.x, scaleStartRandomMax.x),
objectScale.y * scale.y * UnityEngine.Random.Range(scaleStartRandomMin.y, scaleStartRandomMax.y),
objectScale.z * scale.z * UnityEngine.Random.Range(scaleStartRandomMin.z, scaleStartRandomMax.z));
}
}
}
Quaternion GetRotation() {
Quaternion rot = target.rotation;
return rot;
}
Vector3 GetRandomizedPosition() {
Vector3 localPos = new Vector3(UnityEngine.Random.Range(localPositionRandomMin.x, localPositionRandomMax.x),
UnityEngine.Random.Range(localPositionRandomMin.y, localPositionRandomMax.y),
UnityEngine.Random.Range(localPositionRandomMin.z, localPositionRandomMax.z));
Vector3 wpos = target.position;
Vector3 pos;
if (lastPosition == wpos) {
pos = localPos + wpos;
} else {
pos = (Quaternion.LookRotation(wpos - lastPosition) * localPos) + wpos;
}
if (ground) {
Ray ray = new Ray(target.position, Vector3.down);
RaycastHit hit;
if (Physics.Raycast(ray, out hit)) {
pos = hit.point + pos - target.position;
groundNormal = hit.normal;
}
} else {
if (effect == TrailStyle.TextureStamp) {
pos += theRenderer.bounds.center - target.position;
}
}
return pos;
}
Color GetSnapshotColor() {
Color snapshotColor;
if (effect == TrailStyle.SpaceDistortion) {
Vector2 scrPos0 = cam.WorldToViewportPoint(target.position);
Vector2 scrPos1 = cam.WorldToViewportPoint(lastPosition);
Vector2 diff = (scrPos0 - scrPos1).normalized;
diff.x += 0.5f;
diff.y += 0.5f;
snapshotColor.r = diff.x;
snapshotColor.g = diff.y;
snapshotColor.b = 0;
snapshotColor.a = 1f;
} else {
switch (colorSequence) {
case ColorSequence.Random:
snapshotColor = bakedColorStartPalette[UnityEngine.Random.Range(0, BAKED_GRADIENTS_LENGTH)];
break;
case ColorSequence.FixedRandom:
snapshotColor = colorRandomAtStart;
break;
case ColorSequence.Cycle: {
if (colorCycleDuration < 0) {
colorCycleDuration = 0.01f;
}
float t = (Time.time - startTime) / colorCycleDuration;
if (t > 1f && !colorCycleLoop) {
return colorTransparent;
}
int it = (int)((t - (int)t) * BAKED_GRADIENTS_LENGTH);
snapshotColor = bakedColorStartPalette[it];
}
break;
case ColorSequence.PingPong: {
float t = Mathf.PingPong((Time.time - startTime) * pingPongSpeed, 0.999f);
int it = (int)(t * BAKED_GRADIENTS_LENGTH);
snapshotColor = bakedColorStartPalette[it];
}
break;
default:
snapshotColor = color;
break;
}
}
if (cameraDistanceFade) {
snapshotColor.a *= ComputeCameraDistanceFade(target.position, cam.transform);
}
return snapshotColor;
}
float ComputeCameraDistanceFade(Vector3 position, Transform cameraTransform) {
Vector3 heading = position - cameraTransform.position;
float distance = Vector3.Dot(heading, cameraTransform.forward);
if (distance < cameraDistanceFadeNear) {
return 1f - Mathf.Min(1f, cameraDistanceFadeNear - distance);
}
if (distance > cameraDistanceFadeFar) {
return 1f - Mathf.Min(1f, distance - cameraDistanceFadeFar);
}
return 1f;
}
void StoreCurrentPositions() {
if (executeInEditMode || Application.isPlaying) {
Bounds bounds = theRenderer.bounds;
lastCornerMinPos = bounds.min;
lastCornerMaxPos = bounds.max;
lastPosition = target.position;
lastRelativePosition = lastPosition;
if (worldPositionRelativeOption == PositionChangeRelative.OtherGameObject && worldPositionRelativeTransform != null) {
lastRelativePosition -= worldPositionRelativeTransform.position;
}
lastRotation = GetRotation();
CopyRampPositions();
if (parent != null) {
lastParentPosition = parent.position;
lastParentRotation = parent.rotation;
}
}
}
void AddSnapshot() {
if (!_active || (!theRenderer.enabled && !ignoreVisibility)) {
wasInactive = true;
return;
}
if (wasInactive) {
wasInactive = false;
lastRandomizedPosition = GetRandomizedPosition();
StoreCurrentPositions();
}
bool skip = Time.frameCount - startFrameCount < ignoreFrames || Time.timeScale == 0;
if (isLimitedToAnimationStates && !skip) {
skip = true;
int layersCount = animator.layerCount;
int stateHashesLength = stateHashes.Length;
for (int l = 0; l < layersCount && skip; l++) {
AnimatorStateInfo stateInfo = animator.GetCurrentAnimatorStateInfo(l);
int shortNameHash = stateInfo.shortNameHash;
for (int k = 0; k < stateHashesLength; k++) {
if (stateHashes[k].hash != shortNameHash) continue;
// check animation interval constraint
if (stateHashes[k].endTime == 0) { // no constraint
skip = false;
break;
}
float animTime = stateInfo.normalizedTime * stateInfo.length;
if (animTime < stateHashes[k].startTime || animTime > stateHashes[k].endTime) continue;
skip = false;
break;
}
}
}
if (skip) {
prevBakedMeshVertices.Clear();
lastRandomizedPosition = GetRandomizedPosition();
StoreCurrentPositions();
return;
}
float now = Time.time;
int steps = continuous ? maxStepsPerFrame : 0;
if (steps == 0) {
if (checkWorldPosition) {
Vector3 referencePosition = target.position;
Vector3 referenceLastPos = lastPosition;
if (worldPositionRelativeOption == PositionChangeRelative.OtherGameObject && worldPositionRelativeTransform != null) {
referencePosition -= worldPositionRelativeTransform.position;
referenceLastPos = lastRelativePosition;
}
float distance = Vector3.Distance(referencePosition, referenceLastPos);
if (distance >= minDistance) {
if (smooth) {
smoothDuration = now + 1f;
} else {
steps = (int)(distance / minDistance);
}
}
}
if (checkScreenPosition) {
if (minPixelDistance <= 0) {
minPixelDistance = 1;
}
// Difference of corners in viewport from last frame
Vector2 viewportPos0 = cam.WorldToViewportPoint(lastCornerMinPos);
Vector2 viewportPos1 = cam.WorldToViewportPoint(theRenderer.bounds.min);
int pixelDistance = Mathf.Max(Mathf.CeilToInt(Mathf.Abs(viewportPos1.x - viewportPos0.x) * cam.pixelWidth), Mathf.CeilToInt(Mathf.Abs(viewportPos1.y - viewportPos0.y) * cam.pixelHeight));
int stepsCornerMin = pixelDistance / minPixelDistance;
viewportPos0 = cam.WorldToViewportPoint(lastCornerMaxPos);
viewportPos1 = cam.WorldToViewportPoint(theRenderer.bounds.max);
pixelDistance = Mathf.Max((int)(Mathf.Abs(viewportPos1.x - viewportPos0.x) * cam.pixelWidth), (int)(Mathf.Abs(viewportPos1.y - viewportPos0.y) * cam.pixelHeight));
if (pixelDistance >= minPixelDistance) {
if (smooth) {
smoothDuration = now + 1f;
} else {
int stepsCornerMax = pixelDistance / minPixelDistance;
steps = Mathf.Max(steps, Mathf.Max(stepsCornerMax, stepsCornerMin));
}
}
}
if (checkTime) {
if (now - lastIntervalTimeCheck >= timeInterval) {
lastIntervalTimeCheck = now;
steps = Mathf.Max(1, steps);
}
}
}
if (now < smoothDuration) {
steps = maxStepsPerFrame;
}
if (steps <= 0)
return;
Color color = GetSnapshotColor();
if (color.a == 0) return;
if (steps > maxStepsPerFrame) {
steps = maxStepsPerFrame;
}
SetupMesh(true);
Vector3 pos = GetRandomizedPosition();
Vector3 targetPos = Vector3.zero;
Vector3 upwards = Vector3.up;
if (ground && orientToSurface) {
targetPos = pos + groundNormal;
if (target.position != lastPosition) {
upwards = target.position - lastPosition;
} else {
upwards = target.forward;
}
} else if (orient) {
if (lookTarget != null) {
targetPos = lookTarget.position;
} else {
Camera camera = cam;
if (camera == null) {
camera = Camera.main;
}
if (camera != null) {
targetPos = camera.transform.position;
} else {
orient = false;
}
}
}
Vector3 scale = GetSnapshotScale();
float lastFrameTime = now - Time.deltaTime;
Quaternion rotation = GetRotation();
bool hasParent = parent != null;
if (hasParent) {
parentPosition = parent.position;
parentRotation = parent.rotation;
}
for (int k = 0; k < steps; k++) {
trailIndex++;
if (trailIndex >= trail.Length) {
trailIndex = 0;
}
float t = (k + 1f) / steps;
Vector3 p = Vector3.Lerp(lastRandomizedPosition, pos, t);
if (orient) {
trail[trailIndex].matrix = Matrix4x4.TRS(p, Quaternion.LookRotation(p - targetPos, upwards), scale);
} else {
Quaternion rot = Quaternion.Slerp(lastRotation, rotation, t);
trail[trailIndex].matrix = Matrix4x4.TRS(p, rot, scale);
if (hasParent) {
Vector3 ppos = Vector3.Lerp(lastParentPosition, parentPosition, t);
Quaternion prot = Quaternion.Slerp(lastParentRotation, parentRotation, t);
trail[trailIndex].parentMatrix = Matrix4x4.TRS(ppos, prot, parent.localScale).inverse;
}
}
trail[trailIndex].time = (lastFrameTime * (1f - t)) + now * t;
trail[trailIndex].meshIndex = meshPoolIndex;
trail[trailIndex].color = color;
if (usingColorRamp) {
trail[trailIndex].rampStartPos = Vector3.Lerp(rampLastStartPosition, colorRampStart.position, t);
trail[trailIndex].rampEndPos = Vector3.Lerp(rampLastEndPosition, colorRampEnd.position, t);
}
}
lastRandomizedPosition = pos;
StoreCurrentPositions();
}
void AddSnapshot(Vector3 pos, Quaternion rotation) {
if (!_active || (!theRenderer.enabled && !ignoreVisibility))
return;
Color color = GetSnapshotColor();
if (color.a == 0) return;
SetupMesh(true);
Vector3 scale = GetSnapshotScale();
trailIndex++;
if (trailIndex >= trail.Length) {
trailIndex = 0;
}
trail[trailIndex].matrix = Matrix4x4.TRS(pos, rotation, scale);
trail[trailIndex].time = Time.time;
trail[trailIndex].meshIndex = meshPoolIndex;
trail[trailIndex].color = color;
}
void RenderTrail() {
if (duration < 0) {
duration = 0.001f;
}
int count = 0;
float now = Time.time;
// Pick entries and compute transition
for (int i = 0; i < trail.Length; i++) {
float t = now - trail[i].time;
if (t < duration) {
sortIndices[count].t = t / duration;
sortIndices[count].index = i;
count++;
if (count >= stepsBufferSize)
break;
}
}
if (count == 0)
return;
// Sort indices
QuickSort(0, count - 1);
// Build batches
batchNumber = 0;
bool singleBatch = useLastAnimationState || effect == TrailStyle.TextureStamp;
if (singleBatch && count <= MAX_BATCH_INSTANCES) {
SendToGPU(meshPoolIndex, 0, count);
} else {
int batchMeshIndex = trail[sortIndices[0].index].meshIndex;
int batchStartIndex = 0;
int batchInstancesCount = 1;
for (int k = 1; k < count; k++) {
int i = sortIndices[k].index;
int meshIndex = trail[i].meshIndex;
if (meshIndex != batchMeshIndex || batchInstancesCount >= MAX_BATCH_INSTANCES) {
// send previous batch
SendToGPU(batchMeshIndex, batchStartIndex, batchInstancesCount);
// prepare new batch
batchMeshIndex = meshIndex;
batchStartIndex += batchInstancesCount;
batchInstancesCount = 0;
}
batchInstancesCount++;
}
if (batchInstancesCount > 0) {
// send last batch
SendToGPU(batchMeshIndex, batchStartIndex, batchInstancesCount);
}
}
}
void SendToGPU(int meshIndex, int startIndex, int count) {
if (meshIndex < 0 || meshIndex >= meshPool.Length)
return;
Mesh batchMesh = effect == TrailStyle.TextureStamp ? quadMesh : meshPool[meshIndex];
if (batchMesh == null)
return;
int layer = target.gameObject.layer;
if (drawBehind && batchNumber == 0 && (theRenderer.isVisible || !ignoreVisibility)) {
Vector3 pos = target.position;
Vector3 sca;
Mesh mesh;
if (isSkinned) {
mesh = SetupMesh(false);
sca = Vector3.one;
} else {
mesh = meshPool[meshIndex];
sca = target.lossyScale;
}
if (mesh != null) {
Matrix4x4 m = Matrix4x4.TRS(pos, target.rotation, sca);
if (subMeshMask > 0) {
int subMeshCount = mesh.subMeshCount;
for (int k = 0; k < subMeshCount; k++) {
if (((1 << k) & subMeshMask) != 0) {
Graphics.DrawMesh(mesh, m, trailMask, layer, null, k); // runs first in render queue
Graphics.DrawMesh(mesh, m, trailClearMask, layer, null, k); // runs last in render queue
}
}
} else {
Graphics.DrawMesh(mesh, m, trailMask, layer); // runs first in render queue
Graphics.DrawMesh(mesh, m, trailClearMask, layer); // runs last in render queue
}
}
}
// Pack for instancing
for (int o = 0; o < count; o++, startIndex++) {
int index = sortIndices[startIndex].index;
float t = sortIndices[startIndex].t;
int it = (int)(BAKED_GRADIENTS_LENGTH * t) % BAKED_GRADIENTS_LENGTH;
// Assign RGBA
Color baseColor = trail[index].color;
Color color = bakedColorOverTime[it];
colors[o].x = color.r * baseColor.r;
colors[o].y = color.g * baseColor.g;
colors[o].z = color.b * baseColor.b;
colors[o].w = color.a * baseColor.a;
if (fadeOut) colors[o].w *= (1f - t);
// Pass subframe key
subFrameKeys[o] = (float)o / count;
// Set matrix
float scale = bakedScaleOverTime[it];
matrices[o].m00 = trail[index].matrix.m00 * scale;
matrices[o].m01 = trail[index].matrix.m01 * scale;
matrices[o].m02 = trail[index].matrix.m02 * scale;
matrices[o].m03 = trail[index].matrix.m03;
matrices[o].m10 = trail[index].matrix.m10 * scale;
matrices[o].m11 = trail[index].matrix.m11 * scale;
matrices[o].m12 = trail[index].matrix.m12 * scale;
matrices[o].m13 = trail[index].matrix.m13;
matrices[o].m20 = trail[index].matrix.m20 * scale;
matrices[o].m21 = trail[index].matrix.m21 * scale;
matrices[o].m22 = trail[index].matrix.m22 * scale;
matrices[o].m23 = trail[index].matrix.m23;
matrices[o].m30 = trail[index].matrix.m30;
matrices[o].m31 = trail[index].matrix.m31;
matrices[o].m32 = trail[index].matrix.m32;
matrices[o].m33 = trail[index].matrix.m33;
// Color ramp positions
if (usingColorRamp) {
rampStartPositions[o].x = trail[index].rampStartPos.x;
rampStartPositions[o].y = trail[index].rampStartPos.y;
rampStartPositions[o].z = trail[index].rampStartPos.z;
rampEndPositions[o].x = trail[index].rampEndPos.x;
rampEndPositions[o].y = trail[index].rampEndPos.y;
rampEndPositions[o].z = trail[index].rampEndPos.z;
}
if (hasParent) {
parentMatrices[o].m00 = trail[index].parentMatrix.m00;
parentMatrices[o].m01 = trail[index].parentMatrix.m01;
parentMatrices[o].m02 = trail[index].parentMatrix.m02;
parentMatrices[o].m03 = trail[index].parentMatrix.m03;
parentMatrices[o].m10 = trail[index].parentMatrix.m10;
parentMatrices[o].m11 = trail[index].parentMatrix.m11;
parentMatrices[o].m12 = trail[index].parentMatrix.m12;
parentMatrices[o].m13 = trail[index].parentMatrix.m13;
parentMatrices[o].m20 = trail[index].parentMatrix.m20;
parentMatrices[o].m21 = trail[index].parentMatrix.m21;
parentMatrices[o].m22 = trail[index].parentMatrix.m22;
parentMatrices[o].m23 = trail[index].parentMatrix.m23;
parentMatrices[o].m30 = trail[index].parentMatrix.m30;
parentMatrices[o].m31 = trail[index].parentMatrix.m31;
parentMatrices[o].m32 = trail[index].parentMatrix.m32;
parentMatrices[o].m33 = trail[index].parentMatrix.m33;
}
}
// Send batch to pipeline
properties.SetVectorArray(ShaderParams.ColorArray, colors);
if (interpolating || usingColorRamp) {
properties.SetFloatArray(ShaderParams.SubFrameKeyds, subFrameKeys);
}
if (usingColorRamp) {
properties.SetVectorArray(ShaderParams.RampStartPositions, rampStartPositions);
properties.SetVectorArray(ShaderParams.RampEndPositions, rampEndPositions);
}
if (hasParent) {
properties.SetMatrixArray(ShaderParams.ParentMatricesArray, parentMatrices);
properties.SetMatrix(ShaderParams.PivotMatrix, parent.localToWorldMatrix);
}
if (batchNumber < trailMaterial.Length - 1) {
batchNumber++;
}
int batchMeshSubMeshCount = batchMesh.subMeshCount;
if (supportsGPUInstancing) {
for (int s = 0; s < batchMeshSubMeshCount; s++) {
if (((1 << s) & subMeshMask) != 0) {
Graphics.DrawMeshInstanced(batchMesh, s, trailMaterial[batchNumber], matrices, count, properties, UnityEngine.Rendering.ShadowCastingMode.Off, false, layer);
}
}
// Clear stencil buffer
for (int s = 0; s < batchMeshSubMeshCount; s++) {
if (((1 << s) & subMeshMask) != 0) {
Graphics.DrawMeshInstanced(batchMesh, s, trailClearMask, matrices, count, null, UnityEngine.Rendering.ShadowCastingMode.Off, false, layer);
}
}
} else {
// Fallback for GPUs not supporting instancing; better than nothing :(
for (int i = 0; i < count; i++) {
propertyBlock.SetVector(ShaderParams.ColorArray, colors[i]);
for (int s = 0; s < batchMeshSubMeshCount; s++) {
if (((1 << s) & subMeshMask) != 0) {
Graphics.DrawMesh(batchMesh, matrices[i], trailMaterial[batchNumber], layer, null, s, propertyBlock, false, false);
}
}
}
// Clear stencil buffer
for (int s = 0; s < batchMeshSubMeshCount; s++) {
if (((1 << s) & subMeshMask) != 0) {
for (int i = 0; i < count; i++) {
Graphics.DrawMesh(batchMesh, matrices[i], trailClearMask, layer, null, s, null, false, false);
}
}
}
}
}
Mesh SetupMesh(bool bakeMeshNow) {
if (isSkinned && bakeMeshNow) {
int thisFrame = Time.frameCount;
if (thisFrame != bakeTime) {
bakeTime = thisFrame;
meshPoolIndex++;
if (meshPoolIndex >= meshPool.Length) {
meshPoolIndex = 0;
}
skinnedMeshRenderer.BakeMesh(meshPool[meshPoolIndex]);
if (interpolating) {
if (prevBakedMeshVertices.Count > 0) {
meshPool[meshPoolIndex].SetUVs(1, prevBakedMeshVertices);
meshPool[meshPoolIndex].GetVertices(prevBakedMeshVertices);
} else {
meshPool[meshPoolIndex].GetVertices(prevBakedMeshVertices);
meshPool[meshPoolIndex].SetUVs(1, prevBakedMeshVertices);
}
}
}
}
return meshPool[meshPoolIndex];
}
void QuickSort(int min, int max) {
int i = min;
int j = max;
float x = sortIndices[(min + max) / 2].t;
do {
while (sortIndices[i].t < x) {
i++;
}
while (sortIndices[j].t > x) {
j--;
}
if (i <= j) {
SnapshotIndex h = sortIndices[i];
sortIndices[i] = sortIndices[j];
sortIndices[j] = h;
i++;
j--;
}
} while (i <= j);
if (min < j) {
QuickSort(min, j);
}
if (i < max) {
QuickSort(i, max);
}
}
/// <summary>
/// Returns the position of a trail snapshot
/// </summary>
/// <param name="index">Index of the trail snapshot (0 to step buffer size defined in Trail Effect component)</param>
/// <returns>Returns the world space position of the trail snapshot</returns>
public Vector3 GetTrailPosition(int index) {
return new Vector3(trail[index].matrix.m03, trail[index].matrix.m13, trail[index].matrix.m23);
}
}
}