Wednesday, December 3, 2008

Orb code...

Here's the code for the latest version for the spiky orb thing.



b e n




import javax.media.opengl.* ;
import processing.opengl.* ;


//
// Scalar to convert from degrees to radians.
//
final float PI_DIV_180 = PI / 180.0 ;

//
// Switch to control lighting.
// Lights initially off.
//
boolean gLights = false ;

//
// Spherical coordinates of camera.
//
float gCameraR = 130.0 ;
float gCameraPhi = PI / 4 ;
float gCameraTheta = 0.0 ;

//
// Frame counter.
//
float gFrame = 0.0 ;

//
// The orb's current rotation and history.
//
float gBallTheta = 0.0f ;
float gBallPhi = 0.0f ;

RotationHistory gRotationHistory = new RotationHistory( 200 ) ;


/**
* Keyboard controller.
*
* Press a key to change something:-
*
* l - turns lights on/off
*/
void keyPressed()
{
switch ( key )
{
case 'l' :
gLights = ! gLights ;
break ;
}
}

//
// Usual set up.
//
void setup()
{
//
// Enable OpenGL mode.
//
size( 800, 600, OPENGL ) ;
hint( DISABLE_OPENGL_ERROR_REPORT ) ;

//
// Set up the drawing environment.
//
colorMode( RGB, 1 ) ;
background( 0.0 ) ;
smooth() ;
}

void draw()
{
//
// Update the camera.
//
// Currently, the orb is kept at the origin and the camera swoops in and out.
//
gCameraR = 180.0 + ( sin( gFrame/50.0 ) + cos( gFrame / 57 ) ) * 25 ;

//
// Update the animation.
//
// gFrame is the frame counter and is used to animate various parts of the orb.
//
gFrame += 1.0 ;

//
// The orb spins round the origin.
//
gBallTheta += 2.0 * sin( gFrame/53.0 ) ;
gBallPhi += 2.0 * sin( gFrame/83.0 ) ;

//
// The orb's rotation is recorded and used to animate the wavy hairs/dendrites.
//
gRotationHistory.push( gBallTheta, gBallPhi ) ;

//
// Initialise the background.
//
background( 0.0 ) ;
drawStars( 1000, 800, 600 ) ;

//
// Initialsie the camera.
//
setCameraSpherical( gCameraR, gCameraPhi, -gCameraTheta ) ;

//
// Draw the orb.
//
// Keep the same sequence of random numbers from frame to frame.
//
randomSeed( 0 ) ;

//
// Some magic numbers.
//
int numberOfHairs = 10000 ;
float bodyR = 25.0 ;
float minHairLength = 24.0 ;
float shortHairLength = 30.0 ;
float longHairLength = 10.0 ;
float longHairMaxLength = 30.0 ;
float longDendriteLength = 20.0 ;
float longDendriteMaxLength = 60.0 ;

//
// Set up a couple of spinning light sources for the body.
//
float cr = 0.0 ;

if ( gLights )
{
cr = 1.0 ;

lights();

float x = sin( gFrame/30.0) ;
float y = sin( gFrame/37.0) ;
float z = sin( gFrame/43.0) ;

directionalLight( 1.0, 1.0, 1.0, x, y, z );
directionalLight( 0.3, 0.3, 0.3, -x, -y, -z );
}
else
{
cr = 0.0 ;

noLights() ;
}

//
// Draw the body/sphere in the same colour as the hairs.
//
drawBody( bodyR, cr * 1., cr * 0.4, cr * 0.1, 1.0 ) ;

//
// Set up OpenGL for drawing.
//
PGraphicsOpenGL pgl = (PGraphicsOpenGL) g; // g may change
GL gl = pgl.beginGL();

gl.glEnable( gl.GL_BLEND ) ;
gl.glBlendFunc( gl.GL_SRC_ALPHA, gl.GL_ONE_MINUS_SRC_ALPHA ) ;

drawSpikes( gl, 30, bodyR, longHairLength, longHairMaxLength, bodyR + 0.5 ) ;
drawDendrites( gl, 15, bodyR, longDendriteLength, longDendriteMaxLength, bodyR + 0.5 ) ;
drawFurr( gl, 10000, minHairLength, shortHairLength ) ;

pgl.endGL() ;
}

