> show canvas only <


/* built with Studio Sketchpad: 
 *   https://sketchpad.cc
 * 
 * observe the evolution of this sketch: 
 *   https://studio.sketchpad.cc/sp/pad/view/ro.fUr8IvmG6tF/rev.51
 * 
 * authors: 
 *   GoToLoop

 * license (unless otherwise specified): 
 *   creative commons attribution-share alike 3.0 license.
 *   https://creativecommons.org/licenses/by-sa/3.0/ 
 */ 



/**
 * Grid Random Walkers (v2.3.0)
 * by  HowardCornwell (2014/Aug)
 * mod GoToLoop
 *
 * Forum.Processing.org/two/discussion/6838/1d-array-affecting-2d-array#Item_4
 * Studio.ProcessingTogether.com/sp/pad/export/ro.91FXxn8BG2fPL
 */

static final boolean JAVA = 1/2 != 1/2.;
static final int GRID = 20, DOTS = 4, FPS = 30;

final Box[][] grid = new Box[GRID][GRID];
final Point[] points = new Point[DOTS];

PImage bg;

void setup() {
  size(800, 600);
  smooth();
  frameRate(FPS);

  colorMode(HSB, Point.HUES, 1, 1);
  rectMode(CORNER);
  ellipseMode(CENTER);

  strokeWeight(Point.WEIGHT);
  stroke(Point.STROKE);

  final int wg = width / GRID, hg = height / GRID;
  BoxData.dx = wg - BoxData.GAP;
  BoxData.dy = hg - BoxData.GAP;

  for (int i = 0; i != DOTS; points[i++] = new Point());

  for (int row = 0; row != GRID; ++row)  for (int col = 0; col != GRID; 
    grid[row][col] = new Box(col++ * wg, row * hg));

  bg = createGrid();
}

void draw() {
  set(0, 0, bg);

  for (final Point p : points) {
  NextPoint:
    for (final Box[] boxes : grid)  for (final Box b : boxes)
      if (p.isInside(b)) {
        b.fillBox(p.hueColor);
        break NextPoint;
      }
    p.script();
  }
}

PImage createGrid() {
  final PGraphics pg = createGraphics(width, height, JAVA2D);

  pg.beginDraw();
  pg.background(0);

  pg.smooth();
  pg.rectMode(CORNER);

  pg.strokeWeight(BoxData.WEIGHT);
  pg.stroke(BoxData.STROKE);
  pg.noFill();

  for (final Box[] boxes : grid)  for (final Box b : boxes)  b.drawBox(pg);

  pg.endDraw();
  return pg.get();
}

static abstract class BoxData {
  static final int STROKE = -1, WEIGHT = 1, GAP = 5;
  static int dx, dy;
}

final class Box extends BoxData {
  final short x, y;

  Box(final int xx, final int yy) {
    x = (short) xx;
    y = (short) yy;
  }

  void fillBox(final color c) {
    fill(c, 1, 1);
    rect(x, y, dx, dy);
  }

  void drawBox(final PGraphics pg) {
    pg.rect(x, y, dx, dy);
  }
}

final class Point {
  static final int STROKE = 0, WEIGHT = 1, HUES = 360;
  static final int DIAM = 8, SPD = 20, HUE_STEP = 2;

  static final float NOISE_OFFSET = JAVA? .4538 : .467;
  static final float MAX_NOISE_INC = JAVA? .2 : .15;

  float x, y, xoff, yoff, inc;
  color hueColor;

  Point() {
    reset();
  }

  void reset() {
    noiseSeed((int) random(MAX_INT >> 6) * (second() + 1));

    x = .5 * width;
    y = .5 * height;

    xoff = random(-SPD, SPD);
    yoff = random(-SPD, SPD);

    inc = random(-MAX_NOISE_INC, MAX_NOISE_INC);

    hueColor = (int) random(HUES);
  }

  void script() {
    move();
    cycleHue();
    display();
  }

  void move() {
    x += SPD * (noise(xoff += inc) - NOISE_OFFSET);
    y += SPD * (noise(yoff += inc) - NOISE_OFFSET);

    if (x > width || x < 0 || y > height || y < 0)  reset();
  }

  void cycleHue() {
    hueColor = (hueColor + HUE_STEP) % HUES;
  }

  void display() {
    fill(hueColor, 1, 1);
    ellipse(x, y, DIAM, DIAM);
  }

  boolean isInside(final Box b) {
    return x > b.x && x < b.x + Box.dx && y > b.y && y < b.y + Box.dy;
  }
}