• fullscreen
  • Arrow.pde
  • Blob.pde
  • Cloud.pde
  • Delaunay.pde
  • Face.pde
  • Global.pde
  • Line.pde
  • Mass.pde
  • Muscle.pde
  • Particle.pde
  • Plane.pde
  • Point.pde
  • Polygon.pde
  • Polyline.pde
  • Randomizer.pde
  • Ribbon.pde
  • Rigid.pde
  • Segment.pde
  • Shadow.pde
  • Spring.pde
  • Terrain.pde
  • Vector.pde
  • Voronoi.pde
  • golterrain.pde
  • //------ ARROW ------//
    //arrows are mostly used for rendering vectors and things in 3d :: they're not very effecient for every-frame rendering
    class Arrow extends Segment{
      Point p3,p4,p5,p6;
      float arrow_size = 10;
      Arrow(Point $p1,Point $p2){
        super($p1,$p2);
        p3 = new Point(p1.pos.x+l-arrow_size,p1.pos.y+arrow_size/2,p1.pos.z);
        p4 = new Point(p1.pos.x+l-arrow_size,p1.pos.y-arrow_size/2,p1.pos.z);
        p5 = new Point(p1.pos.x+l-arrow_size,p1.pos.y,p1.pos.z+arrow_size/2);
        p6 = new Point(p1.pos.x+l-arrow_size,p1.pos.y,p1.pos.z-arrow_size/2);
        step();
      }
      void step(){
        l = get_length();
        float rz = atan2(p2.pos.y-p1.pos.y,p2.pos.x-p1.pos.x);
        Point temp = new Point(0,0,0);
        temp.match(p2);
        temp.rotate(cos(-rz),sin(-rz),"z",p1.pos);
        float azi = atan2(temp.pos.z-p1.pos.z,temp.pos.x-p1.pos.x);
        float cosa1 = cos(azi);
        float sina1 = sin(azi);
        float cosa2 = cos(rz);
        float sina2 = sin(rz);
        p3.move_to(new PVector(p1.pos.x+l-arrow_size,p1.pos.y+arrow_size/2,p1.pos.z));
        p3.rotate(cosa1,sina1,"y",p1.pos);
        p3.rotate(cosa2,sina2,"z",p1.pos);
        p4.move_to(new PVector(p1.pos.x+l-arrow_size,p1.pos.y-arrow_size/2,p1.pos.z));
        p4.rotate(cosa1,sina1,"y",p1.pos);
        p4.rotate(cosa2,sina2,"z",p1.pos);
        p5.move_to(new PVector(p1.pos.x+l-arrow_size,p1.pos.y,p1.pos.z+arrow_size/2));
        p5.rotate(cosa1,sina1,"y",p1.pos);
        p5.rotate(cosa2,sina2,"z",p1.pos);
        p6.move_to(new PVector(p1.pos.x+l-arrow_size,p1.pos.y,p1.pos.z-arrow_size/2));
        p6.rotate(cosa1,sina1,"y",p1.pos);
        p6.rotate(cosa2,sina2,"z",p1.pos);
      }
      void render(PGraphics $pg){
        super.render($pg);
        $pg.beginShape(TRIANGLE_STRIP);
        $pg.vertex(p3.pos.x,p3.pos.y,p3.pos.z);
        $pg.vertex(p2.pos.x,p2.pos.y,p2.pos.z);
        $pg.vertex(p5.pos.x,p5.pos.y,p5.pos.z);
        $pg.vertex(p2.pos.x,p2.pos.y,p2.pos.z);
        $pg.vertex(p4.pos.x,p4.pos.y,p4.pos.z);
        $pg.vertex(p2.pos.x,p2.pos.y,p2.pos.z);
        $pg.vertex(p6.pos.x,p6.pos.y,p6.pos.z);
        $pg.vertex(p2.pos.x,p2.pos.y,p2.pos.z);
        $pg.vertex(p3.pos.x,p3.pos.y,p3.pos.z);
        $pg.endShape();
        $pg.beginShape();
        $pg.vertex(p3.pos.x,p3.pos.y,p3.pos.z);
        $pg.vertex(p5.pos.x,p5.pos.y,p5.pos.z);
        $pg.vertex(p4.pos.x,p4.pos.y,p4.pos.z);
        $pg.vertex(p6.pos.x,p6.pos.y,p6.pos.z);
        $pg.endShape(CLOSE);
      }
    }
    
    //------ BLOB ------//
    //blobs are groups of curved polygons made from proximity tests within one or more particle clouds
    class Blob{
      int npolygons,nclouds,nparticles;
      float cohesion_distance = 40;
      float outline_distance = 100;
      Polygon[] polygons = new Polygon[1000];
      Polygon[] outlines = new Polygon[1000];
      Cloud[] clouds = new Cloud[100];
      Particle[] particles = new Particle[1000];
      Blob(Cloud[] $clouds){
        npolygons = nclouds = nparticles = 0;
        for(int i=0;i<$clouds.length;i++){
          if($clouds[i]==null) continue;
          add_cloud($clouds[i]);
        }
      }
      void add_cloud(Cloud $c){
        clouds[nclouds++] = $c;
        for(int i=0;i<$c.nparticles;i++) particles[nparticles++] = $c.particles[i];
      }
      Polygon[] get_groups(){
        int ngroups = 0;
        Polygon[] groups = new Polygon[nparticles];
        boolean[] grouped = new boolean[nparticles];
        for(int i=0;i<nparticles;i++){
          if(grouped[i]) continue;
          groups[ngroups++] = new Polygon(get_group(particles[i],grouped));
        }
        groups = (Polygon[]) subset(groups,0,ngroups);
        return groups;
      }
      Particle[] get_group(Particle $p,boolean[] $grouped){
        Particle[] group = new Particle[1];
        group[0] = $p;
        for(int i=0;i<nparticles;i++){
          if($grouped[i]) continue;
          if($p.get_distance_to(particles[i])<=cohesion_distance){
            $grouped[i] = true;
            group = (Particle[]) concat(group,get_group(particles[i],$grouped));
          }
        }
        return group;
      }
      void step(){
        for(int i=0;i<nclouds;i++) clouds[i].step();
        polygons = get_groups();
        npolygons = polygons.length;
        for(int i=0;i<npolygons;i++) outlines[i] = polygons[i].get_outline(outline_distance);
      }
      void render(PGraphics $pg){
        for(int i=0;i<npolygons;i++){
          if(outlines[i]!=polygons[i]){
            if(outlines[i]==null) continue;
            if(outlines[i].is_complex()) continue;
            outlines[i].render($pg,"curve");
          }
        }
        /*
        for(int i=0;i<nclouds;i++){
          clouds[i].render($pg);
        }
        */
      }
    }
    
    //------ CLOUD ------//
    //clouds are groups of particles
    class Cloud{
      int nparticles,npullers,npushers;
      Particle[] particles = new Particle[1000];
      Particle[] pullers = new Particle[100];
      Particle[] pushers = new Particle[100];
      boolean wrapping = false; boolean bouncing = false; boolean locked = false;
      boolean puller_wrapping = false; boolean puller_bouncing = true; boolean puller_locked = true;
      boolean pusher_wrapping = false; boolean pusher_bouncing = true; boolean pusher_locked = true;
      float wander_speed = 0.1; float attract_distance = 10; float attract_speed = 0.07; float repel_distance = 10; float repel_speed = 0.1;
      float puller_wander_speed = 0.01; float puller_attract_distance = 1000; float puller_attract_speed = 0.1; float puller_repel_distance = 0; float puller_repel_speed = 0;
      float pusher_wander_speed = 0.01; float pusher_attract_distance = 0; float pusher_attract_speed = 0; float pusher_repel_distance = 100; float pusher_repel_speed = 0.1;
      PVector gravity = new PVector(0,0.4,0);
      float friction = 0.01; float bounce_friction = 0.05; float dampening = 0;
      Cloud(Particle[] $particles){
        nparticles = npullers = npushers = 0;
        for(int i=0;i<$particles.length;i++){
          if($particles[i]==null) continue;
          add_particle($particles[i]);
        }
      }
      Cloud(Particle[] $particles,Particle[] $pullers,Particle[] $pushers){
        nparticles = npullers = npushers = 0;
        for(int i=0;i<$particles.length;i++){
          if($particles[i]==null) continue;
          add_particle($particles[i]);
        }
        for(int i=0;i<$pullers.length;i++){
          if($pullers[i]==null) continue;
          add_puller($pullers[i]);
        }
        for(int i=0;i<$pushers.length;i++){
          if($pushers[i]==null) continue;
          add_pusher($pushers[i]);
        }
      }
      void add_particle(Particle $p){
        $p.wrapping = wrapping; $p.bouncing = bouncing; $p.locked = locked;
        $p.wander_speed = wander_speed; $p.attract_distance = attract_distance; $p.attract_speed = attract_speed; $p.repel_distance = repel_distance; $p.repel_speed = repel_speed;
        $p.gravity = gravity; $p.friction = friction; $p.bounce_friction = bounce_friction; $p.dampening = dampening;
        particles[nparticles++] = $p;
      }
      void add_puller(Particle $p){
        $p.wrapping = puller_wrapping; $p.bouncing = puller_bouncing; $p.locked = puller_locked;
        $p.wander_speed = puller_wander_speed; $p.attract_distance = puller_attract_distance; $p.attract_speed = puller_attract_speed; $p.repel_distance = puller_repel_distance; $p.repel_speed = puller_repel_speed;
        $p.gravity = gravity; $p.friction = friction; $p.bounce_friction = bounce_friction; $p.dampening = dampening;
        pullers[npullers++] = $p;
      }
      void add_pusher(Particle $p){
        $p.wrapping = pusher_wrapping; $p.bouncing = pusher_bouncing; $p.locked = pusher_locked;
        $p.wander_speed = pusher_wander_speed; $p.attract_distance = pusher_attract_distance; $p.attract_speed = pusher_attract_speed; $p.repel_distance = pusher_repel_distance; $p.repel_speed = pusher_repel_speed;
        $p.gravity = gravity; $p.friction = friction; $p.bounce_friction = bounce_friction; $p.dampening = dampening;
        pushers[npushers++] = $p;
      }
      void apply_force(PVector $force){
        for(int i=0;i<nparticles;i++) particles[i].vel.add($force);
      }
      void wander(boolean $z){
        for(int i=0;i<nparticles;i++) particles[i].wander($z);
      }
      void attract(){
        for(int i=0;i<nparticles;i++){
          for(int j=i+1;j<nparticles;j++) particles[i].attract(particles[j]);
        }
      }
      void repel(){
        for(int i=0;i<nparticles;i++){
          for(int j=i+1;j<nparticles;j++) particles[i].repel(particles[j]);
        }
      }
      void scale_speeds(float $s){
        wander_speed *= $s; attract_speed *= $s; repel_speed *= $s;
        puller_wander_speed *= $s; puller_attract_speed *= $s; puller_repel_speed *= $s;
        pusher_wander_speed *= $s; pusher_attract_speed *= $s; pusher_repel_speed *= $s;
        for(int i=0;i<nparticles;i++){
          particles[i].wander_speed = wander_speed; particles[i].attract_speed = attract_speed; particles[i].repel_speed = repel_speed;
        }
        for(int i=0;i<npullers;i++){
          pullers[i].wander_speed = puller_wander_speed; pullers[i].attract_speed = puller_attract_speed; pullers[i].repel_speed = puller_repel_speed;
        }
        for(int i=0;i<npushers;i++){
          pushers[i].wander_speed = pusher_wander_speed; pushers[i].attract_speed = pusher_attract_speed; pushers[i].repel_speed = pusher_repel_speed;
        }
      }
      void step(){
        for(int i=0;i<npullers;i++){
          for(int j=0;j<nparticles;j++){
            pullers[i].attract(particles[j]);
            pullers[i].repel(particles[j]);
          }
        }
        for(int i=0;i<npushers;i++){
          for(int j=0;j<nparticles;j++){
            pushers[i].attract(particles[j]);
            pushers[i].repel(particles[j]);
          }
        }
        for(int i=0;i<nparticles;i++) particles[i].step();
        for(int i=0;i<npullers;i++) pullers[i].step();
        for(int i=0;i<npushers;i++) pushers[i].step();
      }
      void render(PGraphics $pg,boolean $spherical){
        for(int i=0;i<nparticles;i++) particles[i].render($pg,$spherical);
        for(int i=0;i<npullers;i++) pullers[i].render($pg,$spherical);
        for(int i=0;i<npushers;i++) pushers[i].render($pg,$spherical);
      }
    }
    
    //------ DELAUNAY ------//
    //constructs a delaunay triangulation from a set of points
    class Delaunay{
      int nfaces,npoints;
      Face boundary;
      Face[] faces = new Face[5000];
      Point[] points = new Point[1000];
      Delaunay(Point[] $points,Face $boundary){
        nfaces = npoints = 0;
        boundary = faces[nfaces++] = $boundary;
        for(int i=0;i<$points.length;i++){
          if($points[i]==null) continue;
          add_point($points[i]);
        }
      }
      void synchronize(){
        int ntemp = npoints;
        nfaces = npoints = 0;
        faces[nfaces++] = boundary;
        for(int i=0;i<ntemp;i++) add_point(points[i]);
      }
      void add_point(Point $p){
        for(int i=0;i<npoints;i++){
          if($p.get_distance_to(points[i])<=global.distance_tolerance) return;
        }
        Point[] ps = new Point[3]; ps[0] = $p;
        points[npoints++] = $p;
        Segment[] segments = new Segment[500];
        int nsegments = 0;
        for(int i=0;i<nfaces;i++){
          if(faces[i].is_point_inside_circumcircle($p)){
            segments[nsegments++] = faces[i].segments[0];
            segments[nsegments++] = faces[i].segments[1];
            segments[nsegments++] = faces[i].segments[2];
            faces[i] = null;
          }
        }
        int duplicates;
        for(int i=0;i<nsegments;i++){
          duplicates = 0;
          for(int j=0;j<nsegments;j++){
            if(segments[i].is_identical(segments[j])){ duplicates++; }
          }
          if(duplicates==1){
            ps[1] = segments[i].p1; ps[2] = segments[i].p2;
            faces[nfaces++] = new Face(ps);
          }
        }
        cleanup();
      }
      void cleanup(){
        Face[] temp = new Face[2000];
        int ntemp = 0;
        for(int i=0;i<nfaces;i++){
          if(faces[i]!=null) temp[ntemp++] = faces[i];
        }
        arraycopy(temp,faces);
        nfaces = ntemp;
      }
      void subdivide(){
        Point[] icps = {};
        for(int i=0;i<nfaces;i++){
          if(faces[i].p1==boundary.p1||faces[i].p2==boundary.p1||faces[i].p3==boundary.p1||faces[i].p1==boundary.p2||faces[i].p2==boundary.p2||faces[i].p3==boundary.p2||faces[i].p1==boundary.p3||faces[i].p2==boundary.p3||faces[i].p3==boundary.p3) continue;
          if(faces[i]==null) continue;
          icps = (Point[]) append(icps,faces[i].get_incenter_point());
        }
        for(int i=0;i<icps.length;i++) add_point(icps[i]);
      }
      Point get_line_intersect_point(Line $l){
        //this will return the first face it finds :: fine for vertical projects or relatively vertical projects onto a relatively smooth terrain
        Face f = get_line_intersect_face($l);
        if(f==null) return null;
        return f.get_line_intersect_point($l);
      }
      Face get_line_intersect_face(Line $l){
        //this will return the first face it finds :: fine for vertical projects or relatively vertical projects onto a relatively smooth terrain
        for(int i=0;i<nfaces;i++) if(faces[i].is_line_intersecting($l)) return faces[i];
        return null;
      }
      Point[] get_edge_connected_points(Point $p){
        Point[] temp = {};
        Point[] ps = {};
        for(int i=0;i<nfaces;i++){
          if(faces[i].p1==$p||faces[i].p2==$p||faces[i].p3==$p){
            temp = (Point[]) append(temp,faces[i].p1);
            temp = (Point[]) append(temp,faces[i].p2);
            temp = (Point[]) append(temp,faces[i].p3);
          }
        }
        for(int i=0;i<temp.length;i++){
          boolean found = false;
          for(int j=0;j<ps.length;j++){
            if(temp[i]==ps[j]){
              found = true;
              break;
            }
          }
          if(!found&&temp[i]!=$p) ps = (Point[]) append(ps,temp[i]);
        }
        return ps;
      }
      void smooth(float $weight,int $iterations){
        //for best results, use relatively low weights and high iteration counts
        float[] temp = new float[npoints];
        for(int i=0;i<npoints;i++){
          Point[] neighbors = get_edge_connected_points(points[i]);
          if(neighbors==null) continue;
          temp[i] = points[i].pos.z*(1-$weight);
          for(int j=0;j<neighbors.length;j++) temp[i] += neighbors[j].pos.z*($weight/neighbors.length);
        }
        for(int i=0;i<npoints;i++) points[i].pos.z = temp[i];
        if($iterations>1) smooth($weight,$iterations-1);
      }
      void render(PGraphics $pg){
        for(int i=0;i<nfaces;i++) faces[i].render($pg);
      }
      void render(PGraphics $pg,boolean $bounds){
        if($bounds){
          render($pg);
          return;
        }
        for(int i=0;i<nfaces;i++){
          if(faces[i].p1==boundary.p1||faces[i].p2==boundary.p1||faces[i].p3==boundary.p1||faces[i].p1==boundary.p2||faces[i].p2==boundary.p2||faces[i].p3==boundary.p2||faces[i].p1==boundary.p3||faces[i].p2==boundary.p3||faces[i].p3==boundary.p3) continue;
          faces[i].render($pg);
        }
      }
      void render(PGraphics $pg,PImage $img,float $s){
        for(int i=0;i<nfaces;i++){
          if(faces[i].p1==boundary.p1||faces[i].p2==boundary.p1||faces[i].p3==boundary.p1||faces[i].p1==boundary.p2||faces[i].p2==boundary.p2||faces[i].p3==boundary.p2||faces[i].p1==boundary.p3||faces[i].p2==boundary.p3||faces[i].p3==boundary.p3) continue;
          faces[i].render($pg,$img,$s);
        }
      }
    }
    
    //------ FACE ------//
    //faces are 3-sided polygons that have a few extra functions relating to triangulation and culling
    class Face extends Polygon{
      Point p1,p2,p3,icp,ccp;
      Plane pl;
      float ccr;
      Face(Point[] $points){
        super($points);
        p1 = $points[0]; p2 = $points[1]; p3 = $points[2];
        pl = new Plane(p1,p2,p3);
        icp = get_incenter_point();
        ccp = get_circumcenter_point();
        ccr = get_circumcircle_radius();
      }
      void orient(Point $p){
        if(pl.get_point_side($p)>0) flip();
      }
      void flip(){
        Point temp = p2;
        p2 = p3; p3 = temp;
        pl = new Plane(p1,p2,p3);
      }
      boolean is_acute(){
        float d1,d2,d3,x,h,y,a1,a2,a3;
        d1 = p1.get_distance_to(p2); d2 = p2.get_distance_to(p3); d3 = p3.get_distance_to(p1);
        x = (sq(d1)+sq(d2)-sq(d3))/(2*d2); h = sqrt(sq(d1)-sq(x)); y = d2-x;
        a1 = (atan(h/x)+PI)%PI; a2 = (atan(h/y)+PI)%PI; a3 = PI-(a1+a2);
        if(a1<HALF_PI&&a2<HALF_PI&&a3<HALF_PI) return true;
        return false;
      }
      float get_circumcircle_radius(){
        return ccp.get_distance_to(p1);
      }
      Point get_incenter_point(){
        float d1,d2,d3;
        d1 = p2.get_distance_to(p3); d2 = p3.get_distance_to(p1); d3 = p1.get_distance_to(p2);
        return new Point(((d1*p1.pos.x)+(d2*p2.pos.x)+(d3*p3.pos.x))/(d1+d2+d3),((d1*p1.pos.y)+(d2*p2.pos.y)+(d3*p3.pos.y))/(d1+d2+d3),((d1*p1.pos.z)+(d2*p2.pos.z)+(d3*p3.pos.z))/(d1+d2+d3));
      }
      Point get_circumcenter_point(){
        Point mid12,mid23; Line l12,l23; Segment s;
        pl.nv = pl.get_normal();
        PVector p12 = p2.pos.get(); p12.sub(p1.pos);
        PVector p23 = p3.pos.get(); p23.sub(p2.pos);
        PVector n12 = pl.nv.dir.cross(p12); PVector n23 = pl.nv.dir.cross(p23);
        mid12 = new Point((p2.pos.x+p1.pos.x)/2,(p2.pos.y+p1.pos.y)/2,(p2.pos.z+p1.pos.z)/2); mid23 = new Point((p3.pos.x+p2.pos.x)/2,(p3.pos.y+p2.pos.y)/2,(p3.pos.z+p2.pos.z)/2);
        l12 = new Line(mid12,new Point(mid12.pos.x+n12.x,mid12.pos.y+n12.y,mid12.pos.z+n12.z));
        l23 = new Line(mid23,new Point(mid23.pos.x+n23.x,mid23.pos.y+n23.y,mid23.pos.z+n23.z));
        return l12.get_segment_to_line(l23).p1;
      }
      boolean is_point_inside_circumcircle(Point $p){
        if(ccp.get_distance_to($p)<=ccr) return true;
        return false;
      }
      boolean is_line_intersecting(Line $l){
        if(!pl.is_line_intersecting($l)) return false;
        Point p = pl.get_line_intersect_point($l);
        if(p==null) return false;
        return is_point_inside(p);
      }
      boolean is_segment_intersecting(Segment $s,boolean $inclusive){
        Line l = new Line($s.p1,$s.p2);
        if(!pl.is_line_intersecting(l)) return false;
        Point p = pl.get_line_intersect_point(l);
        if($s.get_distance_to(p.pos)>global.distance_tolerance) return false;
        return is_point_inside(p);
      }
      boolean is_point_inside(Point $p){
        PVector v1 = p1.pos.get(); v1.sub($p.pos); v1.normalize();
        PVector v2 = p2.pos.get(); v2.sub($p.pos); v2.normalize();
        PVector v3 = p3.pos.get(); v3.sub($p.pos); v3.normalize();
        float a1 = acos(min(max(v1.dot(v2),-1),1));
        float a2 = acos(min(max(v2.dot(v3),-1),1));
        float a3 = acos(min(max(v3.dot(v1),-1),1));
        if(abs((a1+a2+a3)-TWO_PI)<=global.angle_tolerance) return true;
        return false;
      }
      Point get_line_intersect_point(Line $l){
        if(!is_line_intersecting($l)) return null;
        return pl.get_line_intersect_point($l);
      }
      boolean cull(){
        float p1x,p1y,p2x,p2y,p3x,p3y;
        p1x = screenX(p1.pos.x,p1.pos.y,p1.pos.z); p1y = screenY(p1.pos.x,p1.pos.y,p1.pos.z);
        p2x = screenX(p2.pos.x,p2.pos.y,p2.pos.z); p2y = screenY(p2.pos.x,p2.pos.y,p2.pos.z);
        p3x = screenX(p3.pos.x,p3.pos.y,p3.pos.z); p3y = screenY(p3.pos.x,p3.pos.y,p3.pos.z);
        if(((p2y-p1y)/(p2x-p1x)-(p3y-p1y)/(p3x-p1x)<0)^(p1x<=p2x==p1x>p3x)) return false;
        return true;
      }
      Face[] subdivide(boolean $incenter){
        if($incenter){
          icp = get_incenter_point();
          return subdivide(icp);
        }else{
          Point m12,m23,m31; Point[] temp = new Point[3]; Face[] faces = new Face[4];
          m12 = new Point((p1.pos.x+p2.pos.x)/2,(p1.pos.y+p2.pos.y)/2,(p1.pos.z+p2.pos.z)/2);
          m23 = new Point((p2.pos.x+p3.pos.x)/2,(p2.pos.y+p3.pos.y)/2,(p2.pos.z+p3.pos.z)/2);
          m31 = new Point((p3.pos.x+p1.pos.x)/2,(p3.pos.y+p1.pos.y)/2,(p3.pos.z+p1.pos.z)/2);
          temp = new Point[] {p1,m12,m31};
          faces[0] = new Face(temp);
          temp = new Point[] {p2,m23,m12};
          faces[1] = new Face(temp);
          temp = new Point[] {p3,m31,m23};
          faces[2] = new Face(temp);
          temp = new Point[] {m12,m23,m31};
          faces[3] = new Face(temp);
          return faces;
        }
      }
      Face[] subdivide(Point $p){
        if(!is_point_inside($p)) return null;
        Point[] temp = new Point[3]; Face[] faces = new Face[3];
        temp = new Point[]{p1,p2,$p};
        faces[0] = new Face(temp);
        temp = new Point[]{p2,p3,$p};
        faces[1] = new Face(temp);
        temp = new Point[]{p3,p1,$p};
        faces[2] = new Face(temp);
        return faces;
      }
      void render(PGraphics $pg,boolean $cull){
        if(!$cull||!cull()){
          super.render($pg);
        }
        //pl.render($pg,icp);
      }
    }
    
    //------ GLOBAL ------//
    //the global class holds all other default classes
    //its only purpose is for organization and to imitate actionscript
    class Global{
      Point zero_point,error_point;
      float[] sines,cosines;
      int w,h,d;
      Polygon bounds;
      Face circumscribed_face;
      float distance_tolerance = .001;
      float simplify_distance_tolerance = 5;
      float slope_tolerance = .001;
      float simplify_slope_tolerance = .01;
      float angle_tolerance = .001;
      int angles = 720;
      Global(int $w,int $h,int $d){
        w = $w; h = $h; d = $d;
      }
      void init(){
        Point[] ps = new Point[4]; ps[0] = new Point(-w/2,-h/2,0); ps[1] = new Point(w/2,-h/2,0); ps[2] = new Point(w/2,h/2,0); ps[3] = new Point(-w/2,h/2,0);
        bounds = new Polygon(ps);
        ps = new Point[3]; ps[0] = new Point(0,-h/2-sqrt(sq(w)-sq(w/2)),0); ps[1] = new Point(-w/2-h/sqrt(3),h/2,0); ps[2] = new Point(w/2+h/sqrt(3),h/2,0);
        circumscribed_face = new Face(ps);
        zero_point = new Point(0,0,0);
        error_point = new Point(-9999,-9999,-9999);
        sines = new float[angles]; cosines = new float[angles];
        for(int i=0;i<angles;i++){
          sines[i] = (float) Math.sin(((float) i/angles)*TWO_PI);
          cosines[i] = (float) Math.cos(((float) i/angles)*TWO_PI);
        }
      }
      float constrainX(float $x){
        return min(w/2,max(-w/2,$x));
      }
      float constrainY(float $y){
        return min(h/2,max(-h/2,$y));
      }
      float constrainZ(float $z){
        return min(0,max(-d,$z));
      }
      Point random_point(){
        //xy coordinates only
        return new Point(random_coord());
      }
      Point random_point(boolean $z){
        if($z){
          return new Point(random_coord(true));
        }else{
          return random_point();
        }
      }
      PVector random_coord(){
        //xy coordinates only
        return new PVector(random(-w/2,w/2),random(-h/2,h/2),0);
      }
      PVector random_coord(boolean $z){
        if($z){
          return new PVector(random(-w/2,w/2),random(-h/2,h/2),random(-d/2,d/2));
        }else{
          return random_coord();
        }
      }
      float sin(float $n){
        while($n<0) $n += TWO_PI;
        return sines[(int)(angles*($n%TWO_PI)/TWO_PI)];
      }
      float cos(float $n){
        while($n<0) $n += TWO_PI;
        return cosines[(int)(angles*($n%TWO_PI)/TWO_PI)];
      }
      float randomize(float $n,float $r){
        return $n+random(-$r/2,$r/2);
      }
      void render(PGraphics $pg){
        $pg.line(-w/2,-h/2,d/2,-w/2,-h/2,-d/2);
        $pg.line(-w/2,h/2,d/2,-w/2,h/2,-d/2);
        $pg.line(w/2,h/2,d/2,w/2,h/2,-d/2);
        $pg.line(w/2,-h/2,d/2,w/2,-h/2,-d/2);
        $pg.line(-w/2,-h/2,-d/2,-w/2,h/2,-d/2);
        $pg.line(-w/2,h/2,-d/2,w/2,h/2,-d/2);
        $pg.line(w/2,h/2,-d/2,w/2,-h/2,-d/2);
        $pg.line(w/2,-h/2,-d/2,-w/2,-h/2,-d/2);
      }
    }
    
    //------ LINE ------//
    class Line{
      float a,b;
      Segment s;
      Line(Point $p1,Point $p2){
        if($p2.pos.x-$p1.pos.x!=0){
          a = ($p2.pos.y-$p1.pos.y)/($p2.pos.x-$p1.pos.x);
        }else{
          a = 999999999;
        }
        b = $p1.pos.y-a*$p1.pos.x;
        s = new Segment($p1,$p2);
      }
      void crop(){
        if(global.bounds.is_splittable(this)) s = get_polygon_chord(global.bounds);
      }
      boolean is_ray_intersecting(PVector $xy,boolean $inclusive){
        //xy coordinates only :: for inside-outside testing, etc. :: ray points right, starting from the given coordinates
        float pbua1 = (s.p2.pos.x-s.p1.pos.x)*($xy.y-s.p1.pos.y)-(s.p2.pos.y-s.p1.pos.y)*($xy.x-s.p1.pos.x);
        float pbu2 = s.p2.pos.y-s.p1.pos.y;
        if(pbu2==0) return false;
        float pbua = pbua1/pbu2;
        if($inclusive){
          if(pbua>=0) return true;
        }else{
          if(pbua>0) return true;
        }
        return false;
      }
      boolean is_line_intersecting(Line $l){
        //xy coordinates only
        float pbu2 = (s.p2.pos.y-s.p1.pos.y)*($l.s.p2.pos.x-$l.s.p1.pos.x)-(s.p2.pos.x-s.p1.pos.x)*($l.s.p2.pos.y-$l.s.p1.pos.y);
        if(pbu2==0){ return false; }
        return false;
      }
      boolean is_coord_on(PVector $xyz){
        float d = get_distance_to($xyz);
        if(d<=global.distance_tolerance){ return true; }
        return false;
      }
      Segment get_polygon_chord(Polygon $po){
        //xy coordinates only
        Point[] temp1 = new Point[$po.nsegments];
        int n1 = 0;
        for(int i=0;i<$po.nsegments;i++){
          if($po.segments[i].is_line_intersecting(this,true)) temp1[n1++] = $po.segments[i].get_intersect_point(s);
        }
        if(n1>=2){
          if(n1!=2){
            //remove duplicates in case of on-point intersection
            Point[] temp2 = new Point[n1];
            int n2 = 0;
            for(int i=0;i<n1;i++){
              boolean found = false;
              for(int j=0;j<n2;j++){
                if(temp1[i].get_distance_to(temp2[j])<global.distance_tolerance){
                  found = true;
                  break;
                }
              }
              if(!found) temp2[n2++] = temp1[i];
            }
            if(n2==2) return new Segment(new Point(temp2[0].pos.get()),new Point(temp2[1].pos.get()));
          }else{
            return new Segment(new Point(temp1[0].pos.get()),new Point(temp1[1].pos.get()));
          }
        }
        return null;
      }
      Point get_closest_point(PVector $xyz){
        float u = (($xyz.x-s.p1.pos.x)*(s.p2.pos.x-s.p1.pos.x)+($xyz.y-s.p1.pos.y)*(s.p2.pos.y-s.p1.pos.y)+($xyz.z-s.p1.pos.z)*(s.p2.pos.z-s.p1.pos.z))/sq(dist(s.p1.pos.x,s.p1.pos.y,s.p1.pos.z,s.p2.pos.x,s.p2.pos.y,s.p2.pos.z));
        return new Point(s.p1.pos.x+u*(s.p2.pos.x-s.p1.pos.x),s.p1.pos.y+u*(s.p2.pos.y-s.p1.pos.y),s.p1.pos.z+u*(s.p2.pos.z-s.p1.pos.z));
      }
      float get_distance_to(PVector $xyz){
        Point p = get_closest_point($xyz);
        return $xyz.dist(p.pos);
      }
      Segment get_segment_to_line(Line $l){
        //must test for zero distance before using this function
        float p13x,p13y,p13z,p43x,p43y,p43z,p21x,p21y,p21z,d1343,d4321,d1321,d4343,d2121,pbmua,pbmub;
        p13x = s.p1.pos.x-$l.s.p1.pos.x; p13y = s.p1.pos.y-$l.s.p1.pos.y; p13z = s.p1.pos.z-$l.s.p1.pos.z;
        p43x = $l.s.p2.pos.x-$l.s.p1.pos.x; p43y = $l.s.p2.pos.y-$l.s.p1.pos.y; p43z = $l.s.p2.pos.z-$l.s.p1.pos.z;
        p21x = s.p2.pos.x-s.p1.pos.x; p21y = s.p2.pos.y-s.p1.pos.y; p21z = s.p2.pos.z-s.p1.pos.z;
        d1343 = p13x*p43x+p13y*p43y+p13z*p43z; d4321 = p43x*p21x+p43y*p21y+p43z*p21z; d1321 = p13x*p21x+p13y*p21y+p13z*p21z; d4343 = p43x*p43x+p43y*p43y+p43z*p43z; d2121 = p21x*p21x+p21y*p21y+p21z*p21z;
        pbmua = (d1343*d4321-d1321*d4343)/(d2121*d4343-d4321*d4321);
        pbmub = (d1343+d4321*pbmua)/d4343;
        Point p1 = new Point(s.p1.pos.x+pbmua*p21x,s.p1.pos.y+pbmua*p21y,s.p1.pos.z+pbmua*p21z);
        Point p2 = new Point($l.s.p1.pos.x+pbmub*p43x,$l.s.p1.pos.y+pbmub*p43y,$l.s.p1.pos.z+pbmub*p43z);
        return new Segment(p1,p2);
      }
      void render(PGraphics $pg){
        s.render($pg);
      }
    }
    
    //------ MASS ------//
    //masses can maintain their radius from other masses and can be a part of a spring system
    class Mass extends Particle{
      Mass(PVector $pos,PVector $vel,float $r,float $density,boolean $forces){
        super($pos,$vel,$r,$forces);
        bouncing = true;
        density = $density;
        m = PI*sq(r)*density;
      }
      Mass(PVector $pos,float $r,float $density,boolean $forces){
        super($pos,$r,$forces);
        bouncing = true;
        density = $density;
        m = PI*sq(r)*density;
      }
      void bump(Mass $m){
        //xy coordinates only
        float d = dist($m.pos.x,$m.pos.y,pos.x,pos.y);
        float rr = $m.r+r;
        if(d<rr&&d!=0){
          float a = atan2($m.pos.y-pos.y,$m.pos.x-pos.x);
          float cosa = cos(a);
          float sina = sin(a);
          PVector v1 = new PVector(cosa*vel.x+sina*vel.y,cosa*vel.y-sina*vel.x);
          PVector v2 = new PVector(cosa*$m.vel.x+sina*$m.vel.y,cosa*$m.vel.y-sina*$m.vel.x);
          float p = v1.x*m+v2.x*$m.m;
          float v = v1.x-v2.x;
          v1.x = (p-$m.m*v)/(m+$m.m);
          v2.x = v+v1.x;
          vel.set(cosa*v1.x-sina*v1.y,cosa*v1.y+sina*v1.x,vel.z);
          $m.vel.set(cosa*v2.x-sina*v2.y,$m.vel.y = cosa*v2.y+sina*v2.x,$m.vel.z);
          float diff = bounce_friction*((r+$m.r)-d)/2;
          float cosd = cosa*diff;
          float sind = sina*diff;
          pos.sub(cosd,sind,0);
          $m.pos.add(cosd,sind,0);
        }
      }
    }
    
    //------ MUSCLE ------//
    //a spring that expands and contracts repeatedly
    class Muscle extends Spring{
      float ttl,ottl;
      int frame,frames;
      Muscle(Mass $m1,Mass $m2,float $k,float $dampening,float $tl,float $ttl,int $frames){
        super($m1,$m2,$k,$dampening,$tl);
        ttl = ottl = $ttl;
        frames = $frames;
        frame = 0;
      }
      void step(){
        frame++;
        tl = otl+(ttl-otl)/2+cos(PI+TWO_PI*(frame%frames)/frames)*(ttl-otl)/2;
        super.step();
      }
    }
    
    
    //------ PARTICLE ------//
    //the particle class is stage-aware, in that it can wrap its position from one side of the screen to the other
    //particles have a constant mass, but have a display radius
    class Particle extends Point{
      PVector vel,ovel,acc;
      float r;
      boolean wrapping = false; boolean bouncing = false; boolean locked = false;
      float wander_speed = 0.05; float attract_distance = 5; float attract_speed = 0.005; float repel_distance = 5; float repel_speed = 0;
      PVector gravity = new PVector(0,0,0);
      float friction = 0; float bounce_friction = 0; float dampening = 0;
      float m = 1;
      float density = 0.2;
      Particle(PVector $pos,PVector $vel,float $r,boolean $forces){
        super($pos);
        vel = $vel; r = $r;
        ovel = new PVector();
        ovel.set(vel);
        acc = new PVector();
        if(!$forces){
          wander_speed = 0;
          attract_speed = 0;
          repel_speed = 0;
          gravity = new PVector();
          friction = 0;
          bounce_friction = 0;
          dampening = 0;
        }
      }
      Particle(PVector $pos,float $r,boolean $forces){
        super($pos);
        r = $r;
        vel = new PVector();
        ovel = new PVector();
        acc = new PVector();
        if(!$forces){
          wander_speed = 0;
          attract_speed = 0;
          repel_speed = 0;
          gravity = new PVector();
          friction = 0;
          bounce_friction = 0;
          dampening = 0;
        }
      }
      void attract(Particle $p){
        if(attract_speed==0||(locked&&$p.locked)) return;
        float d = get_distance_to($p);
        if(d<attract_distance&&d>global.distance_tolerance){
          PVector dxyz = $p.pos.get(); dxyz.sub(pos);;
          float f = (attract_speed*m*$p.m)/d; 
          if(!locked&&!$p.locked){
            acc.add(dxyz.x*(f/2)/m,dxyz.y*(f/2)/m,dxyz.z*(f/2)/m);
            $p.acc.sub(dxyz.x*(f/2)/$p.m,dxyz.y*(f/2)/$p.m,dxyz.z*(f/2)/$p.m);
          }else if(locked){
            $p.acc.sub(dxyz.x*f/$p.m,dxyz.y*f/$p.m,dxyz.z*f/$p.m);
          }else{
            acc.add(dxyz.x*f/m,dxyz.y*f/m,dxyz.z*f/m);
          }
        }
      }
      void repel(Particle $p){
        if(repel_speed==0||(locked&&$p.locked)) return;
        float d = get_distance_to($p);
        if(d<repel_distance){
          PVector dxyz = $p.pos.get(); dxyz.sub(pos);
          float f = (repel_speed*m*$p.m)/d; 
          if(!locked&&!$p.locked){
            acc.sub(dxyz.x*(f/2)/m,dxyz.y*(f/2)/m,dxyz.z*(f/2)/m);
            $p.acc.add(dxyz.x*(f/2)/$p.m,dxyz.y*(f/2)/$p.m,dxyz.z*(f/2)/$p.m);
          }else if(locked){
            $p.acc.add(dxyz.x*f/$p.m,dxyz.y*f/$p.m,dxyz.z*f/$p.m);
          }else{
            acc.sub(dxyz.x*f/m,dxyz.y*f/m,dxyz.z*f/m);
          }
        }
      }
      void wander(boolean $z){
        if(wander_speed==0) return;
        acc.add(random(-wander_speed,wander_speed),random(-wander_speed,wander_speed),0);
        if($z) acc.add(0,0,random(-wander_speed,wander_speed));
      }
      void step(){
        if(!locked) acc.add(gravity);
        vel.add(acc);
        vel.mult(1.0-friction);
        vel.mult(1.0-dampening);
        move(vel);
        acc.set(0,0,0);
        ovel.set(vel);
        if(bouncing) bounce();
        if(wrapping) wrap();
      }
      void bounce(){
        if(pos.x>global.w/2-r||pos.x<-global.w/2+r){
          vel.x *= -(1-bounce_friction);
          pos.x = constrain(pos.x,-global.w/2+r,global.w/2-r);
        }
        if(pos.y>global.h/2-r||pos.y<-global.h/2+r){
          vel.y *= -(1-bounce_friction);
          pos.y = constrain(pos.y,-global.h/2+r,global.h/2-r);
        }
        if(pos.z>-r||pos.z<-global.d+r){
          vel.z *= -(1-bounce_friction);
          pos.z = constrain(pos.z,-global.d,0);
        }
      }
      void wrap(){
        //xy coordinates only
        if(pos.x>global.w/2-r||pos.x<-global.w/2+r){
          pos.x += 3*global.w/2;
          pos.x %= global.w;
          pos.x -= global.w/2;
        }
        if(pos.y>global.h/2-r||pos.y<-global.h/2+r){
          pos.y += 3*global.h/2;
          pos.y %= global.h;
          pos.y -= global.h/2;
        }
      }
      void render(PGraphics $pg){
        if(r>0.5){
          $pg.pushMatrix();
          $pg.translate(pos.x,pos.y,pos.z);
          $pg.ellipse(0,0,r*2,r*2);
          $pg.popMatrix();
        }else{
          $pg.point(pos.x,pos.y,pos.z);
        }
      }
      void render(PGraphics $pg,boolean $spherical){
        if($spherical&&r>0.5){
          $pg.pushMatrix();
          $pg.translate(pos.x,pos.y,pos.z);
          $pg.sphereDetail(ceil(3*sqrt(r)));
          $pg.sphere(r);
          $pg.popMatrix();
        }else{
          render($pg);
        }
      }
    }
    
    //------ PLANE ------//
    class Plane{
      Vector nv;
      float d;
      Point p1,p2,p3;
      PVector v12,v23,v31;
      Plane(Point $p1,Point $p2,Point $p3){
        p1 = $p1; p2 = $p2; p3 = $p3;
        v12 = p2.pos.get(); v12.sub(p1.pos);
        v23 = p3.pos.get(); v23.sub(p2.pos);
        v31 = p1.pos.get(); v31.sub(p3.pos);
        nv = new Vector(0,0,0);
        nv = get_normal();
        d = get_d();
      }
      Vector get_normal(){
        v12.set(p2.pos); v12.sub(p1.pos);
        v23.set(p3.pos); v23.sub(p2.pos);
        nv.dir.set(v12.cross(v23));
        nv.dir.normalize();
        return nv;
      }
      float get_d(){
        return -nv.dir.dot(p1.pos);
      }
      int get_point_side(Point $p){
        nv = get_normal();
        d = get_d();
        float side = nv.dir.dot($p.pos)-d;
        if(side<0) return 1;
        if(side>0) return -1;
        return 0;
      }
      boolean is_plane_parallel(Plane $pl){
        nv = get_normal();
        $pl.nv = $pl.get_normal();
        PVector v = nv.dir.get(); v.sub($pl.nv.dir);
        float m = v.mag();
        if(m==0||m==2) return true;
        return false;
      }
      boolean is_line_intersecting(Line $l){
        nv = get_normal();
        float pbmud = nv.dir.x*($l.s.p2.pos.x-$l.s.p1.pos.x)+nv.dir.y*($l.s.p2.pos.y-$l.s.p1.pos.y)+nv.dir.z*($l.s.p2.pos.z-$l.s.p1.pos.z);
        if(pbmud==0) return false; //plane and line do not intersect
        return true;
      }
      boolean is_segment_intersecting(Segment $s,boolean $inclusive){
        float pbmud,pbmu;
        nv = get_normal();
        pbmud = nv.dir.x*($s.p2.pos.x-$s.p1.pos.x)+nv.dir.y*($s.p2.pos.y-$s.p1.pos.y)+nv.dir.z*($s.p2.pos.z-$s.p1.pos.z);
        if(pbmud==0){ return false; } //plane and line do not intersect
        pbmu = (nv.dir.x*$s.p2.pos.x+nv.dir.y*$s.p2.pos.y+nv.dir.z*$s.p2.pos.z+d)/pbmud;
        if($inclusive){
          if(pbmu<0||pbmu>1) return false;
        }else{
          if(pbmu<=0||pbmu>=1) return false;
        }
        return true;
      }
      Point get_3plane_intersection_point(Plane $pl1,Plane $pl2){
        if(is_plane_parallel($pl1)||is_plane_parallel($pl2)||$pl1.is_plane_parallel($pl2)) return null;
        d = get_d();
        $pl1.d = $pl1.get_d();
        $pl2.d = $pl2.get_d();
        PVector n12,n23,n31;
        n12 = $pl1.nv.dir.cross($pl2.nv.dir); n23 = $pl2.nv.dir.cross(nv.dir); n31 = nv.dir.cross($pl1.nv.dir);
        float pbden = $pl1.nv.dir.dot(n23);
        return new Point(($pl1.d*n23.x+$pl2.d*n31.x+d*n12.x)/pbden,($pl1.d*n23.y+$pl2.d*n31.y+d*n12.y)/pbden,($pl1.d*n23.z+$pl2.d*n31.z+d*n12.z)/pbden);
      }
      Point get_line_intersect_point(Line $l){
        if(!is_line_intersecting($l)) return null;
        d = get_d();
        float pbmud = nv.dir.x*($l.s.p2.pos.x-$l.s.p1.pos.x)+nv.dir.y*($l.s.p2.pos.y-$l.s.p1.pos.y)+nv.dir.z*($l.s.p2.pos.z-$l.s.p1.pos.z);
        if(pbmud!=0){
          float pbmu = (nv.dir.x*$l.s.p2.pos.x+nv.dir.y*$l.s.p2.pos.y+nv.dir.z*$l.s.p2.pos.z+d)/pbmud;
          return new Point($l.s.p2.pos.x+($l.s.p1.pos.x-$l.s.p2.pos.x)*pbmu,$l.s.p2.pos.y+($l.s.p1.pos.y-$l.s.p2.pos.y)*pbmu,$l.s.p2.pos.z+($l.s.p1.pos.z-$l.s.p2.pos.z)*pbmu);
        }else{
          return null;
        }    
      }
      void render(PGraphics $pg,Point $p){
        nv.render($pg,$p);
      }
    }
    
    //------ POINT ------//
    class Point{
      float speed = 0.03;
      PVector pos,opos,oopos,tar,otar;
      Point(float $x,float $y,float $z){
        pos = new PVector($x,$y,$z); opos = new PVector($x,$y,$z); oopos = new PVector($x,$y,$z); tar = new PVector($x,$y,$z); otar = new PVector($x,$y,$z);
      }
      Point(PVector $pos){
        pos = $pos;
        opos = $pos.get(); oopos = $pos.get(); tar = $pos.get(); otar = $pos.get();
      }
      void match(Point $p){
        pos.set($p.pos);
        opos.set($p.opos);
        tar.set($p.tar);
        otar.set($p.otar);
      }
      void reset(){
        pos.set(oopos);
        opos.set(oopos);
        tar.set(oopos);
        otar.set(oopos);
      }
      void move(PVector $xyz){
        opos.set(pos);
        otar.set(tar);
        pos.add($xyz);
        tar.set(pos);
      }
      void move_to(PVector $xyz){
        opos.set(pos);
        otar.set(tar);
        pos.set($xyz);
        tar.set($xyz);
      }
      void direct(PVector $xyz){
        otar.set(tar);
        tar.add($xyz);
      }
      void direct_to(PVector $xyz){
        otar.set(tar);
        tar.set($xyz);
      }
      void rotate(float $cosa,float $sina,String $axis,PVector $xyz){
        float dx,dy,dz;
        if($axis=="x"){
          dz = pos.z-$xyz.z; dy = pos.y-$xyz.y;
          pos.set(pos.x,$xyz.y+dy*$cosa+dz*$sina,$xyz.z+dz*$cosa-dy*$sina);
        }else if($axis=="y"){
          dx = pos.x-$xyz.x; dz = pos.z-$xyz.z;
          pos.set($xyz.x+dx*$cosa-dz*$sina,pos.y,$xyz.z+dz*$cosa+dx*$sina);
        }else if($axis=="z"){
          dx = pos.x-$xyz.x; dy = pos.y-$xyz.y;
          pos.set($xyz.x+dx*$cosa-dy*$sina,$xyz.y+dy*$cosa+dx*$sina,pos.z);
        }
        tar.set(pos); otar.set(pos); opos.set(pos);
      }
      float get_distance_to(PVector $xyz){
        return pos.dist($xyz);
      }
      Point[] get_closest_points(int $n,Point[] $ps){
        float[] ds = new float[$ps.length];
        Point[] temp = new Point[$ps.length];
        int ntemp = 0;
        for(int i=0;i<$ps.length;i++){
          if($ps[i]==null||$ps[i]==this) continue;
          temp[ntemp++] = $ps[i];
          ds[ntemp-1] = get_distance_to($ps[i]);
        }
        if(ntemp==0) return null;
        for(int i=0;i<ntemp-1;i++){
          for(int j=0;j<ntemp-1-i;j++){
            if(ds[j]>ds[j+1]){
              Point temp1 = temp[j]; temp[j] = temp[j+1]; temp[j+1] = temp1;
              float ds1 = ds[j]; ds[j] = ds[j+1]; ds[j+1] = ds1;
            }
          }
        }
        return (Point[]) subset(temp,0,$n);
      }
      float get_distance_to(Point $p){
        return pos.dist($p.pos);
      }
      void step(){
        opos.set(pos);
        pos.add((tar.x-pos.x)*speed,(tar.y-pos.y)*speed,(tar.z-pos.z)*speed);
      }
      void render(PGraphics $pg){
        $pg.pushMatrix();
        $pg.translate(pos.x,pos.y,pos.z);
        $pg.ellipse(0,0,6,6);
        $pg.popMatrix();
      }
      void render(PGraphics $pg,boolean $spherical){
        if($spherical){
          $pg.pushMatrix();
          $pg.translate(pos.x,pos.y,pos.z);
          $pg.sphereDetail(5);
          $pg.sphere(4);
          $pg.popMatrix();
        }else{
          render($pg);
        }
      }
    }
    
    //------ POLYGON ------//
    class Polygon{
      int npoints,nsegments;
      Point[] points = new Point[1000];
      Segment[] segments = new Segment[1000];
      Point centroid;
      Polygon(Point[] $points){
        npoints = nsegments = 0;
        for(int i=0;i<$points.length;i++){
          if($points[i]==null) continue;
          add_point($points[i]);
        }
      }
      Polygon(){
        npoints = nsegments = 0;
      }
      void reset(){
        for(int i=0;i<nsegments;i++) segments[i].reset();
      }
      void rotate(float $a,String $axis,PVector $xyz){
        float cosa = cos($a);
        float sina = sin($a);
        for(int i=0;i<npoints;i++) points[i].rotate(cosa,sina,$axis,$xyz);
      }
      void rotate(float $a,String $axis){
        float cosa = cos($a);
        float sina = sin($a);
        centroid = get_centroid_point();
        for(int i=0;i<npoints;i++) points[i].rotate(cosa,sina,$axis,centroid.pos);
      }
      void add_point(Point $p){
        for(int i=0;i<npoints;i++){
          if($p.get_distance_to(points[i])<=global.distance_tolerance) return;
        }
        points[npoints++] = $p;
        if(npoints>2){
          if(npoints==3){
            //create first closing segment
            segments[nsegments] = new Segment(points[npoints-2],points[npoints-1]);
            segments[nsegments+1] = new Segment(points[npoints-1],points[0]);
            nsegments += 2;
          }else{
            //replace closing segment
            segments[nsegments-1] = new Segment(points[npoints-2],points[npoints-1]);
            segments[nsegments++] = new Segment(points[npoints-1],points[0]);
          }
        }else if(npoints>1){
          segments[nsegments++] = new Segment(points[npoints-2],points[npoints-1]);
        }
      }
      void insert_point(Point $p,Point $after){
        //this is not an efficient way to insert points, just the easiest
        int ntemp = npoints;
        Point[] temp = new Point[1000];
        arraycopy(points,temp);
        npoints = 0;
        nsegments = 0;
        for(int i=0;i<ntemp;i++){
          add_point(temp[i]);
          if(temp[i]==$after) add_point($p);
        }
      }
      void delete_point(Point $p){
        //this is not an efficient way to remove points, just the easiest :: for reducing lots of large polygons, this should target only the segment to be removed, rather than rebuilding the entire polygon
        int ntemp = npoints;
        Point[] temp = new Point[1000];
        arraycopy(points,temp);
        npoints = 0;
        nsegments = 0;
        for(int i=0;i<ntemp;i++){
          if(temp[i]!=$p) add_point(temp[i]);
        }
      }
      Polygon get_bounding_box(){
        //xy coordinates only
        PVector xymin = new PVector(9999,9999);
        PVector xymax = new PVector(-9999,-9999);
        for(int i=0;i<npoints;i++){
          xymin.set(min(points[i].pos.x,xymin.x),min(points[i].pos.y,xymin.y),0);
          xymax.set(max(points[i].pos.x,xymax.x),max(points[i].pos.y,xymax.y),0);
        }
        Point[] ps = new Point[4]; ps[0] = new Point(xymin.x,xymin.y,0); ps[1] = new Point(xymax.x,xymin.y,0); ps[2] = new Point(xymax.x,xymax.y,0); ps[3] = new Point(xymin.x,xymax.y,0);
        return new Polygon(ps);
      }
      float get_length(){
        float l = 0;
        for(int i=0;i<nsegments;i++) l += segments[i].get_length();
        return l;
      }
      void simplify(){
        //xy coordinates only
        if(nsegments<3) return;
        for(int i=0;i<nsegments;i++){
          if(segments[i].get_length()<global.simplify_distance_tolerance||abs(segments[i].get_slope()-segments[(i+1)%nsegments].get_slope())<global.simplify_slope_tolerance){
            delete_point(points[(i+1)%npoints]);
            simplify();
            break;
          }
        }
      }
      void densify(float $d){
        for(int i=0;i<nsegments;i++){
          if(segments[i].get_length()>$d){
            insert_point(segments[i].get_midpoint(),segments[i].p1);
            densify($d);
            break;
          }
        }
      }
      void divide(int $n){
        float nl = get_length()/$n;
        float nls = 0; float sl = 0; float sls = 0; int ntemp = 0;
        Point[] temp = new Point[1000];
        for(int i=0;i<nsegments;i++){
          sl = segments[i].get_length();
          sls += sl;
          while(nls<sls){
            temp[ntemp++] = segments[i].get_percentage_point((sl-(sls-nls))/sl);
            nls += nl;
          }
        }
        //repopulate with new points
        npoints = 0;
        nsegments = 0;
        for(int i=0;i<ntemp;i++) add_point(temp[i]);
      }
      boolean is_complex(){
        //xy coordinates only
        if(npoints!=nsegments) return true;
        for(int i=0;i<nsegments;i++){
          for(int j=i+1;j<nsegments;j++){
            if(j==i+1||j==(i+nsegments-1)%nsegments){
              if(segments[i].is_segment_intersecting(segments[j],false)) return true;
            }else{
              if(segments[i].is_segment_intersecting(segments[j],true)) return true;
            }
          }
        }
        return false;
      }
      boolean is_coord_inside(PVector $xy){
        //xy coordinates only :: this technique is slower but more accurate than the ray intersection method :: this checks the sum of the angles between all point pairs and the tested point
        float a1,a2;
        float a = 0;
        for(int i=0;i<nsegments;i++){
          a1 = atan2(segments[i].p1.pos.y-$xy.y,segments[i].p1.pos.x-$xy.x);
          a2 = atan2(segments[i].p2.pos.y-$xy.y,segments[i].p2.pos.x-$xy.x);
          a += (a2-a1+5*PI)%TWO_PI-PI; //make sure a falls between -pi and pi
        }
        if(abs(a)+global.angle_tolerance<PI) return false;
        return true;
      }
      boolean is_polygon_inside(Polygon $po){
        //xy coordinates only
        for(int i=0;i<npoints;i++){
          if(!is_coord_inside(points[i].pos)) return false;
        }
        return true;
      }
      boolean is_polygon_intersecting(Polygon $po){
        //xy coordinates only
        for(int i=0;i<nsegments;i++){
          for(int j=0;j<nsegments;j++){
            if(segments[i].is_segment_intersecting(segments[j],true)) return true;
          }
        }
        if($po.is_polygon_inside(this)) return true;
        if(is_polygon_inside($po)) return true;
        return false;
      }
      boolean is_splittable(Line $l){
        //xy coordinates only
        if(npoints<3) return false;
        if(is_complex()) return false;
        int ints = 0;
        for(int i=0;i<npoints;i++){
          if($l.is_coord_on(points[i].pos)){
            ints++;
          }else if(segments[i].is_line_intersecting($l,false)){
            ints++;
          }
        }
        if(ints==2) return true;
        return false;
      }
      float get_distance_to(PVector $xyz){
        if(npoints==0) return 9999;
        if(npoints==1) return points[0].get_distance_to($xyz);
        float mind = segments[0].get_distance_to($xyz);
        float d = mind;
        for(int i=1;i<nsegments;i++){
          d = segments[i].get_distance_to($xyz);
          if(d<mind) mind = d;
        }
        return d;
      }
      float get_signed_area(){
        //xy coordinates only :: this method of calculating the area will return a signed value: negative means counterclockwise orientation, positive means clockwise :: the signed area is needed for the centroid calculation
        if(npoints<3) return 0;
        if(is_complex()) return 0;
        float a = 0;
        for(int i=0;i<npoints;i++) a += points[i].pos.x*points[(i+1)%npoints].pos.y-points[(i+1)%npoints].pos.x*points[i].pos.y;
        return (a*0.5);
      }
      float get_area(){
        //xy coordinates only
        return abs(get_signed_area());
      }
      float get_width(){
        float xmin = 9999; float xmax = -9999;
        for(int i=0;i<npoints;i++){
          xmin = min(points[i].pos.x,xmin);
          xmax = max(points[i].pos.x,xmax);
        }
        return (xmax-xmin);
      }
      float get_height(){
        float ymin = 9999; float ymax = -9999;
        for(int i=0;i<npoints;i++){
          ymin = min(points[i].pos.y,ymin);
          ymax = max(points[i].pos.y,ymax);
        }
        return (ymax-ymin);
      }
      Point get_closest_point(PVector $xyz){
        //xy coordinates only
        if(npoints==0) return null;
        if(npoints==1) return points[0];
        float d;
        float mind = segments[0].get_distance_to($xyz);
        Point p = segments[0].get_closest_point($xyz);
        for(int i=1;i<nsegments;i++){
          d = segments[i].get_distance_to($xyz);
          if(d<mind){
            mind = d;
            p = segments[i].get_closest_point($xyz);
          }
        }
        return p;
      }
      Point get_points_centroid_point(){
        //this retrieves the centroid of the points that compose the polygon, not the centroid of the polygon! :: they're not the same thing, it's a cruel world
        if(npoints==0) return null;
        PVector cen = new PVector();
        for(int i=0;i<npoints;i++) cen.add(points[i].pos);
        cen.div(npoints);
        return new Point(cen);
      }
      Point get_centroid_point(){
        //xy coordinates only
        if(npoints==0) return null;
        float a = get_signed_area();
        if(a==0) return null;
        float temp;
        PVector cen = new PVector();
        for(int i=0;i<npoints;i++){
          temp = points[i].pos.x*points[(i+1)%npoints].pos.y-points[(i+1)%npoints].pos.x*points[i].pos.y;
          cen.add((points[i].pos.x+points[(i+1)%npoints].pos.x)*temp,(points[i].pos.y+points[(i+1)%npoints].pos.y)*temp,0);
        }
        cen.div(a*6);
        return new Point(cen);
      }
      Polygon get_convex_hull(){
        if(npoints<4) return this;
        //xy coordinates only :: returns the convex hull for this polygon's set of points :: this is a relatively costly function as it is scripted here
        float a,d;
        int ip = 0; int ntemp = 0; Point[] temp = new Point[npoints]; float[] as = new float[npoints]; float[] ds = new float[npoints];
        //find the topmost point
        for(int i=1;i<npoints;i++){
          if(points[i].pos.y<points[ip].pos.y) ip = i;
        }
        //if two points are colinear with the topmost point, take only the one furthest away
        for(int i=0;i<npoints;i++){
          if(i!=ip){
            a = (atan2(points[ip].pos.y-points[i].pos.y,points[ip].pos.x-points[i].pos.x)+PI)%TWO_PI;
            d = points[ip].get_distance_to(points[i]);
            if(d>global.distance_tolerance){
              boolean found = false;
              for(int j=0;j<ntemp;j++){
                if(a==as[j]){
                  if(d>ds[j]){
                    //remove the closer point
                    Point[] temp1 = (Point[]) subset(temp,0,j); Point[] temp2 = (Point[]) subset(temp,j+1,temp.length-(j+1)); temp = (Point[]) concat(temp1,temp2);
                    float[] as1 = subset(as,0,j); float[] as2 = subset(as,j+1,temp.length-(j+1)); as = concat(as1,as2);
                    float[] ds1 = subset(ds,0,j); float[] ds2 = subset(ds,j+1,temp.length-(j+1)); ds = concat(ds1,ds2);
                    ntemp--; j--;
                  }else{
                    //skip this point
                    found = true;
                    break;
                  }
                }
              }
              if(!found){
                //add point to array
                temp[ntemp] = points[i]; as[ntemp] = a; ds[ntemp++] = d;
              }
            }
          }
        }
        //bubble sort the arrays according to angle
        for(int i=0;i<ntemp-1;i++){
          for(int j=0;j<ntemp-1-i;j++){
            if(as[j]>as[j+1]){
              Point temp1 = temp[j]; temp[j] = temp[j+1]; temp[j+1] = temp1;
              float as1 = as[j]; as[j] = as[j+1]; as[j+1] = as1;
            }
          }
        }
        temp[ntemp++] = points[ip];
        temp = (Point[]) subset(temp,0,ntemp);
        temp = (Point[]) reverse(temp);
        //find convex hull points
        if(ntemp>3){
          for(int i=0;i<ntemp;i++){
            float pbside = (temp[(i+1+ntemp)%ntemp].pos.y-temp[(i+ntemp)%ntemp].pos.y)*(temp[(i+2+ntemp)%ntemp].pos.x-temp[(i+ntemp)%ntemp].pos.x)-(temp[(i+1+ntemp)%ntemp].pos.x-temp[(i+ntemp)%ntemp].pos.x)*(temp[(i+2+ntemp)%ntemp].pos.y-temp[(i+ntemp)%ntemp].pos.y);
            if(pbside<0){
              //remove the point if it lies on the wrong side
              if((i+1+ntemp)%ntemp==0){
                temp = (Point[]) subset(temp,1,ntemp-1);
              }else if((i+1+ntemp)%ntemp==ntemp-1){
                temp = (Point[]) subset(temp,0,ntemp-1);
              }else{
                Point[] temp1 = (Point[]) subset(temp,0,(i+1+ntemp)%ntemp);
                Point[] temp2 = (Point[]) subset(temp,(i+2+ntemp)%ntemp,ntemp-(i+2+ntemp)%ntemp);
                temp = (Point[]) concat(temp1,temp2);
              }
              i -= 2; ntemp--;
              temp = (Point[]) subset(temp,0,ntemp);
            }
          }
        }
        //return new polygon
        Polygon po = new Polygon(temp);
        po.simplify();
        return po;
      }
      Polygon get_outline(float $d){
        if(npoints<4) return this;
        float a,mina;
        int n = 0; int active = 0; int next = 0; int previous = 0; int ip = 0; int ntemp = 0; Point[] temp = new Point[npoints]; float cosa = cos(-PI+0.001); float sina = sin(-PI+0.001);
        //find the topmost point
        for(int i=0;i<npoints;i++){
          if(points[i].pos.y<points[ip].pos.y) ip = i;
        }
        active = ip;
        Vector ov = new Vector(-100,0.001,0);
        Vector v = new Vector(0,0,0);
        //repeat until we hit the topmost point again
        while((active!=ip||n==0)&&n<100){
          mina = 9999;
          for(int j=0;j<npoints;j++){
            if(j!=active&&points[active].get_distance_to(points[j])<=$d){
              v.set(points[j]);
              v.sub(points[active]);
              a = -PVector.angleBetween(v.dir,ov.dir);
              if(a<0) a += TWO_PI;
              if(a<mina){
                mina = a;
                next = j;
              }
            }
          }
          ov.set(points[next]);
          ov.sub(points[active]);
          ov.rotate(cosa,sina,"z");
          if(next==previous) return null;
          previous = active;
          active = next;
          temp[ntemp++] = points[active];
          if(ntemp>=temp.length-1) return null;
          n++;
        }
        return new Polygon(temp);
      }
      Polygon[] split(Line $l){
        //xy coordinates only
        if(npoints<3) return null;
        if(is_complex()) return null;
        int ip = 0; Point[] ints = new Point[npoints]; float[] intsi = new float[npoints+2]; int nints = 0;
        Point[] temp1 = new Point[npoints+2]; float[] temp1i = new float[npoints+2]; int ntemp1 = 0; Point[] temp2 = new Point[npoints+2]; int ntemp2 = 0;
        for(int i=0;i<npoints;i++){
          if($l.is_coord_on(points[i].pos)){
            ints[nints] = points[i]; intsi[nints++] = i;
            temp1[ntemp1] = points[i]; temp1i[ntemp1++] = i;
            ip = i;
          }else if(segments[i].is_line_intersecting($l,false)){
            Point lint = $l.s.get_intersect_point(segments[i]);
            ints[nints] = lint; intsi[nints++] = i+0.5;
            if(nints%2==0){
              temp1[ntemp1] = points[i];
              temp1i[ntemp1++] = i;
            }
            temp1[ntemp1] = lint; temp1i[ntemp1++] = i+0.5; 
            ip = i;
          }else if(nints%2==1){
            temp1[ntemp1] = points[i]; temp1i[ntemp1++] = i;
          }
        }
        for(int i=ip;i<ip+npoints;i++){
          boolean found = false;
          for(int j=0;j<ntemp1;j++){
            if(i%npoints==temp1i[j]){
              found = true;
              break;
            }
          }
          if(!found) temp2[ntemp2++] = points[i%npoints];
        }
        temp2[ntemp2] = new Point(ints[0].pos.get());
        temp2[ntemp2] = new Point(ints[1].pos.get());
        return new Polygon[]{new Polygon(temp1),new Polygon(temp2)};
      }
      void render(PGraphics $pg){
        if(npoints<3) return;
        $pg.beginShape();
        for(int i=0;i<npoints;i++) $pg.vertex(points[i].pos.x,points[i].pos.y,points[i].pos.z);
        $pg.endShape(CLOSE);
        /*
        for(int i=0;i<npoints;i++){
          points[i].render($pg);
        }
        */
      }
      void render(PGraphics $pg,String $type){
        if(npoints<3) return;
        $pg.beginShape();
        if($type=="line"){
          for(int i=0;i<npoints;i++) $pg.vertex(points[i].pos.x,points[i].pos.y,points[i].pos.z);
        }else if($type=="curve"){
          $pg.curveTightness(-0.8);
          $pg.curveVertex(points[npoints-1].pos.x,points[npoints-1].pos.y,points[npoints-1].pos.z);
          for(int i=0;i<npoints;i++) $pg.curveVertex(points[i].pos.x,points[i].pos.y,points[i].pos.z);
          $pg.curveVertex(points[0].pos.x,points[0].pos.y,points[0].pos.z);
          $pg.curveVertex(points[1].pos.x,points[1].pos.y,points[1].pos.z);
        }else if($type=="bezier"){
          $pg.vertex((points[npoints-1].pos.x+points[0].pos.x)/2,(points[npoints-1].pos.y+points[0].pos.y)/2,(points[npoints-1].pos.z+points[0].pos.z)/2);
          for(int i=0;i<npoints;i++) $pg.bezierVertex(points[i].pos.x,points[i].pos.y,points[i].pos.z,points[i].pos.x,points[i].pos.y,points[i].pos.z,(points[(i+1)%npoints].pos.x+points[i].pos.x)/2,(points[(i+1)%npoints].pos.y+points[i].pos.y)/2,(points[(i+1)%npoints].pos.z+points[i].pos.z)/2);
        }
        $pg.endShape(CLOSE);
      }
      void render(PGraphics $pg,PImage $img,float $s){
        if(npoints<3) return;
        $pg.beginShape();
        $pg.texture($img);
        for(int i=0;i<npoints;i++) $pg.vertex(round(points[i].pos.x),round(points[i].pos.y),round(points[i].pos.z),round($s*(points[i].pos.x+global.w/2)),round($s*(points[i].pos.y+global.h/2)));
        $pg.endShape(CLOSE);
      }
    }
    
    //------ POLYLINE ------//
    class Polyline{
      Point[] points = new Point[1000];
      Segment[] segments = new Segment[1000];
      int npoints,nsegments;
      Polyline(Segment[] $segments){
        npoints = nsegments = 0;
        add_point($segments[0].p1);
        for(int i=0;i<$segments.length;i++){
          if($segments[i]==null) continue;
          add_segment($segments[i]);
        }
      }
      Polyline(Point[] $points){
        npoints = nsegments = 0;
        for(int i=0;i<$points.length;i++){
          if($points[i]==null) continue;
          add_point($points[i]);
        }
      }
      Polyline(){
        npoints = nsegments = 0;
      }
      float get_length(){
        float l = 0;
        for(int i=0;i<nsegments;i++){
          l += segments[i].get_length();
        }
        return l;
      }
      void add_point(Point $p){
        points[npoints++] = $p;
        if(npoints>1) add_segment(new Segment(points[npoints-2],$p));
      }
      void add_segment(Segment $s){
        segments[nsegments++] = $s;
      }
      void render(PGraphics $pg){
        if(nsegments<1) return;
        for(int i=0;i<nsegments;i++) segments[i].render($pg);
      }
    }
    
    //------ RANDOMIZER ------//
    //a randomizer loads a number of true random numbers from random.org :: this will fail without an internet connection and won't run online without a signed applets
    class Randomizer{
      String url = "http://www.random.org/integers/?num=%n%&min=%low%&max=%high%&col=1&base=10&format=plain&rnd=new";
      int n,o,low,high;
      float[] randoms;
      Randomizer(int $n,int $high){
        n = $n;
        low = 0;
        high = $high;
        o = 0;
        url = url.replaceAll("%n%",str(n));
        url = url.replaceAll("%low%",str(low));
        url = url.replaceAll("%high%",str(high));
        load();
      }
      void load(){
        randoms = float(loadStrings(url));
      }
      float random(float $low,float $high){
        o++;
        o %= randoms.length;
        return $low+($high-$low)*randoms[o]/(float)high;
      }
    }
    
    //------ RIBBONS ------//
    //ribbons create a three dimensional particle path
    class Ribbon{
      Particle pa;
      float w,tw;
      int l,frame;
      PVector[][] tail;
      Ribbon(Particle $pa,float $w,float $tw,int $l){
        pa = $pa; w = $w; tw = $tw; l = $l;
        tail = new PVector[l][2];
        frame = 0;
      }
      void step(){
        PVector dpos = pa.pos.get();
        dpos.sub(pa.opos);
        PVector pxyz = pa.pos.cross(dpos);
        pxyz.normalize();
        tail[frame%l] = new PVector[]{pa.pos.get(),pxyz};
        frame++;
      }
      void render(PGraphics $pg){
        int j;
        float rw;
        $pg.beginShape(QUAD_STRIP);
        for(int i=frame%l;i<frame%l+l;i++){
          j = i%l;
          if(tail[j]==null||tail[j][0]==null) continue;
          rw = tw+(w-tw)*(i-frame%l)/l;
          $pg.vertex(tail[j][0].x+tail[j][1].x*rw,tail[j][0].y+tail[j][1].y*rw,tail[j][0].z+tail[j][1].z*rw);
          $pg.vertex(tail[j][0].x-tail[j][1].x*rw,tail[j][0].y-tail[j][1].y*rw,tail[j][0].z-tail[j][1].z*rw);
        }
        $pg.endShape();
      }
    }
    
    //------ RIGID ------//
    //rigids are polygons that attempt to hold their shape when subjected to forces
    class Rigid extends Polygon{
      int nmasses,nsprings;
      Mass[] masses = new Mass[100];
      Spring[] springs = new Spring[1000];
      PVector gravity = new PVector(0,1,0);
      float friction = 0.1; float bounce_friction = 0.4;
      float k,dampening;
      Rigid(Mass[] $masses,float $k,float $dampening){
        super($masses);
        k = $k; dampening = $dampening;
        nmasses = nsprings = 0;
        for(int i=0;i<$masses.length;i++){
          if($masses[i]==null) break;
          add_mass($masses[i]);
        }
      }
      void rotate(float $a,String $axis,boolean $points){
        float cosa = cos($a);
        float sina = sin($a);
        if($points){
          Point pcentroid = get_points_centroid_point();
          for(int i=0;i<npoints;i++) points[i].rotate(cosa,sina,$axis,pcentroid.pos);
        }else{
          rotate($a,$axis);
        }
      }
      void add_mass(Mass $m){
        $m.gravity = gravity; $m.friction = friction; $m.bounce_friction = bounce_friction;
        masses[nmasses++] = $m;
        for(int i=0;i<nmasses-1;i++) add_spring(new Spring(masses[nmasses-1],masses[i],k,dampening,masses[nmasses-1].get_distance_to(masses[i])));
      }
      void add_spring(Spring $sp){
        springs[nsprings++] = $sp;
      }
      void set_gravity(PVector $gravity){
        gravity = $gravity;
        for(int i=0;i<nmasses;i++) masses[i].gravity = gravity;
      }
      void step(){
        for(int i=0;i<nsprings;i++) springs[i].step();
        for(int i=0;i<nmasses;i++) masses[i].step();
      }
      void render(PGraphics $pg){
        super.render($pg);
        //for(int i=0;i<nsprings;i++) springs[i].render($pg);
        //for(int i=0;i<nmasses;i++) masses[i].render($pg);
      }
    }
    
    //------ SEGMENT ------//
    class Segment{
      float l,ol;
      Point p1,p2;
      Segment(Point $p1,Point $p2){
        p1 = $p1; p2 = $p2;
        l = ol = get_length();
      }
      void reset(){
        p1.reset();
        p2.reset();
      }
      float get_length(){
        return p1.get_distance_to(p2);
      }
      Point get_midpoint(){
        return get_percentage_point(0.5);
      }
      Point get_percentage_point(float $n){
        return new Point(lerp(p1.pos.x,p2.pos.x,$n),lerp(p1.pos.y,p2.pos.y,$n),lerp(p1.pos.z,p2.pos.z,$n));
      }
      float get_angle(){
        //xy coordinates only
        return atan2(p2.pos.y-p1.pos.y,p2.pos.x-p1.pos.x);
      }
      float get_slope(){
        //xy coordinates only
        if(p2.pos.x!=p1.pos.x){
          return ((p2.pos.y-p1.pos.y)/(p2.pos.x-p1.pos.x));
        }else{
          return 999999999;
        }
      }
      boolean is_identical(Segment $s){
        if((p1==$s.p1&&p2==$s.p2)||(p2==$s.p1&&p1==$s.p2)){ return true; }
        return false;
      }
      boolean is_coincident(Segment $s){
        float slope1 = get_slope();
        float slope2 = $s.get_slope();
        if((is_coord_on($s.p1.pos)||is_coord_on($s.p2.pos))&&abs(slope1-slope2)<=global.slope_tolerance){ return true; }
        return false;
      }
      boolean is_ray_intersecting(PVector $xy,boolean $inclusive){
        //xy coordinates only :: for inside-outside testing, etc. :: ray points right, starting from the given coordinates
        float pbua1 = (p2.pos.x-p1.pos.x)*($xy.y-p1.pos.y)-(p2.pos.y-p1.pos.y)*($xy.x-p1.pos.x);
        float pbu2 = p2.pos.y-p1.pos.y;
        float pbub1 = $xy.y-p1.pos.y;
        if(pbu2==0){ return false; }
        float pbua = pbua1/pbu2;
        float pbub = pbub1/pbu2;
        if($inclusive){
          if(pbua>=0&&pbub>0&&pbub<=1){ return true; }
        }else{
          if(pbua>0&&pbub>0&&pbub<=1){ return true; }
        }
        return false;
      }
      boolean is_line_intersecting(Line $l,boolean $inclusive){
        //xy coordinates only
        float pbua1 = (p2.pos.x-p1.pos.x)*($l.s.p1.pos.y-p1.pos.y)-(p2.pos.y-p1.pos.y)*($l.s.p1.pos.x-p1.pos.x);
        float pbu2 = (p2.pos.y-p1.pos.y)*($l.s.p2.pos.x-$l.s.p1.pos.x)-(p2.pos.x-p1.pos.x)*($l.s.p2.pos.y-$l.s.p1.pos.y);
        float pbub1 = ($l.s.p2.pos.x-$l.s.p1.pos.x)*($l.s.p1.pos.y-p1.pos.y)-($l.s.p2.pos.y-$l.s.p1.pos.y)*($l.s.p1.pos.x-p1.pos.x);
        if(pbu2==0){ return false; }
        float pbua = pbua1/pbu2;
        float pbub = pbub1/pbu2;
        if($inclusive){
          if(pbub>=0&&pbub<=1){ return true; }
        }else{
          if(pbub>0&&pbub<1){ return true; }
        }
        return false;
      }
      boolean is_segment_intersecting(Segment $s,boolean $inclusive){
        //xy coordinates only
        float pbua1 = (p2.pos.x-p1.pos.x)*($s.p1.pos.y-p1.pos.y)-(p2.pos.y-p1.pos.y)*($s.p1.pos.x-p1.pos.x);
        float pbu2 = (p2.pos.y-p1.pos.y)*($s.p2.pos.x-$s.p1.pos.x)-(p2.pos.x-p1.pos.x)*($s.p2.pos.y-$s.p1.pos.y);
        float pbub1 = ($s.p2.pos.x-$s.p1.pos.x)*($s.p1.pos.y-p1.pos.y)-($s.p2.pos.y-$s.p1.pos.y)*($s.p1.pos.x-p1.pos.x);
        if(pbu2==0){ return false; }
        float pbua = pbua1/pbu2;
        float pbub = pbub1/pbu2;
        if($inclusive){
          if(pbub>=0&&pbub<=1&&pbua>=0&&pbua<=1){ return true; }
        }else{
          if(pbub>0&&pbub<1&&pbua>0&&pbua<1){ return true; }
        }
        return false;
      }
      boolean is_coord_on(PVector $xyz){
        float d = get_distance_to($xyz);
        if(d<=global.distance_tolerance){ return true; }
        return false;
      }
      float get_distance_to(PVector $xyz){
        Point p = get_closest_point($xyz);
        return $xyz.dist(p.pos);
      }
      Point get_closest_point(PVector $xyz){
        float u = (($xyz.x-p1.pos.x)*(p2.pos.x-p1.pos.x)+($xyz.y-p1.pos.y)*(p2.pos.y-p1.pos.y)+($xyz.z-p1.pos.z)*(p2.pos.z-p1.pos.z))/sq(dist(p1.pos.x,p1.pos.y,p1.pos.z,p2.pos.x,p2.pos.y,p2.pos.z));
        PVector xyz = new PVector(p1.pos.x+u*(p2.pos.x-p1.pos.x),p1.pos.y+u*(p2.pos.y-p1.pos.y),p1.pos.z+u*(p2.pos.z-p1.pos.z));
        if(((xyz.x>p1.pos.x&&xyz.x<p2.pos.x)||(xyz.x<p1.pos.x&&xyz.x>p2.pos.x))||((xyz.y>p1.pos.y&&xyz.y<p2.pos.y)||(xyz.y<p1.pos.y&&xyz.y>p2.pos.y))||((xyz.z>p1.pos.z&&xyz.z<p2.pos.z)||(xyz.z<p1.pos.z&&xyz.z>p2.pos.z))){
          if(p1.get_distance_to(xyz)<p2.get_distance_to(xyz)){
            return new Point(p1.pos.get());
          }else{
            return new Point(p2.pos.get());
          }
        }
        return new Point(xyz);
      }
      Point get_intersect_point(Segment $s){
        //xy coordinates only :: must test for on-segment intersection before using this, otherwise it will project the segments as lines to their intersection point :: it will also return {9999,9999,9999} for parallel lines (should be tested for prior to using this)
        float pbu2 = (p2.pos.y-p1.pos.y)*($s.p2.pos.x-$s.p1.pos.x)-(p2.pos.x-p1.pos.x)*($s.p2.pos.y-$s.p1.pos.y);
        if(pbu2!=0){
          float pbua1 = (p2.pos.x-p1.pos.x)*($s.p1.pos.y-p1.pos.y)-(p2.pos.y-p1.pos.y)*($s.p1.pos.x-p1.pos.x);
          float pbua = pbua1/pbu2;
          return new Point($s.p1.pos.x+($s.p2.pos.x-$s.p1.pos.x)*pbua,$s.p1.pos.y+($s.p2.pos.y-$s.p1.pos.y)*pbua,0);
        }
        return null;
      }
      void render(PGraphics $pg){
        $pg.line(p1.pos.x,p1.pos.y,p1.pos.z,p2.pos.x,p2.pos.y,p2.pos.z);
      }
    }
    
    //------ SHADOW ------//
    //shadows are polygons cast on to terrains
    class Shadow{
      Polygon po1,po2;
      Terrain te;
      float sun_ry = PI/4;
      float sun_rz = PI;
      Shadow(Polygon $po1,Terrain $te){
        po1 = $po1; te = $te;
        project();
      }
      void project(){
        Point sun = new Point(100,0,0); sun.rotate(cos(sun_ry),sin(sun_ry),"y",global.zero_point.pos); sun.rotate(cos(sun_rz),sin(sun_rz),"z",global.zero_point.pos);
        Line sunlight = new Line(global.zero_point,sun);
        Point[] points = {};
        for(int i=0;i<po1.nsegments;i++) points = (Point[]) concat(points,project_segment(po1.segments[i],sunlight));
        po2 = new Polygon(points);
      }
      Point[] project_segment(Segment $s,Line $l){
        Point[] points = {};
        Point p1 = get_projected_point($s.p1,$l);
        if(p1!=null){
          points = (Point[]) append(points,p1);
          Point p2 = get_projected_point($s.p2,$l);
          if(p2!=null){
            //get inbetween points
            Segment s = new Segment(p1,p2);
            Segment[] segments = te.get_edge_intersect_segments(s);
            if(segments.length>0){
              float[] ds = new float[segments.length+1];
              ds[0] = 0;
              for(int i=0;i<segments.length;i++){
                //get intersect points
                Point temp = s.get_intersect_point(segments[i]);
                if(temp!=null){
                  //get xy distance to p1
                  ds[i+1] = dist(temp.pos.x,temp.pos.y,p1.pos.x,p1.pos.y);
                  //project from the XY plane up/down to segment
                  temp = te.get_line_intersect_point(new Line(temp,new Point(temp.pos.x,temp.pos.y,temp.pos.z-100)));
                  if(temp!=null){ points = (Point[]) append(points,temp); }
                }            
              }
              //sort by xy distance to p1
              for(int i=0;i<points.length-1;i++){
                for(int j=0;j<points.length-1-i;j++){
                  if(ds[j]>ds[j+1]){
                    Point points1 = points[j]; points[j] = points[j+1]; points[j+1] = points1;
                    float ds1 = ds[j]; ds[j] = ds[j+1]; ds[j+1] = ds1;
                  }
                }
              }
            }
          }
        }
        return points;
      }
      Point get_projected_point(Point $p,Line $l){
        PVector dxyz = $p.pos.get();
        dxyz.sub($l.s.p1.pos);
        $l.s.p1.move(dxyz);
        $l.s.p2.move(dxyz);
        return get_closest_point(te.get_line_intersect_points($l),$p);
      }
      Point get_closest_point(Point[] $points,Point $p){
        if($points.length>0){
          float[] ds = new float[$points.length];
          for(int i=0;i<$points.length;i++) ds[i] = $points[i].get_distance_to($p);
          for(int i=0;i<$points.length-1;i++){
            for(int j=0;j<$points.length-1-i;j++){
              if(ds[j]>ds[j+1]){
                Point points1 = $points[j]; $points[j] = $points[j+1]; $points[j+1] = points1;
                float ds1 = ds[j]; ds[j] = ds[j+1]; ds[j+1] = ds1;
              }
            }
          }
          return $points[0];
        }else{
          return null;
        }
      }
      void render(PGraphics $pg){
        po2.render($pg);
      }
    }
    
    //------ SPRING ------//
    //springs join and move masses
    class Spring extends Segment{
      float k,dampening,tl,otl;
      Mass m1,m2;
      Spring(Mass $m1,Mass $m2,float $k,float $dampening,float $tl){
        super($m1,$m2);
        m1 = $m1; m2 = $m2; k = $k; dampening = $dampening; tl = otl = $tl;
        m1.dampening = dampening; m2.dampening = dampening;
      }
      void step(){
        PVector rest1 = new PVector();
        PVector rest2 = new PVector();
        float p,l;
        l = get_length();
        p = 1-(l-tl)/l;
        if(m1.locked){
          rest1 = m1.pos.get();
          rest2.set(m1.pos.x+(m2.pos.x-m1.pos.x)*p,m1.pos.y+(m2.pos.y-m1.pos.y)*p,m1.pos.z+(m2.pos.z-m1.pos.z)*p);
        }else if(m2.locked){
          rest1.set(m2.pos.x-(m2.pos.x-m1.pos.x)*p,m2.pos.y-(m2.pos.y-m1.pos.y)*p,m2.pos.z-(m2.pos.z-m1.pos.z)*p);
          rest2 = m2.pos.get();
        }else{
          rest2.set(m1.pos.x+(m2.pos.x-m1.pos.x)*p,m1.pos.y+(m2.pos.y-m1.pos.y)*p,m1.pos.z+(m2.pos.z-m1.pos.z)*p);
          rest1.set(m2.pos.x-(m2.pos.x-m1.pos.x)*p,m2.pos.y-(m2.pos.y-m1.pos.y)*p,m2.pos.z-(m2.pos.z-m1.pos.z)*p);
        }
        m1.acc.add(-k*(m1.pos.x-rest1.x)/m1.m,-k*(m1.pos.y-rest1.y)/m1.m,-k*(m1.pos.z-rest1.z)/m1.m);
        m2.acc.add(-k*(m2.pos.x-rest2.x)/m2.m,-k*(m2.pos.y-rest2.y)/m2.m,-k*(m2.pos.z-rest2.z)/m2.m);
      }
      void render(PGraphics $pg,boolean $zigzag){
        if($zigzag&&m1.pos.z==0&&m2.pos.z==0&&m1.vel.z==0){
          float l = get_length();
          int kinks = max(6,ceil(otl/10));
          $pg.pushMatrix();
          $pg.translate(m1.pos.x,m1.pos.y);
          $pg.rotateZ(get_angle());
          $pg.beginShape();
          for(int i=0;i<kinks;i++){
            if(i<=3||i>=kinks-4){
              $pg.vertex(i*l/(kinks-1),0);
            }else{
              if(i%2==0){
                $pg.vertex(i*l/(kinks-1),15);
              }else{
                $pg.vertex(i*l/(kinks-1),-15);
              }
            }
          }
          $pg.endShape();
          $pg.popMatrix();
        }else{
          super.render($pg);
        }
      }
    }
    
    //------ TERRAIN ------//
    //a grid of connected points and faces
    class Terrain{
      Point[][] points;
      Face[][][] faces;
      int xpoints,ypoints;
      PVector pos,siz;
      float xspacing,yspacing;
      Terrain(PVector $pos,PVector $siz,float $density){
        pos = $pos; siz = $siz;
        xpoints = round(siz.x*$density)+1;
        ypoints = round(siz.y*$density)+1;
        xspacing = siz.x/(xpoints-1);
        yspacing = siz.y/(ypoints-1);
        points = new Point[xpoints][ypoints];
        for(int i=0;i<xpoints;i++){
          for(int j=0;j<ypoints;j++) points[i][j] = new Point(pos.x+i*xspacing,pos.y+j*yspacing,pos.z);
        }
        faces = new Face[xpoints-1][ypoints-1][2];
        for(int i=0;i<xpoints-1;i++){
          for(int j=0;j<ypoints-1;j++){
            Point[] ps1 = new Point[3]; Point[] ps2 = new Point[3];
            if((i%2==0&&j%2==1)||(i%2==1&&j%2==0)){
              ps1[0] = points[i][j]; ps1[1] = points[i+1][j]; ps1[2] = points[i][j+1];
              ps2[0] = points[i+1][j+1]; ps2[1] = points[i][j+1]; ps2[2] = points[i+1][j];
            }else{
              ps1[0] = points[i][j+1]; ps1[1] = points[i][j]; ps1[2] = points[i+1][j+1];
              ps2[0] = points[i+1][j]; ps2[1] = points[i+1][j+1]; ps2[2] = points[i][j];
            }
            faces[i][j][0] = new Face(ps1);
            faces[i][j][1] = new Face(ps2);
          }
        }
      }
      Terrain(PVector $pos,PVector $siz,float $density,PGraphics $heightmap){
        pos = $pos; siz = $siz;
        xpoints = round(siz.x*$density)+1;
        ypoints = round(siz.y*$density)+1;
        xspacing = siz.x/(xpoints-1);
        yspacing = siz.y/(ypoints-1);
        points = new Point[xpoints][ypoints];
        for(int i=0;i<xpoints;i++){
          for(int j=0;j<ypoints;j++) points[i][j] = new Point(pos.x+i*xspacing,pos.y+j*yspacing,pos.z);
        }
        faces = new Face[xpoints-1][ypoints-1][2];
        for(int i=0;i<xpoints-1;i++){
          for(int j=0;j<ypoints-1;j++){
            Point[] ps1 = new Point[3]; Point[] ps2 = new Point[3];
            if((i%2==0&&j%2==1)||(i%2==1&&j%2==0)){
              ps1[0] = points[i][j]; ps1[1] = points[i+1][j]; ps1[2] = points[i][j+1];
              ps2[0] = points[i+1][j+1]; ps2[1] = points[i][j+1]; ps2[2] = points[i+1][j];
            }else{
              ps1[0] = points[i][j+1]; ps1[1] = points[i][j]; ps1[2] = points[i+1][j+1];
              ps2[0] = points[i+1][j]; ps2[1] = points[i+1][j+1]; ps2[2] = points[i][j];
            }
            faces[i][j][0] = new Face(ps1);
            faces[i][j][1] = new Face(ps2);
          }
        }
        synchronize($heightmap,false);
      }
      void synchronize(PGraphics $heightmap,boolean $target){
        PVector spacing = new PVector($heightmap.width/(xpoints-1),$heightmap.height/(ypoints-1));
        if($target){
          for(int i=0;i<xpoints;i++){
            for(int j=0;j<ypoints;j++) points[i][j].tar.z = siz.z*brightness($heightmap.get(floor(i*spacing.x)-((i==xpoints-1)?1:0),floor(j*spacing.y)-((j==ypoints-1)?1:0)))/255;
          }
        }else{
          for(int i=0;i<xpoints;i++){
            for(int j=0;j<ypoints;j++) points[i][j].pos.z = siz.z*brightness($heightmap.get(floor(i*spacing.x)-((i==xpoints-1)?1:0),floor(j*spacing.y)-((j==ypoints-1)?1:0)))/255;
          }
        }
      }
      void randomize(){
        for(int i=0;i<xpoints;i++){
          for(int j=0;j<ypoints;j++) points[i][j].pos.z = random(siz.z);
        }
      }
      void smooth(float $weight,int $iterations){
        //for best results, use relatively low weights and high iteration counts
        float[][] temp = new float[xpoints][ypoints];
        for(int i=0;i<xpoints;i++){
          for(int j=0;j<ypoints;j++){
            //smooth by weighted average of adjacent points
            int neighbors = (i==0||i==xpoints-1)?((j==0||j==ypoints-1)?2:3):((j==0||j==ypoints-1)?3:4);
            float z = points[i][j].pos.z*(1-$weight);
            if(i>0) z += points[i-1][j].pos.z*$weight/neighbors;
            if(i<xpoints-1) z += points[i+1][j].pos.z*$weight/neighbors;
            if(j>0) z += points[i][j-1].pos.z*$weight/neighbors;
            if(j<ypoints-1) z += points[i][j+1].pos.z*$weight/neighbors;
            temp[i][j] = z;
          }
        }
        for(int i=0;i<xpoints;i++){
          for(int j=0;j<ypoints;j++) points[i][j].pos.z = temp[i][j];
        }
        if($iterations>0) smooth($weight,$iterations-1);
      }
      Point get_line_intersect_point(Line $l){
        //this will return the first point it finds :: fine for vertical projects or relatively vertical projects onto a relatively smooth terrain
        Face f = get_line_intersect_face($l);
        if(f==null) return null;
        return f.get_line_intersect_point($l);
      }
      Point[] get_line_intersect_points(Line $l){
        //this will return all intersecting points, very costly on large terrains
        Point[] ps = {};
        Face[] fs = get_line_intersect_faces($l);
        for(int i=0;i<fs.length;i++) ps = (Point[]) append(ps,fs[i].get_line_intersect_point($l));
        return ps;
      }
      Face get_line_intersect_face(Line $l){
        //this will return the first face it finds :: fine for vertical projects or relatively vertical projects onto a relatively smooth terrain
        for(int i=0;i<xpoints-1;i++){
          for(int j=0;j<ypoints-1;j++){
            if(faces[i][j][0].is_line_intersecting($l)) return faces[i][j][0];
            if(faces[i][j][1].is_line_intersecting($l)) return faces[i][j][1];
          }
        }
        return null;
      }
      Face[] get_line_intersect_faces(Line $l){
        //this will return all intersecting faces, very costly on large terrains
        Face[] fs = {};
        for(int i=0;i<xpoints-1;i++){
          for(int j=0;j<ypoints-1;j++){
            if(faces[i][j][0].is_line_intersecting($l)) fs = (Face[]) append(fs,faces[i][j][0]);
            if(faces[i][j][1].is_line_intersecting($l)) fs = (Face[]) append(fs,faces[i][j][1]);
          }
        }
        return fs;
      }
      Segment[] get_edge_intersect_segments(Segment $s){
        //xy coordinates only :: coordinates are projected onto the XY plane :: this is a very expensive function, do not use on a frame-by-frame basis
        Segment temp = new Segment(points[0][0],points[0][1]);
        Segment[] segments = new Segment[1000];
        int nsegments = 0;
        for(int i=0;i<xpoints-1;i++){
          for(int j=0;j<ypoints-1;j++){
            //test cross segment
            if((i%2==0&&j%2==1)||(i%2==1&&j%2==0)){
              temp.p1 = points[i][j+1]; temp.p2 = points[i+1][j];
            }else{
              temp.p1 = points[i][j]; temp.p2 = points[i+1][j+1];
            }
            if(temp.is_segment_intersecting($s,true)) segments[nsegments++] = new Segment(temp.p1,temp.p2);
            //test right segment if on right grid square
            if(i==xpoints-2){
              temp.p1 = points[i+1][j]; temp.p2 = points[i+1][j+1];
              if(temp.is_segment_intersecting($s,true)) segments[nsegments++] = new Segment(temp.p1,temp.p2);
            }
            //test bottom segment if on bottom grid square
            if(j==ypoints-2){
              temp.p1 = points[i][j+1]; temp.p2 = points[i+1][j+1];
              if(temp.is_segment_intersecting($s,true)) segments[nsegments++] = new Segment(temp.p1,temp.p2);
            }
            //test left segment
            temp.p1 = points[i][j]; temp.p2 = points[i+1][j];
            if(temp.is_segment_intersecting($s,true)) segments[nsegments++] = new Segment(temp.p1,temp.p2);
            //test top segment
            temp.p1 = points[i][j]; temp.p2 = points[i][j+1];
            if(temp.is_segment_intersecting($s,true)) segments[nsegments++] = new Segment(temp.p1,temp.p2);
          }
        }
        return (Segment[]) subset(segments,0,nsegments);
      }
      void rotate(float $cosa,float $sina,String $axis,PVector $xyz){
        for(int i=0;i<xpoints;i++){
          for(int j=0;j<ypoints;j++) points[i][j].rotate($cosa,$sina,$axis,$xyz);
        }
      }
      void step(){
        for(int i=0;i<xpoints;i++){
          for(int j=0;j<ypoints;j++) points[i][j].step();
        }
      }
      void render(PGraphics $pg){
        for(int i=0;i<xpoints;i++){
          for(int j=0;j<ypoints;j++){
            if(i<xpoints-1&&j<ypoints-1){
              $pg.fill(255*faces[i][j][0].p1.pos.z/siz.z);
              $pg.fill(255*faces[i][j][1].p1.pos.z/siz.z);
            }
          }
        }
      }
      void render(PGraphics $pg,PImage $img,float $s){
        for(int i=0;i<xpoints;i++){
          for(int j=0;j<ypoints;j++){
            if(i<xpoints-1&&j<ypoints-1){
              faces[i][j][0].render($pg,$img,$s);
              faces[i][j][1].render($pg,$img,$s);
            }
          }
        }
      }
    }
    
    //------ VECTOR ------//
    class Vector{
      float m,om;
      PVector dir;
      Vector(PVector $dir){
        dir = $dir;
        om = m = get_magnitude();
      }
      Vector(float $x,float $y,float $z){
        dir = new PVector($x,$y,$z);
        om = m = get_magnitude();
      }
      Vector(Point $p){
        dir = $p.pos.get();
        om = m = get_magnitude();
      }
      float get_magnitude(){
        return dir.mag();
      }
      void normalize(){
        m = dir.mag();
        if(m==0){ return; }
        dir.normalize();
        m = 1;
      }
      void rotate(float $cosa,float $sina,String $axis){
        float tx,ty,tz;
        if($axis=="x"){
          tz = dir.z*$cosa-dir.y*$sina; ty = dir.y*$cosa+dir.z*$sina;
          dir.set(dir.x,ty,tz);
        }else if($axis=="y"){
          tx = dir.x*$cosa-dir.z*$sina; tz = dir.z*$cosa+dir.x*$sina;
          dir.set(tx,dir.y,tz);
        }else if($axis=="z"){
          tx = dir.x*$cosa-dir.y*$sina; ty = dir.y*$cosa+dir.x*$sina;
          dir.set(tx,ty,dir.z);
        }
      }
      void set(PVector $xyz){
        dir.set($xyz);
      }
      void set(Vector $v){
        dir.set($v.dir);
      }
      void set(float $x,float $y,float $z){
        dir.set($x,$y,$z);
      }
      void set(Point $p){
        dir.set($p.pos);
      }
      void add(Vector $v){
        dir.add($v.dir);
      }
      void add(float $x,float $y,float $z){
        dir.add($x,$y,$z);
      }
      void add(Point $p){
        dir.add($p.pos);
      }
      void sub(Vector $v){
        dir.sub($v.dir);
      }
      void sub(float $x,float $y,float $z){
        dir.sub($x,$y,$z);
      }
      void sub(Point $p){
        dir.sub($p.pos);
      }
      void scale(float $s){
        dir.mult($s);
      }
      void render(PGraphics $pg,Point $p){
        if(m==1){
          $pg.line($p.pos.x,$p.pos.y,$p.pos.z,$p.pos.x+dir.x*10,$p.pos.y+dir.y*10,$p.pos.z+dir.z*10);
        }else{
          $pg.line($p.pos.x,$p.pos.y,$p.pos.z,$p.pos.x+dir.x,$p.pos.y+dir.y,$p.pos.z+dir.z);
        }
      }
    }
    
    //------ VORONOI ------//
    //creates a voronoi diagram based on its corresponding delaunay triangulation
    class Voronoi{
      Polygon[] polygons;
      int npolygons;
      Delaunay de;
      Voronoi(Delaunay $de){
        de = $de;
        npolygons = 0;
        polygons = new Polygon[2000];
        synchronize();
      }
      void synchronize(){
        npolygons = 0;
        Point[] ps;
        int nps;
        for(int i=0;i<de.npoints;i++){
          ps = new Point[100];
          nps = 0;
          //find circumcenters of all polygons that include this point
          for(int j=0;j<de.nfaces;j++){
            if(de.faces[j].p1==de.points[i]||de.faces[j].p2==de.points[i]||de.faces[j].p3==de.points[i]) ps[nps++] = de.faces[j].ccp;
          }
          //order points
          float[] as = new float[nps];
          for(int j=0;j<nps;j++) as[j] = atan2(ps[j].pos.y-de.points[i].pos.y,ps[j].pos.x-de.points[i].pos.x)+TWO_PI;
          for(int j=0;j<nps-1;j++){
            for(int k=0;k<nps-1-j;k++){
              if(as[k]>as[k+1]){
                Point ps1 = ps[k]; ps[k] = ps[k+1]; ps[k+1] = ps1;
                float as1 = as[k]; as[k] = as[k+1]; as[k+1] = as1;
              }
            }
          }
          polygons[npolygons++] = new Polygon(ps);
        }
      }
      void render(PGraphics $pg){
        for(int i=0;i<npolygons;i++) polygons[i].render($pg);
      }
    }
    
    Global global;
    import peasy.*;
    PeasyCam cam;
    
    int w = 40;
    int h = 40;
    int cw = 1;
    int ch = 1;
    int nx = floor(w/cw);
    int ny = floor(h/ch);
    Cell[][] cells = new Cell[nx][ny];
    boolean paused = false;
    int[] B = new int[]{3,6};
    int[] S = new int[]{3,2};
    float divine = 0;
    float speed = 0.03;
    float sc = 15;
    int steps = 100;
    PGraphics heightmap,stage;
    Particle light;
    Terrain terrain;
    color[] colors = new color[]{color(80,190,255),color(0,255,100),color(0,100,50),color(40,100,200),color(80,100,200),color(255,255,255)};
    //color[] colors = new color[]{color(0),color(255)};
    
    void setup(){
      size(900,600,P3D);
      cam = new PeasyCam(this,650);
      //cam.setRotations(-1.216809,-0.30693975,0.09650442);
      //cam.lookAt(-15.475208,11.422351,2.4894624);
      cam.setRotations(-1.1393493,0.59715605,-0.23067099);
      cam.lookAt(1.6000938,-2.0371575,-3.7414675);
      heightmap = createGraphics(w,h,P2D);
      stage = createGraphics(floor(w*sc),floor(h*sc),P2D);
      global = new Global(stage.width,stage.height,stage.height);
      global.init();
      for(int i=0;i<nx;i++){
        for(int j=0;j<ny;j++){
          cells[i][j] = new Cell(i,j);
        }
      }
      for(int i=0;i<nx;i++){
        for(int j=0;j<ny;j++){
          cells[i][j].init();
        }
      }
      terrain = new Terrain(new PVector(-stage.width/2,-stage.height/2,0),new PVector(stage.width,stage.height,100),0.06);
      light = new Particle(global.random_coord(),new PVector(random(-1,1)*10,random(-1,1)*10,0),10,false);
      light.pos.z = 250;
      light.bouncing = true;
    }
    
    void draw(){
      background(0);
      heightmap.beginDraw();
      heightmap.background(0);
      if(frameCount%steps==0||frameCount<steps){
        if(!paused){
          for(int i=0;i<nx;i++){
            for(int j=0;j<ny;j++){
              cells[i][j].test();
            }
          }
        }
      }
      for(int i=0;i<nx;i++){
        for(int j=0;j<ny;j++){
          cells[i][j].step();
          cells[i][j].render(heightmap);
        }
      }
      heightmap.filter(BLUR,cw*2);
      heightmap.filter(THRESHOLD,0.4);
      heightmap.filter(BLUR,cw*3);
      heightmap.endDraw();
      stage.beginDraw();
      stage.background(0);
      stage.image(heightmap,0,0,stage.width+cw*stage.width/w,stage.height+ch*stage.height/h);
      stage.endDraw();
      //light.step();
      pointLight(255,255,255,light.pos.x,light.pos.y,250);
      noStroke();
      fill(50);
      terrain.synchronize(stage,true);
      terrain.step();
      for(int i=0;i<terrain.xpoints;i++){
        for(int j=0;j<terrain.ypoints;j++){
          if(i<terrain.xpoints-1&&j<terrain.ypoints-1){
            fill(getColor(terrain.faces[i][j][0].p1.pos.z/terrain.siz.z));
            terrain.faces[i][j][0].render(this.g);
            fill(getColor(terrain.faces[i][j][1].p1.pos.z/terrain.siz.z));
            terrain.faces[i][j][1].render(this.g);
          }
        }
      }
      noLights();
      float[] rotations = cam.getRotations();
      rotateX(rotations[0]);
      rotateY(rotations[1]);
      rotateZ(rotations[2]);
      translate(-width/2,-height/2,0);
      noFill();
      stroke(255);
      for(int i=0;i<nx;i++){
        for(int j=0;j<ny;j++) cells[i][j].render(this.g);
      }
      image(heightmap,0,h*1.5);
      if(frameCount%100==0) println(millis());
    }
    
    color getColor(float $p){
      int c1 = max(0,floor($p*(colors.length)));
      int c2 = min(colors.length-1,ceil($p*(colors.length)));
      if(c1==c2) return colors[c2];
      float r = red(colors[c1])+(red(colors[c2])-red(colors[c1]))*($p*colors.length-c1);
      float g = green(colors[c1])+(green(colors[c2])-green(colors[c1]))*($p*colors.length-c1);
      float b = blue(colors[c1])+(blue(colors[c2])-blue(colors[c1]))*($p*colors.length-c1);
      return color(r,g,b);
    }
    
    class Cell{
      int x,y;
      boolean alive = (random(-1,1)>0)?true:false;
      boolean next = alive;
      float a = (alive)?255:0;
      float ta = a;
      Cell[] neighbors = new Cell[8];
      Cell(int $x,int $y){
        x = $x; y = $y;
      }
      void init(){
        neighbors = new Cell[]{
          cells[(x-1+nx)%nx][(y-1+ny)%ny],
          cells[x][(y-1+ny)%ny],
          cells[(x+1)%nx][(y-1+ny)%ny],
          cells[(x-1+nx)%nx][y],
          cells[(x+1)%nx][y],
          cells[(x-1+nx)%nx][(y+1)%ny],
          cells[x][(y+1)%ny],
          cells[(x+1)%nx][(y+1)%ny]
        };
      }
      void test(){
        int n = 0;
        for(int i=0;i<8;i++){
          if(neighbors[i].alive) n++;
        }
        for(int i=0;i<B.length;i++){
          if(n==B[i]){
            next = (divine>0&&random(0,1)<divine)?false:true;
            return;
          }
        }
        for(int i=0;i<S.length;i++){
          if(n==S[i]){
            next = (divine>0&&random(0,1)<divine)?!alive:alive;
            return;
          }
        }
        next = (divine>0&&random(0,1)<divine)?true:false;
      }
      void step(){
        if(next!=alive) ta = (next)?255:0;
        a += (ta-a)*speed;
        alive = next;
      }
      void render(PGraphics $pg){
        $pg.noStroke();
        $pg.fill(255,a);
        $pg.rect(x*cw,y*ch,cw,ch);
      }
    }
    
    /*
    void mousePressed(){
      paused = true;
      cells[floor((w/width)*mouseX/cw)][floor((h/height)*mouseY/ch)].alive = !cells[floor((w/width)*mouseX/cw)][floor((h/height)*mouseY/ch)].alive;
      cells[floor((w/width)*mouseX/cw)][floor((h/height)*mouseY/ch)].next = cells[floor((w/width)*mouseX/cw)][floor((h/height)*mouseY/ch)].alive;
    }
    */
    
    void keyPressed(){
      if(key=='1'){
        terrain = new Terrain(new PVector(-stage.width/2,-stage.height/2,0),new PVector(stage.width,stage.height,100),0.01,stage);
      }else if(key=='2'){
        terrain = new Terrain(new PVector(-stage.width/2,-stage.height/2,0),new PVector(stage.width,stage.height,100),0.02,stage);
      }else if(key=='3'){
        terrain = new Terrain(new PVector(-stage.width/2,-stage.height/2,0),new PVector(stage.width,stage.height,100),0.03,stage);
      }else if(key=='4'){
        terrain = new Terrain(new PVector(-stage.width/2,-stage.height/2,0),new PVector(stage.width,stage.height,100),0.04,stage);
      }else if(key=='5'){
        terrain = new Terrain(new PVector(-stage.width/2,-stage.height/2,0),new PVector(stage.width,stage.height,100),0.05,stage);
      }else if(key=='6'){
        terrain = new Terrain(new PVector(-stage.width/2,-stage.height/2,0),new PVector(stage.width,stage.height,100),0.06,stage);
      }else if(key=='0'){
        terrain = new Terrain(new PVector(-stage.width/2,-stage.height/2,0),new PVector(stage.width,stage.height,100),100/(float)stage.width,stage);
      }else{
        //paused = !paused;
        println(cam.getRotations());
        println(cam.getLookAt());
      }
    }
    

    code

    tweaks (0)

    license

    advertisement

    Don Havey

    HighLife Terrain

    Add to Faves Me Likey@! 27
    You must login/register to add this sketch to your favorites.

    This sketch starts with a cellular automaton, blurs it, does some other filtering, then uses the resultant image as a height map for a terrain. The HighLife algorithm is incremented only once every 100 loops... between those times the height map is eased from one state to the next. The result is a continuously morphing SimCity-like terrain.

    Not optimized much. PeasyCam controls.

    Don Havey
    10 Apr 2010
    Oh and pressing numbers 1-6 on the keyboard changes the terrain density.
    Tiemen Rapati
    26 Apr 2010
    Well, I think it's very nice. And I also like the idea of the cell automata as heightmap. Well done.
    impressive, next-level thinking
    You need to login/register to comment.