Thursday, January 15, 2009

Ball of Confusion

I've been thinking of something a bit easier for Chapter 33 and have headed down plotting an icosahedron. I've added tessalation... I don't think that's too much of an addition, and a bit of animation. Code is below... sorry for the bad formatting and crap comments. Here's a picture of what the output looks like.




import processing.opengl.*;

boolean gFullScreen = false ;
boolean gAnimate = true ;
int gWindowWidth = 640 ;
int gWindowHeight = 480 ;

void setup()
{
size( gFullScreen ? screen.width : gWindowWidth, gFullScreen ? screen.height : gWindowHeight, OPENGL ) ;

colorMode(RGB, 255 ) ;

setSubdivsionDepth( 4 ) ;
createSphere() ;
}


float rx = 0.0 ;
float ry = 0.0 ;
float rz = 0.0 ;

boolean gFlat = false ;
int gSmoothValue = 3 ;
int gSubDivisionDepth = 0 ;
int gFrameCounter = 0 ;

color calc_color( float s1, float s2, float s3 )
{
// float x = gFrameCounter * 0.01f ;
//
// int r = round( 255 * noise( x, s1, 1 ) ) ;
// int g = round( 255 * noise( x, s2, 2 ) ) ;
// int b = round( 255 * noise( x, s3, 3 ) ) ;
//
// return color( r, g, b ) ;

float r = 127 *sin( PI * gFrameCounter * s1 ) + 127 ;
float g = 127 *sin( PI * gFrameCounter * s2 ) + 127 ;
float b = 127 *sin( PI * gFrameCounter * s3 ) + 127 ;
float a = 255 ; // 50 *sin( PI * gFrameCounter * (s3+s2+s1) ) + 205 ;

return color( (int) r, (int) g, (int) b, (int) a ) ;

}

void draw()
{
background( 0 ) ;

lights() ;

spotLight( 255, 255, 255, 400, 400, 400, -1, -1, -1, PI/2, 2) ;
// directionalLight(51, 102, 126, -1, 0, 0);

translate( width/2, height/2 ) ;

// rotateX( PI * rx / 180.0 ) ;
// rotateY( PI * ry / 180.0 ) ;
// rotateZ( PI * rz / 180.0 ) ;

rotateX( PI * gFrameCounter / 300.0 ) ;
rotateY( PI * gFrameCounter / 370.0 ) ;
rotateZ( PI * gFrameCounter / 330.0 ) ;

// color colour1 = calc_color( 127, 245, 300 ) ;
// color colour2 = calc_color( 100, 145, 350 ) ;
//
// drawIcosahedron( BallRadius, gSubDivisionDepth, colour1, colour2 ) ;

createSphere() ;
drawTessalatedIcosahedorn( gVerticies, gColourTable, gSubDivisionDepth ) ;
//
//gColourTable = generateColourTable( color( 39, 39, 200 ), gSubDivisionDepth ) ;
// drawTessalatedIcosahedorn( gVerticies, gColourTable, gSubDivisionDepth ) ;

if ( gAnimate )
gFrameCounter++ ;

}



void keyPressed()
{
switch ( key )
{

case 'p' :
case 'P' :
gAnimate = ! gAnimate ;
break ;

case '+' :
case '=' :
gSmoothValue += gSmoothValue > 0 ? -1 : 0 ;
createSphere() ;
break ;

case 'a' :
case 'A' :
gFlat = ! gFlat ;
break ;

case '-' :
case '_' :
gSmoothValue += gSmoothValue < gSubDivisionDepth - 1 ? 1 : 0 ;
// createSphere() ;
break ;

case '1' :
case '2' :
case '3' :
case '4' :
case '5' :
case '6' :
case '7' :
case '8' :
case '9' :

setSubdivsionDepth( key - '1' + 1 ) ;

// createSphere() ;

break ;

}
}

//
// Define some magic numbers.
//
float BallRadius = 160 ;
int MaxSubDivisionDepth = 7 ;

//
// The radii for each level.
//
float[] gRadii = null ;

//
// The colour table.
//
color[] gColourTable = null ;

//
// Declare the array of verticies used to define the tringulated icosahedron.
//
float[] gVerticies = null ;

//
// Define the verticies for an icosahedron.
//
final int MaxColours = 20 * (int) pow( 4, MaxSubDivisionDepth - 1 ) ;

void createSphere()
{
generateRadii( gFlat, BallRadius, gSubDivisionDepth ) ;
tessalateIcosahedron( gRadii, gSubDivisionDepth ) ;
generateColourTable( color( 39, 39, 200 ), gSubDivisionDepth ) ;
}

