using System; using System.Collections.Generic; using UltimateWater.Internal; using UnityEngine; using UnityEngine.Serialization; namespace UltimateWater { [AddComponentMenu("Water/Waves Particle System", 1)] [RequireComponent(typeof(DynamicWater))] public sealed class WaveParticleSystem : MonoBehaviour, IOverlaysRenderer { [FormerlySerializedAs("waterWavesParticlesShader")] [SerializeField] [HideInInspector] private Shader _WaterWavesParticlesShader; [FormerlySerializedAs("maxParticles")] [SerializeField] private int _MaxParticles = 50000; [FormerlySerializedAs("maxParticlesPerTile")] [SerializeField] private int _MaxParticlesPerTile = 2000; [FormerlySerializedAs("prewarmTime")] [SerializeField] private float _PrewarmTime = 40f; [FormerlySerializedAs("timePerFrame")] [SerializeField] [Tooltip("Allowed execution time per frame.")] private float _TimePerFrame = 0.8f; private WaveParticlesQuadtree _Particles; private Water _Water; private Material _WaterWavesParticlesMaterial; private float _SimulationTime; private float _TimePerFrameExp; private bool _Prewarmed; private readonly List _Plugins; public int ParticleCount { get { return _Particles.Count; } } public float SimulationTime { get { return _SimulationTime; } } public WaveParticleSystem() { _Plugins = new List(); } public bool Spawn(WaveParticle particle, int clones, float waveShapeIrregularity, float centerElevation = 2f, float edgesElevation = 0.35f) { if (particle == null || _Particles.FreeSpace < clones * 2 + 1) { return false; } particle.Group = new WaveParticlesGroup(_SimulationTime); particle.BaseAmplitude *= _Water.UniformWaterScale; particle.BaseFrequency /= _Water.UniformWaterScale; WaveParticle waveParticle = null; float min = 1f / waveShapeIrregularity; for (int i = -clones; i <= clones; i++) { WaveParticle waveParticle2 = particle.Clone(particle.Position + new Vector2(particle.Direction.y, 0f - particle.Direction.x) * ((float)i * 1.48f / particle.BaseFrequency)); if (waveParticle2 == null) { continue; } waveParticle2.AmplitudeModifiers2 = UnityEngine.Random.Range(min, 1f) * (edgesElevation + (0.5f + Mathf.Cos((float)Math.PI * (float)i / (float)clones) * 0.5f) * (centerElevation - edgesElevation)); waveParticle2.LeftNeighbour = waveParticle; if (waveParticle != null) { waveParticle.RightNeighbour = waveParticle2; if (i == clones) { waveParticle2.DisallowSubdivision = true; } } else { waveParticle2.Group.LeftParticle = waveParticle2; waveParticle2.DisallowSubdivision = true; } if (!_Particles.AddElement(waveParticle2)) { return waveParticle != null; } waveParticle = waveParticle2; } return true; } public void RenderOverlays(DynamicWaterCameraData overlays) { } public void RenderFoam(DynamicWaterCameraData overlays) { if (base.enabled) { RenderParticles(overlays); } } public void RegisterPlugin(IWavesParticleSystemPlugin plugin) { if (!_Plugins.Contains(plugin)) { _Plugins.Add(plugin); } } public void UnregisterPlugin(IWavesParticleSystemPlugin plugin) { _Plugins.Remove(plugin); } public bool AddParticle(WaveParticle particle) { if (particle != null) { if (particle.Group == null) { throw new ArgumentException("Particle has no group"); } return _Particles.AddElement(particle); } return false; } private void LateUpdate() { if (!_Prewarmed) { Prewarm(); } UpdateSimulation(Time.deltaTime); } private void OnValidate() { _TimePerFrameExp = Mathf.Exp(_TimePerFrame * 0.5f); if (_WaterWavesParticlesShader == null) { _WaterWavesParticlesShader = Shader.Find("UltimateWater/Particles/Particles"); } if (_Particles != null) { _Particles.DebugMode = _Water.ShaderSet.LocalEffectsDebug; } } private void Awake() { _Water = GetComponent(); OnValidate(); } private void OnEnable() { CheckResources(); } private void OnDisable() { FreeResources(); } private void Prewarm() { _Prewarmed = true; while (_SimulationTime < _PrewarmTime) { UpdateSimulationWithoutFrameBudget(0.1f); } } private void UpdateSimulation(float deltaTime) { _SimulationTime += deltaTime; UpdatePlugins(deltaTime); _Particles.UpdateSimulation(_SimulationTime, _TimePerFrameExp); } private void UpdateSimulationWithoutFrameBudget(float deltaTime) { _SimulationTime += deltaTime; UpdatePlugins(deltaTime); _Particles.UpdateSimulation(_SimulationTime); } private void UpdatePlugins(float deltaTime) { int count = _Plugins.Count; for (int i = 0; i < count; i++) { _Plugins[i].UpdateParticles(_SimulationTime, deltaTime); } } private void RenderParticles(DynamicWaterCameraData overlays) { Spray component = GetComponent(); if (component != null && component.ParticlesBuffer != null) { Graphics.SetRandomWriteTarget(3, component.ParticlesBuffer); } if (!_Water.ShaderSet.LocalEffectsDebug) { Graphics.SetRenderTarget(new RenderBuffer[2] { overlays.DynamicDisplacementMap.colorBuffer, overlays.NormalMap.colorBuffer }, overlays.DynamicDisplacementMap.depthBuffer); } else { Graphics.SetRenderTarget(new RenderBuffer[3] { overlays.DynamicDisplacementMap.colorBuffer, overlays.NormalMap.colorBuffer, overlays.GetDebugMap(true).colorBuffer }, overlays.DynamicDisplacementMap.depthBuffer); } Shader.SetGlobalMatrix("_ParticlesVP", GL.GetGPUProjectionMatrix(overlays.Camera.PlaneProjectorCamera.projectionMatrix, true) * overlays.Camera.PlaneProjectorCamera.worldToCameraMatrix); Vector4 localMapsShaderCoords = overlays.Camera.LocalMapsShaderCoords; float uniformWaterScale = GetComponent().UniformWaterScale; _WaterWavesParticlesMaterial.SetFloat("_WaterScale", uniformWaterScale); _WaterWavesParticlesMaterial.SetVector("_LocalMapsCoords", localMapsShaderCoords); _WaterWavesParticlesMaterial.SetPass(_Water.ShaderSet.LocalEffectsDebug ? 1 : 0); _Particles.Render(overlays.Camera.LocalMapsRect); Graphics.ClearRandomWriteTargets(); } private void CheckResources() { if (_WaterWavesParticlesMaterial == null) { _WaterWavesParticlesMaterial = new Material(_WaterWavesParticlesShader) { hideFlags = HideFlags.DontSave }; } if (_Particles == null) { _Particles = new WaveParticlesQuadtree(new Rect(-1000f, -1000f, 2000f, 2000f), _MaxParticlesPerTile, _MaxParticles) { DebugMode = _Water.ShaderSet.LocalEffectsDebug }; } } private void FreeResources() { if (_WaterWavesParticlesMaterial != null) { _WaterWavesParticlesMaterial.Destroy(); _WaterWavesParticlesMaterial = null; } } } }