fullscreen //------ 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());
}
}
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.