public void calcNearByParticles(RoiParticle particle, ref List <RoiParticle> nearParticles) { nearParticles.Clear(); Vector2 gridIndex = toGridIndex(particle.position); // search for near particles in 9x9 grid for (int i = (int)gridIndex.x - 1; i <= gridIndex.x + 1; i++) { for (int j = (int)gridIndex.y - 1; j <= gridIndex.y + 1; j++) { // sanity check: we are not outside the simulation bounds if (i < 0 || j < 0 || i > xCells || j > yCells) { continue; } int key = toKey(i, j); if (hashMap.ContainsKey(key)) { List <RoiParticle> _paticles = hashMap[key]; for (var k = 0; k < _paticles.Count; k++) { var d = (particle.position - _paticles[k].position).sqrMagnitude; if (d < this.squaredSupportRadius && d > 0) // if within support radius, add { nearParticles.Add(_paticles[k]); } } } } } }
public void emitParticles(float deltaTime, List <RoiParticle> particles, ObjectPooler <RoiParticle> objectPooler, int maxParticles) { // acumulate time till we exceed frequency timer = timer + deltaTime; float period = 1.0f / frequency; if (timer > period) { // get current emit angle if (angularVelocity == 0) { // if not angle velocity, just use emit angle currentAngle = emitAngle; } else { // if we have angle velocity, ignore emit angle, and rotate by angular velocity. currentAngle += angularVelocity; } // get base velocity and color of emitting particle Vector2 velocity = Utils.getVector2(strength, currentAngle); Vector2 perpendicularVelocity = new Vector2(-velocity.y, velocity.x).normalized; float a = velocityRandomness * 0.01f; for (var j = -1; j <= 1; j++) { Vector2 pos = (Vector2)_transform.position + 0.04f * j * perpendicularVelocity; if (particles.Count < maxParticles) { RoiParticle roiParticle = objectPooler.GetObject(); roiParticle.Update( pos, new Vector2(velocity.x + Random.Range(-a, a), velocity.y + Random.Range(-a, a)), particleSize, fluidColor ); particles.Add(roiParticle); } else { break; } } // reset timer, instead of zero take difference timer = timer - period; } }
private void FixedUpdate() { float deltaTime = Time.fixedDeltaTime; removeOutOfBoundsParticles(); // remove out of bound particles spatialHash.updateGrid(roiParticles); // change speed of simulation deltaTime = deltaTime * timeScale; // update the number of particles numberOfParticles = roiParticles.Count; if (numberOfParticles > _nearParticles.Length) { setUpNearParticlesArray(); } for (int i = 0; i < numberOfParticles; i++) { RoiParticle iParticle = roiParticles[i]; if (!iParticle.isNew) { // apply prediction relaxation scheme // section 3.0 of the paper - "Particle-based Viscoelastic Fluid Simulation" iParticle.velocity = (iParticle.position - iParticle.originalPosition) / deltaTime; } iParticle.isNew = false; // apply gravity iParticle.velocity = new Vector2(iParticle.velocity.x, iParticle.velocity.y - this.gravity * deltaTime); spatialHash.calcNearByParticles(iParticle, ref _nearParticles[i]); applyViscosity(iParticle, deltaTime, _nearParticles[i]); } for (int i = 0; i < numberOfParticles; i++) { RoiParticle iParticle = roiParticles[i]; // save the original particle position, then apply velocity. iParticle.originalPosition = iParticle.position; iParticle.position = iParticle.position + iParticle.velocity * deltaTime; } if (enableDensityRelaxation) { doubleDensityRelaxation(deltaTime, _nearParticles); } }
//------------------------------------------------------------------------------------- // APPLY DOUBLE DENSITY RELAXATION // section 4 of the paper - "Particle-based Viscoelastic Fluid Simulation" // Simon Clavet, Philippe Beaudoin, and Pierre Poulin // ------------------------------------------------------------------------------------ private void doubleDensityRelaxation(float deltaTime, List <RoiParticle>[] nearParticles) { for (int i = 0; i < numberOfParticles; i++) { RoiParticle iParticle = roiParticles[i]; List <RoiParticle> iNearParticles = nearParticles[i]; float density = 0; float nearDensity = 0; for (int j = 0; j < iNearParticles.Count; j++) { float r = (iParticle.position - iNearParticles[j].position).magnitude; if (r <= 0 || r > particleRadius) { continue; } float q = 1 - (r / particleRadius); density += q * q; nearDensity += q * q * q; } float pressure = stiffness * (density - restDensity); float nearPressure = nearStiffness * nearDensity; Vector2 iFinalPosition = Vector2.zero; for (int j = 0; j < iNearParticles.Count; j++) { Vector2 dp = (iParticle.position - iNearParticles[j].position); float r = dp.magnitude; if (r <= 0 || r > particleRadius) { continue; } float q = 1 - (r / particleRadius); float Dij = deltaTime * deltaTime * (pressure * q + nearPressure * q * q) * 0.5f; Vector2 finalDp = (Dij / r) * dp; iNearParticles[j].position += finalDp; iFinalPosition -= finalDp; } iParticle.position += iFinalPosition; } }
//------------------------------------------------------------------------------------- // APPLY VISCOSITY // section 5.3 of the paper - "Particle-based Viscoelastic Fluid Simulation" // Simon Clavet, Philippe Beaudoin, and Pierre Poulin // ------------------------------------------------------------------------------------ // Some modifications are done which is inspired from, // https://github.com/Erkaman/gl-water2d private void applyViscosity(RoiParticle iParticle, float deltaTime, List <RoiParticle> nearParticles) { for (int j = 0; j < nearParticles.Count; j++) { RoiParticle jParticle = nearParticles[j]; Vector2 dp = iParticle.position - jParticle.position; float r = dp.magnitude; Vector2 dp_norm = dp / r; if (r <= 0 || r > particleRadius) { continue; } float u = Vector2.Dot(iParticle.velocity - jParticle.velocity, dp_norm); float I_div2_mag = 0; if (u > 0) { I_div2_mag = deltaTime * (1 - (r / particleRadius)) * (sigma * u + beta * u * u) * 0.5f; if (I_div2_mag > u) { I_div2_mag = u; } } else { I_div2_mag = deltaTime * (1 - (r / particleRadius)) * (sigma * u - beta * u * u) * 0.5f; if (I_div2_mag < u) { I_div2_mag = u; } } iParticle.velocity = iParticle.velocity - I_div2_mag * dp_norm; jParticle.velocity = jParticle.velocity + I_div2_mag * dp_norm; } }