#if (OBI_BURST && OBI_MATHEMATICS && OBI_COLLECTIONS) using Unity.Jobs; using Unity.Collections; using Unity.Mathematics; using Unity.Burst; using Unity.Collections.LowLevel.Unsafe; using System.Threading; namespace Obi { [BurstCompile] unsafe struct EmitParticlesJob : IJobParallelFor { [NativeDisableParallelForRestriction] public NativeArray outputPositions; [NativeDisableParallelForRestriction] public NativeArray outputVelocities; [NativeDisableParallelForRestriction] public NativeArray outputColors; [NativeDisableParallelForRestriction] public NativeArray outputAttributes; [NativeDisableParallelForRestriction] public NativeArray dispatchBuffer; public uint emitterShape; public float4 emitterPosition; public quaternion emitterRotation; public float4 emitterSize; public float lifetime; public float lifetimeRandom; public float particleSize; public float sizeRandom; public float buoyancy; public float drag; public float airdrag; public float airAging; public float isosurface; public float4 foamColor; public float randomSeed; public float deltaTime; public void Execute(int i) { int* dispatch = (int*)dispatchBuffer.GetUnsafePtr(); // atomically increment alive particle counter: int count = Interlocked.Add(ref dispatch[3], 1) - 1; if (count < outputPositions.Length) { // initialize foam particle in a random position inside the cylinder spawned by fluid particle: float3 radialVelocity; float4 pos; if (emitterShape == 0) BurstMath.RandomInCylinder(randomSeed + i, -new float4(0, 1, 0, 0) * emitterSize.y * 0.5f, new float4(0, 1, 0, 0), emitterSize.y, math.max(emitterSize.x, emitterSize.z) * 0.5f, out pos, out radialVelocity); else BurstMath.RandomInBox(randomSeed + i, float4.zero, emitterSize, out pos, out radialVelocity); float2 random = BurstMath.Hash21(randomSeed - i); // calculate initial life/size/color: float initialLife = math.max (0, lifetime - lifetime * random.x * lifetimeRandom); float initialSize = particleSize - particleSize * random.y * sizeRandom; outputPositions[count] = new float4(emitterPosition.xyz + math.rotate(emitterRotation, pos.xyz), 0); outputVelocities[count] = new float4(0,0,0, buoyancy); outputColors[count] = foamColor; outputAttributes[count] = new float4(1, 1 / initialLife, initialSize, BurstMath.PackFloatRGBA(new float4(airAging / 50.0f, airdrag, drag, isosurface))); } } } [BurstCompile] unsafe struct GenerateParticlesJob : IJobParallelFor { [ReadOnly] [DeallocateOnJobCompletion] public NativeArray activeParticles; [ReadOnly] public NativeArray positions; [ReadOnly] public NativeArray velocities; [ReadOnly] public NativeArray principalRadii; [ReadOnly] public NativeArray fluidData; [NativeDisableParallelForRestriction] public NativeArray angularVelocities; [NativeDisableParallelForRestriction] public NativeArray outputPositions; [NativeDisableParallelForRestriction] public NativeArray outputVelocities; [NativeDisableParallelForRestriction] public NativeArray outputColors; [NativeDisableParallelForRestriction] public NativeArray outputAttributes; [NativeDisableParallelForRestriction] public NativeArray dispatchBuffer; public float2 vorticityRange; public float2 velocityRange; public float potentialIncrease; public float potentialDiffusion; public float foamGenerationRate; public float lifetime; public float lifetimeRandom; public float particleSize; public float sizeRandom; public float buoyancy; public float drag; public float airdrag; public float airAging; public float isosurface; public float4 foamColor; public float randomSeed; public float deltaTime; public void Execute(int i) { int* dispatch = (int*)dispatchBuffer.GetUnsafePtr(); int p = activeParticles[i]; float4 angVel = angularVelocities[p]; float2 potential = BurstMath.UnpackFloatRG(angVel.w); // calculate foam potential increase: float vorticityPotential = BurstMath.Remap01(fluidData[p].z, vorticityRange.x, vorticityRange.y); float velocityPotential = BurstMath.Remap01(math.length(velocities[p].xyz), velocityRange.x, velocityRange.y); float potentialDelta = velocityPotential * vorticityPotential * deltaTime * potentialIncrease; // update foam potential: potential.y = math.saturate(potential.y * potentialDiffusion + potentialDelta); // calculate amount of emitted particles potential.x += foamGenerationRate * potential.y * deltaTime; int emitCount = (int)potential.x; potential.x -= emitCount; for (int j = 0; j < emitCount; ++j) { // atomically increment alive particle counter: int count = Interlocked.Add(ref dispatch[3], 1) - 1; if (count < outputPositions.Length) { // initialize foam particle in a random position inside the cylinder spawned by fluid particle: float3 radialVelocity; float4 pos; BurstMath.RandomInCylinder(randomSeed + p + j, positions[p], math.normalizesafe(velocities[p]), math.length(velocities[p]) * deltaTime, principalRadii[p].x, out pos, out radialVelocity); float2 random = BurstMath.Hash21(randomSeed - p - j); // calculate initial life/size/color: float initialLife = velocityPotential * (lifetime - lifetime * random.x * lifetimeRandom); float initialSize = particleSize - particleSize * random.y * sizeRandom; outputPositions[count] = pos; outputVelocities[count] = velocities[p] + new float4(radialVelocity, buoyancy); outputColors[count] = foamColor; outputAttributes[count] = new float4(1, 1/initialLife, initialSize, BurstMath.PackFloatRGBA(new float4(airAging/50.0f, airdrag, drag, isosurface))); } } angVel.w = BurstMath.PackFloatRG(potential); angularVelocities[p] = angVel; } } [BurstCompile] unsafe struct UpdateParticlesJob : IJobParallelForDefer { [ReadOnly] public NativeArray positions; [ReadOnly] public NativeArray orientations; [ReadOnly] public NativeArray velocities; [ReadOnly] public NativeArray angularVelocities; [ReadOnly] public NativeArray principalRadii; [ReadOnly] public NativeArray fluidData; [ReadOnly] public NativeArray fluidMaterial; [ReadOnly] public NativeArray simplices; [ReadOnly] public SimplexCounts simplexCounts; [ReadOnly] public NativeMultilevelGrid grid; [DeallocateOnJobCompletion] [ReadOnly] public NativeArray gridLevels; [ReadOnly] public Poly6Kernel densityKernel; [ReadOnly] public NativeArray inputPositions; [ReadOnly] public NativeArray inputVelocities; [ReadOnly] public NativeArray inputColors; [ReadOnly] public NativeArray inputAttributes; [NativeDisableParallelForRestriction] public NativeArray outputPositions; [NativeDisableParallelForRestriction] public NativeArray outputVelocities; [NativeDisableParallelForRestriction] public NativeArray outputColors; [NativeDisableParallelForRestriction] public NativeArray outputAttributes; [NativeDisableParallelForRestriction] public NativeArray dispatchBuffer; [ReadOnly] public Oni.SolverParameters parameters; public float3 agingOverPopulation; public int minFluidNeighbors; public float deltaTime; public int currentAliveParticles; static readonly int4[] offsets = { new int4(0, 0, 0, 1), new int4(1, 0, 0, 1), new int4(0, 1, 0, 1), new int4(1, 1, 0, 1), new int4(0, 0, 1, 1), new int4(1, 0, 1, 1), new int4(0, 1, 1, 1), new int4(1, 1, 1, 1) }; public void Execute(int i) { int* dispatch = (int*)dispatchBuffer.GetUnsafePtr(); int count = Interlocked.Add(ref dispatch[3], -1); if (count < inputPositions.Length && inputAttributes[count].x > 0) { int aliveCount = Interlocked.Add(ref dispatch[7], 1) - 1; float4 attributes = inputAttributes[count]; float4 packedData = BurstMath.UnpackFloatRGBA(attributes.w); int offsetCount = ((int)parameters.mode == 1) ? 4 : 8; float4 advectedVelocity = float4.zero; float4 advectedAngVelocity = float4.zero; float kernelSum = -packedData.w; uint neighbourCount = 0; float4 diffusePos = inputPositions[count]; for (int k = 0; k < gridLevels.Length; ++k) { int l = gridLevels[k]; float radius = NativeMultilevelGrid.CellSizeOfLevel(l); float interactionDist = radius * 0.5f; float4 cellCoords = math.floor(diffusePos / radius); cellCoords[3] = 0; if ((int)parameters.mode == 1) cellCoords[2] = 0; float4 posInCell = diffusePos - (cellCoords * radius + new float4(interactionDist)); int4 quadrant = (int4)math.sign(posInCell); quadrant[3] = l; for (int o = 0; o < offsetCount; ++o) { int cellIndex; if (grid.TryGetCellIndex((int4)cellCoords + offsets[o] * quadrant, out cellIndex)) { var cell = grid.usedCells[cellIndex]; for (int n = 0; n < cell.Length; ++n) { int simplexStart = simplexCounts.GetSimplexStartAndSize(cell[n], out int simplexSize); for (int a = 0; a < simplexSize; ++a) { int p = simplices[simplexStart + a]; float4 normal = diffusePos - positions[p]; normal[3] = 0; if ((int)parameters.mode == 1) normal[2] = 0; float d = math.length(normal); if (d <= interactionDist) { float3 radii = fluidMaterial[p].x * (principalRadii[p].xyz / principalRadii[p].x); float4 angVel = new float4(math.cross(angularVelocities[p].xyz, normal.xyz), 0); advectedAngVelocity += angVel * densityKernel.W(d, radii.x) / densityKernel.W(0, radii.x); normal.xyz = math.mul(math.conjugate(orientations[p]), normal.xyz) / radii; d = math.length(normal) * radii.x; // scale by volume (* 1 / normalized density) float w = densityKernel.W(d, radii.x) / fluidData[p].x; kernelSum += w; advectedVelocity += velocities[p] * w; neighbourCount++; } } } } } } float4 forces = float4.zero; float velocityScale = 1; float agingScale = 1 + BurstMath.Remap01(currentAliveParticles / (float)inputPositions.Length, agingOverPopulation.x, agingOverPopulation.y) * (agingOverPopulation.z - 1); // foam/bubble particle: if (kernelSum > BurstMath.epsilon && neighbourCount >= minFluidNeighbors) { // advection: forces = packedData.z / deltaTime * (advectedVelocity / (kernelSum + packedData.w) + advectedAngVelocity - inputVelocities[count]); // buoyancy: forces -= new float4(parameters.gravity * parameters.foamGravityScale * inputVelocities[count].w * math.saturate(kernelSum), 0); } else // ballistic: { // gravity: forces += new float4(parameters.gravity * parameters.foamGravityScale, 0); // atmospheric drag/aging: velocityScale = packedData.y; agingScale *= packedData.x * 50; } // don't change 4th component, as its used to store buoyancy control parameter. forces[3] = 0; // update particle data: attributes.x -= attributes.y * deltaTime * agingScale; outputAttributes[aliveCount] = attributes; outputColors[aliveCount] = inputColors[count]; // integrate: outputVelocities[aliveCount] = (inputVelocities[count] + forces * deltaTime) * velocityScale; outputPositions[aliveCount] = new float4((inputPositions[count] + outputVelocities[aliveCount] * deltaTime).xyz,neighbourCount); } } } [BurstCompile] unsafe struct CopyJob : IJobParallelForDefer { [ReadOnly] public NativeArray inputPositions; [ReadOnly] public NativeArray inputVelocities; [ReadOnly] public NativeArray inputColors; [ReadOnly] public NativeArray inputAttributes; [NativeDisableParallelForRestriction] public NativeArray outputPositions; [NativeDisableParallelForRestriction] public NativeArray outputVelocities; [NativeDisableParallelForRestriction] public NativeArray outputColors; [NativeDisableParallelForRestriction] public NativeArray outputAttributes; [NativeDisableParallelForRestriction] public NativeArray dispatchBuffer; public void Execute(int i) { if (i == 0) { dispatchBuffer[3] = dispatchBuffer[7]; dispatchBuffer[7] = 0; } outputPositions[i] = inputPositions[i]; outputVelocities[i] = inputVelocities[i]; outputColors[i] = inputColors[i]; outputAttributes[i] = inputAttributes[i]; } } } #endif