Code

float tolerance = 0.2;
float cellDim = 3.0;
int poly_render_step = 3;

//TODO: crash condition in the diagonal cases 

Metaball[] meta_list = new Metaball[4];

float[] vx = new float[4];
float[] vy = new float[4];


void setup()
{
  size(400,400);
  ellipseMode(CENTER);
  meta_list[0] = new Metaball();
  meta_list[1] = new Metaball();
  meta_list[2] = new Metaball();
  meta_list[3] = new Metaball();
  meta_list[0].x = 65;
  meta_list[0].y = 215;
  meta_list[0].r = 150;
  meta_list[1].x = 200;
  meta_list[1].y = 20;
  meta_list[1].r = 100;
  meta_list[2].x = 300;
  meta_list[2].y = 250;
  meta_list[2].r = 50;
  meta_list[3].x = 200;
  meta_list[3].y = 300;
  meta_list[3].r = 120;
  
  vx[0] = 1;
  vx[1] = -1;
  vx[2] = 1;
  vx[3] = -1;
  vy[0] = -1;
  vy[1] = 1;
  vy[2] = -1;
  vy[3] = 1;
  
}

void draw()
{
  background(155,155,255);
  Polygon[] p = marching_squares(meta_list);
  stroke(255,255,100);
  fill(195,195,255);
  strokeWeight(3);
    
  for (int i=0; i<p.length; i++)
    p[i].draw();

  for (int i=0; i<4; i++)
  {
    meta_list[i].x += vx[i];
    meta_list[i].y += vy[i];
    if (meta_list[i].x <= 0.0 || meta_list[i].x >= 400.0)
      vx[i] *= -1;
    if (meta_list[i].y <= 0.0 || meta_list[i].y >= 400.0)
      vy[i] *= -1;
  }
}

public class Metaball
{
  //public variables
  public float x=0, y=0, r=0;

  float testPoint(float tx, float ty)
  {
    float tr = dist(x,y,tx,ty);
    if (tr < r) return density_function(tr/r);
    else return 0.0;
  }

  float density_function(float t)
  {
    return (1.0f-t)*(1.0f-t);
  }
}

public class Polygon
{
  //consider all these read-only
  float[] xpts = new float[5];
  float[] ypts = new float[5];
  int length = 0;
  int capacity = 5;

  void addPoint(float x, float y)
  {
    if (length == capacity)
    {
      capacity *= 2;
      float[] nx = new float[capacity];
      float[] ny = new float[capacity];
      arraycopy(xpts,0,nx,0,length);
      arraycopy(ypts,0,ny,0,length);
      xpts = nx;
      ypts = ny;
    }
    xpts[length] = x;
    ypts[length] = y;
    length++;
  }

  void draw()
  {
    beginShape(POLYGON);
    for (int p=0; p<length; p+=poly_render_step)
      vertex(xpts[p],ypts[p]);
    endShape();
  }
}