/**
* Draw dendrites.
* These are the long thin, multi-coloured lines.
*
* Note, at the moment dendrites are regenerated on each frame - not very efficient!!!
*/
void drawDendrites( GL gl, int count, float br, float l1, float l2, float p1 )
{
for ( int i = 0 ; i < count ; ++ i )
{
//
// Calculate a random length and position for the dendrite.
//
float length = random( l1, l2 ) ;
float theta = random( 0.0, 360.0 ) ;
float phi = random( 0.0, 180.0 ) ;

gl.glPushMatrix() ;
gRotationHistory.record( 0 ).draw( gl ) ;
drawPatch( gl, br+0.1, theta, phi, 4, 16 ) ;
gl.glPopMatrix() ;

color c1 = color( abs( sin( gFrame /100.0 ) ), abs( sin( gFrame /123.0 ) ), abs( sin( gFrame /176.0 ) ), 1.0 ) ;
color c2 = color( abs( sin( gFrame /57.0 ) ), abs( sin( gFrame /23.0 ) ), abs( sin( gFrame /67.0 ) ), 0.0) ;

//
// Use a hair class to do the drawing.
//
Hair hair = new Hair( length*2, br, theta + random( 1.0 ), phi + random( 1.0 ), c1, c2, 1.0 ) ;
hair.draw( gl ) ;
}
}

/**
* Draw the long spikes.
*
* The spikes are the thick orange things.
*
* Note, at the moment spikes and patches are regenerated on each frame - not very efficient!!!
*
* @param gl the OpenGL context
* @param count the number of spikes to draw
* @param br the radius of the orb body/sphere
* @param l1 the minimum length of a spike
* @param l2 the mximum length of a spike
* @Param p1 the angular radius of the patch at the base of each spike
*/
void drawSpikes( GL gl, int count, float br, float l1, float l2, float p1 )
{
//
// Draw each spike in turn.
//
for ( int i = 0 ; i < count ; ++ i )
{
//
// Calculate a random position for the spike.
//
float theta = random( 0.0, 360.0 ) ;
float phi = random( 0.0, 180.0 ) ;

//
// Draw an ornage patch at the base of the spike.
//
gl.glPushMatrix() ;

gRotationHistory.record( 0 ).draw( gl ) ;
drawPatch( gl, br+0.1, theta, phi, 4, 16 ) ;

gl.glPopMatrix() ;

//
// The length of the spike is between l1 and l2.
//
float length = random( l1, l2 ) ;

//
// Draw the spike.
// Spikes get their thickness from being composed of many hairs.
//
for ( int j = 0 ; j < 10 ; ++j )
{
//
// Draw this hair at a random length.
//
float l = j == 9 ? length : random( l1, length ) ;

Hair hair = new Hair( l, br, theta + random( 1.0 ), phi + random( 1.0 ), color( 1.0, 1.0, 1.0, 1.0 ), color( 1.0, 0.4, 0.1, 0.1 ), 1.0 ) ;
hair.draw( gl ) ;
}
}
}

/**
* Draw the furry bits.
* @param gl the OpenGL context
* @param count the number of furr hairs
* @param r1 the minimum length of the furr
* @param r2 the mximum length of the furr
*/
void drawFurr( GL gl, int count, float r1, float r2 )
{
gl.glPushMatrix() ;

gRotationHistory.record( 0 ).draw( gl ) ;

float PI_DIV_180 = PI / 180.0 ;

gl.glBegin( gl.GL_LINES ) ;

for ( int i = 0 ; i < count ; ++ i )
{
//
// Calculate a random position for the furr hair.
//
float theta = random( 360.0 ) ;
float phi = random( 180.0 ) ;

float x1 = cos( theta * PI_DIV_180 )*sin( phi * PI_DIV_180 ) ;
float y1 = sin( theta * PI_DIV_180 ) * sin( phi * PI_DIV_180 ) ;
float z1 = cos( phi * PI_DIV_180 ) ;

//
// Randomise the length of the furr.
//
float r = r1 + ( r2 - r1 ) * random( 1.0 ) ;

//
// Choose a colour for the furr.
//
float c1 = 1.0f ;
float c2 = 1.0f ; //0.4f ;

//
// Furr is drawn semi-opaque at the base and transparent at the tip.
//
gl.glColor4f( c1, c1, c1, 0.5 ) ;
gl.glVertex3f( x1 *r1 , y1*r1, z1*r1 ) ;


gl.glColor4f( c2, c2, c2, 0.0) ;
gl.glVertex3f( x1*r, y1*r, z1*r ) ;
}

gl.glEnd() ;

gl.glPopMatrix() ;

}

