Wednesday, May 13, 2009

Final version of Ball of Confusion for Chapter 33


Here is the code for my fragment. It needs OpenGL, but it should work reasonably well in P3D mode - just change change OPENGL to P3D in setup().



b e n


//
// BOC - v1
//
import processing.opengl.*;

void setup()
{
size( 1024, 768, OPENGL ) ;

colorMode( RGB, 1.0f ) ;
}


void draw()
{
background( 0.25f ) ;

// Move the origin so that the scene is centered on the screen.
translate( width/2, height/2, 0.0f ) ;

// Set up the lighting.
ambientLight( 0.025f, 0.025f, 0.025f ) ;
directionalLight( 0.2f, 0.2f, 0.2f, -1, -1, -1);
spotLight( 1.0f, 1.0f, 1.0f, -200, 0, 300, 1, 0, -1, PI/4.0f, 20) ;

// Rotate the local coordiante system.
smoothRotation( 5.0f, 6.7f, 7.3f ) ;

// Draw the inner object.
noStroke() ;
fill( smoothColour( 10.0f, 12.0f, 7.0f ) ) ;
drawIcosahedron( 5, 60.0f, false ) ;

// Rotate the local coordiante system again.
smoothRotation( 4.5f, 3.7f, 7.3f ) ;

// Draw the outer object.
stroke( 0.2f ) ;
fill( smoothColour( 6.0f, 9.2f, 0.7f ) ) ;
drawIcosahedron( 5, 200.0f, true ) ;
}

/**
* Generate a vector whose components change smoothly over time in the range [ 0, 1 ].
* Each component uses a sin() function to map the current time in milliseconds somewhere
* in the range [ 0, 1 ].A 'speed' factor is specified for each component.
*/
PVector smoothVector( float s1, float s2, float s3 )
{
float mills = 0.00003f * millis() ;

float x = 0.5f * sin( mills * s1 ) + 0.5f ;
float y = 0.5f * sin( mills * s2 ) + 0.5f ;
float z = 0.5f * sin( mills * s3 ) + 0.5f ;

return new PVector( x, y, z ) ;
}

/**
* Generate a colour which smoothly changes over time.
* The speed of each component is controlled by the parameters s1, s2 and s3.
*/
color smoothColour( float s1, float s2, float s3 )
{
PVector v = smoothVector( s1, s2, s3 ) ;

return color( v.x, v.y, v.z ) ;
}

/**
* Rotate the current coordinate system.
* Uses smoothVector() to smoothy animate the rotation.
*/
void smoothRotation( float s1, float s2, float s3)
{
PVector r1 = smoothVector( s1, s2, s3 ) ;

rotateX( 2.0f * PI * r1.x ) ;
rotateY( 2.0f * PI * r1.y ) ;
rotateX( 2.0f * PI * r1.z ) ;
}

/**
* Draw an icosahedron defined by a radius r and recursive depth d.
* Geometry data will be saved into dst. If spherical is true then the icosahedron
* is projected onto the sphere with radius r.
*/
void drawIcosahedron( int depth, float r, boolean spherical )
{
// Calcualte the vertex data for an icosaheron inscribed by a sphere radius 'r'.
// Use 4 Golden Ratio rectangles as the basis.
float gr = ( 1.0f + sqrt(5.0f) ) / 2.0f ;
float h = r / sqrt( 1.0f + gr * gr ) ;

PVector[] v =
{
new PVector( 0,-h,h*gr ), new PVector( 0,-h,-h*gr ), new PVector( 0,h,-h*gr ), new PVector( 0,h,h*gr ),
new PVector( h,-h*gr,0 ), new PVector( h,h*gr,0 ), new PVector( -h,h*gr,0 ), new PVector( -h,-h*gr,0 ),
new PVector( -h*gr,0,h ), new PVector( -h*gr,0,-h ), new PVector( h*gr,0,-h ), new PVector( h*gr,0,h )
} ;

// Draw the 20 triangular faces of the icosahedron.
if ( ! spherical )
r = 0.0f ;

beginShape( TRIANGLES ) ;

drawTriangle( depth, r, v[0], v[7],v[4] ) ;
drawTriangle( depth, r, v[0], v[4], v[11] ) ;
drawTriangle( depth, r, v[0], v[11], v[3] ) ;
drawTriangle( depth, r, v[0], v[3], v[8] ) ;
drawTriangle( depth, r, v[0], v[8], v[7] ) ;

drawTriangle( depth, r, v[1], v[4], v[7] ) ;
drawTriangle( depth, r, v[1], v[10], v[4] ) ;
drawTriangle( depth, r, v[10], v[11], v[4] ) ;
drawTriangle( depth, r, v[11], v[5], v[10] ) ;
drawTriangle( depth, r, v[5], v[3], v[11] ) ;
drawTriangle( depth, r, v[3], v[6], v[5] ) ;
drawTriangle( depth, r, v[6], v[8], v[3] ) ;
drawTriangle( depth, r, v[8], v[9], v[6] ) ;
drawTriangle( depth, r, v[9], v[7], v[8] ) ;
drawTriangle( depth, r, v[7], v[1], v[9] ) ;

drawTriangle( depth, r, v[2], v[1], v[9] ) ;
drawTriangle( depth, r, v[2], v[10], v[1] ) ;
drawTriangle( depth, r, v[2], v[5], v[10] ) ;
drawTriangle( depth, r, v[2], v[6], v[5] ) ;
drawTriangle( depth, r, v[2], v[9], v[6] ) ;

endShape() ;
}

/**
* Draw a triangle either immediately or subdivide it first.
* If depth is 1 then draw the triangle otherwise subdivide first.
*/
void drawTriangle( int depth, float r, PVector p1, PVector p2, PVector p3 )
{
if ( depth == 1 )
{
vertex( p1.x, p1.y, p1.z ) ;
vertex( p2.x, p2.y, p2.z ) ;
vertex( p3.x, p3.y, p3.z ) ;
}
else
{
// Calculate the mid points of this triangle.
PVector v1 = PVector.mult( PVector.add( p1, p2 ), 0.5f ) ;
PVector v2 = PVector.mult( PVector.add( p2, p3 ), 0.5f ) ;
PVector v3 = PVector.mult( PVector.add( p3, p1 ), 0.5f ) ;

if ( r != 0.0f )
{
// Project the verticies out onto the sphere with radius r.
v1.normalize() ; v1.mult( r ) ;
v2.normalize() ; v2.mult( r ) ;
v3.normalize() ; v3.mult( r ) ;
}

// Generate the next level of detail.
depth -- ;

drawTriangle( depth, r, p1, v1, v3 ) ;
drawTriangle( depth, r, v1, p2, v2 ) ;
drawTriangle( depth, r, v2, p3, v3 ) ;

// Uncomment out the next line to include the central part of the triangle.
// drawTriangle( depth, r, v1, v2, v3 ) ;
}
}

3 comments:

monkstone said...

How much nicer this code must look using PVector rather than a custom vector class. I was having doubts whether I should be using Affine transforms (translate, rotate) as early as chunk 42, I guess its very catch 22 when you writing this sort of book that all the code is understood at each stage?

monkstone said...

Just rechecked my remit for chunk 42, and whilst I was about it looked at your chunk, it seems you've got licence to use stuff that hasn't been introduced as the only bit likely to cover opengl. I'm tempted to use translate and rotate in chunk 42 and forward reference the reader as it makes the algorithms simpler.

lazydog said...

Hi Martin, imho I think the meaning of rotate(), translate() etc are pretty self explanatory anyway. Perhaps how they accomplish their results is more difficult but they don't need to know that to be able to use them.

b e n