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.
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;