/**
* Draw the black sphere.
* The sphere is used to hide any background geometry.
* Note, at the moment the animated 'orb' is alwaysdrawn at the origin.
* @param r the radius of the sphere
* @param alpha the alpha value to draw the sphere with
*/
void drawBody( float r, float red, float green, float blue, float alpha )
{
noStroke() ;
fill( red, green, blue, alpha ) ;

sphere( r ) ;
}

/**
* Set up the camera using spherical coordinates.
* @param r the distance from the pov to the camera
* @param phi the polar angle with the z-axis
* @param theta the azimuthal angle in the x/y-plane
*/
void setCameraSpherical( float r, float phi, float theta )
{
//
// Work out the x/y-z-coordinates of the camera.
//
float x = r * cos( theta )*sin( phi ) ;
float y = r * sin( theta ) * sin( phi ) ;
float z = r * cos( phi ) ;

//
// Work out the up vector.
// This is simply the image of the point ( 1, 0, 0 ) under the
// the given spherical map.
//
float nx = cos( theta ) * cos( phi ) ;
float ny = sin( theta ) * cos( phi ) ;
float nz = - sin( phi ) ;

// beginCamera() ;
// camera() ;
camera( x, y, z, 0.0, 0.0, 0.0, nx, ny, nz ) ;
// endCamera() ;
}

/**
* Draw a 'circular' patch on the surfce of the orb.
* @param gl the current gl context
* @param r the r-coordinate of the patch
* @param theta the azimuth angle of the centre of the patch, in degrees
* @param phi the altitude andgle of the centre of the patch, in degrees
* @param ar the angular radius of the patch in degrees
* @param smoothness the number of segments to draw the patch with
*/
void drawPatch( GL gl, float r, float theta, float phi, float ar, int smoothness )
{
//
// Draw a patch at the zenith and use a transform to get it in the right place.
//
gl.glPushMatrix() ;

gl.glRotatef( theta, 0.0, 0.0, 1.0 ) ;
gl.glRotatef( phi, 0.0, 1.0, 0.0 ) ;

gl.glBegin( gl.GL_TRIANGLE_FAN ) ;

//
// Draw the patch - it will be shaped like an umbrella.
//
float x = r * sin( ar * PI_DIV_180 ) ;
float z = r * cos( ar * PI_DIV_180 ) ;

gl.glColor4f( 1.0, 1.0, 1.0, 1.0 ) ;
gl.glVertex3f( 0.0, 0.0, r ) ;

gl.glColor4f( 1.0, 0.0, 0.0, 0.0 ) ;

for ( float i = 0.0 ; i <= smoothness ; ++i )
gl.glVertex3f( x * cos( TWO_PI * i / smoothness ), x * sin( TWO_PI * i / smoothness ), z ) ;

gl.glEnd() ;

gl.glPopMatrix() ;

}


void drawStars( int count, float width, float height )
{
//
// fill( 1.0 ) ;
// stroke( 1.0 ) ;
//
// beginShape( POINTS ) ;
//
// for ( int i = 0 ; i < count ; ++ i )
// vertex( random( width ), random( height ), -50 ) ;
//
// endShape() ;
}

/********************************************************************************************************************************************************************/

/********************************************************************************************************************************************************************/

