// ================================= // Namespaces. // ================================= using UnityEngine; using System.Collections.Generic; // ================================= // Define namespace. // ================================= namespace MirzaBeig { namespace Scripting { namespace Effects { // ================================= // Classes. // ================================= [RequireComponent(typeof(ParticleSystem))] public class ParticleFlocking : MonoBehaviour { // ================================= // Nested classes and structures. // ================================= // ... public struct Voxel { public Bounds bounds; public int[] particles; public int particleCount; } // ================================= // Variables. // ================================= // ... [Header("N^2 Mode Settings")] public float maxDistance = 0.5f; [Header("Forces")] public float cohesion = 0.5f; public float separation = 0.25f; [Header("Voxel Mode Settings")] public bool useVoxels = true; public bool voxelLocalCenterFromBounds = true; public float voxelVolume = 8.0f; public int voxelsPerAxis = 5; int previousVoxelsPerAxisValue; Voxel[] voxels; new ParticleSystem particleSystem; ParticleSystem.Particle[] particles; Vector3[] particlePositions; ParticleSystem.MainModule particleSystemMainModule; [Header("General Performance Settings")] [Range(0.0f, 1.0f)] public float delay = 0.0f; float timer; public bool alwaysUpdate = false; bool visible; // ================================= // Functions. // ================================= // ... void Start() { particleSystem = GetComponent(); particleSystemMainModule = particleSystem.main; } // ... void OnBecameVisible() { visible = true; } void OnBecameInvisible() { visible = false; } // ... void buildVoxelGrid() { int voxelCount = voxelsPerAxis * voxelsPerAxis * voxelsPerAxis; voxels = new Voxel[voxelCount]; float voxelSize = voxelVolume / voxelsPerAxis; float voxelSizeHalf = voxelSize / 2.0f; float voxelVolumeHalf = voxelVolume / 2.0f; Vector3 positionBase = transform.position; int i = 0; for (int x = 0; x < voxelsPerAxis; x++) { float posX = (-voxelVolumeHalf + voxelSizeHalf) + (x * voxelSize); for (int y = 0; y < voxelsPerAxis; y++) { float posY = (-voxelVolumeHalf + voxelSizeHalf) + (y * voxelSize); for (int z = 0; z < voxelsPerAxis; z++) { float posZ = (-voxelVolumeHalf + voxelSizeHalf) + (z * voxelSize); voxels[i].particleCount = 0; voxels[i].bounds = new Bounds(positionBase + new Vector3(posX, posY, posZ), Vector3.one * voxelSize); i++; } } } } // ... void LateUpdate() { if (alwaysUpdate || visible) { if (useVoxels) { int voxelCount = voxelsPerAxis * voxelsPerAxis * voxelsPerAxis; if (voxels == null || voxels.Length < voxelCount) { buildVoxelGrid(); } } int maxParticles = particleSystemMainModule.maxParticles; if (particles == null || particles.Length < maxParticles) { particles = new ParticleSystem.Particle[maxParticles]; particlePositions = new Vector3[maxParticles]; if (useVoxels) { for (int i = 0; i < voxels.Length; i++) { voxels[i].particles = new int[maxParticles]; } } } timer += Time.deltaTime; if (timer >= delay) { float deltaTime = timer; timer = 0.0f; particleSystem.GetParticles(particles); int particleCount = particleSystem.particleCount; float cohesionDeltaTime = cohesion * deltaTime; float separationDeltaTime = separation * deltaTime; for (int i = 0; i < particleCount; i++) { particlePositions[i] = particles[i].position; } if (useVoxels) { int voxelCount = voxels.Length; float voxelSize = voxelVolume / voxelsPerAxis; for (int i = 0; i < particleCount; i++) { for (int j = 0; j < voxelCount; j++) { if (voxels[j].bounds.Contains(particlePositions[i])) { voxels[j].particles[voxels[j].particleCount] = i; voxels[j].particleCount++; break; } } } for (int i = 0; i < voxelCount; i++) { if (voxels[i].particleCount > 1) { for (int j = 0; j < voxels[i].particleCount; j++) { Vector3 directionToLocalCenter; Vector3 localCenter = particlePositions[voxels[i].particles[j]]; if (voxelLocalCenterFromBounds) { directionToLocalCenter = voxels[i].bounds.center - particlePositions[voxels[i].particles[j]]; } else { for (int k = 0; k < voxels[i].particleCount; k++) { if (k == j) { continue; } localCenter += particlePositions[voxels[i].particles[k]]; } localCenter /= voxels[i].particleCount; directionToLocalCenter = localCenter - particlePositions[voxels[i].particles[j]]; } float distanceToLocalCenterSqr = directionToLocalCenter.sqrMagnitude; directionToLocalCenter.Normalize(); Vector3 force = Vector3.zero; force += directionToLocalCenter * cohesionDeltaTime; force -= directionToLocalCenter * ((1.0f - (distanceToLocalCenterSqr / voxelSize)) * separationDeltaTime); Vector3 particleVelocity = particles[voxels[i].particles[j]].velocity; particleVelocity.x += force.x; particleVelocity.y += force.y; particleVelocity.z += force.z; particles[voxels[i].particles[j]].velocity = particleVelocity; } voxels[i].particleCount = 0; } } } else { float maxDistanceSqr = maxDistance * maxDistance; Vector3 p1p2_difference; for (int i = 0; i < particleCount; i++) { int localCount = 1; Vector3 localCenter = particlePositions[i]; for (int j = 0; j < particleCount; j++) { if (j == i) { continue; } 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); // TODO: Expand vector arithmetic for performance. if (distanceSqr <= maxDistanceSqr) { localCount++; localCenter += particlePositions[j]; } } if (localCount != 1) { localCenter /= localCount; Vector3 directionToLocalCenter = localCenter - particlePositions[i]; float distanceToLocalCenterSqr = directionToLocalCenter.sqrMagnitude; directionToLocalCenter.Normalize(); Vector3 force = Vector3.zero; force += directionToLocalCenter * cohesionDeltaTime; force -= directionToLocalCenter * ((1.0f - (distanceToLocalCenterSqr / maxDistanceSqr)) * separationDeltaTime); Vector3 particleVelocity = particles[i].velocity; particleVelocity.x += force.x; particleVelocity.y += force.y; particleVelocity.z += force.z; particles[i].velocity = particleVelocity; } //Vector3 velocity = particles[i].velocity; //if (velocity != Vector3.zero) //{ // particles[i].rotation3D = Quaternion.LookRotation(velocity, Vector3.up).eulerAngles; //} } } particleSystem.SetParticles(particles, particleCount); } } } // ... void OnDrawGizmosSelected() { //buildVoxelGrid(); //for (int i = 0; i < voxels.Length; i++) //{ // Gizmos.DrawWireCube(voxels[i].bounds.center, voxels[i].bounds.size); //} float size = voxelVolume / voxelsPerAxis; float sizeHalf = size / 2.0f; float totalSizeHalf = voxelVolume / 2.0f; Vector3 positionBase = transform.position; Gizmos.color = Color.red; Gizmos.DrawWireCube(positionBase, Vector3.one * voxelVolume); Gizmos.color = Color.white; for (int x = 0; x < voxelsPerAxis; x++) { float posX = (-totalSizeHalf + sizeHalf) + (x * size); for (int y = 0; y < voxelsPerAxis; y++) { float posY = (-totalSizeHalf + sizeHalf) + (y * size); for (int z = 0; z < voxelsPerAxis; z++) { float posZ = (-totalSizeHalf + sizeHalf) + (z * size); Gizmos.DrawWireCube(positionBase + new Vector3(posX, posY, posZ), Vector3.one * size); } } } } // ================================= // End functions. // ================================= } // ================================= // End namespace. // ================================= } } } // ================================= // --END-- // // =================================