using System; using System.Collections; using System.Collections.Generic; using ComputeShaderUtility; using UnityEngine; using UnityEngine.Assertions; using UnityEngine.Rendering; public struct Droplet { public Vector3 position; public Vector3 velocity; public uint enabled; public uint airborne; } public class BloodComputeShader : MonoBehaviour { public static BloodComputeShader Instance { get; private set; } public int numParticles = 1000; public ComputeShader bloodCompute; public Mesh mesh; public Material instancedMaterial; public float size; public float scoreMult = 1.0f; public int activeParticles = 0; public long score = 0; public int mop1Clean = 0; public int mop2Clean = 0; public float squeakVolume = 0.0f; public AudioSource squeakPlayer; public float splatterVolume = 0.0f; public AudioSource splatterPlayer; public float RumbleAmount = 100; ComputeBuffer particleBuffer; ComputeBuffer positionBuffer; ComputeBuffer numParticlesConsumedBuffer; ComputeBuffer argsBuffer; ComputeBuffer freeParticleBuffer; const int InitDustKernel = 0; const int UpdateDustKernel = 1; const int CollectAllKernel = 2; AsyncGPUReadbackRequest readbackRequest; AsyncGPUReadbackRequest freeBloodReadRequest; public Mop mop1; public Mop mop2; public float CleanRadius = 2f; [SerializeField] uint bufferLookPointer = 0; private bool gameHasStarted = false; private void Awake() { if (Instance != null) { Destroy(gameObject); return; } Instance = this; GameManager.OnPlayersReady += OnPlayersReady; } void Start() { ComputeHelper.CreateStructuredBuffer(ref particleBuffer, numParticles); ComputeHelper.CreateStructuredBuffer(ref positionBuffer, numParticles); Droplet[] particles = new Droplet[numParticles]; for (int i = 0; i < numParticles; i++) { particles[i] = new Droplet() { position = Vector3.zero, velocity = Vector3.zero, enabled = 0, airborne = 0 }; } particleBuffer.SetData(particles); bloodCompute.SetBuffer(UpdateDustKernel, "particles", particleBuffer); bloodCompute.SetBuffer(UpdateDustKernel, "positions", positionBuffer); // Init dust particle positions bloodCompute.SetBuffer(InitDustKernel, "particles", particleBuffer); bloodCompute.SetBuffer(InitDustKernel, "positions", positionBuffer); bloodCompute.SetBuffer(CollectAllKernel, "particles", particleBuffer); bloodCompute.SetBuffer(CollectAllKernel, "positions", positionBuffer); bloodCompute.SetInt("numParticles", numParticles); // Create args buffer uint[] args = new uint[5]; args[0] = (uint)mesh.GetIndexCount(0); args[1] = (uint)numParticles; args[2] = (uint)mesh.GetIndexStart(0); args[3] = (uint)mesh.GetBaseVertex(0); args[4] = 0; // offset argsBuffer = new ComputeBuffer(1, 5 * sizeof(uint), ComputeBufferType.IndirectArguments); argsBuffer.SetData(args); ComputeHelper.CreateStructuredBuffer(ref numParticlesConsumedBuffer, 3); numParticlesConsumedBuffer.SetData(new uint[] { 0, 0, 0 }); bloodCompute.SetBuffer(UpdateDustKernel, "numParticlesConsumed", numParticlesConsumedBuffer); // Initialize with empty data ComputeHelper.CreateStructuredBuffer(ref freeParticleBuffer, numParticles); RequestAsyncReadback(); instancedMaterial.SetBuffer("positionBuffer", positionBuffer); } void RequestAsyncReadback() { readbackRequest = AsyncGPUReadback.Request(numParticlesConsumedBuffer); } void RequestAllBloodStates() { freeBloodReadRequest = AsyncGPUReadback.Request(freeParticleBuffer); } void OnPlayersReady(GameObject[] players) { mop1 = players[0].GetComponent(); mop2 = players[1].GetComponent(); gameHasStarted = true; } // Update is called once per frame void Update() { if (!gameHasStarted) return; Assert.IsNotNull(mop1); Assert.IsNotNull(mop2); bloodCompute.SetFloat("deltaTime", Time.deltaTime); bloodCompute.SetInt("numParticles", numParticles); bloodCompute.SetFloat("size", size); bloodCompute.SetFloat("gravity", 9.8f); bloodCompute.SetVector("mop1Pos", mop1.transform.position); bloodCompute.SetVector("mop2Pos", mop2.transform.position); bloodCompute.SetFloat("CleanRadius", CleanRadius); // if (Input.GetKeyUp(KeyCode.Alpha9)) { // cleanAllBlood(); // } if (readbackRequest.hasError) { RequestAllBloodStates(); Debug.Log("Async readback error"); return; } bool putBuffer = false; uint[] bufferData = {0,0,0}; if (readbackRequest.done) { bufferData = readbackRequest.GetData().ToArray(); // Blood cleaned if (bufferData[0] > 0 || bufferData[1] > 0) { // Debug.Log("Cleaned " + bufferData[0]); uint totalBloodCleaned = bufferData[0] + bufferData[1]; activeParticles -= (int)totalBloodCleaned; score += totalBloodCleaned; // this doesnt exist but ok // float mappedRumble = Convert.ToSingle(bufferData[0]).Remap(0, RumbleAmount, 0, 0.1f); //RumbleManager.StartRumble(-1, 0, mappedRumble, 0.1f); squeakVolume += 0.3f; mop1Clean += (int)bufferData[0]; mop2Clean += (int)bufferData[1]; // Reset counter putBuffer = true; bufferData[0] = 0; bufferData[1] = 0; } // Blood hitting the floor if (bufferData[1] > 0) { splatterVolume += bufferData[1] / 10.0f; // Debug.Log("splat x" + bufferData[1]); putBuffer = true; bufferData[2] = 0; } RequestAsyncReadback(); } if (putBuffer) { numParticlesConsumedBuffer.SetData(bufferData); bloodCompute.SetBuffer(UpdateDustKernel, "numParticlesConsumed", numParticlesConsumedBuffer); } ComputeHelper.Dispatch(bloodCompute, numParticles, 1, 1, UpdateDustKernel); Graphics.DrawMeshInstancedIndirect(mesh, 0, instancedMaterial, new Bounds(Vector3.zero, Vector3.one * 1000), argsBuffer); splatterVolume *= 0.9f; splatterPlayer.volume = splatterVolume; squeakPlayer.volume = squeakVolume; squeakVolume *= 0.8f; if (splatterVolume < 0.001) splatterVolume = 0; if (squeakVolume < 0.001) squeakVolume = 0; mop1Clean = (int)(mop1Clean * 0.8f); mop2Clean = (int)(mop2Clean * 0.8f); } void FixedUpdate() { } public void cleanAllBlood() { ComputeHelper.Dispatch(bloodCompute, numParticles, 1, 1, CollectAllKernel); } public void createBlood(Vector3 wher, int muchies, float powah) { StartCoroutine(penisBlood(wher, muchies, powah)); } IEnumerator penisBlood(Vector3 loc, int amount, float power) { RequestAllBloodStates(); // Wait until we get the state of all the particles from the gpu while (!freeBloodReadRequest.done) { yield return new WaitForEndOfFrame(); } // Find N particles which are disabled uint i = bufferLookPointer; int found = 0; Droplet[] particles = new Droplet[numParticles]; var as_Particles = freeBloodReadRequest.GetData().ToArray(); int length = as_Particles.Length; // particleBuffer.GetData(particles); uint[] particleIndeces = new uint[amount]; // oof while (i < numParticles) { if (as_Particles[i % (length - 1)].enabled == 0) { // Found unused particle particleIndeces[found] = i; found++; if (found >= amount) break; } i++; } bufferLookPointer = (uint)((bufferLookPointer + amount) % numParticles); // Debug.Log(string.Join(", ", particleIndeces)); // send data to gpu freeParticleBuffer.SetData(particleIndeces); bloodCompute.SetBuffer(InitDustKernel, "freeParticles", freeParticleBuffer); // Test for race conditions // yield return new WaitForSeconds(1.0f); bloodCompute.SetFloat("particleVel", power / 4.0f); bloodCompute.SetVector("particleInitPos", loc); bloodCompute.SetInt("particlesToInitialize", found); Vector3 pow = UnityEngine.Random.insideUnitSphere * power; pow.z = Mathf.Abs(pow.z); bloodCompute.SetVector("initialVelocity", pow); ComputeHelper.Dispatch(bloodCompute, amount, 1, 1, InitDustKernel); activeParticles += found; yield return null; } void OnDestroy() { ComputeHelper.Release(particleBuffer, positionBuffer, argsBuffer, numParticlesConsumedBuffer, freeParticleBuffer); } public void createBloodTest(int amount) { createBlood(Vector3.zero, amount, 10.0f); } }