Polygon[] marching_squares(Metaball[] cv)
{
  Polygon[] r_poly = new Polygon[cv.length];
  int x,y;
  boolean still_inside, tl, tr, bl, br, union_flag;
  int initial_edge_x, initial_edge_y, r_val, num_polys=0;
  clear_cache();
  clear_visits();

  noStroke();
  fill(0,255,255);

  for (int i=0; i<cv.length; i++)
  {
    //start at the cell containing the center
    //march right until you hit the edge
    x = floor(cv[i].x/cellDim);
    y = floor(cv[i].y/cellDim);
    union_flag = false;

    do
    {
      if (search_visits(x,y))
      {
        union_flag = true;
        break;
      }
      
      //debug_rendering
      //rect(x*cellDim, y*cellDim, cellDim, cellDim);
      
      //top-left corner
      r_val = search_cache(x,y);
      if (r_val == -1)
      {
        tl = inside_test(x,y,cv);
        cache_computation(x,y,tl);
      }
      else tl = (r_val==1);
      //bottom-left corner
      r_val = search_cache(x,y+1);
      if (r_val == -1)
      {
        bl = inside_test(x,y+1,cv);
        cache_computation(x,y+1,bl);
      }
      else bl = (r_val==1);
      //top-right corner
      r_val = search_cache(x+1,y);
      if (r_val == -1)
      {
        tr = inside_test(x+1,y,cv);
        cache_computation(x+1,y,tr);
      }
      else tr = (r_val==1);
      //bottom-right corner
      r_val = search_cache(x+1,y+1);
      if (r_val == -1)
      {
        br = inside_test(x+1,y+1,cv);
        cache_computation(x+1,y+1,br);
      }
      else br = (r_val==1);
      visit_cell(x,y);
      //Are we still in the metaball?
      if (still_inside = (tl && bl && tr && br))
        x ++;

    }
    while(still_inside);

    if(union_flag)
      continue;

    initial_edge_x = x;
    initial_edge_y = y;

    r_poly[num_polys] = new Polygon();

    do
    {
      visit_cell(x,y);
      //top-left corner
      r_val = search_cache(x,y);
      if (r_val == -1)
      {
        tl = inside_test(x,y,cv);
        cache_computation(x,y,tl);
      }
      else tl = (r_val==1);
      //bottom-left corner
      r_val = search_cache(x,y+1);
      if (r_val == -1)
      {
        bl = inside_test(x,y+1,cv);
        cache_computation(x,y+1,bl);
      }
      else bl = (r_val==1);
      //top-right corner
      r_val = search_cache(x+1,y);
      if (r_val == -1)
      {
        tr = inside_test(x+1,y,cv);
        cache_computation(x+1,y,tr);
      }
      else tr = (r_val==1);
      //bottom-right corner
      r_val = search_cache(x+1,y+1);
      if (r_val == -1)
      {
        br = inside_test(x+1,y+1,cv);
        cache_computation(x+1,y+1,br);
      }
      else br = (r_val==1);
      //now march clockwise around edge, building a polygon
      if (!tl && !tr && bl && !br) //CASE 1
      {
        r_poly[num_polys].addPoint((x+0.5)*cellDim, (y+1.0)*cellDim);
        y+=1.0;
      }
      else if (!tl && !tr && !bl && br) //CASE 2
      {
        r_poly[num_polys].addPoint((x+1.0)*cellDim, (y+0.5)*cellDim);
        x+=1.0;
      }
      else if (!tl && !tr && bl && br) //CASE 3
      {
        r_poly[num_polys].addPoint((x+1.0)*cellDim, (y+0.5)*cellDim);
        x+=1.0;
      }
      else if (!tl && tr && !bl && !br) //CASE 4
      {
        r_poly[num_polys].addPoint((x+0.5)*cellDim, (y+0.0)*cellDim);
        y-=1.0;
      }
      else if (!tl && tr && bl && !br) //CASE 5
      {
        //let's just call them attached
        //POSSIBLE BUG! FIX
        if (r_poly[num_polys].xpts[r_poly[num_polys].length-1] == (x+1.0)*cellDim)
        {
          r_poly[num_polys].addPoint((x+0.5)*cellDim, (y+1.0)*cellDim);
          y+=1.0;
        }
        else
        {
          r_poly[num_polys].addPoint((x+0.5)*cellDim, (y+0.0)*cellDim);
          y-=1.0;
        }
      }
      else if (!tl && tr && !bl && br) //CASE 6
      {
        r_poly[num_polys].addPoint((x+0.5)*cellDim, (y+0.0)*cellDim);
        y-=1.0;
      }
      else if (!tl && tr && bl && br) //CASE 7
      {
        r_poly[num_polys].addPoint((x+0.5)*cellDim, (y+0.0)*cellDim);
        y-=1.0;
      }
      else if (tl && !tr && !bl && !br) //CASE 8
      {
        r_poly[num_polys].addPoint((x+0.0)*cellDim, (y+0.5)*cellDim);
        x-=1.0;
      }
      else if (tl && !tr && bl && !br) //CASE 9
      {

        r_poly[num_polys].addPoint((x+0.5)*cellDim, (y+1.0)*cellDim);
        y+=1.0;
      }
      else if (tl && !tr && !bl && br) //CASE 10
      {
        //let's just call them attached
        //POSSIBLE BUG! FIX!
        if (r_poly[num_polys].ypts[r_poly[num_polys].length-1] == (y+0.0)*cellDim)
        {
          r_poly[num_polys].addPoint((x+1.0)*cellDim, (y+0.5)*cellDim);
          x+=1.0;
        }
        else
        {
          r_poly[num_polys].addPoint((x+0.0)*cellDim, (y+0.5)*cellDim);
          x-=1.0;
        }
      }
      else if (tl && !tr && bl && br) //CASE 11
      {
        r_poly[num_polys].addPoint((x+1.0)*cellDim, (y+0.5)*cellDim);
        x+=1.0;
      }
      else if (tl && tr && !bl && !br) //CASE 12
      {
        r_poly[num_polys].addPoint((x+0.0)*cellDim, (y+0.5)*cellDim);
        x-=1.0;
      }
      else if (tl && tr && bl && !br) //CASE 13
      {
        r_poly[num_polys].addPoint((x+0.5)*cellDim, (y+1.0)*cellDim);
        y+=1.0;
      }
      else if (tl && tr && !bl && br) //CASE 14
      {
        r_poly[num_polys].addPoint((x+0.0)*cellDim, (y+0.5)*cellDim);
        x-=1.0;
      }
      else
      {
        println("OH SHIT!");
      }
      //debug rendering
      //rect(x*cellDim, y*cellDim, cellDim, cellDim);
    }
    while(!(x == initial_edge_x && y == initial_edge_y));
    num_polys++;
  }
  
  if (num_polys < r_poly.length)
  {
    Polygon[] new_poly = new Polygon[num_polys];
    arraycopy(r_poly,0,new_poly,0,num_polys);
    r_poly = new_poly;
  }
  
  return r_poly;
}

