Accidentally made this, while originally trying to do something else.

import java.util.*;

PShape masterShape;
Grid grid;
ArrayList<Bubble> bubbles;
int yMarker = 0;
int xMarker = 0;
boolean rightMouseDown = false;
ArrayList<Point> send;

////////////////////////////////////////////////////////////////////
boolean showMouse = true;
int bubbleAlpha = 50;
int backGround = color(0);
int circleSegments = 40;

// set this to add some noise value to vector p, based on coords x, y
public void noiseFunction(float x, float y, PVector p) {

  // perlin noise  
  p.x+= (noise(x*0.01, y*0.01) - 0.5) * 2.0;
  p.y+= (noise(x*0.01 + 3000, y*0.01) - 0.5) * 2.0;

  float ang = atan2(y - height*0.5, x - width*0.5);
  float dist = dist(x, y, width*0.5, height*0.5);

  // ziggy zag (like m3)
  //p.x+= cos(ang+HALF_PI)*sin(dist * 0.02);  
  //p.y+= sin(ang+HALF_PI)*sin(dist * 0.02);
}

// called by the mouse bubble for colors
public int randCol() {
  return color(random(127)+127, random(127)+127, random(127)+127);
}
////////////////////////////////////////////////////////////////////

public void setup() {
  size(1000, 1000, P2D);
  send = new ArrayList<Point>();
  grid = new Grid(0, 0, width, height);
  bubbles = new ArrayList<Bubble>();
  //bubbles.add(new Bubble(width>>1, height>>1, 200, 10, 0));
}

public void draw() {
  background(backGround);
  masterShape = createShape(GROUP);
  if (bubbles.size() > 0) {
    Iterator<Bubble> it = bubbles.listIterator();
    while (it.hasNext ()) {
      Bubble b = it.next();
      if (b.update()) it.remove();
       else b.display(bubbleAlpha, masterShape);
    }
  }
  if (masterShape != null) shape(masterShape);
  if (showMouse) showMouseShape();

}

public void showMouseShape() {
    if (rightMouseDown) {
    fill(0, 10);
    if (send.size() > 0) {
      stroke(0);
      for (int i = 0; i < send.size(); i++) {
        int i2 = (i+1) % send.size();
        line(send.get(i2).pos.x, send.get(i2).pos.y, send.get(i).pos.x, send.get(i).pos.y);   
      }
      fill(0);
      ellipse(send.get(0).pos.x, send.get(0).pos.y,5,5);
      noFill();
    }
    if (send.size() < 2) {
      stroke(64);
      float size = dist(xMarker, yMarker, mouseX, mouseY) * 2.0;
      ellipse(xMarker, yMarker, size, size);   
    }
  }
}

public class Bubble {
  ArrayList<Point> points;
  float collapse = 5;
  float expand = 11;    // more than double the collapse value
  int col = 0;
  boolean dead = false;
  public Bubble() {
    points = new ArrayList<Point>();
  }
  public Bubble(float x, float y, float r, int seg, int col) {
    this();
    for (int i = 0; i < seg; i++) {
      float ang = i*TWO_PI/seg;
      points.add(new Point( x+cos(ang)*r, y+sin(ang)*r ));
    }
    this.col = col;
  }
  public Bubble(ArrayList<Point> list, int col) {
    this();
    points.addAll(list);
    this.col = col;
  }

  public boolean update() {
    if (points.size() > 1) {               // if there is only 1 point it can't grow.                                               
      for (Point p : points) {
        grid.sendForce( p );
        p.update();
        //p.display();
      }
      /// I think my logic here is sound, 'vortexes' can cause the points to grow though
      // check for points that are too close, or need to be split
      // the index will 'follow' an object if it's previous is deleted
      ArrayList<Point> pointsToAdd = new ArrayList<Point>();                 // remove points too close to index, until the index is flagged or ignored  
      for (int i = points.size () -1; i > 0; i--) {                         // go backwards so objects aren't skipped when one is deleted
        float distbetween = points.get(i).distance(points.get(i-1));     
        if (distbetween > expand) {                                      
          pointsToAdd.add(points.get(i));                               // flag the current one .add adds before the object at the index
        } else if (distbetween < collapse) {
          points.remove(i-1);                                           // don't remove the current index because we may have previously flagged it to be increased
        }
      }
      // check the first index against the last if there is more than 2
      if (points.size() > 1) {
        float distbetween = points.get(0).distance(points.get(points.size()-1));
        if (distbetween > expand) {
          pointsToAdd.add(points.get(0));
        } else if (distbetween < collapse) {
          points.remove(0);                                             // remove 0, because it couldn't have been flagged to add a point (in previous above loop)
        }
      }

      // add new points marked by pointsToAdd
      if (pointsToAdd.size() > 1 && points.size() > 1)                      // there are points to add and at least a point  
        for (Point p : pointsToAdd) {
          int index = points.indexOf(p);
          int i2 = (index + points.size() - 1) % points.size();

          points.add(index, new Point((points.get(index).pos.x + points.get(i2).pos.x)*0.5, (points.get(index).pos.y + points.get(i2).pos.y)*0.5));
        }
      return false;
    } else {
      return true;
    }
  }

  public void display(PShape group) {
    display( 255, group );
  }
  public void display(int alpha, PShape group) {
    if (points.size() > 2) {
      PShape shape = createShape();
      shape.beginShape();
      shape.noStroke();
      shape.fill( col, alpha );
      for (Point p : points) {
        shape.vertex( p.pos.x, p.pos.y );
      }
      shape.endShape();
      group.addChild(shape);
    }
  }
}

public class Point {
  PVector pos;
  PVector vel;
  PVector forceSum;
  boolean accelerate = false;
  public Point ( float x, float y, float xv, float yv) {
    pos = new PVector( x, y );
    vel = new PVector( xv, yv );
    forceSum = new PVector( 0, 0 );
  }
  public Point( float x, float y ) {
    this ( x, y, 0, 0 );
  }

  public void update() {
    if (accelerate) {
      vel.add( forceSum );
    } else {
      vel.x = forceSum.x;
      vel.y = forceSum.y;
    }

    pos.add( vel );

    forceSum.x = 0;
    forceSum.y = 0;
  }

  public void display() {
    noStroke();
    fill(0);
    ellipse((int)pos.x, (int)pos.y, 4, 4);
  }

  public float distance( Point p ) {
    return dist( pos.x, pos.y, p.pos.x, p.pos.y );
  }

  public float mDistance( Point p ) {                                
    return max( abs( p.pos.x - pos.x ), abs( p.pos.y - pos.y ) );
  }
}


public class Grid {
  PVector[] f;
  int xg, yg, w, h;
  public Grid(int xx, int yy, int ww, int hh) {
    xg = xx;
    yg = yy;
    w = abs(w);
    h = abs(h); 
    if (w > 0 && h > 0) {                      // grid has dimensions
      f = new PVector[w*h];
      for (int r = 0; r < h; r++) {
        for (int c = 0; c < w; c++) {
          addNoise( c, r, f[c + r*w] );
        }
      }
    }
  }

  public void addNoise(float x, float y, PVector p) {  // for different grids with different noiseFunctions
    noiseFunction(x, y, p);                            // did this so the noise can be a method up near draw
  }

  public void sendForce( Point p ) {
    if (f != null) {
      int xi = (int)p.pos.x - xg;                        // position in grid
      int yi = (int)p.pos.y - yg; 
      if (xi >= 0 && xi < w && yi >= 0 && yi < h ) {     // inside grid 
        p.forceSum.add(f[xi + yi * w]);
        return;
      }
    }
    addNoise(p.pos.x, p.pos.y, p.forceSum);                                 // refer to the noise function if value isn't a saved one
  }
}

public void mousePressed() {
  if ((mouseButton == LEFT) && rightMouseDown) {
    send.add(new Point(mouseX, mouseY));
  }
  if (mouseButton == RIGHT) {
    rightMouseDown = true;
    xMarker = mouseX;
    yMarker = mouseY;
  }
}

public void mouseReleased() {
  if (mouseButton == RIGHT) {
    if (send.size() > 1) {
      bubbles.add(new Bubble(send, randCol() ));
      send.clear();
    } else {
      bubbles.add(new Bubble(xMarker, yMarker, dist(xMarker, yMarker, mouseX, mouseY), circleSegments, randCol() ));
    }
    rightMouseDown = false;
  }
}
/r/processing Thread Link - youtube.com