void setSubdivsionDepth( int depth )
{
depth = max( 1, depth ) ;
depth = min( depth, MaxSubDivisionDepth ) ;

if ( depth != gSubDivisionDepth )
{
//
// Allocate the radii table.
//
gRadii = new float[ depth + 1 ] ;

//
// Alocate the colour table.
//
gColourTable = new color[ 20 * (int) pow( 4, depth - 1 ) ] ;

//
// Allocate an array of PVertex large enougth for the tessalation.
// The number of triangles PVertex objects required is calculated as:-
//
// Each triangle is defined by 9 floats, 3 for each vertex
// The icosahedron consists of 3 equalateral triangles.
// Each triangle is subdivided into 4 smaller triangles.
// Subdivision occurs at most, MaxSubDivisionDepth - 1 times.
//
gVerticies = new float[ 3 * 3 * 20 * (int) pow( 4, depth - 1 ) ] ;

gSubDivisionDepth = depth ;
}

}


/**
* Generate the radii for each level of subdivision.
*
* Parameters : depth - the subdivision depth of the sphere
*
* Returns : an array of radii values
*/
void generateRadii( boolean flat, float baseRadius, int depth )
{


if ( flat )
for ( int i = 1 ; i <= depth ; ++i )
gRadii[ i ] = baseRadius ;
else
for ( int i = depth ; i >= 0 ; --i )
{
float r = 2.0f * noise( gFrameCounter*0.003f, ( (float) i) / depth ) - 1.0 ;
gRadii[ i ] = baseRadius * ( 1 + 0.9 * r ) ;
}

}

/**
* Generate the colour table.
*
* Parameters : depth - the subdivision depth of the sphere
*
* Returns : an array of color values
*/
void generateColourTable( color baseColor, int depth )
{
color c1 = calc_color( 0.00400, 0.002200, 0.00300 ) ;
color c2 = calc_color( 0.00328, 0.00328, 0.00328 ) ;
color c3 = calc_color( 0.00960, 0.00780, 0.00450 ) ;
color c4 = calc_color( 0.00420, 0.00520, 0.00620 ) ;
color c5 = calc_color( 0.00496, 0.00780, 0.00450 ) ;

// color c1 = color( 40, 0, 0 ) ;
// color c2 = color( 128, 0, 128 ) ;
// color c3 = color( 96, 78, 45 ) ;
// color c4 = color( 12, 12, 12 ) ;
// color c5 = color( 196, 78, 45 ) ;

color[][] colourSet1 = {
{
c1, c1, c1, c2 }
,
{
c1, c1, c1, c2 }
,
{
c1, c1, c1, c2 }
,
{
c4, c4, c4, c5 }
}
;

color[][] colourSet2 = {
{
c1, c1, c1, c1 }
,
{
c1, c1, c1, c1 }
,
{
c1, c1, c1, c1 }
,
{
c3, c3, c3, c4 }
}
;

//
// Example colour table generator 1.
//
if ( true )
{
int index = 0 ;

for ( int i = 0 ; i < 20 ; ++i )
for ( int j = 0 ; j < (int) pow( 4, depth - 1 ) ; ++j )
gColourTable[ index++ ] = colourSet1[ ( j / 16 ) % 4 ][ j % 4 ] ;
}

//
// Example colour table generator 2.
//
if ( false )
{
int index = 0 ;

for ( int i = 0 ; i < 5 ; ++i )
for ( int j = 0 ; j < (int) pow( 4, depth - 1 ) ; ++j )
gColourTable[ index++ ] = colourSet1[ j % 4 ][ j % colourSet1[ j % 4 ].length ] ;

for ( int i = 0 ; i < 10 ; ++i )
for ( int j = 0 ; j < (int) pow( 4, depth - 1 ) ; ++j )
gColourTable[ index++ ] = colourSet2[ j % 4 ][ j % colourSet2[ j % 4 ].length ] ;

for ( int i = 0 ; i < 5 ; ++i )
for ( int j = 0 ; j < (int) pow( 4, depth - 1 ) ; ++j )
gColourTable[ index++ ] = colourSet1[ j % 4 ][ j % colourSet1[ j % 4 ].length ] ;
}
}

