//-----------------// // Escher's Knot // // by Henry Rogers // // 5/4/2011 - v1.0 // //-----------------// /* UI Keyboard Controls: c - cycle; r - revolve; g - gamut; w - wireframe; */ /* GUI Controls: mouse click button - change mouse click drag - rotate. */ //import com.processinghacks.arcball.*; import processing.opengl.*; //---- globals int _e = 43; // for elizabeth int _n = 72; // number of boxes along curve int _u = 8; // number of rendering tweens per key frame int _p = _n * _u; // number of points or key frames on curve int _b = 4; // number of points in width of box int _a = 2; // curve scaling factor int _c = 0; // to cycle or not to cycle int _f = 0; // frame iterator for cycling int _g = 1; // to show gamut or not int _h = 0; // hue interator for gamut int _o = 360; // hue color base int _r = 0; // to revolve or not to revolve int _m = 0; // mobius iterator for rotation int _w = 0; // to show wireframe or solids int _x = 90; // initial x rotation int _y = 70; // initial y rotation int _z = 90; // initial z rotation int _s = 54; // initial model scale float _vx = 0; // velocity vector for rotation float _vy = 0; // velocity vector for rotation float _d1 = 0.38; // distance from corner to first midpoint on side float _d2 = 0.62; // distance from corner to second midpoint on side // - mobius is the rotation of the box around the curve // -- _p is the number of key frames along curve // -- 6 is the number of rotation sections in trefoil knot (this does not change) // -- so there are 96 rotation angles to bring tube //float _q1 = HALF_PI * 3 / _p; // this is the angle that needs fixing to be true Escher Knot float _q1 = 0; float _q2 = TWO_PI * 6 / _p; // advance revolution angle (mobius) by mobius iterator boolean _l = false; // lock hover state when button is pressed PImage _t; // texture image TrefoilCurve _k; // Trefoil knot class // button UI controls Button btnCycle; // start/stop cycling Button btnRevolve; // start/stop revolution Button btnWireframe; // show/hide wireframe void setup() { size( 540, 540, OPENGL ); //smooth(); //textFont( loadFont("font.vlw"), 18 ); //textMode(SHAPE); PFont font = loadFont("Arial.vlw"); textFont(font,12); // set color mode to be colorMode( HSB, _o, 100, 100 ); // instantiate trefoil curve _k = new TrefoilCurve( _a, _d1, _d2 ); // instantiate GUI button controls btnCycle = new Button( 6, 6, color(50,40,80), "Cycle" ); btnRevolve = new Button( btnCycle.right() + 6, 6, color(50,40,80), "Revolve" ); btnWireframe = new Button( btnRevolve.right() + 6, 6, color(50,40,80), "Wireframe" ); } void draw() { background( 59, 7, 85 ); //lights(); // adjust lighting depending on gamut or texture mode if ( _g == 1 ) { //lightSpecular( 30, 40, 50 ); //directionalLight( 0, 0, 20, 0, 0, -1 ); } else { lightSpecular( 25, 50, 72 ); pointLight( 25, 50, 100, 0, 0, -12 ); directionalLight( 25, 50, 72, 0, 0, -1 ); } // set initial view port translate( width/2, height/2+20, -height/2 ); rotateX( radians( _x ) ); rotateY( radians( _y ) ); rotateZ( radians( _z ) ); scale( _s ); int i,j; // loop through key frames on curve for ( j=0; j<_p; j=j+_u ) { i = j + _f; // advance tube by frame iterator // draw sides of tube drawTube( i, ( HALF_PI * 0 ) + ( _q1 * j ) + ( _q2 * _m ) ); drawTube( i, ( HALF_PI * 1 ) + ( _q1 * j ) + ( _q2 * _m ) ); drawTube( i, ( HALF_PI * 2 ) + ( _q1 * j ) + ( _q2 * _m ) ); drawTube( i, ( HALF_PI * 3 ) + ( _q1 * j ) + ( _q2 * _m ) ); } // persist rendering mode control iterators _f = ( _f + _c ) % _u; // advance frame iterator if cycling _h = ( _h + _c ) % _o; // advance hue iterator if cycling _m = ( _m + _r ) % (_p/6); // advance mobius iterator if revolving // gui io drawGUI(); // rotate view port _x += _vx; _y += _vy; _vx *= 0.95; _vy *= 0.95; // comment out below if using ArcBall if ( mousePressed && mouseButton == LEFT ) { _vx -= (mouseY-pmouseY) * 0.05; _vy += (mouseX-pmouseX) * 0.05; } } class TrefoilCurve { float _scalar = 0.0; // curve scaling factor float _distance1 = 0.0; // distance from corner to first midpoint on side float _distance2 = 0.0; // distance from corner to second midpoint on side //---- constructor TrefoilCurve( int scalar, float midpoint1, float midpoint2 ) { _scalar = scalar; _distance1 = midpoint1; _distance2 = midpoint2; } //---- derivatives // calculate first differential private PVector dtFirst( float t ) { float x = _scalar * ( 3 * cos(3*t) ); float y = _scalar * ( 4 * cos(2*t) + cos(t) ); float z = _scalar * ( 4 * sin(2*t) - sin(t) ); return new PVector( x, y, z ) ; } // calculate second differential private PVector dtSecond( float t ) { float x = _scalar * ( -9 * sin(3*t) ); float y = _scalar * ( -8 * sin(2*t) - sin(t) ); float z = _scalar * ( 8 * cos(2*t) - cos(t) ); return new PVector( x, y, z ) ; } //---- Frenet frame // orthagonal to the normal plane private PVector getTangent( float t ) { PVector tangentVector = dtFirst(t); tangentVector.normalize(); return tangentVector; } // orthagonal to the rectifying plane private PVector getNormal( float t ) { PVector normalVector = dtFirst(t).cross( dtSecond(t) ).cross( dtFirst(t) ); // PVector normalVector = dtSecond(t); // NO! normalVector.normalize(); return normalVector; } // orthagonal to the osculating plane private PVector getBinormal( float t ) { PVector binormalVector = dtFirst(t).cross( dtSecond(t) ); binormalVector.normalize(); return binormalVector; } /* Trefoil knot: x = sin(t) + 2sin(2t) y = sin(3t) z = cos(t) - 2cos(2t) 0 <= t <= 2Pi */ // parameterization in 1995 PhD thesis of Aaron Trautwein at The University of Iowa is right hand trefoil // utilizing standard parameterization // center of tube on curve private PVector getOrigin( float t ) { float x = _scalar * ( sin(3*t) ); float y = _scalar * ( sin(t) + 2*sin(2*t) ); float z = _scalar * ( cos(t) - 2*cos(2*t) ); return new PVector( x, y, z ) ; } // get points along side of tube private PVector[] getPoints( PVector B, PVector N ) { PVector[] points = new PVector[4]; points[0] = PVector.add( B, N ); points[3] = PVector.sub( B, N ); // calculate side vector PVector side = PVector.sub( points[3], points[0] ); points[1] = PVector.add( points[0], PVector.mult( side, _distance1 ) ); points[2] = PVector.add( points[0], PVector.mult( side, _distance2 ) ); return points; } // ----------------- // public function // ----------------- // o -- o -- o -- o // | | // o o // | tube | // o o // | | // o -- o -- o -- o // build vectors for all points on side of tube public PVector[] getSide( float theta, float mobius ) { PVector origin = getOrigin(theta); PVector tangent = getTangent(theta); PVector[] points = getPoints( getBinormal(theta), getNormal(theta) ); PVector[] side = new PVector[4]; PMatrix3D rotationMatrix = new PMatrix3D(); // advance rotation matrix by side iterator plus mobius factor rotationMatrix.rotate( mobius, tangent.x, tangent.y, tangent.z ); // println(mobius); // for each point on side for ( int j=0; j<4; j++ ) { side[j] = new PVector(); // rotate first rotationMatrix.mult( points[j], side[j] ); // then translate side[j].add( origin ); } return side; } } //---- draw graphical user interface void drawGUI() { camera(); noLights(); // if there is not an active hover state if ( _l == false ) { // if a mouse button was pressed, lock hover state until mouse button is released if ( ( btnCycle.update() | btnRevolve.update() | btnWireframe.update() ) && mousePressed ) { _l = true; } } btnCycle.display(); btnRevolve.display(); btnWireframe.display(); } //---- io controls void mouseReleased() { // release lock hover state _l = false; // if a button is pressed, change its state and invert its value if ( btnCycle.pressed() ) { _c = ( 1 - abs(_c) ) * btnRevolve.getPreviousState(); // start/stop cycling } if ( btnRevolve.pressed() ) { _r = ( 1 - abs(_r) ) * btnRevolve.getPreviousState(); // start/stop revolution } if ( btnWireframe.pressed() ) { _w = 1 - _w; // show/hide wireframe } } void keyReleased() { switch( key ) { case 's': _s = _s - 5; // decrease scale break; case 'S': _s = _s + 5; // increase scale break; case 'c': _c = 1 - _c; // start/stop cycling break; case 'r': _r = 1 - _r; // start/stop revolution break; case 'w': _w = 1 - _w; // show/hide wireframe break; } } //---- gui classes class Button { float x, y; float w, h; String label; color clrBase, clrHighlight, clrBorder, clrCurrent; boolean depressed = false; int previousState = 1; // constructor Button( float xPosition, float yPosition, color btnColor, String btnText ) { x = xPosition; y = yPosition; label = btnText; w = textWidth(label) + 16; h = textAscent() + textDescent() + 8; clrBase = clrCurrent = btnColor; clrBorder = color( hue(btnColor), saturation(btnColor), brightness(btnColor)-30 ); clrHighlight = color( hue(btnColor), saturation(btnColor), brightness(btnColor)+30 ); } void display() { stroke( clrBorder ); strokeWeight( 1 ); fill( clrCurrent ); rect( x, y, w, h ); fill( clrBorder ); text(label, x + 20, y + h + 4, 30); } boolean update() { if ( over() ) { clrCurrent = clrHighlight; return true; } else if ( depressed == false ) { clrCurrent = clrBase; return false; } else { return false; } } boolean pressed() { if ( over() ) { if ( depressed == false ) { depressed = true; clrCurrent = clrHighlight; previousState *= -1; } else { depressed = false; } return true; } else { return false; } } boolean over() { if ( mouseX >= x && mouseX <= x+w && mouseY >= y && mouseY <= y+h ) { return true; } return false; } float right() { return x+w; } int getPreviousState() { return previousState; } } void drawTube( int i, float mobius ) { // --0-- // 1| |3 // --2-- int s = ( i + _b ) % _p; // secondary int t = ( i + _u ) % _p; // tertiary // if showing gamut, advance hue interator else show orginal hue int h = ceil( _o - ( i / _u * 5 ) + _h ) % _o; // get vectors for points along side of tube PVector[] I = _k.getSide( i * TWO_PI / _p, mobius ); // points 1, 2, 3, 4 PVector[] S = _k.getSide( s * TWO_PI / _p, mobius ); // points 9, 8, 5, 4 PVector[] T = _k.getSide( t * TWO_PI / _p, mobius ); // points 7, 6 // 0___1___2__3 I // |___ ___| S // 9 |__| 4 T // 7 6 noStroke(); noFill(); // if not in wireframe mode if ( _w != 1 ) { fill( h, 40, 60 ); //---- draw sides of tube as mesh beginShape(); vertex( I[0].x,I[0].y,I[0].z ); //0 vertex( I[3].x,I[3].y,I[3].z ); //1 vertex( S[3].x,S[3].y,S[3].z ); //8 vertex( S[0].x,S[0].y,S[0].z ); //8 vertex( I[0].x,I[0].y,I[0].z ); //0 endShape(CLOSE); beginShape(TRIANGLES); arrayVector( S[1] ); //8 arrayVector( S[2] ); //5 arrayVector( T[2] ); //6 arrayVector( T[2] ); //6 arrayVector( T[1] ); //7 arrayVector( S[1] ); //8 endShape(CLOSE); strokeWeight( 2 ); stroke( 0, 0, 12 ); noFill(); } // in wireframe mode else { strokeWeight( 1 ); stroke( h, 40, 60 ); } //---- draw outline of tube // 2 __ 1 i // 3 |__ 4 a // 3 | 5 b beginShape(); arrayVector( I[1] ); //1 arrayVector( I[0] ); //2 arrayVector( S[0] ); //3 arrayVector( S[1] ); //4 arrayVector( T[1] ); //5 endShape(); // 1 __ 2 i // 4 __| 3 a // 5 | b beginShape(); arrayVector( I[2] ); //1 arrayVector( I[3] ); //2 arrayVector( S[3] ); //3 arrayVector( S[2] ); //4 arrayVector( T[2] ); //5 endShape(); } // overloaded core functions void arrayVector( PVector v ) { vertex( v.x, v.y, v.z ); }