using System; using NWH.DWP2.WaterData; using NWH.Common.Utility; using UnityEngine; using UnityEngine.Serialization; using Random = UnityEngine.Random; namespace NWH.DWP2.WaterObjects { /// /// Class for generating water particles based on simulation data. /// [RequireComponent(typeof(ParticleSystem))] public class WaterParticleSystem : MonoBehaviour { public WaterObject ReferenceWaterObject; /// /// Should the particle system emit? /// [UnityEngine.Tooltip(" Should the particle system emit?")] public bool emit = true; /// /// Render queue of the particle material. /// [UnityEngine.Tooltip(" Render queue of the particle material.")] public int renderQueue = 2700; /// /// Elevation above water at which the particles will spawn. Used to avoid clipping. /// [Tooltip("Elevation above water at which the particles will spawn. Used to avoid clipping.")] [Range(0f, 0.1f)] public float surfaceElevation = 0.016f; /// /// Initial size of the particle. /// [Tooltip("Initial size of the particle.")] [Range(0f, 64f)] public float startSize = 4f; /// /// Velocity object has to have to emit particles. /// [FormerlySerializedAs("sleepTresholdVelocity")] [Tooltip("Velocity object has to have to emit particles.")] [Range(0.1f, 5f)] public float sleepThresholdVelocity = 1.5f; /// /// Determines how much velocity of the object will affect initial particle speed. /// [Tooltip("Determines how much velocity of the object will affect initial particle speed.")] [Range(0f, 5f)] public float initialVelocityModifier = 0.01f; /// /// Limit initial alpha to this value. /// [Tooltip("Limit initial alpha to this value.")] [Range(0f, 1f)] public float maxInitialAlpha = 0.15f; /// /// Multiplies initial alpha by this value. Alpha cannot be higher than maxInitialAlpha. /// [Tooltip("Multiplies initial alpha by this value. Alpha cannot be higher than maxInitialAlpha.")] [Range(0f, 10f)] public float initialAlphaModifier = 0.4f; /// /// How many particles should be emitted each 'emitTimeInterval' seconds. /// [Tooltip("How many particles should be emitted each 'emitTimeInterval' seconds.")] [Range(0f, 20f)] public int emitPerCycle = 6; /// /// Determines how often the particles will be emitted. /// [Tooltip("Determines how often the particles will be emitted.")] [Range(0f, 0.1f)] public float emitTimeInterval = 0.04f; /// /// Script will try to predict where the object will be in the next n frames. /// [UnityEngine.Tooltip(" Script will try to predict where the object will be in the next n frames.")] public int positionExtrapolationFrames = 4; private float _timeElapsed; private WaterObject _targetWaterObject; private ParticleSystem _particleSystem; private int[] _waterlineIndices; private ParticleSystem.NoiseModule _noiseModule; private int _prevTriCount; private int _waterlineCount; private void Start() { if (ReferenceWaterObject == null) { ReferenceWaterObject = GetComponentInParent(); } _targetWaterObject = transform.GetComponentInParentsOrChildren(true); if (_targetWaterObject == null) { Debug.LogError( $"{name}: WaterParticleSystem requires WaterObject attached to the same object or one of parent objects to function."); return; } _particleSystem = GetComponent(); if (_particleSystem == null) { Debug.LogError("No ParticleSystem found."); } _particleSystem.GetComponent().material.renderQueue = renderQueue; _noiseModule = _particleSystem.noise; _prevTriCount = -999; } private void LateUpdate() { if (!emit) { return; } int triCount = _targetWaterObject.triangleCount; if (triCount > 0 && _prevTriCount != triCount) { _waterlineIndices = new int[triCount]; } if (_targetWaterObject.targetRigidbody.velocity.magnitude > sleepThresholdVelocity) { EmitNew(); } _timeElapsed += Time.deltaTime; _prevTriCount = triCount; } private void OnDestroy() { if (!Application.isPlaying) { DestroyImmediate(_particleSystem); } } private void EmitNew() { if (_targetWaterObject == null) { return; } int triCount = _targetWaterObject.triangleCount; if (emit && _timeElapsed >= emitTimeInterval && triCount > 0f) { _timeElapsed = 0; int emitted = 0; // Emit allowed number of particles float elevation = 0; if (ReferenceWaterObject != null) { elevation = ReferenceWaterObject.GetWaterHeightSingle(Vector3.zero); } else { Debug.LogWarning("Will not emit. WaterDataProvider is not present in the scene."); } _waterlineCount = 0; for (int i = 0; i < triCount; i++) { if (_targetWaterObject.ResultStates[i] != 1) continue; _waterlineIndices[_waterlineCount] = i; _waterlineCount++; } if (_waterlineCount == 0) { return; } float noise = startSize > 1f ? Mathf.Sqrt(startSize) * 0.1f : startSize * 0.1f; _noiseModule.strengthX = noise; _noiseModule.strengthY = 0f; _noiseModule.strengthZ = noise; while (emitted < emitPerCycle) { int i = Random.Range(0, _waterlineCount); int waterLineTriIndex = _waterlineIndices[i]; EmitParticle( _targetWaterObject.ResultP0s[waterLineTriIndex * 6 + 2], _targetWaterObject.ResultP0s[waterLineTriIndex * 6 + 1], elevation, _targetWaterObject.ResultVelocities[waterLineTriIndex], _targetWaterObject.ResultNormals[waterLineTriIndex], _targetWaterObject.ResultForces[waterLineTriIndex], _targetWaterObject.ResultAreas[waterLineTriIndex]); emitted++; } } } private void OnDrawGizmosSelected() { if (_waterlineIndices == null) { return; } Gizmos.color = Color.magenta; for (int i = 0; i < _waterlineIndices.Length; i++) { if (_targetWaterObject.ResultStates[_waterlineIndices[i]] != 1) continue; Vector3 a = _targetWaterObject.ResultP0s[_waterlineIndices[i] * 6 + 2]; Vector3 b = _targetWaterObject.ResultP0s[_waterlineIndices[i] * 6 + 1]; Gizmos.DrawLine(a, b); } } /// /// Emit a single particle /// /// First point of water line /// Second point of water line /// Water elevation /// Triangle velocity /// Triangle normal /// Triangle force /// Triangle area private void EmitParticle(Vector3 p0, Vector3 p1, float elevation, Vector3 velocity, Vector3 normal, Vector3 force, float area) { if (area < 0.0001f) { return; } // Start velocity Vector3 startVelocity = normal * velocity.magnitude; startVelocity.y = 0f; startVelocity *= initialVelocityModifier; // Start position Vector3 emissionPoint = (p0 + p1) / 2f; emissionPoint += Time.deltaTime * positionExtrapolationFrames * velocity; emissionPoint.y = elevation + surfaceElevation; float normalizedForce = force.magnitude / area; float startAlpha = Mathf.Clamp(normalizedForce * 0.00005f * initialAlphaModifier, 0f, maxInitialAlpha); Color startColor = new Color(1f, 1f, 1f, startAlpha); float size = startSize; if (startAlpha < 0.001f) { return; } ParticleSystem.EmitParams emitParams = new ParticleSystem.EmitParams { startColor = startColor, position = emissionPoint, velocity = startVelocity, startSize = size, }; _particleSystem.Emit(emitParams, 1); _particleSystem.Play(); } } }