
const mouse = {
    x: 0,
    y: 0,
};
  
window.addEventListener('mousemove', function(e){
    mouse.x = e.x;
    mouse.y = e.y;
});

let canvas;
let ctx;
let flowfield;
let flowfieldAnimation = null;

function startFlowfieldAnimation() {
    if (flowfieldAnimation != null) cancelAnimationFrame(flowfieldAnimation);

    // canvas.width = window.innerWidth;
    // canvas.height = window.innerHeight;

    canvas.width = 300;
    canvas.height = 400;

    flowfield = new FlowfieldEffect(ctx, canvas.width, canvas.height);
    flowfield.animate(0);
}

window.addEventListener('resize', function(e){
    // startFlowfieldAnimation();
});
  
window.onload = function(){
  canvas = document.getElementById('canvas1');
  ctx = canvas.getContext('2d');

  startFlowfieldAnimation();
}

class FlowfieldEffect {
  #ctx;
  #width;
  #height;
  #count;
  #radius;

  constructor(ctx, width, height, gradient) {
      this.#ctx = ctx;
      this.#ctx.lineWidth = 0.3;
      this.#width = width;
      this.#height = height;
      this.gradient;
      this.#createGradient();
      this.#ctx.strokeStyle = this.gradient;

      this.#radius = 1.9; // 1.9
      this.vr = 0.03;
      this.cellSize = 10;

      this.interval = 1000/60;
      this.timer = 0;
      this.lastTime = 0;

      this.a = Math.random() * 4 - 2;
      this.b = Math.random() * 4 - 2;
      this.c = Math.random() * 4 - 2;
      this.d = Math.random() * 4 - 2;
  }

  #createGradient(){
      this.gradient = this.#ctx.createLinearGradient(0, 0,this.#width, this.#height);
      this.gradient.addColorStop("0.1","#ff5c33");
      this.gradient.addColorStop("0.2", "#ff66b3");
      this.gradient.addColorStop("0.4", "#ccccff");
      this.gradient.addColorStop("0.6", "#b3ffff");
      this.gradient.addColorStop("0.8", "#80ff80");
      this.gradient.addColorStop("0.9", "#ffff33");
  }

  #getValue(x, y){
      return (Math.cos(mouse.y/3 * x * 0.00005) + Math.sin(mouse.x/3 * y * 0.00005)) * this.#radius;
  }

  #drawLine(x1,y1,x2,y2) {
    this.#ctx.beginPath();
    this.#ctx.moveTo(x1,y1);
    this.#ctx.lineTo(x2,y2);
    this.#ctx.stroke();
  }

  #draw(angle,x,y){
      let positionX = x;
      let positionY = y;
      let dx = mouse.x - positionX;
      let dy = mouse.y - positionY;
      let distance = (dx * dx + dy * dy);
      let length = distance > 150000 ? distance : 150000;
      if (length > 900000) length = 900000;
      this.#drawLine(positionX,positionY, positionX+Math.cos(angle) * length/10000, positionY+Math.sin(angle) * length/10000);
  }

  cliffordAttractor(x, y, a, b, c, d) {
    // clifford attractor
    // http://paulbourke.net/fractals/clifford/
    
    // scale down x and y
    let scale = 0.005;
    let x0 = (x - this.#width / 2) * scale;
    let y0 = (y - this.#height / 2)  * scale;
  
    // attactor gives new x, y for old one. 
    let x1 = Math.sin(a * y0) + c * Math.cos(a * x0);
    let y1 = Math.sin(b * x0) + d * Math.cos(b * y0);
  
    // find angle from old to new. that's the value.
    return Math.atan2(y1 - y0, x1 - x0);
  }

  // http://paulbourke.net/fractals/henonmap/
  henonAttractor(x,y,a,b){
    let scale = 100000;
    let x0 = x*scale;
    let y0 = y*scale;
    let x1 = 1 + y - a*x*x;
    let y1 = b*x;

    return Math.atan2(y1 - y0, x1 - x0);
  }

    // http://paulbourke.net/fractals/henonmap/
  thornFractal(x,y,a,b){
    let scale = 1;
    let c1 = a; 0.102
    let c2 = b; -0.04
    let x0 = x*scale;
    let y0 = y*scale;
    let x1 = x0/Math.cos(y0)+c1;
    let y1 = y0/Math.sin(x0)+c2;
  
    return Math.atan2(y1 - y0, x1 - x0);
  }

  getValue(x,y){
      //return (x + y) * 0.01 * Math.PI * 2;
      //return (x + y) * 0.001 * Math.PI * 2;
      //return (Math.sin(x * 0.01) + Math.sin(y * 0.0001)) * Math.PI * 2;
      return this.cliffordAttractor(x,y,this.cellSize,this.b,this.c,this.d);
      //return this.henonAttractor(x, y, this.a, this.b);
      //return this.thornFractal(x, y, this.a, this.b);
  }

  drawMethod0() {
    let rayon = this.cellSize / 2;

    for (let y = 0; y < this.#height; y += this.cellSize){
        for (let x = 0; x < this.#width; x += this.cellSize){
            let angle = this.getValue(x,y);

            let x0 = x + rayon;
            let y0 = y + rayon;

            let x1 = x0 + Math.cos(angle)*rayon;
            let y1 = y0 + Math.sin(angle)*rayon;

            let x2 = x0 + Math.cos(angle+Math.PI)*rayon*2;
            let y2 = y0 + Math.sin(angle+Math.PI)*rayon*2;

            this.#drawLine(x1, y1, x2, y2);
        }
    }
  }

  drawMethod1() {
    this.#radius += this.vr;
    if (this.#radius > 5  || this.#radius < -5) this.vr *= -1
    for (let y = 0; y < this.#height; y += this.cellSize){
        for (let x = 0; x < this.#width; x += this.cellSize){
            const angle = this.#getValue(x, y);
            this.#draw(angle,x,y);
        }
    }
  }

  animate(timeStamp){
    let deltaTime = timeStamp - this.lastTime;
    this.lastTime = timeStamp;

    this.drawMethod = this.drawMethod0;

    this.a = Math.random() * 4 - 2;
    this.b = Math.random() * 4 - 2;
    this.c = Math.random() * 4 - 2;
    this.d = Math.random() * 4 - 2;

    if (this.timer > this.interval){
        this.#ctx.clearRect(0, 0, this.#width, this.#height);

        this.drawMethod();

        this.timer = 0;
    } else {
        this.timer += deltaTime;
    }

    flowfieldAnimation = requestAnimationFrame(this.animate.bind(this));
  }
}