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

1421 lines
58 KiB
C#
Raw Normal View History

2023-11-02 20:58:55 +08:00
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);
}
}
}