Previous page : Motion in the ocean… I mean in the atmosphere
The whole program
I have by purpose not used any frameworks or external modules, and also no OOP, even if I use objects. The reasons are that I can use it in the Programming 1 course I hold, where OOP is not part of the course, and also because I am not a great fan of OOP. OK, here is the code:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 |
'use strict'; /************************************************** * * A simulation of an atmosphere 1.4 * By Mauritz Blomqvist 2021-02-12. * This is free to use - but I'm happy if you add * a link to this page * https://properhoc.com/physics/themodynamics/a-simulation-of-an-atmosphere/ * in your code. * * This basically simulates a slice of an * atmosphere from the ground up to the * vacuum of space. * * In this 3000 "atoms", particles, are * undergoing totally elastic collision with * each other and the walls. * The particles are also influenced by gravity * so if they move upward they will slow down * until they eventually start to fall down again. * * The gas is initially in the lower end of the * atmosphere, and then they are "released". * After a minute or so the distribution of * particles keep somewhat constant. * **************************************************/ // Initialization of the canvas. const canvas = document.getElementById('canvas'); const ctx = canvas.getContext('2d'); canvas.style = 'border:1px solid #0F0000;'; // The slice of space is 600 pixels wide // and 6000 pixels high. canvas.width = 600; canvas.height = 6000; // Number of particles. let n = 3000; // Free fall acceleration * dt. let gDt = 0.01; // The size of one atom - and related variables. let radius = 2; let diameter = 2 * radius; let diameter2 = (2 * radius) ** 2; // Initial speed of the atoms. let v0max = 3; // To speed up the calculations of the // particle-particle interactions the particles are // sorted in vertical slots of the same width // as the diameter of one atom (or slightly larger). // Then we only need to check the slot and // adjacent slots for let nSlots = Math.ceil(canvas.width / diameter); let slots; // A click on the restart button // will create new particles placed // in the bottom part of the canvas. document .getElementById('restart') .addEventListener('click', () => createParticles(n)); // This function is used to check if new particles overlap. function same(x, y) { for (let i = 0; i < particles.length; i++) { let d = Math.sqrt((particles[i].x - x) ** 2 + (particles[i].y - y) ** 2); if (d <= 2 * radius) return true; } return false; } // This will create new particles. // They will be given a random direction of // motion, and a random position within the lowest 500 // pixels of the canvas. // If a new particle overlaps with any already existing // a new position is tried until the particle // is placed at a previously empty position. let particles; function createParticles(n) { particles = []; let particle; for (let i = 0; i < n; i++) { let dir = Math.random() * 2 * Math.PI; do { particle = { x: Math.random() * (canvas.width - 2 * radius) + radius, y: canvas.height - Math.random() * (500 - 2 * radius) + radius, vx: v0max * Math.cos(dir), vy: v0max * Math.sin(dir), }; } while (same(particle.x, particle.y)); particles.push(particle); } } createParticles(n); // This will draw particle number i as a small circle. function drawParticle(i) { ctx.beginPath(); ctx.arc(particles[i].x, particles[i].y, radius, 0, 2 * Math.PI); ctx.stroke(); } // This will draw all particles. Particle // nr 0 will be red, all the rest will // be blue. function draw() { ctx.strokeStyle = 'RED'; drawParticle(0); ctx.strokeStyle = 'BLUE'; for (let i = 1; i < n; i++) { drawParticle(i); } } // This function moves the particles // and checks if a particle will bounce on a // wall. If so it will change the direction // of the perpendicular component of the // motion. If the particle moves in the vertical // direction the velocity will change due to // gravity. // As the particle moves say downward, it will change // its velocity by gdt, but the average velocity will be // (vOld+vNew)/2=(vOld+vOld+gdt)/2=vOld+gdt/2. // This can be seen in hte code in a few places. function move() { for (let i = 0; i < n; i++) { particles[i].x += particles[i].vx; particles[i].y += particles[i].vy + gDt / 2; if (particles[i].y < radius) { if (particles[i].vy < 0) particles[i].vy *= -1; particles[i].y += particles[i].vy - gDt / 2; } else if (particles[i].y > canvas.height - radius) { if (particles[i].vy > 0) particles[i].vy *= -1; particles[i].y += particles[i].vy - gDt / 2; } else particles[i].vy += gDt; if (particles[i].x < radius) { if (particles[i].vx < 0) particles[i].vx *= -1; particles[i].x += particles[i].vx; } else if (particles[i].x > canvas.width - radius) { if (particles[i].vx > 0) particles[i].vx *= -1; particles[i].x += particles[i].vx; } } } // This is to check if particle i and j // has collided. The horizontal distance between // the particles is checked to see if a collision // is possible, if so, then the actual distance is // checked. If a collision is still // possible then it is checked if the particles are moving // toward each other => collision, or not. // Then the calculations to simulate // a totally elastic collision is done. function collision(i, j) { let dy = particles[i].y - particles[j].y; if (Math.abs(dy) < diameter) { let dx = particles[i].x - particles[j].x; let dy = particles[i].y - particles[j].y; let d2 = dx ** 2 + dy ** 2; if (d2 < diameter2) { let dvx = particles[j].vx - particles[i].vx; let dvy = particles[j].vy - particles[i].vy; let dvs = dx * dvx + dy * dvy; if (dvs > 0) { dvs = dvs / d2; dvx = dvs * dx; dvy = dvs * dy; particles[i].vx += dvx; particles[i].vy += dvy; particles[j].vx -= dvx; particles[j].vy -= dvy; } } } } // In this function particle-particle interactions // is checked. First the particles will // be placed in vertical slots with a width // equal to (or slightly larger than) the // radius of the particles. // Then we loop over the slots, checking for // collisions among particles in the same slot, // and then with particles in the slot to the left. // I.e. the slots are checked all in 0 against all in 0, // all in 0 against all in 1, all in 1 against all in 1, // i.e. 0-0, 0-1, 1-1, 1-2, 2-2 and so on until the last slot // where we of course do not check against the non existent // next slot. // In this way we ensure that all possible pairs of particles // that could possibly collide are checked. function particleParticle() { slots = Array(nSlots) .fill(null) .map(() => []); // Place the particles in the correct slot. for (let i = 0; i < n; i++) { let s = Math.floor(particles[i].x / diameter); s = Math.max(s, 0); s = Math.min(s, nSlots - 1); slots[s].push(i); } for (let s = 0; s < nSlots; s++) { // Check all pairs of particles in the current slot. for (let i = 0; i < slots[s].length - 1; i++) { for (let j = i + 1; j < slots[s].length; j++) { collision(slots[s][i], slots[s][j]); } } // Check all particles in the current slot // against all particles in the slot to the right // unless we are at the last slot. if (s !== nSlots - 1) { for (let i = 0; i < slots[s].length; i++) { for (let j = 0; j < slots[s + 1].length; j++) { collision(slots[s][i], slots[s + 1][j]); } } } } } // Checks if the Escape key is pressed, if so // set running to false, that will cause // the main loop to halt. // If the escape key is pressed again the // main loop is called again. document.addEventListener('keydown', keyDown); let key = ''; let running = true; function keyDown(event) { key = event.key; if (key === 'Escape') { if (running) { running = false; } else { running = true; requestAnimationFrame(theMainLoop); } } } // The main loop. The canvas is cleared, // Then the particles are moved, and collisions // with the walls are done. // Next particle-particle interactions are checked // and done. // Then the particles are drawn. // If the running flag is set the main loop // is called again. function theMainLoop() { ctx.clearRect(0, 0, canvas.width, canvas.height); move(); particleParticle(); draw(); if (running) requestAnimationFrame(theMainLoop); } requestAnimationFrame(theMainLoop); |
And the HTML:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Atmosphere</title> </head> <body> <h2>A simulation of an atmosphere</h2> <p>Scroll down to see the lower end of the simulated atmosphere.</p> <p>For an explanation see <a href="https://properhoc.com/physics/themodynamics/a-simulation-of-an-atmosphere/"> here </a>.</p> <canvas id="canvas"></canvas> <br> <p id="p1"></p> <button id="restart">Restart</button> <script src="atmosphere4.js"></script> </body> </html> |
Up a level : Thermodynamics
Previous page : Motion in the ocean… I mean in the atmosphereLast modified: