import processing.core.*; 
import processing.xml.*; 

import java.applet.*; 
import java.awt.Dimension; 
import java.awt.Frame; 
import java.awt.event.MouseEvent; 
import java.awt.event.KeyEvent; 
import java.awt.event.FocusEvent; 
import java.awt.Image; 
import java.io.*; 
import java.net.*; 
import java.text.*; 
import java.util.*; 
import java.util.zip.*; 
import java.util.regex.*; 

public class sketch_aug25a extends PApplet {



// How much will each vertex "wiggle" (note: relative to distance from origin (first vertex in curvy))
final float WIGGLE_MODIFIER = 0.01f;  
// As a curvy grows, the next vertex can be within this many pixels of the last one
final int POINT_SPACING_MAX_DISTANCE = 50;
// Set to true if you have some red/cyan 3D glasses handy.
final boolean MODE_3D = false;
// When in 3D mode, how much stereo separation? (note: relative to distance from origin)
final float OFFSET_3D = 0.05f;
// maximum number of points (vertices) for one curvy
final int MAX_POINTS = 300;
// maximum number of curvys (faster systems can possibly increase this)
final int MAX_CURVYS = 25;
// a 'z' portion of a vertex can be between 0 and this number
final int MAX_Z_DISTANCE = 450;
// initial angle of rotation
float myAngle = 0.0f;
// initail scale
float myScale = 0.0f;
// the amount the angle will change each draw cycle
final float ANGLE_CHANGE = 0.001f;
// the maximum scale until zooming in stops
final float MAX_SCALE = 0.75f;

ArrayList<Curvy> curvys = new ArrayList<Curvy>();

// when creating a new curvy, use this function.  Will return the correct type of
// curvy depending on 3D mode.
public Curvy GetNewCurvy()
{
  if ( MODE_3D )
    return new Curvy3D();
  else
    return new Curvy();
}
// The maximum number of curvys needs to be cut in half when in 3D mode as there are
// twice as many splines being drawn.
public int GetMaxCurvys()
{
  if ( MODE_3D )
    return PApplet.parseInt(MAX_CURVYS/2.0f);
  else
    return MAX_CURVYS;
}

public void setup()
{
  size(800, 600, P3D);
  smooth();  
  curvys.add(GetNewCurvy());  // get things started
  //pushMatrix();
}

public void draw()
{   
  // clear the screen
  background(0);
  
  // you can zoom in and then rotate if you uncomment the next line
  //if ( !zoomIn() ) rotateScreen();
  
  // or you can do both at the same time with the next 2 lines
  zoomIn();
  rotateScreen();  
    
  // tell each curvy to draw itself
  for ( int i=0; i < curvys.size(); i++ )
  {
    Curvy thisCurvy = curvys.get(i);
    thisCurvy.draw();
  }

  // possibly add another curvy
  if ( !curvys.isEmpty() )
  {
    Curvy lastCurvy = curvys.get(curvys.size()-1);
    if ( lastCurvy.HasGrown() )
    {
      if ( curvys.size() >= GetMaxCurvys() )
      {
        // too many curvys, pick one at random and kill it
        curvys.remove((int)random(0,curvys.size()-1));
      }
      // add a new curvy
      curvys.add(GetNewCurvy());
    }
  }
 
}

public void rotateScreen()
{
  myAngle += ANGLE_CHANGE;
  if(myAngle >= TWO_PI || myAngle < 0)
    myAngle = 0.0f;   
  
  translate(width/2, height/2);
  
  rotateX(myAngle);
  rotateY(myAngle);
  
  translate(width/-2, height/-2);
  
}

public boolean zoomIn()
{
  if ( myScale < MAX_SCALE )
  {   
    myScale += 0.005f * (MAX_SCALE - myScale);
  }
    translate(width/2,height/2);
    scale(myScale);
    translate(-width/2,-height/2);
    
    return myScale < MAX_SCALE;
}

class Curvy
{
  // true if adding points, false if removing them
  protected boolean growing=true;  
  // holds all of the vertices for this spline
  protected ArrayList<Point> allPoints = new ArrayList<Point>();
  // will set to true when MAX_POINTS has been reached
  protected boolean hasGrown = false;
  
  protected Colour myColour = new Colour();
  protected class Colour
  {
    public int r,g,b;
    public Colour() 
    { 
      r=(int)random(0,255);
      g=(int)random(0,255);
      b=(int)random(0,255);
    }
  }
  
  protected class Point
  {
    public int x;
    public int y;
    public int z;
    
    public Point()
    {
      // assume center of the screen
      // until told otherwise
      x=(int)width/2;
      y=(int)height/2;
      z=0;
    }
    
