/// <summary> /// Checks the distance between the particles and corrects it, if they are to near. /// </summary> /// <param name="particles">The particles.</param> /// <param name="grid">The grid.</param> private void CheckParticleDistance(FluidParticles particles, IndexGrid grid) { float minDist = 0.5f * CellSpace; float minDistSq = minDist * minDist; Vector2 dist; for (int i = 0; i < particles.Count; i++) { foreach (var nIdx in grid.GetNeighbourIndex(particles[i])) { Vector2.Sub(ref particles[nIdx].Position, ref particles[i].Position, out dist); float distLenSq = dist.LengthSquared; if (distLenSq < minDistSq) { if (distLenSq > Constants.FLOAT_EPSILON) { float distLen = (float)Math.Sqrt((double)distLenSq); Vector2.Mult(ref dist, 0.5f * (distLen - minDist) / distLen, out dist); Vector2.Sub(ref particles[nIdx].Position, ref dist, out particles[nIdx].Position); Vector2.Sub(ref particles[nIdx].PositionOld, ref dist, out particles[nIdx].PositionOld); Vector2.Add(ref particles[i].Position, ref dist, out particles[i].Position); Vector2.Add(ref particles[i].PositionOld, ref dist, out particles[i].PositionOld); } else { float diff = 0.5f * minDist; particles[nIdx].Position.Y -= diff; particles[nIdx].PositionOld.Y -= diff; particles[i].Position.Y += diff; particles[i].PositionOld.Y += diff; } } } } }
/// <summary> /// Create particles evenly spaced on ground of the boundary. /// </summary> public static FluidParticles Create(int nParticles, float cellSpace, RectangleF domain, float particleMass) { FluidParticles particles = new FluidParticles(nParticles); // Init. Particle positions float x0 = domain.X + cellSpace; float x = x0; float y = domain.Y; for (int i = 0; i < nParticles; i++) { if (x == x0) { y += cellSpace; } Vector2 pos = new Vector2(x, y); particles.Add(new FluidParticle { Position = pos, PositionOld = pos, Mass = particleMass, }); x = x + cellSpace < domain.Width ? x + cellSpace : x0; } return(particles); }
/// <summary> /// Updates the particles posotions using integration and clips them to the domain space. /// </summary> /// <param name="particles">The particles.</param> /// <param name="dTime">The time step.</param> private void UpdateParticles(FluidParticles particles, float dTime) { float r = this.Domain.Right; float l = this.Domain.X; // Rectangle contains coordinates inverse on y float t = this.Domain.Bottom; float b = this.Domain.Y; foreach (var particle in particles) { // Clip positions to domain space if (particle.Position.X < l) { particle.Position.X = l + Constants.FLOAT_EPSILON; } else if (particle.Position.X > r) { particle.Position.X = r - Constants.FLOAT_EPSILON; } if (particle.Position.Y < b) { particle.Position.Y = b + Constants.FLOAT_EPSILON; } else if (particle.Position.Y > t) { particle.Position.Y = t - Constants.FLOAT_EPSILON; } // Update velocity + position using forces particle.Update(dTime); // Reset force particle.Force = Vector2.Zero; } }
/// <summary> /// Simulates the specified particles. /// </summary> /// <param name="particles">The particles.</param> /// <param name="globalForce">The global force.</param> /// <param name="dTime">The time step.</param> public void Calculate(FluidParticles particles, Vector2 globalForce, float dTime) { m_grid.Refresh(particles); CalculatePressureAndDensities(particles, m_grid); CalculateForces(particles, m_grid, globalForce); UpdateParticles(particles, dTime); CheckParticleDistance(particles, m_grid); }
public IndexGrid(float cellSpace, RectangleF domain, FluidParticles particles) { this.CellSpace = cellSpace; this.Domain = domain; this.Width = (int)(this.Domain.Width / this.CellSpace); this.Height = (int)(this.Domain.Height / this.CellSpace); this.Refresh(particles); }
/// <summary> /// Renders the specified particles using the size of the texture (specified in ctor). /// </summary> /// <param name="particles">The particles.</param> /// <param name="domain">The source rectangle (min, max of particle position).</param> public void Render(FluidParticles particles, RectangleF domain) { Vector2[] destCoordinates = new Vector2[] { new Vector2(domain.Left, domain.Bottom), new Vector2(domain.Right, domain.Bottom), new Vector2(domain.Right, domain.Top), new Vector2(domain.Left, domain.Top) }; this.Render(particles, domain, destCoordinates); }
/// <summary> /// Renders the specified particles. /// </summary> /// <param name="particles">The particles.</param> /// <param name="domain">The source rectangle (min, max of possible particle position).</param> /// <param name="destCoordinates">The destination coordinates, lenght of the array has to be 4. /// Index 0: upper left corner. /// Index 1: upper right corner. /// Index 2: lower right corner. /// Index 3: lower left corner</param> public void Render(FluidParticles particles, RectangleF domain, Vector2[] destCoordinates) { if (destCoordinates.Length != 4) { throw new ArgumentOutOfRangeException("destCoordinates.Length != 4"); } BeginDraw(); // // Render-To-Texture // RenderToTex(particles, domain); // // Render-To-Framebuffer // //// Clear Frame-Buffer //if (m_fboRender == 0) //{ // GL.Clear(ClearBufferMask.ColorBufferBit); //} // AlphaTest checks if the energy (alpha) is greater or equal than the threshold GL.Enable(EnableCap.AlphaTest); // Draw single blue quad (background) GL.Disable(EnableCap.Texture2D); GL.Color4(Color); GL.Begin(BeginMode.Quads); GL.Vertex2(destCoordinates[0].X, destCoordinates[0].Y); GL.Vertex2(destCoordinates[1].X, destCoordinates[1].Y); GL.Vertex2(destCoordinates[2].X, destCoordinates[2].Y); GL.Vertex2(destCoordinates[3].X, destCoordinates[3].Y); GL.End(); // Draw single quad with rendered alpha texture GL.Enable(EnableCap.Texture2D); BindRenderTexture(); GL.Begin(BeginMode.Quads); GL.TexCoord2(0.0f, 1.0f); GL.Vertex2(destCoordinates[0].X, destCoordinates[0].Y); GL.TexCoord2(1.0f, 1.0f); GL.Vertex2(destCoordinates[1].X, destCoordinates[1].Y); GL.TexCoord2(1.0f, 0.0f); GL.Vertex2(destCoordinates[2].X, destCoordinates[2].Y); GL.TexCoord2(0.0f, 0.0f); GL.Vertex2(destCoordinates[3].X, destCoordinates[3].Y); GL.End(); EndDraw(); }
/// <summary> /// Renders to texture. /// </summary> /// <param name="particles">The particles.</param> /// <param name="domain">The source rectangle (min, max of particle position).</param> private void RenderToTex(FluidParticles particles, RectangleF domain) { // // Render-To-Texture // // Bind FBO if available and clear it if (m_fboRender != 0) { GL.Ext.BindFramebuffer(FramebufferTarget.FramebufferExt, m_fboRender); GL.Clear(ClearBufferMask.ColorBufferBit); } // Set correct viewport GL.PushAttrib(AttribMask.ViewportBit); GL.Viewport(0, 0, m_texSize, m_texSize); GL.Disable(EnableCap.AlphaTest); GL.Enable(EnableCap.Texture2D); GL.BindTexture(TextureTarget.Texture2D, m_texAttentuation); // Trasnform radius from screen to domain float rat = m_radiusSquared / m_texSize; float radW = rat * domain.Width; float radH = rat * domain.Height; // Draw particles as quads with the size of the squared radius GL.Begin(BeginMode.Quads); foreach (var particle in particles) { GL.TexCoord2(0.0f, 0.0f); GL.Vertex2(particle.Position.X - radW, particle.Position.Y - radH); GL.TexCoord2(1.0f, 0.0f); GL.Vertex2(particle.Position.X + radW, particle.Position.Y - radH); GL.TexCoord2(1.0f, 1.0f); GL.Vertex2(particle.Position.X + radW, particle.Position.Y + radH); GL.TexCoord2(0.0f, 1.0f); GL.Vertex2(particle.Position.X - radW, particle.Position.Y + radH); } GL.End(); // Copy framebuffer to texture if fbo isn't available if (m_fboRender == 0) { BindRenderTexture(); GL.CopyTexImage2D(TextureTarget.Texture2D, 0, PixelInternalFormat.Rgba, 0, 0, m_texSize, m_texSize, 0); } else { GL.Ext.BindFramebuffer(FramebufferTarget.FramebufferExt, 0); } GL.PopAttrib(); }
/// <summary> /// Consumes the specified particles if they are in in the radius. /// </summary> /// <param name="particles">The particles.</param> public void Consume(FluidParticles particles) { if (this.Enabled) { for (int i = particles.Count - 1; i >= 0; i--) { float distSq = (particles[i].Position - this.Position).LengthSquared; if (distSq < m_radiusSquared) { particles.RemoveAt(i); } } } }
/// <summary> /// Updates the particles (remove, emit, ...). /// </summary> /// <param name="dTime">The delta time.</param> public FluidParticles Update(double dTime) { FluidParticles emitted = null; // Consume particles in a certain range if (this.HasConsumers) { foreach (var consumer in this.Consumers) { consumer.Consume(this.Particles); } } // Remove old particles if (this.TestMaxLife) { for (int i = this.Particles.Count - 1; i >= 0; i--) { if (this.Particles[i].Life >= this.MaxLife) { this.Particles.RemoveAt(i); } } } // Check if emit is allowed if (m_wasMaxReached && !this.DoRebirth) { // NOP } else if (this.Particles.Count < this.MaxParticles) { if (this.HasEmitters) { // Emit new particles foreach (var emitter in this.Emitters) { emitted = emitter.Emit(dTime); this.Particles.AddRange(emitted); } } } else { m_wasMaxReached = true; } return(emitted); }
/// <summary> /// Calculates the pressure and densities. /// </summary> /// <param name="particles">The particles.</param> /// <param name="grid">The grid.</param> private void CalculatePressureAndDensities(FluidParticles particles, IndexGrid grid) { Vector2 dist; foreach (var particle in particles) { particle.Density = 0.0f; foreach (var nIdx in grid.GetNeighbourIndex(particle)) { Vector2.Sub(ref particle.Position, ref particles[nIdx].Position, out dist); particle.Density += particle.Mass * this.SKGeneral.Calculate(ref dist); } particle.UpdatePressure(); } }
/// <summary> /// Solves collisions only for the particles and the bounding volumes associated with this instance. /// </summary> /// <param name="particles">The particles.</param> /// <returns>True, if a collision occured.</returns> public bool Solve(FluidParticles particles) { bool hasCollided = false; Vector2 penetration, penNormal, v, vn, vt; float penLen, dp; foreach (var bv in this.BoundingVolumes) { foreach (var particle in particles) { if (bv.Intersects(particle.BoundingVolume, out penNormal, out penLen)) { hasCollided = true; Vector2.Mult(ref penNormal, penLen, out penetration); if (particle.BoundingVolume.IsFixed) { bv.Position += penetration; } else { particle.BoundingVolume.Position -= penetration; // Calc new velocity using elastic collision with friction // -> Split oldVelocity in normal and tangential component, revert normal component and add it afterwards // v = pos - oldPos; //vn = n * Vector2.Dot(v, n) * -Bounciness; //vt = t * Vector2.Dot(v, t) * (1.0f - Friction); //v = vn + vt; //oldPos = pos - v; Vector2.Sub(ref particle.Position, ref particle.PositionOld, out v); Vector2 tangent = penNormal.PerpendicularRight; dp = Vector2.Dot(v, penNormal); Vector2.Mult(ref penNormal, dp * -this.Bounciness, out vn); dp = Vector2.Dot(v, tangent); Vector2.Mult(ref tangent, dp * (1.0f - this.Friction), out vt); Vector2.Add(ref vn, ref vt, out v); particle.Position -= penetration; Vector2.Sub(ref particle.Position, ref v, out particle.PositionOld); } } } } return(hasCollided); }
public void Refresh(FluidParticles particles) { m_grid = new List <int> [this.Width, this.Height]; if (particles != null) { for (int i = 0; i < particles.Count; i++) { int gridIndexX = GetGridIndexX(particles[i]); int gridIndexY = GetGridIndexY(particles[i]); // Add particle to list if (m_grid[gridIndexX, gridIndexY] == null) { m_grid[gridIndexX, gridIndexY] = new List <int>(); } m_grid[gridIndexX, gridIndexY].Add(i); } } }
/// <summary> /// Emits particles. /// </summary> /// <param name="dTime">The delta time.</param> /// <returns></returns> public FluidParticles Emit(double dTime) { FluidParticles particles = new FluidParticles(); if (this.Enabled) { // Calc particle count based on frequency m_time += dTime; int nParts = (int)(this.Frequency * m_time); if (nParts > 0) { // Create Particles for (int i = 0; i < nParts; i++) { // Calc velocity based on the distribution along the normalized direction float dist = (float)m_randGen.NextDouble() * this.Distribution - this.Distribution * 0.5f; Vector2 normal = this.Direction.PerpendicularRight; Vector2.Mult(ref normal, dist, out normal); Vector2 vel = this.Direction + normal; vel.Normalize(); float velLen = (float)m_randGen.NextDouble() * (this.VelocityMax - this.VelocityMin) + this.VelocityMin; Vector2.Mult(ref vel, velLen, out vel); // Calc Oldpos (for right velocity) using simple euler // oldPos = this.Position - vel * m_time; Vector2 oldPos = this.Position - vel * (float)m_time; particles.Add(new FluidParticle { Position = this.Position, PositionOld = oldPos, Velocity = vel, Mass = this.ParticleMass }); } // Reset time m_time = 0.0; } } return(particles); }
/// <summary> /// Calculates the pressure and viscosity forces. /// </summary> /// <param name="particles">The particles.</param> /// <param name="grid">The grid.</param> /// <param name="globalForce">The global force.</param> private void CalculateForces(FluidParticles particles, IndexGrid grid, Vector2 globalForce) { Vector2 f, dist; float scalar; for (int i = 0; i < particles.Count; i++) { // Add global force to every particle particles[i].Force += globalForce; foreach (var nIdx in grid.GetNeighbourIndex(particles[i])) { // Prevent double tests if (nIdx < i) { if (particles[nIdx].Density > Constants.FLOAT_EPSILON) { Vector2.Sub(ref particles[i].Position, ref particles[nIdx].Position, out dist); // pressure // f = particles[nIdx].Mass * ((particles[i].Pressure + particles[nIdx].Pressure) / (2.0f * particles[nIdx].Density)) * WSpikyGrad(ref dist); scalar = particles[nIdx].Mass * (particles[i].Pressure + particles[nIdx].Pressure) / (2.0f * particles[nIdx].Density); f = this.SKPressure.CalculateGradient(ref dist); Vector2.Mult(ref f, scalar, out f); particles[i].Force -= f; particles[nIdx].Force += f; // viscosity // f = particles[nIdx].Mass * ((particles[nIdx].Velocity - particles[i].Velocity) / particles[nIdx].Density) * WViscosityLap(ref dist) * Constants.VISC0SITY; scalar = particles[nIdx].Mass * this.SKViscosity.CalculateLaplacian(ref dist) * this.Viscosity * 1 / particles[nIdx].Density; f = particles[nIdx].Velocity - particles[i].Velocity; Vector2.Mult(ref f, scalar, out f); particles[i].Force += f; particles[nIdx].Force -= f; } } } } }