/**
* Draw the tessalated icosahedron.
*/
void drawTessalatedIcosahedorn( float[] verticies, color[] colorTable, int depth )
{
int triangles = 20 * (int) pow( 4, depth - 1 ) ;

int index = 0 ;
int colour = 0 ;

noStroke() ;

beginShape( TRIANGLES ) ;

for ( int i = 0 ; i < triangles ; ++i )
{
fill( colorTable[ colour++ ] ) ;

vertex( verticies[ index++ ], gVerticies[ index++ ], verticies[ index++ ] ) ;
vertex( verticies[ index++ ], gVerticies[ index++ ], verticies[ index++ ] ) ;
vertex( verticies[ index++ ], gVerticies[ index++ ], verticies[ index++ ] ) ;
}

endShape() ;
}
/**
* Create the vertex data for the triangulated icosahedron.
*
* Parameters : radius - the radius of circumscribing sphere
* : depth - the subdivision depth in the range [ 1, MaxSubDivisionDepth ]
*
* Returns : an array of PVertex objects defining the tessalated icosahedron
*/
void tessalateIcosahedron( float[] radius, int depth )
{
//
// Define the vertex data for a basic icosaheron.
//
float gr = ( 1 + sqrt(5) ) / 2.0 ;
float r = radius[ depth ] / sqrt( 1 + gr*gr ) ;

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

int index = 0 ;

index = subdivideTriangle( depth, index, v[0], v[7],v[4] ) ;
index = subdivideTriangle( depth, index, v[0], v[4], v[11] ) ;
index = subdivideTriangle( depth, index, v[0], v[11], v[3] ) ;
index = subdivideTriangle( depth, index, v[0], v[3], v[8] ) ;
index = subdivideTriangle( depth, index, v[0], v[8], v[7] ) ;

index = subdivideTriangle( depth, index, v[1], v[4], v[7] ) ;
index = subdivideTriangle( depth, index, v[1], v[10], v[4] ) ;
index = subdivideTriangle( depth, index, v[10], v[11], v[4] ) ;
index = subdivideTriangle( depth, index, v[11], v[5], v[10] ) ;
index = subdivideTriangle( depth, index, v[5], v[3], v[11] ) ;
index = subdivideTriangle( depth, index, v[3], v[6], v[5] ) ;
index = subdivideTriangle( depth, index, v[6], v[8], v[3] ) ;
index = subdivideTriangle( depth, index, v[8], v[9], v[6] ) ;
index = subdivideTriangle( depth, index, v[9], v[7], v[8] ) ;
index = subdivideTriangle( depth, index, v[7], v[1], v[9] ) ;

index = subdivideTriangle( depth, index, v[2], v[1], v[9] ) ;
index = subdivideTriangle( depth, index, v[2], v[10], v[1] ) ;
index = subdivideTriangle( depth, index, v[2], v[5], v[10] ) ;
index = subdivideTriangle( depth, index, v[2], v[6], v[5] ) ;
index = subdivideTriangle( depth, index, v[2], v[9], v[6] ) ;
}

/**
* Create a triangle or tessalte it further.
* If the current depth is 1 then a triangle is saved otherwise
* the triangle is subdivided into 4 small triangles.
*
* Parameters : radius - the radius of circumscribing sphere
* : depth - the current subdivision depth
* : triangles - an array - where to save the end triangle
* : index - an index into the above array, where to save the triangle
* : p1 - a vector defining the first vertex of the triangle
* : p2 - a vector defining the second vertex of the triangle
* : p3 - a vector defining the coordinates of the third vertex of the triangle
*
* Returns : the index for the next triangle
*/
int subdivideTriangle( int depth, int index, PVector p1, PVector p2, PVector p3 )
{
if ( depth == 1 )
{
gVerticies[ index++ ] = p1.x ;
gVerticies[ index++ ] = p1.y ;
gVerticies[ index++ ] = p1.z ;

gVerticies[ index++ ] = p2.x ;
gVerticies[ index++ ] = p2.y ;
gVerticies[ index++ ] = p2.z ;

gVerticies[ index++ ] = p3.x ;
gVerticies[ index++ ] = p3.y ;
gVerticies[ index++ ] = p3.z ;
}
else
{
PVector v1 = PVector.add( p1, p2 ) ;
PVector v2 = PVector.add( p2, p3 ) ;
PVector v3 = PVector.add( p3, p1 ) ;

if ( depth <= gSubDivisionDepth - gSmoothValue )
{
v1.mult( 0.5 ) ;
v2.mult( 0.5 ) ;
v3.mult( 0.5 ) ;
}
else
{
float r = gRadii[ depth ] ;

v1.normalize() ;
v1.mult( r ) ;

v2.normalize() ;
v2.mult( r ) ;

v3.normalize() ;
v3.mult( r ) ;
}
index = subdivideTriangle( depth - 1, index, p1, v1, v3 ) ;
index = subdivideTriangle( depth - 1, index, v1, p2, v2 ) ;
index = subdivideTriangle( depth - 1, index, v2, p3, v3 ) ;
index = subdivideTriangle( depth - 1, index, v1, v2, v3 ) ;
}

//
//
//
return index ;
}

3 comments:

monkstone said...

"Way funky ball", in some ways I prefer it to the hairy orb thing. Neat usage of PVector class, John (Wilson) was going to ask Darrel about introducing it in the book, since its obviously such a useful class.

John Wilson said...

Yep - this is not as complex as hairy orb thing (I think I love you) - and so IMO is more appropriate for the book. Another cool demo - you clever so and so!

Ps: Check out http://en.wikipedia.org/wiki/Simulated_annealing for jumping out of local minima

lazydog said...

Hi Martin, yup good idea about PVector. Also, noise() looks like another useful function to explain.

John, is that a polite way of saying... you've done the ball thing now move on!

b e n