CacheNode cacheHead;
VisitNode visitHead;

boolean inside_test(int x, int y, Metaball[] cv)
{
  float sum = 0.0;
  for (int i=0; i<cv.length; i++)
  {
    sum += cv[i].testPoint(x*cellDim,y*cellDim);
  }
  return (sum > tolerance);
}

void clear_visits()
{
  visitHead = null;
}

void visit_cell(int x, int y)
{
  VisitNode newNode = new VisitNode(x,y);
  newNode.next = visitHead;
  visitHead = newNode;
}

boolean search_visits(int x, int y)
{
  for (VisitNode c = visitHead; c!=null; c=c.next)
    if (c.x == x && c.y == y)
      return true;
  return false;
}

void clear_cache()
{
  cacheHead = null;
}

void cache_computation(int x, int y, boolean flag)
{
  CacheNode newNode = new CacheNode(x,y,flag);
  newNode.next = cacheHead;
  cacheHead = newNode;
}

int search_cache(int x, int y)
{
  for (CacheNode c = cacheHead; c!=null; c=c.next)
    if (c.x == x && c.y == y)
    {
      if (c.flag)
        return 1;
      else
        return 0;
    }
  return -1;
}

class VisitNode
{
  int x,y;
  VisitNode next = null;
  VisitNode(int u, int v)
  {
    x = u;
    y = v;
  }
}

class CacheNode
{
  int x,y;
  boolean flag;
  CacheNode next = null;
  CacheNode(int u, int v, boolean f)
  {
    x=u;
    y=v;
    flag=f;
  }
}

031 -- Experimenting with Curves: Due 10/11

Statement:Select a curve from the Mathworld curves site. I recommend selecting curves whose equations take the standard explicit form y = f(x), or curves which take the parametric form y = f(t), x = g(t). See this example for some code that deals with Polar curves in the form r = f(theta). (For some simple parametric curves to get started with, consider the Spirograph-like roulette curves.) Create a simple interaction in which the mouseX and mouseY are used to continuously govern two parameters of the curve. You may also want to look at these interactive applets for some more inspiration.

Connect the shape's internal parameters to the mouse in such a way as to develop a satisfying exploration of the graphic and dynamic possibilities of this shape.
Metaballs!

hide statement