Fluids are really cool and also very common - they're all around you! The human body is 65% water, and the Earth is 2/3 water.. while gasses are like fluids brother - they have a very similar set of properties - like air, smoke and clouds.
If you drop in in water or watch smoke float up into the sky - I'm sure you noticed the patterns of similarity?
Anyhow, fluid and gas dynamics are all around you! The're magical and beautiful - and you can emulate their characteristics in code.
So how do you go about simulating these things? Is it hard?
Well fluid dynamics is a big area! With lots of different models - with levels of accuracy and computation cost - however, for the most part of this tutorial I'll focus on `visually' realistic simulations that make numerical approximations. Looks and moves correctly! Also fast and we can interact with it in real-time.
Fluids and gases are everywhere! They're inside you, they're all around you, they are both simple and complex. Their beauty and magic in static and dynamic form are beyond words.
Few concepts and their details - which we're going to try and include:
• Diffusion: In a cup of tea, sugar molecules diffuse from areas of high concentration to low concentration, sweetening the drink evenly.
• Vortex: A whirlpool forms when water rushes down a drain, creating a swirling vortex of water.
• Buoyancy: A ship floats because its weight is less than the weight of water it displaces, thanks to buoyancy.
• Viscosity: Honey flows slowly due to its high viscosity, resisting deformation and sticking to surfaces.
Simple Discrate Fluid/Gas Simulation Demo
The code uses JavaScript for the fluid/gas dynamics - as it's easy to follow and can run anywhere (just need a web browser). Focus on 2D simulations - so that it doesn't get overy complex. But it should be enough to show swirly ink-like animation patterns.
The concept implementation has been broken down into key functions - each performing a specific task (like diffusion and buoyancy).
The full code is also implemented on a demo page (LINK).
The following gives the full working code (simplfied) that can run from a single file (no external libraries or resources required).
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Fluid and Gas Dynamics Simulation - XBDEV - Educational Tech Demo</title> <style> canvas { border: 1px solid black; } </style> </head> <body> <canvas id="canvas" width="400" height="400"></canvas> <script> // Get the canvas element and its context const canvas = document.getElementById('canvas'); const ctx = canvas.getContext('2d');
// Define the size of the grid const resolution = 2.0; const cols = canvas.width / resolution; const rows = canvas.height / resolution;
// Create arrays to hold the density, u velocity, and v velocity let density = new Array(cols).fill(0).map(() => new Array(rows).fill(0)); let u = new Array(cols).fill(0).map(() => new Array(rows).fill(0)); let v = new Array(cols).fill(0).map(() => new Array(rows).fill(0));
// Initialize density with a letter pattern const letter = "G"; // Change the letter here ctx.font = "bold 150px Arial"; ctx.fillText('X', 50, 150);
// Convert the letter on canvas to the density array const imageData = ctx.getImageData(0, 0, canvas.width, canvas.height); for (let i = 0; i < cols; i++) { for (let j = 0; j < rows; j++) { if (imageData.data[(j * 4 * imageData.width) + (i * 4) + 3] > 128) { density[i][j] = 1.0; }
} }
// initial velcoity for (let i = 0; i < cols; i++) { for (let j = 0; j < rows; j++) { const x = i * resolution - canvas.width / 2; const y = j * resolution - canvas.height / 2; // Introduce some randomness to the velocity initialization u[i][j] = Math.cos( 8 * i / cols ) * Math.sin( 6 * i / cols ) * 0.1; v[i][j] = Math.sin( 16 * i / cols ) * Math.sin( 8 * i / cols ) * 0.5; } }
// Function to update the simulation function update() { // Apply diffusion let diff = 0.01; diffuse(diff, u); diffuse(diff, v);
applyBuoyancy(density, v);
// Apply curl curl();
// Advect density advect(density, u, v);
// Draw the updated fields render();
// Request animation frame for continuous update requestAnimationFrame(update); }
// Function to apply diffusion function diffuse(diff, field) { const dt = 0.1; // Time step const visc = 0.0001; // Viscosity const amount = dt * visc * (cols - 2) * (rows - 2) * diff;
for (let k = 0; k < 10; k++) { // Iterations for stability for (let i = 1; i < cols - 1; i++) { for (let j = 1; j < rows - 1; j++) { field[i][j] = (field[i][j] + amount * ( field[i - 1][j] + field[i + 1][j] + field[i][j - 1] + field[i][j + 1])) / (1 + 4 * amount); } } } }
// Function to apply buoyancy force function applyBuoyancy(density, v) { const gravity = 0.05; // Strength of gravity const buoyancyAlpha = 0.1; // Buoyancy coefficient for density difference const buoyancyBeta = 0.1; // Buoyancy coefficient for vertical velocity const ambientTemperature = 0; // Ambient temperature
for (let i = 0; i < cols; i++) { for (let j = 0; j < rows; j++) { const densityDifference = density[i][j] - ambientTemperature; const buoyancyForce = -buoyancyAlpha * densityDifference + buoyancyBeta * v[i][j];
// Apply buoyancy force to vertical velocity v[i][j] += buoyancyForce * gravity; } } }
// Function to apply curl function curl() { const dt = 0.1; // Time step const curlCoefficient = 1.0;
// Function to advect the density field function advect(field, u, v) { const dt = 0.1; // Time step
const temp = new Array(cols).fill(0).map(() => new Array(rows).fill(0));
for (let b=0; b<7; b++) for (let i = 1; i < cols - 1; i++) { for (let j = 1; j < rows - 1; j++) { let x = i - u[i][j] * dt / resolution; let y = j - v[i][j] * dt / resolution;
// Add some diffusion-like behavior //x += (Math.random() * 2 - 1) * 0.1; // Introduce random noise in x direction //y += (Math.random() * 2 - 1) * 0.1; // Introduce random noise in y direction
// Clamp advection position to canvas boundaries if (x < 0.5) x = 0.5; if (x > cols - 0.5) x = cols - 0.5; if (y < 0.5) y = 0.5; if (y > rows - 0.5) y = rows - 0.5;