package shual;

import p666.*;

// FOX IN A BUBBLE
// made for and inspired by www.shual.com
// by ariel malka | december 2004 | www.chronotext.org

// technically: it's a real-time posterized glowing meta-blob rendered through an error-diffusion screen
// on another level: i'm a geek and i've been dreaming to explore/code dithering and posterization for a while...

// CREDITS
// dithering algo (Jarvis, Judice & Ninke) adapted from: www.markschulze.net/halftone/
// glowing blobs algo inspired by: www.ghostagency.net/processing/glow/ (part of the sketchbook of Alessandro Capozzo)
// meta-inspiration and coding metaphors: www.processing.org

public class Boing extends BApplet
{
  int N_BLOBS = 4; // number of blobs
  float D = 67f; // rest distance between blobs
  float R = 0.15f; // inter-blob elasticity (max = 0.5)
  float BIG = 480000f; // affects blob radius and glow
  float DV = 0.5f; // blob motion speed

  public void setup()
  {
    size(400, 300);

    grid = new float[width][height]; // used for dithering
    blob_init();
  }

  public void loop()
  {
    blob_motion();

    // too lazy not to do the following manually...
    blob_constrain(0, 1);
    blob_constrain(0, 2);
    blob_constrain(0, 3);
    blob_constrain(1, 2);
    blob_constrain(1, 3);
    blob_constrain(2, 3);

    render();
    filter();
  }

  // --- RENDERING BLOBS (NOT TO THE SCREEN BUT TO THE DITHERING GRID) ---

  void render()
  {
    float[][] dst = grid;
    int w = width;
    int h = height;
    float[] bx = blob_x;
    float[] by = blob_y;

    for (int x = 0; x < w; x++)
    {
      float[] dst_x = dst[x];
      for (int y = 0; y < h; y++)
      {
        float dens = 0;
        for (int i = 0; i < N_BLOBS; i++)
        {
          float dx = x - bx[i];
          float dy = y - by[i];
          dens += BIG / (dx * dx + dy * dy);
        }
        if (dens > 255)
        {
          dens = 255;
        }
        dst_x[y] = (int) (dens / 32) * 32; // 256 / 32 = 8 (levels of posterization)
      }
    }
  }

  // --- ERROR-DIFFUSION DITHERING ---

  // Jarvis, Judice & Ninke
  float jarvisFilter[][] = { { 0 / 48f, 0 / 48f, 0 / 48f, 7 / 48f, 5 / 48f }, {
      3 / 48f, 5 / 48f, 7 / 48f, 5 / 48f, 3 / 48f }, {
      1 / 48f, 3 / 48f, 5 / 48f, 3 / 48f, 1 / 48f }
  };

  float grid[][];

  void filter()
  {
    int[] dst = pixels;
    float filter[][] = jarvisFilter; // hey, change me to "Stucki" or "Floyd & Steinberg"!
    int w = width;
    int h = height;
    float[][] src = grid;

    for (int x1 = 0; x1 < w; x1++)
    {
      float[] src_x1 = src[x1];
      for (int y1 = 0; y1 < h; y1++)
      {
        float error = src_x1[y1];
        if (error <= 127.5f)
        {
          dst[y1 * w + x1] = 0xffffff;
        }
        else
        {
          error -= 255;
          dst[y1 * w + x1] = 0x5cb976; // shual green
        }

        for (int ix = 0; ix < 3; ix++)
        {
          int x2 = x1 + ix;
          if (x2 < w)
          {
            float[] src_x2 = src[x2];
            float[] filter_x = filter[ix];
            for (int iy = -2; iy < 3; iy++)
            {
              int y2 = y1 + iy;
              if (y2 >= 0 && y2 < h)
              {
                src_x2[y2] += error * filter_x[iy + 2];
              }
            }
          }
        }
      }
    }
  }

  // --- BLOB STUFF ---

  float[] blob_x, blob_y;
  float[] blob_vx, blob_vy;

  void blob_init()
  {
    blob_x = new float[N_BLOBS];
    blob_y = new float[N_BLOBS];
    blob_vx = new float[N_BLOBS];
    blob_vy = new float[N_BLOBS];

    for (int i = 0; i < N_BLOBS; i++)
    {
      blob_x[i] = width / 2 + random(-D, D);
      blob_y[i] = height / 2 + random(-D, D);
    }
  }

  void blob_motion()
  {
    for (int i = 0; i < N_BLOBS; i++)
    {
      blob_vx[i] += random(-DV, DV);
      blob_vy[i] += random(-DV, DV);

      blob_x[i] += blob_vx[i];
      blob_y[i] += blob_vy[i];

      if (blob_x[i] < 0)
      {
        blob_x[i] = 0;
        blob_vx[i] *= -1;
      }
      else if (blob_x[i] > width)
      {
        blob_x[i] = width;
        blob_vx[i] *= -1;
      }

      if (blob_y[i] < 0)
      {
        blob_y[i] = 0;
        blob_vy[i] *= -1;
      }
      else if (blob_y[i] > height)
      {
        blob_y[i] = height;
        blob_vy[i] *= -1;
      }
    }
  }

  void blob_constrain(int n1, int n2)
  {
    float dx = blob_x[n2] - blob_x[n1];
    float dy = blob_y[n2] - blob_y[n1];
    float len = (float) Math.sqrt(dx * dx + dy * dy);
    float diff = (len - D) / len;
    dx *= R * diff;
    dy *= R * diff;

    blob_x[n1] += dx;
    blob_y[n1] += dy;
    blob_x[n2] -= dx;
    blob_y[n2] -= dy;
  }
}
