class Complex {
  constructor(real, imaginary){
    this.real = real;
    this.imaginary = imaginary;
  }
  copy(){
    return new Complex(this.real, this.imaginary)
  }
  square(){
    return new Complex(this.real*this.real - this.imaginary*this.imaginary, 2*this.real*this.imaginary);
  }
  add(o){
    return new Complex(this.real+o.real, this.imaginary+o.imaginary)
  }
  sub(o){
    return new Complex(this.real-o.real, this.imaginary-o.imaginary)
  }
  isFinite(){
    return isFinite(this.real) && isFinite(this.imaginary);
  }
  static zero(){
    return new Complex(0, 0);
  }
}
    
    class ComplexField {
  constructor(start, end, resX, resY) {
    this.start = start;
    this.end = end;
    this.resX = resX;
    this.resY = resY;
    
    this.field = new Array(resX);
    for (let r = 0; r <= resX; r++){
      this.field[r] = new Array(resY);
      for (let i = 0; i <= resY; i++){
        this.field[r][i] = new FieldPoint(this.indexToPoint(r, i));
      }
    }
    console.log(this.field[0].length);
  }
  
  stepSize(){
    let step = {};
    let size = this.end.sub(this.start);
    step.x = size.real/this.resX;
    step.y = size.imaginary/this.resY;
    return step;
  }
  
  indexToPoint(x, y){
    let step = this.stepSize();
    let unOff = new Complex(x*step.x, y*step.y);
    return unOff.add(this.start);
  }
  
}

class FieldPoint {
  constructor(pos){
    this.pos = pos;
    this.data = {};
  }
}

class MandelField extends ComplexField {
  constructor(start, end, resX, resY, theme){
    super(start, end, resX, resY);
    this.theme = theme;

    for (let r = 0; r <= resX; r++){
      for (let i = 0; i <= resY; i++){
        this.field[r][i].data.mandelTotal = Complex.zero();
        this.field[r][i].data.finite = true;
        set(r, i, color(this.theme.mandelShade));
      }
    }

    updatePixels();
    this.iters = 0;
  }
  
  mandelIteration() {
    for (let r = 0; r <= this.resX; r++){
      for (let i = 0; i <= this.resY; i++){
        let fieldPoint = this.field[r][i];
        if (fieldPoint.data.finite){
          fieldPoint.data.mandelTotal = fieldPoint.data.mandelTotal.square().add(fieldPoint.pos);
          fieldPoint.data.finite = fieldPoint.data.mandelTotal.isFinite();
          
          if (!fieldPoint.data.finite)
            set(r, i, this.itersToColour());
          if (!fieldPoint.data.finite && this.iters == 0 && this.theme.relative)
            this.iters = 1;
        }
      }
    }
    updatePixels();
    if (!this.theme.relative || this.iters != 0)
      this.iters ++;
  }
  itersToColour(){
    let f = this.iters/(this.theme.decayRate+this.iters)
    return 256*(1-this.theme.decayToWhite+(2*this.theme.decayToWhite-1)*f);
  }
  
}

class MandelTheme{
  constructor(mandelShade, decayToWhite, decayRate, relative = 0){
    this.mandelShade = mandelShade;
    this.decayToWhite = decayToWhite;
    this.decayRate = decayRate;
    this.relative = relative;
  }
}
    
    let field;
let theme;

let mouseStartX;
let mouseStartY;

let mouseDown = false;

function mandelSetup(start, end, w) {
    let size = end.sub(start);
    let h = floor(w * size.imaginary / size.real);

    resizeCanvas(w, h);
    for (let x = 0; x < width; x++)
        for (let y = 0; y < height; y++)
            set(x, y, color(256));
    updatePixels();
    field = new MandelField(start, end, w, h, theme);
}

function setup() {
    sketchesCreateCanvas(600, 600);
    rectMode(CORNERS);
    noFill();

    //theme = new MandelTheme(15, 0, 35); // White to black
    //theme = new MandelTheme(0, 0, 700); // Classic
    //theme = new MandelTheme(0, 0, 3); // Dark
    //theme = new MandelTheme(256, 1, 70); // Black to white
    //theme = new MandelTheme(256, 1, 700); // Inverse classic
    //theme = new MandelTheme(0, 1, 20); // High Contrast
    theme = new MandelTheme(256, 1, 500, 1); // Small Level

    mandelSetup(new Complex(-2, -1.5), new Complex(1, 1.5), 600)
}

function draw() {
    field.mandelIteration();

    if (mouseDown) {
        stroke(25, 200, 255);
        rect(mouseStartX, mouseStartY, mouseX, mouseY);
    }
}

function mousePressed(event) {
    if (event.button != 0 || event.target.id != "defaultCanvas0") return;

    mouseStartX = mouseX;
    mouseStartY = mouseY;

    mouseDown = true;
}

function mouseReleased(event) {
    if (event.button != 0 || event.target.id != "defaultCanvas0" || !mouseDown) return;

    let mouseEnd = field.indexToPoint(mouseX, mouseY);
    let mouseStart = field.indexToPoint(mouseStartX, mouseStartY);

    mouseDown = false;

    mandelSetup(mouseStart, mouseEnd, 600)
}
    
    
Return to Sketch