Poly pol; int nshapes; FilledShape[] shapes; int ctr; BFont f; Rotor roti; float fibbo = 0.61803398874989; void setup() { size ( 400, 400 ); f = loadFont("OCR-A_II.vlw"); textFont(f, 24); roti = new Rotor ( 10, 0.2 ); roti.addChild( 7, 0.2 ); roti.addChild( 5, 0.1 ); roti.addChild( 5, 0.1 ); pol = new Poly(); nshapes = 0; shapes = new FilledShape[4]; ellipseMode( CENTER_DIAMETER ); } void loop() { background ( 0 ); roti.setdtheta ( HALF_PI * 0.3 * ( 0.5 - noise( millis() * 0.001 ) ) ); roti.setrad ( 80 * ( 0.4 + noise( 0.2, millis() * 0.0002 ) ) ); push(); translate ( width/2, height/2 ); stroke(64, 64, 64 ); noFill(); roti.spin(); roti.render(); pop(); if ( ( ctr++ ) % 1 == 0 ) { pol.addPt( roti.findPt() ); pol.isect(pol.nsegs()-1); } stroke ( 96, 96, 96 ); pol.render(); cleanShapes(); for ( int i = 0 ; i < nshapes; i++ ) { shapes[i].render(); } } void cleanShapes() { int i, j, k, dead; for ( i= 0 ; i < nshapes; i++ ) { dead = 0; while ( i+dead < nshapes && shapes[i+dead].dead() ) dead++; if ( dead > 0 ) { nshapes -= dead; for ( k = i; k < nshapes; k++ ) shapes[k] = shapes[k+dead]; } } } void mouseMoved ( ) { /* if ( (ctr++)%1 == 0 ) { pol.addPt( new Point ( mouseX, mouseY ) ); pol.isect(pol.nsegs()-1); } */ } void mousePressed() { for ( int s = pol.nsegs()- 1; s >= 2; s-- ) { int ts = pol.nsegs(); if ( pol.isect ( s ) ) { print ( "isect at " + s + " of " + ts ); s = pol.nsegs() - 1; } } } void addShape(FilledShape s) { if ( nshapes == shapes.length ) { FilledShape[] tmp = new FilledShape[shapes.length * 2]; System.arraycopy ( shapes, 0 , tmp, 0 , shapes.length ); shapes = tmp; } shapes[nshapes++] = s; } class Point { float x; float y; float nx; float ny; Point ( float xn, float yn ) { x = xn; y = yn; } Point add ( Vector d ) { return new Point( x + d.dx, y + d.dy ); } void sum ( Point p ) { x += p.x; y += p.y; } void setNext ( Point p ) { setNext ( p.x, p.y ); } void setNext ( float xt, float yt ) { nx = xt; ny = yt; } void step ( ) { x = nx; y = ny; } void moveTo ( float xn, float yn ) { x = xn; y = yn; } void set ( Point p ) { x = p.x; y = p.y; } double dist( Point p ) { return Math.sqrt( (x-p.x)*(x-p.x) + (y-p.y)*(y-p.y) ); } Vector sub ( Point p ) { return new Vector ( x - p.x , y - p.y ); } Point blend( Point p, float a ) { return new Point ( x * (1-a) + p.x * a, y * ( 1-a ) + p.y * a ); } void mul ( float m ) { x *= m; y *= m; } void render() { point ( x, y ); } void vert() { vertex( x, y ); } void cvert() { curveVertex( x, y ); } } class Vector { float dx; float dy; Vector ( float dx, float dy ) { this.dx = dx; this.dy = dy; } Vector ( Point p ) { dx = p.x; dy = p.y; } double len() { return Math.sqrt ( dx*dx + dy*dy ); } double ang() { return Math.atan2( dy, dx ); } Vector rot(float ang) { float ndx = dx * cos( ang ) - dy * sin ( ang ); float ndy = dx * sin( ang ) + dy * cos ( ang ); return new Vector ( ndx, ndy); } Vector scale(float s) { return new Vector ( dx * s, dy * s ); } Vector norm() { if ( len() == 0 ) return this; else return this.scale( float( 1.0 / len() ) ); } float dot( Vector b ) { return dx*b.dx + dy * b.dy ; } Vector left () { return new Vector ( -dy , dx ); } } class LineSeg { Point p1; Point p2; LineSeg( Point p1, Point p2 ) { this.p1 = p1; this.p2 = p2; } float dx() { return p2.x - p1.x; } float dy() { return p2.y - p1.y; } Vector v() { return p2.sub(p1); } void render() { line ( p1.x, p1.y, p2.x, p2.y ); } boolean isect ( LineSeg cx, Point res ) { //pdb version float den = cx.dy()*dx() - cx.dx()*dy(); float numA= cx.dx()*(p1.y - cx.p1.y) - cx.dy()*(p1.x - cx.p1.x); float numB= dx() *(p1.y - cx.p1.y) - dy()*(p1.x - cx.p1.x); if ( den == 0 ) { if ( numA == 0 && numB == 0 ) { res.set(p1); return true; } else return false; } else { float ua = numA / den; float ub = numB / den; if ( ( 0.0 <= ua && ua <= 1.0 ) && ( 0.0 <= ub && ub <= 1.0 ) ) { res.set( p1.add( p2.sub(p1).scale(ua) ) ); return true; } else { return false; } } } float dihedral ( LineSeg s ) { if ( p2 != s.p1 ) return 0; Vector n = v().left().norm(); Vector sn = s.v().left().norm(); float dp = n.dot(sn); float ang = (float)Math.acos( dp ); if ( sn.dot( v() ) < 0 ) ang *= -1.0; // float a1 = Math.atan2( dy, dx ); // float a2 = Math.atan2( s.dy, s.dx ); return ang; } } class Poly { color col; Point[] points; LineSeg[] segs; int npts; int ptmax; Poly() { npts = 0; ptmax = 4; points = new Point[ptmax]; segs = new LineSeg[ptmax]; col = color ( random(255), random(255) , random(255) ); } int nsegs() { return ( npts > 0 ) ? npts-1 : 0 ; } void addPt( Point p ) { if ( npts == 0 ) { points[0] = p; npts++; } else { if ( npts == ptmax ) { expand_arrays(); } points[npts] = p; segs[npts-1] = new LineSeg( points[npts-1], points[npts] ); npts++; } } boolean isect(int n) { Point xpt = new Point(0,0); for ( int i = n-2 ; i >= 0; i-- ) { if ( segs[n].isect(segs[i], xpt) ) { clip ( i, xpt, n ); return true; } } return false; } float curvature( int i ) { if ( i == 0 || i == npts-1 ) return 0; else return segs[i-1].dihedral(segs[i]); } void clip ( int i, Point xpt, int n ) { //segment i intersects segment n at xpt. //regroove the line to fit that. //first, create the shape around the loop float tcurv = 0; FilledShape s = new FilledShape(); s.addPt( xpt ); for ( int j = i+1 ; j <= n; j++ ) { s.addPt( points[j] ); tcurv += curvature(j); } float lg = 160 + random(48); float sm = 64 + random(48); col = ( tcurv > 0 ) ? color ( lg, 128, sm , 128 ) : color ( sm, 128, lg, 128 ); s.col = col; addShape(s); // abbreviate Point[] tail = new Point[npts - (n+1)]; System.arraycopy(points, n+1, tail, 0, tail.length); npts = i+1; //chop the path to before the intersection... addPt( xpt ); for ( int t= 0 ; t < tail.length; t++ ) addPt( tail[t] ); } void slide() { int i ; for ( i = 1; i < npts-1 ; i++) { points[i].setNext( points[i].blend( points[i-1].blend( points[i+1], 0.5 ) , 0.02 ) ); } for ( i = 1; i < npts-1 ; i++ ) { points[i].step(); } } void expand_arrays() { ptmax *= 2; //expand pts array Point[] ptmp = new Point[ptmax]; System.arraycopy ( points, 0 , ptmp, 0, points.length ); points = ptmp; //segs array is always expanded 1 insert before necessary, //but this makes the bookkeeping easier. LineSeg[] stmp = new LineSeg[ptmax]; System.arraycopy ( segs, 0 , stmp, 0, segs.length ); segs = stmp; } void render() { stroke(255,255,255,64); for ( int i = 0 ; i < npts-1; i++ ) { segs[i].render(); } // for ( int i = 0; i < npts ; i++ ) text( "a:"+curvature(i), points[i].x, points[i].y ); } } class FilledShape { color col; Point pts[]; int npts; int life; FilledShape() { npts = 0; pts = new Point[1]; life = 320; } void addPt ( Point p ) { if ( npts == pts.length ) { Point[] tmp = new Point[pts.length * 2]; System.arraycopy ( pts, 0 , tmp, 0 , pts.length ); pts = tmp; } pts[npts++] = new Point ( p.x, p.y ); } void render ( ) { push(); noStroke(); if ( life > 32 ) { fill ( col ); if ( life > 312 ) { fill ( red(col), green(col), blue(col), 16 * ( 320 - life ) ); } } else { fill ( red(col), green(col), blue(col), 4 * life ); translate ( 0, 0, ( 32 - life ) ); } beginShape(POLYGON); for ( int i = 0 ; i < npts ; i++ ) pts[i].vert(); endShape(); pop(); shrink(); if ( life > 0 ) life--; } boolean dead() { return ( life <= 0 );} void shrink () { if ( npts < 3 ) return; int i; float tlen = 0; Point avg = new Point ( 0, 0 ); for ( i = 0 ; i < npts; i++ ) { int ring = (i+1) % npts; int r2 = ( i + npts -1 ) % npts; pts[i].setNext( pts[i].blend( pts[ring].blend(pts[r2],0.5 ), 0.06 ) ) ; tlen += pts[i].dist(pts[ring]); avg.sum( pts[i] ); } avg.mul ( 1.0 / (float)npts ); for ( i = 0 ; i < npts; i++ ) pts[i].step(); float nlen = 0; for ( i = 0 ; i < npts; i++ ) { int ring = (i+1) % npts; nlen += pts[i].dist(pts[ring]); } float fact = tlen / nlen; for ( i = 0 ; i < npts; i++ ) { pts[i].set ( avg.add( pts[i].sub(avg).scale(fact)) ); } } } class Rotor { float rad; float drad; float theta; float dtheta; Rotor child; Rotor( float rad, float theta ) { this.rad = rad; this.theta = theta; this.drad = 0; this.dtheta = 0; child = null; } void addChild( float rad, float theta ) { if ( child == null ) child = new Rotor( rad, theta ); else child.addChild( rad, theta ); } void render() { push(); rotateZ(theta); ellipse( 0, 0, 2*rad, 2 * rad); line ( 0, 1, rad, 1 ); line ( 0, -1, rad, -1 ); if ( child != null ) { translate( rad, 0 ); child.render(); } pop(); } Point findPt() { Point r; push(); translate ( width/2 , height/2 ); r = getPt(); pop(); return r; } Point getPt() { rotateZ(theta); translate(rad, 0); if ( child != null ) { return child.getPt(); } else { return new Point ( screenX(0,0,0), screenY(0,0,0) ); } } void setrad( float f ) { rad = f; } void setdtheta( float f ) { dtheta = f;} void spin() { rad += drad; theta += dtheta; if ( child != null ) { child.drad = 0.2 * (fibbo * rad - child.rad); child.dtheta += 0.1 * (-(dtheta*1.2) - child.dtheta); child.spin(); } } }