    public void Move(float modifier)
    {
      x += random(-modifier,modifier+1);// for some unknown reason this
      y += random(-modifier,modifier+1);// tends toward negative,       
      z += random(-modifier,modifier+1);// add 1 to positive to balance it out
      
      // stay on the screen
      if ( x > width ) x = width;
      if ( x < 0 ) x = 0;
      if ( y > height ) y = height;
      if ( y < 0 ) y = 0;
      if ( z > MAX_Z_DISTANCE ) z = MAX_Z_DISTANCE;
      if ( z < 0 ) z = 0;
    }
  }

  public void draw()
  {
    if ( growing )
    {
      if (allPoints.size() < MAX_POINTS )
        addPoint();
      else
      {
        growing=false;
        hasGrown=true;
      }
    }
  
    if ( !growing )
    {
      if ( allPoints.size() > 0 )
        removePoint();
      else
        growing=true;
    }
   
    movePoints();  
    try
    {  
      drawCurves();  
    } catch (Exception ex)
    {
    }
    
  }
 
  public boolean HasGrown() { return hasGrown; }
  
  protected void addPoint()
  {
    Point lastPoint = null;
    if ( !allPoints.isEmpty() )    
      lastPoint = allPoints.get(allPoints.size()-1);
    
    Point thisPoint = new Point();
    if ( null == lastPoint )
    {
      thisPoint.x = (int)random(0,width);
      thisPoint.y = (int)random(0,height);
      thisPoint.z = (int)random(0, MAX_Z_DISTANCE);
    }
    else
    {
      thisPoint.x = lastPoint.x + (int)random(-POINT_SPACING_MAX_DISTANCE, POINT_SPACING_MAX_DISTANCE);
      thisPoint.y = lastPoint.y + (int)random(-POINT_SPACING_MAX_DISTANCE, POINT_SPACING_MAX_DISTANCE);
      thisPoint.z = lastPoint.z + (int)random(-POINT_SPACING_MAX_DISTANCE, POINT_SPACING_MAX_DISTANCE);
      if ( thisPoint.x > width )  thisPoint.x = width;
      if ( thisPoint.x < 0 ) thisPoint.x = 0;
      if ( thisPoint.y > height ) thisPoint.y = height;
      if ( thisPoint.y < 0 ) thisPoint.y = 0;
      if ( thisPoint.z > MAX_Z_DISTANCE ) thisPoint.z = MAX_Z_DISTANCE;
      if ( thisPoint.z < 0 ) thisPoint.z = 0;
    }
    
    allPoints.add(thisPoint);
  }
  
  protected void removePoint()
  {
    allPoints.remove(allPoints.size()-1);
  }
  
  protected void movePoints()
  {
    for ( int i=1; i < allPoints.size(); i++ )
    {
      Point thisPoint = allPoints.get(i);
      thisPoint.Move(i * WIGGLE_MODIFIER);
    }
  }
  
  protected void drawCurves()
  {
    if ( allPoints.isEmpty() )
      return;
      
    noFill();    
    stroke (myColour.r, myColour.g, myColour.b);
  
    beginShape();
    Point startPoint = allPoints.get(0);
    Point endPoint = allPoints.get(allPoints.size()-1);
    
    curveVertex(startPoint.x, startPoint.y, startPoint.z);
    for ( int i=0; i < allPoints.size(); i++ )
    {    
      Point thisPoint = allPoints.get(i);
      curveVertex(thisPoint.x, thisPoint.y, thisPoint.z);
    }
    curveVertex(endPoint.x, endPoint.y, endPoint.z);
    endShape();
  }
}

class Curvy3D extends Curvy
{
  protected void drawCurves()
  {
    if ( allPoints.isEmpty() )
      return;
      
    drawCurves("red");
    drawCurves("cyan");
  }
  
  private void drawCurves(String colour)
  {
    noFill();
    
    if ( "red" == colour )
    {
      stroke(255, 0, 0);
    }
    else 
    {
      stroke(0, 255, 255);
    }
  
    beginShape();
    Point startPoint = allPoints.get(0);
    Point endPoint = allPoints.get(allPoints.size()-1);
    
    curveVertex(startPoint.x, startPoint.y, startPoint.z);
    for ( int i=0; i < allPoints.size(); i++ )
    {    
      Point thisPoint = allPoints.get(i);
      
      if ( "cyan" == colour )
      {
        int offset = (int)(i * OFFSET_3D);
        curveVertex(thisPoint.x+offset, thisPoint.y+offset, thisPoint.z+offset);        
      }
      else 
      {
        curveVertex(thisPoint.x, thisPoint.y, thisPoint.z);
      }
    }
    curveVertex(endPoint.x, endPoint.y, endPoint.z);
    endShape();
  }
}


  static public void main(String args[]) {
    PApplet.main(new String[] { "--bgcolor=#F0F0F0", "sketch_aug25a" });
  }
}
