UPF/Assets/NEW VFX TO CHECK/Scripting/Effects/Particle Plexus/Scripts/ParticlePlexus.cs

842 lines
26 KiB
C#

// =================================
// Namespaces.
// =================================
using UnityEngine;
using System.Collections.Generic;
using System.Threading;
// =================================
// Define namespace.
// =================================
namespace MirzaBeig
{
namespace Scripting
{
namespace Effects
{
// =================================
// Classes.
// =================================
[RequireComponent(typeof(ParticleSystem))]
[AddComponentMenu("Effects/Particle Plexus")]
public class ParticlePlexus : MonoBehaviour
{
// =================================
// Nested classes and structures.
// =================================
// ...
// =================================
// Variables.
// =================================
// ...
public float maxDistance = 1.0f;
public int maxConnections = 5;
public int maxLineRenderers = 100;
[Space]
[Range(0.0f, 1.0f)]
public float widthFromParticle = 0.125f;
[Space]
[Range(0.0f, 1.0f)]
public float colourFromParticle = 1.0f;
[Range(0.0f, 1.0f)]
public float alphaFromParticle = 1.0f;
[Space]
public AnimationCurve alphaOverNormalizedDistance = AnimationCurve.Linear(0.0f, 1.0f, 1.0f, 1.0f);
//public float fadeInTime = 0.0f;
new ParticleSystem particleSystem;
ParticleSystem.Particle[] particles;
Vector3[] particlePositions;
Color[] particleColours;
float[] particleSizes;
ParticleSystem.MainModule particleSystemMainModule;
[Space]
public LineRenderer lineRendererTemplate;
List<LineRenderer> lineRenderers = new List<LineRenderer>();
//[System.Serializable]
//public struct LineRendererData
//{
// public float currentStartParticleID;
// public float currentEndParticleID;
// public float previousStartParticleID;
// public float previousEndParticleID;
// public float timer;
//}
//List<LineRendererData> lineRendererData = new List<LineRendererData>();
Transform _transform;
[Header("Triangle Mesh Settings")]
public MeshFilter trianglesMeshFilter;
Mesh trianglesMesh;
List<int[]> allConnectedParticles = new List<int[]>();
[Space]
// Triangles can be limited to those with particles who
// are a fraction of the distance limit from other particles.
// Example:
// 1.0 == use same distance as maxDistance.
// 0.5 == half of maxDistance.
[Range(0.0f, 1.0f)]
public float maxDistanceTriangleBias = 1.0f;
[Space]
// Additional filtering for triangle generation so that it's more based on distance to adjacent particles.
public bool trianglesDistanceCheck = false;
[Space]
[Range(0.0f, 1.0f)]
public float triangleColourFromParticle = 1.0f;
[Range(0.0f, 1.0f)]
public float triangleAlphaFromParticle = 1.0f;
[Header("General Performance Settings")]
[Range(0.0f, 1.0f)]
public float delay = 0.0f;
float timer;
public bool alwaysUpdate = false;
bool visible;
//List<Vector4> customParticleData = new List<Vector4>();
//int uniqueParticleID;
// =================================
// Functions.
// =================================
// ...
void Start()
{
particleSystem = GetComponent<ParticleSystem>();
particleSystemMainModule = particleSystem.main;
_transform = transform;
if (trianglesMeshFilter)
{
trianglesMesh = new Mesh();
trianglesMeshFilter.mesh = trianglesMesh;
// Unparent since I don't want it following me, but I may have
// parented in the scene for organization.
// EDIT: Nevermind, let the user deal with this as they see fit.
//if (trianglesMeshFilter.transform != transform)
//{
// trianglesMeshFilter.transform.parent = null;
//}
}
}
// ...
void OnDisable()
{
for (int i = 0; i < lineRenderers.Count; i++)
{
lineRenderers[i].enabled = false;
}
}
// ...
void OnBecameVisible()
{
visible = true;
}
void OnBecameInvisible()
{
visible = false;
}
// ...
void LateUpdate()
{
if (trianglesMeshFilter)
{
switch (particleSystemMainModule.simulationSpace)
{
case ParticleSystemSimulationSpace.World:
{
// Make sure this is always at origin, or triangle mesh moves out of sync since
// the mesh triangle vertices are already set to the world space particle positions.
trianglesMeshFilter.transform.position = Vector3.zero;
break;
}
case ParticleSystemSimulationSpace.Local:
{
// In local space it should follow me.
trianglesMeshFilter.transform.position = transform.position;
trianglesMeshFilter.transform.rotation = transform.rotation;
break;
}
case ParticleSystemSimulationSpace.Custom:
{
// In custom space it should follow the custom transform.
trianglesMeshFilter.transform.position = particleSystemMainModule.customSimulationSpace.position;
trianglesMeshFilter.transform.rotation = particleSystemMainModule.customSimulationSpace.rotation;
break;
}
}
}
// Filter doesn't exist but mesh is there? That means the filter reference was lost. Clear the mesh.
// AKA... If mesh filter is gone (deleted and/or reference nulled), clear the mesh.
else if (trianglesMesh)
{
trianglesMesh.Clear();
}
int lineRenderersCount = lineRenderers.Count;
// In case max line renderers value is changed at runtime -> destroy extra.
if (lineRenderersCount > maxLineRenderers)
{
for (int i = maxLineRenderers; i < lineRenderersCount; i++)
{
Destroy(lineRenderers[i].gameObject);
}
lineRenderers.RemoveRange(maxLineRenderers, lineRenderersCount - maxLineRenderers);
//lineRendererData.RemoveRange(maxLineRenderers, lineRenderersCount - maxLineRenderers);
lineRenderersCount -= lineRenderersCount - maxLineRenderers;
}
if (alwaysUpdate || visible)
{
// Prevent constant allocations so long as max particle count doesn't change.
int maxParticles = particleSystemMainModule.maxParticles;
if (particles == null || particles.Length < maxParticles)
{
particles = new ParticleSystem.Particle[maxParticles];
particlePositions = new Vector3[maxParticles];
particleColours = new Color[maxParticles];
particleSizes = new float[maxParticles];
}
float deltaTime = Time.deltaTime;
timer += deltaTime;
if (timer >= delay)
{
timer = 0.0f;
int lrIndex = 0;
allConnectedParticles.Clear();
// Only update if drawing/making connections.
if (maxConnections > 0 && maxLineRenderers > 0)
{
particleSystem.GetParticles(particles);
//particleSystem.GetCustomParticleData(customParticleData, ParticleSystemCustomData.Custom1);
int particleCount = particleSystem.particleCount;
float maxDistanceSqr = maxDistance * maxDistance;
ParticleSystemSimulationSpace simulationSpace = particleSystemMainModule.simulationSpace;
ParticleSystemScalingMode scalingMode = particleSystemMainModule.scalingMode;
Transform customSimulationSpaceTransform = particleSystemMainModule.customSimulationSpace;
Color lineRendererStartColour = lineRendererTemplate.startColor;
Color lineRendererEndColour = lineRendererTemplate.endColor;
float lineRendererStartWidth = lineRendererTemplate.startWidth * lineRendererTemplate.widthMultiplier;
float lineRendererEndWidth = lineRendererTemplate.endWidth * lineRendererTemplate.widthMultiplier;
// Save particle properties in a quick loop (accessing these is expensive and loops significantly more later, so it's better to save them once now).
for (int i = 0; i < particleCount; i++)
{
particlePositions[i] = particles[i].position;
particleColours[i] = particles[i].GetCurrentColor(particleSystem);
particleSizes[i] = particles[i].GetCurrentSize(particleSystem);
// Default is 0.0f, so if default, this is a new particle and I need to assign a custom ID.
//if (customParticleData[i].x == 0.0f)
//{
// // ++value -> increment first, then return that value.
// // That way it won't be zero (default) the first time I use it.
// customParticleData[i] = new Vector4(++uniqueParticleID, 0, 0, 0);
//}
}
//particleSystem.SetCustomParticleData(customParticleData, ParticleSystemCustomData.Custom1);
Vector3 p1p2_difference;
// If in world space, there's no need to do any of the extra calculations... simplify the loop!
if (simulationSpace == ParticleSystemSimulationSpace.World)
{
for (int i = 0; i < particleCount; i++)
{
if (lrIndex == maxLineRenderers)
{
break;
}
Color particleColour = particleColours[i];
Color lineStartColour = Color.LerpUnclamped(lineRendererStartColour, particleColour, colourFromParticle);
float lineStartColourOriginalAlpha = Mathf.LerpUnclamped(lineRendererStartColour.a, particleColour.a, alphaFromParticle);
lineStartColour.a = lineStartColourOriginalAlpha;
float lineStartWidth = Mathf.LerpUnclamped(lineRendererStartWidth, particleSizes[i], widthFromParticle);
int connections = 0;
int[] connectedParticles = new int[maxConnections + 1];
for (int j = i + 1; j < particleCount; j++)
{
p1p2_difference.x = particlePositions[i].x - particlePositions[j].x;
p1p2_difference.y = particlePositions[i].y - particlePositions[j].y;
p1p2_difference.z = particlePositions[i].z - particlePositions[j].z;
//float distanceSqr = Vector3.SqrMagnitude(p1p2_difference);
float distanceSqr =
p1p2_difference.x * p1p2_difference.x +
p1p2_difference.y * p1p2_difference.y +
p1p2_difference.z * p1p2_difference.z;
if (distanceSqr <= maxDistanceSqr)
{
LineRenderer lr;
if (lrIndex == lineRenderersCount)
{
lr = Instantiate(lineRendererTemplate, _transform, false);
lineRenderers.Add(lr);
lineRenderersCount++;
}
lr = lineRenderers[lrIndex]; lr.enabled = true;
lr.SetPosition(0, particlePositions[i]);
lr.SetPosition(1, particlePositions[j]);
float alphaAttenuation = alphaOverNormalizedDistance.Evaluate(distanceSqr / maxDistanceSqr);
lineStartColour.a = lineStartColourOriginalAlpha * alphaAttenuation;
lr.startColor = lineStartColour;
particleColour = particleColours[j];
Color lineEndColour = Color.LerpUnclamped(lineRendererEndColour, particleColour, colourFromParticle);
lineEndColour.a = Mathf.LerpUnclamped(lineRendererEndColour.a, particleColour.a, alphaFromParticle);
lr.endColor = lineEndColour;
lr.startWidth = lineStartWidth;
lr.endWidth = Mathf.LerpUnclamped(lineRendererEndWidth, particleSizes[j], widthFromParticle);
lrIndex++;
connections++;
// Intentionally taken AFTER connections++ (because index = 0 is the i'th / line origin particle).
connectedParticles[connections] = j;
if (connections == maxConnections || lrIndex == maxLineRenderers)
{
break;
}
}
}
if (connections >= 2)
{
connectedParticles[0] = i;
allConnectedParticles.Add(connectedParticles);
}
}
}
else
{
Vector3 position = Vector3.zero;
Quaternion rotation = Quaternion.identity;
Vector3 localScale = Vector3.one;
Transform simulationSpaceTransform = _transform;
switch (simulationSpace)
{
case ParticleSystemSimulationSpace.Local:
{
position = simulationSpaceTransform.position;
rotation = simulationSpaceTransform.rotation;
localScale = simulationSpaceTransform.localScale;
break;
}
case ParticleSystemSimulationSpace.Custom:
{
simulationSpaceTransform = customSimulationSpaceTransform;
position = simulationSpaceTransform.position;
rotation = simulationSpaceTransform.rotation;
localScale = simulationSpaceTransform.localScale;
break;
}
default:
{
throw new System.NotSupportedException(
string.Format("Unsupported scaling mode '{0}'.", simulationSpace));
}
}
// I put these here so I can take out the default exception case.
// Else I'd have a compiler error for potentially unassigned variables.
Vector3 p1_position = Vector3.zero;
Vector3 p2_position = Vector3.zero;
for (int i = 0; i < particleCount; i++)
{
if (lrIndex == maxLineRenderers)
{
break;
}
switch (simulationSpace)
{
case ParticleSystemSimulationSpace.Local:
case ParticleSystemSimulationSpace.Custom:
{
switch (scalingMode)
{
case ParticleSystemScalingMode.Hierarchy:
{
p1_position = simulationSpaceTransform.TransformPoint(particlePositions[i]);
break;
}
case ParticleSystemScalingMode.Local:
{
// Order is important.
//p1_position = Vector3.Scale(particlePositions[i], localScale);
p1_position.x = particlePositions[i].x * localScale.x;
p1_position.y = particlePositions[i].y * localScale.y;
p1_position.z = particlePositions[i].z * localScale.z;
p1_position = rotation * p1_position;
//p1_position += position;
p1_position.x += position.x;
p1_position.y += position.y;
p1_position.z += position.z;
break;
}
case ParticleSystemScalingMode.Shape:
{
// Order is important.
p1_position = rotation * particlePositions[i];
//p1_position += position;
p1_position.x += position.x;
p1_position.y += position.y;
p1_position.z += position.z;
break;
}
default:
{
throw new System.NotSupportedException(
string.Format("Unsupported scaling mode '{0}'.", scalingMode));
}
}
break;
}
}
Color particleColour = particleColours[i];
Color lineStartColour = Color.LerpUnclamped(lineRendererStartColour, particleColour, colourFromParticle);
float lineStartColourOriginalAlpha = Mathf.LerpUnclamped(lineRendererStartColour.a, particleColour.a, alphaFromParticle);
lineStartColour.a = lineStartColourOriginalAlpha;
float lineStartWidth = Mathf.LerpUnclamped(lineRendererStartWidth, particleSizes[i], widthFromParticle);
int connections = 0;
int[] connectedParticles = new int[maxConnections + 1];
for (int j = i + 1; j < particleCount; j++)
{
// Note that because particles array is not sorted by distance,
// but rather by spawn time (I think), the connections made are
// not necessarily the closest.
switch (simulationSpace)
{
case ParticleSystemSimulationSpace.Local:
case ParticleSystemSimulationSpace.Custom:
{
switch (scalingMode)
{
case ParticleSystemScalingMode.Hierarchy:
{
p2_position = simulationSpaceTransform.TransformPoint(particlePositions[j]);
break;
}
case ParticleSystemScalingMode.Local:
{
// Order is important.
//p2_position = Vector3.Scale(particlePositions[j], localScale);
p2_position.x = particlePositions[j].x * localScale.x;
p2_position.y = particlePositions[j].y * localScale.y;
p2_position.z = particlePositions[j].z * localScale.z;
p2_position = rotation * p2_position;
//p2_position += position;
p2_position.x += position.x;
p2_position.y += position.y;
p2_position.z += position.z;
break;
}
case ParticleSystemScalingMode.Shape:
{
// Order is important.
p2_position = rotation * particlePositions[j];
//p2_position += position;
p2_position.x += position.x;
p2_position.y += position.y;
p2_position.z += position.z;
break;
}
default:
{
throw new System.NotSupportedException(
string.Format("Unsupported scaling mode '{0}'.", scalingMode));
}
}
break;
}
}
p1p2_difference.x = particlePositions[i].x - particlePositions[j].x;
p1p2_difference.y = particlePositions[i].y - particlePositions[j].y;
p1p2_difference.z = particlePositions[i].z - particlePositions[j].z;
// Note that distance is always calculated in WORLD SPACE.
// Scaling the particle system will stretch the distances
// and may require adjusting the maxDistance value.
// I could also do it in local space (which may actually make more
// sense) by just getting the difference of the positions without
// all the transformations. This also provides opportunity for
// optimization as I can limit the world space transform calculations
// to only happen if a particle is within range.
// Think about: Putting in a bool to switch between the two?
//float distanceSqr = Vector3.SqrMagnitude(p1p2_difference);
float distanceSqr =
p1p2_difference.x * p1p2_difference.x +
p1p2_difference.y * p1p2_difference.y +
p1p2_difference.z * p1p2_difference.z;
// If distance to particle within range, add new vertex position.
// The larger the max distance, the quicker connections will
// reach its max, terminating the loop earlier. So even though more lines have
// to be drawn, it's still faster to have a larger maxDistance value because
// the call to Vector3.Distance() is expensive.
if (distanceSqr <= maxDistanceSqr)
{
LineRenderer lr;
if (lrIndex == lineRenderersCount)
{
lr = Instantiate(lineRendererTemplate, _transform, false);
lineRenderers.Add(lr);
lineRenderersCount++;
//lineRendererData.Add(new LineRendererData());
}
lr = lineRenderers[lrIndex];
//LineRendererData lrd = lineRendererData[lrIndex];
lr.enabled = true;
//lrd.previousStartParticleID = lrd.currentStartParticleID;
//lrd.previousEndParticleID = lrd.currentEndParticleID;
//lrd.currentStartParticleID = customParticleData[i].x;
//lrd.currentEndParticleID = customParticleData[j].x;
lr.SetPosition(0, p1_position);
lr.SetPosition(1, p2_position);
//if (lrd.currentStartParticleID != lrd.previousStartParticleID || lrd.currentEndParticleID != lrd.previousEndParticleID)
//{
// lrd.timer = 0.0f;
//}
//if (lrd.timer < 1.0f)
//{
// lrd.timer += deltaTime / fadeInTime;
//}
//if (lrd.timer > 1.0f)
//{
// lrd.timer = 1.0f;
//}
float alphaAttenuation = alphaOverNormalizedDistance.Evaluate(distanceSqr / maxDistanceSqr);
//float alphaAttenuation = lrd.timer * alphaOverNormalizedDistance.Evaluate(distanceSqr / maxDistanceSqr);
lineStartColour.a = lineStartColourOriginalAlpha * alphaAttenuation;
lr.startColor = lineStartColour;
particleColour = particleColours[j];
Color lineEndColour = Color.LerpUnclamped(lineRendererEndColour, particleColour, colourFromParticle);
lineEndColour.a = Mathf.LerpUnclamped(lineRendererEndColour.a, particleColour.a, alphaFromParticle) * alphaAttenuation;
lr.endColor = lineEndColour;
lr.startWidth = lineStartWidth;
lr.endWidth = Mathf.LerpUnclamped(lineRendererEndWidth, particleSizes[j], widthFromParticle);
//lineRendererData[lrIndex] = lrd;
lrIndex++;
connections++;
// Intentionally taken AFTER connections++ (because index = 0 is the i'th / line origin particle).
connectedParticles[connections] = j;
if (connections == maxConnections || lrIndex == maxLineRenderers)
{
break;
}
}
}
if (connections >= 2)
{
connectedParticles[0] = i;
allConnectedParticles.Add(connectedParticles);
}
}
}
}
// Disable remaining line renderers from the pool that weren't used.
for (int i = lrIndex; i < lineRenderersCount; i++)
{
if (lineRenderers[i].enabled)
{
lineRenderers[i].enabled = false;
}
}
// I check against the filter rather than the mesh because the mesh should always exist as long as the filter reference is there.
// This way I can stop drawing/updating should the filter reference be lost.
if (trianglesMeshFilter)
{
// Triangles mesh.
// For efficiency (and my own general laziness), I only bother taking the first triangle formed.
// It doesn't matter all that much since this is an abstract effect anyway.
int vertexCount = allConnectedParticles.Count * 3;
Vector3[] vertices = new Vector3[vertexCount];
int[] triangles = new int[vertexCount];
Vector2[] uv = new Vector2[vertexCount];
Color[] colours = new Color[vertexCount];
float maxDistanceSqr = (maxDistance * maxDistance) * maxDistanceTriangleBias;
for (int i = 0; i < allConnectedParticles.Count; i++)
{
int[] connectedParticles = allConnectedParticles[i];
float distanceSqr = 0.0f;
if (trianglesDistanceCheck)
{
Vector3 particlePositionA = particlePositions[connectedParticles[1]];
Vector3 particlePositionB = particlePositions[connectedParticles[2]];
//distance = Vector3.Distance(particlePositionA, particlePositionB);
Vector3 difference;
difference.x = particlePositionA.x - particlePositionB.x;
difference.y = particlePositionA.y - particlePositionB.y;
difference.z = particlePositionA.z - particlePositionB.z;
distanceSqr =
difference.x * difference.x +
difference.y * difference.y +
difference.z * difference.z;
}
if (distanceSqr < maxDistanceSqr)
{
int i3 = i * 3;
vertices[i3 + 0] = particlePositions[connectedParticles[0]];
vertices[i3 + 1] = particlePositions[connectedParticles[1]];
vertices[i3 + 2] = particlePositions[connectedParticles[2]];
uv[i3 + 0] = new Vector2(0.0f, 0.0f);
uv[i3 + 1] = new Vector2(0.0f, 1.0f);
uv[i3 + 2] = new Vector2(1.0f, 1.0f);
triangles[i3 + 0] = i3 + 0;
triangles[i3 + 1] = i3 + 1;
triangles[i3 + 2] = i3 + 2;
colours[i3 + 0] = particleColours[connectedParticles[0]];
colours[i3 + 1] = particleColours[connectedParticles[1]];
colours[i3 + 2] = particleColours[connectedParticles[2]];
colours[i3 + 0] = Color.LerpUnclamped(Color.white, particleColours[connectedParticles[0]], triangleColourFromParticle);
colours[i3 + 1] = Color.LerpUnclamped(Color.white, particleColours[connectedParticles[1]], triangleColourFromParticle);
colours[i3 + 2] = Color.LerpUnclamped(Color.white, particleColours[connectedParticles[2]], triangleColourFromParticle);
colours[i3 + 0].a = Mathf.LerpUnclamped(1.0f, particleColours[connectedParticles[0]].a, triangleAlphaFromParticle);
colours[i3 + 1].a = Mathf.LerpUnclamped(1.0f, particleColours[connectedParticles[1]].a, triangleAlphaFromParticle);
colours[i3 + 2].a = Mathf.LerpUnclamped(1.0f, particleColours[connectedParticles[2]].a, triangleAlphaFromParticle);
}
}
trianglesMesh.Clear();
trianglesMesh.vertices = vertices;
trianglesMesh.uv = uv;
trianglesMesh.triangles = triangles;
trianglesMesh.colors = colours;
}
}
}
}
// =================================
// End functions.
// =================================
}
// =================================
// End namespace.
// =================================
}
}
}
// =================================
// --END-- //
// =================================