/**
* Class hair.
* Represents a hair by length, position and colour.
*/
class Hair
{
//
// The length of the hair.
//
float length ;

//
// The start point of the hair.
//
float r, theta, phi ;

//
// The start and end point colours of the hair.
//
float r1, g1, b1, a1 ;
float r2, g2, b2, a2 ;

//
// How smooth to draw the hair.
//
int sections ;

/**
* Default constructor is hidden - use the full constructor.
*/
private Hair()
{
}

/**
* Constructor initialised with position, length, colour and smoothness.
* @param length the lenght of the hair
* @param r the r-coordiante to the root of the hair
* @param theta the azimuth angle of the hair
* @param phi the altitude angle of the hair
* @param c1 the start point colour of the hair
* @param c2 the end point colour of the hair
* @param smoothness how smooth to draw the hair
*/
public Hair( float length, float r, float theta, float phi, color c1, color c2, float smoothness )
{

this.length = length ;

this.r = r ;
this.theta = theta * PI_DIV_180 ;
this.phi = phi * PI_DIV_180 ;

r1 = red( c1 ) ;
g1 = green( c1 ) ;
b1 = blue( c1 ) ;
a1 = alpha( c1 ) ;

r2 = red( c2 ) ;
g2 = green( c2 ) ;
b2 = blue( c2 ) ;
a2 = alpha( c2 ) ;

this.sections = round( length / smoothness ) ;

//
// this.x1 = length *cos( theta )*sin( phi ) ;
// this.y1 = length * sin( theta ) * sin( phi ) ;
// this.z1 = length * cos( phi ) ;
}

/**
* Draw the hair.
* The hair is drawn in sections. The number of sections is defined by
* the hair's smoothness attribute.
* @param gl the current OpenGL context
*/
public void draw( GL gl )
{
gl.glPushMatrix() ;

//
// The hair is drawn in sections.
//
gl.glBegin( gl.GL_LINE_STRIP ) ;

for ( int i = 0 ; i <= sections ; ++i )
{
//
// Calculate the fractional position along the hair of this vertex.
//
float dh = float( i ) / float( sections ) ;

//
// Work out the coordinates of the vertex.
//
float h = r + length * dh ;
float x = h * cos( this.theta ) * sin( this.phi ) ;
float y = h * sin( this.theta ) * sin( this.phi ) ;
float z = h * cos( this.phi ) ;

//
// Rotate the vertex by the position of the ball at i frames ago.
//
float theta = gRotationHistory.record( -i ).getTheta() * PI_DIV_180 ;
float phi = gRotationHistory.record( -i ).getPhi() * PI_DIV_180 ;

float x1= x * cos( phi ) + z * sin( phi ) ;
float y1 = y ;
float z1 = - x * sin( phi ) + z * cos( phi ) ;

x = x1 * cos( theta ) - y1 * sin( theta ) ;
y = x1 * sin( theta ) + y1 * cos( theta ) ;
z = z1 ;

//
// Calculate the colour of the vertex by interpolating between the start
// and end colours of the hair.
//
gl.glColor4f( r1 + ( r2 - r1 ) * dh, g1 + ( g2 - g1 ) * dh, b1 + ( b2 - b1 ) * dh, a1 + ( a2 - a1 ) * dh ) ;

//
// Draw the vertx, it will form the start/end of a line segment.
//
gl.glVertex3f( x, y, z ) ;
}

gl.glEnd() ;

gl.glPopMatrix() ;
}

}

/********************************************************************************************************************************************************************/

/********************************************************************************************************************************************************************/

/**
* Class RotationHistory
* This class is used to record the position/coordiantes of the sphere during animation.
* The last 'n' frames of animation are stored.
*/
class RotationHistory
{
//
// How deep the history buffer is.
//
int historyDepth = 0 ;
RotationRecord[] history = new RotationRecord[ 0 ] ;

//
// The current frame counter.
//
int frame = 0 ;

/**
* Default constructor initialises the animator with a history level of 10.
*/
public RotationHistory()
{
historyDepth = 100 ;
history = new RotationRecord[ historyDepth ] ;

frame = 0 ;

//
// Initialise the history to a static case.
//
for ( int i = 0 ; i < historyDepth ; ++i )
history[ i ] = new RotationRecord() ;
}

/**
* Constructor initialised with a history depth.
* @param depth how deep the history buffer is
*/
public RotationHistory( int depth )
{
historyDepth = depth ;
history = new RotationRecord[ depth ] ;

frame = 0 ;

//
// Initialise the history to a static case.
//
for ( int i = 0 ; i < historyDepth ; ++i )
history[ i ] = new RotationRecord() ;
}

/**
* Push a new rotation record into the history.
* The oldest record is lost.
* @param theta the azimuth angle
* @param phi the altitude angle
*/
public void push( float theta, float phi )
{
frame++ ;

history[ frame % historyDepth ] = new RotationRecord( theta, phi ) ;
}


/**
* Get a history record.
* @param index the history index, 0 = current, -1 = previous record etc
* @return the history rotation record
*/
public RotationRecord record( int index )
{
return history[ abs( frame + index ) % historyDepth ] ;
}

}

