block by HarryStevens 606665118b6aca3b8a3e306b3c65f52c

Pendulum Party

Full Screen

index.html

<!DOCTYPE html>
<html>
<head>
  <style>
    body {
      margin: 0;
    }
  </style>
</head>
<body>
  <canvas></canvas>
  <script>
  const vecmath = {
    // Add vector w to vector v
    add: (v, w) => {
      let out = [];
      for (let i = 0; i < v.length; i++){
        out[i] = v[i] + w[i];
      }
      return out;
    },
    // Translate position of vector v by an angle in radians and a distance in pixels
    trans: (v, ang, dist) => [v[0] + dist * Math.cos(ang), v[1] + dist * Math.sin(ang)]
  };

  // The Pendulum class
  class Pendulum {
    constructor(){
      this._acceleration = 0;
      this._angle = Math.PI / 4;
      this._damping = 1.0;
      this._gravity = 0.4;
      this._length = 200;
      this._origin = [innerWidth / 2, 0];
      this._velocity = 0;
      this._position = vecmath.trans(this._origin, this._angle, this._length);
      
      return this;    
    }

    acceleration(num){
      return num ? (this._acceleration = num, this) : this._acceleration;
    }
    
    angle(num){
      return num ? (this._angle = num, this) : this._angle;
    }
    
    damping(num){
      return num ? (this._damping = num, this) : this._damping;
    }
    
    gravity(num){
      return num ? (this._gravity = num, this) : this._gravity;
    }
    
    length(num){
      return num ? (this._length = num, this) : this._length;
    }
    
    origin(arr){
      return arr ? (this._origin = arr, this) : this._origin;
    }
    
    position(arr){
      return arr ? (this._position = arr, this) : this._position;
    }
    
    velocity(num){
      return num ? (this._velocity = num, this) : this._velocity;
    }
    
    tick(){
      return this
        .acceleration((-1 * this.gravity() / this.length()) * Math.sin(this.angle()))
        .velocity((this.velocity() + this.acceleration()) * this.damping())
        .angle(this.angle() + this.velocity())
        .position(
          vecmath.add(
            [this.length() * Math.sin(this.angle()), this.length() * Math.cos(this.angle())],
            this.origin()
          )
        );
    }
  }

  // Create the pendula
  const pendula = [];
  const rows = 8;
  const cols = 10;
  let i = 1;
  for (let row = 0; row < rows; row++){
    const evenRow = row % 2 === 0;
    
    for (let col = 0; col < cols; col++){

      if (evenRow && col === cols - 1) continue;
      
      const px = innerWidth / cols;
      const x = px / 2 + col * px + (evenRow ? px / 2 : 0);
      const y = 10 + row * innerHeight / rows;

      pendula.push(
        new Pendulum()
          .gravity(.15)
          .length(-10 + px / 2)
          .origin([x, y])
          .angle(Math.PI / 2 * (i % 2 === 0 ? 1 : -1))
      );   

      i++;
    }
  }
  
  // Draw to Canvas
  const canvas = document.querySelector("canvas");
  canvas.width = innerWidth;
  canvas.height = innerHeight;
  const ctx = canvas.getContext("2d");
  ctx.strokeStyle = "#aaa";
  
  function tick(){
    requestAnimationFrame(tick);

    ctx.clearRect(0, 0, innerWidth, innerHeight);

    pendula.forEach((p, i, e) => {
      draw(p, `rgb(200, 40, ${(i + 1) / e.length * 255})`);
    });
  }

  tick();
  
  function draw(p, fill){
    p.tick();

    ctx.fillStyle = fill;

    ctx.beginPath();
    ctx.moveTo(...p.origin());
    ctx.lineTo(...p.position());
    ctx.stroke();
    
    ctx.beginPath();    
    ctx.arc(...p.origin(), 3, 0, Math.PI * 2)
    ctx.fill();
    
    ctx.beginPath();    
    ctx.arc(...p.position(), 10, 0, Math.PI * 2)
    ctx.fill();
  }
  </script>

</body>
</html>