All the code together

Up a level : Themodynamics
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:

'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:

<!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 : Themodynamics
Previous page : Motion in the ocean… I mean in the atmosphereLast modified: Feb 12, 2021 @ 17:59