/********************************************************************************************************************************************************************/

/********************************************************************************************************************************************************************/

/**
* Class RotationRecord.
* Used to store the azimuth and altitude angles of rotation.
*/
class RotationRecord
{
//
// The rotation angles.
//
float theta ;
float phi ;

/**
* Default constructor initialised with zero rotation.
*/
public RotationRecord()
{
theta = 0.0 ;
phi = 0.0 ;
}

/**
* Constructed initialised with rotation angles theta and phi.
* @param theta the azimuth angle
* @param phi the altitude angle
*/
public RotationRecord( float theta, float phi )
{
this.theta = theta ;
this.phi = phi ;
}

/**
* Accessor to the orientation angle theta.
* @return the value of rotation angle theta
*/
public float getTheta()
{
return theta ;
}

/**
* Accessor to the orientation angle phi.
* @return the value of rotation angle phi
*/
public float getPhi()
{
return phi ;
}

/**
* Draw the rotation as a transform.
* @param gl the current OpenGL context
*/
public void draw( GL gl )
{
gl.glRotatef( theta, 0.0, 0.0, 1.0 ) ;
gl.glRotatef( phi, 0.0, 1.0, 0.0 ) ;
}
}

11 comments:

monkstone said...

You know what I still just get a black square even when running your code within the processing IDE, so I'm not sure what the problem is. I even tried to sort out the possible namespace issue re javax and processing opengl but that made no difference.

monkstone said...

Well I suppose we've been warned off direct access to GL in processing, results are unpredictable could vary with graphics driver etc etc:-

http://www.processing.org/reference/libraries/opengl/

lazydog said...

Hi Martin,

Yup, I read that bit in the reference manual too.

Does OpenGL mode work for you for programs that do not access the OpenGL context directly, for example the scripts under File/Examples/Library/OpenGL?

b e n

monkstone said...

Now thats really weird, the library opengl examples don't run under gentoo linux 2008 (which was built with opengl support). So I had this brainwave try it on Ubuntu 8.10 (even though its got gcj java not sun java). You know what both library examples and your downloaded example work fine. So its something specific to my gentoo setup. Nothing to do with native GL then...
Theres not a lot of support for linux users from the processing guys they reckon we'll just figure it out so I guess I'll have to do that.

monkstone said...

Actually gcj comment is a red herring since processing comes with its own jdk on linux.

John Wilson said...

b e n - this is cool! Maybe you should save this for the 'Processing for Demi-Gods' book?

John

lazydog said...

Thanks for trying that out Martin. Good to know it works in Linux.

b e n

monkstone said...

Just experimenting for the hell of it, you know I said library examples did not work on my gentoo setup, well you know the spacejunk opengl example, it worked for me on gentoo when I replaced OPENGL with P3D. Aha I thought, could I do that with your applet, but it did not work. Which is a pity because although I can run your example on Ubuntu, it doesn't work in the browser probably because of the gcj plugin (ie not sun).

monkstone said...

Fantastic bit of 3d work by the way, I was exploring your code in netbeans, (still couldn't get it to run on gentoo). Using pmd analysis I was alerted to the lack of a 'default' on the light switch case statement, and possible masking of the PI**180 variable I don't know whether that could be interesting to you?

lazydog said...

Hi Martin

I'm surprised Netbeans hasn't thrown more stuff up at the code!

Would be interesting to find out why it won't work in gentoo. Sounds like it's getting past the background() call.

b e n

monkstone said...

Well there was an unused variable, no of hairs or something, but apart from that it didn't complain too much.