Archive for June, 2009

25
Jun
09

Random Gradient Displace CIFilter Code

Jiggles image around based on random greyscale gradients.

CIFilter code (top panel with ‘Edit Filter Function’ enabled):

/*
	Combine channels of 3 monochrome input images.
	R-channel from ImageR
	G-channel from ImageG
	B-channel from ImageB
	
	toneburst 2009
*/

kernel vec4 tb_combineChannels(sampler ImageR, sampler ImageG, sampler ImageB)
{
	float R = sample(ImageR, samplerCoord(ImageR)).r;
	float G = sample(ImageG, samplerCoord(ImageG)).r;
	float B = sample(ImageB, samplerCoord(ImageB)).r;

	return vec4(R, G, B, 1.0);
}

/*
	Displace Image input pixels based on RGB values of DisplaceImg.
	Adjustable displacement amount.
	
	toneburst 2009	
*/

kernel vec4 tb_displace(sampler Image, sampler DisplaceImg, float DisplaceAmt)
{
	vec4 dImg = sample(DisplaceImg, samplerCoord(DisplaceImg));
	vec2 dAmt = (dImg.rg * 2.0 - 1.0) * DisplaceAmt;
	return sample(Image, samplerCoord(Image) + dAmt);
}

JavaScript (bottom panel):

/*
	Creates random gradient in RGB channels.
	whenever 'Trigger' pressed and displaces input image based
	on gradient RGB values.
	
	toneburst 2009
*/

function __image main(__image Image, __boolean Trigger, __number DisplaceAmt) {
	
	if(Trigger) {
		var dims = new Vec(Image.extent.width,Image.extent.height);
		var gradDims = new Vec(0,0,dims.x,dims.y);
	
		var black = new Vec(0,0,0,1);
		var white = new Vec(1,1,1,1);
	
		var p0 = new Vec(Math.random() * dims.x, Math.random() * dims.y);
		var p1 = new Vec(Math.random() * dims.x, Math.random() * dims.y);
	
		var gradR = Filter.CILinearGradient(p0, p1, black, white);
		gradR = Filter.CICrop(gradR, gradDims);

		p0 = new Vec(Math.random() * dims.x, Math.random() * dims.y);
		p1 = new Vec(Math.random() * dims.x, Math.random() * dims.y);
	
		var gradG = Filter.CILinearGradient(p0, p1, black, white);
		gradG = Filter.CICrop(gradG, gradDims);
	
		p0 = new Vec(Math.random() * dims.x, Math.random() * dims.y);
		p1 = new Vec(Math.random() * dims.x, Math.random() * dims.y);
	
		var gradB = Filter.CILinearGradient(p0, p1, black, white);
		gradB = Filter.CICrop(gradB, gradDims);
	
		var gradRGB = tb_combineChannels.apply(gradR.definition, null, gradR, gradG, gradB);

		// Call kernel function and return result
		return tb_displace.apply(gradRGB.definition,null,Image,gradRGB,DisplaceAmt);
	} else {
		return Image;
	}
}
25
Jun
09

3D Perlin Vertex Noise GLSL Sourcecode

This is the GLSL vertex shader code for 3D noise. Fragment shader is the same as for the 2D variant.

/*
	3D Perlin-Noise in the vertex shader, based originally on
	vBomb.fx HLSL vertex noise shader, from the NVIDIA Shader Library.
	http://developer.download.nvidia.com/shaderlibrary/webpages/shader_library.html#vbomb
	
	Original Perlin function substituted for Stefan Gustavson's
	texture-lookup-based Perlin implementation.
	
	Quartz Composer setup
	toneburst 2009
	https://machinesdontcare.wordpress.com
*/

////////////////////////
//  3D Perlin Noise   //
////////////////////////

/*
	3D Perlin-Noise from example by Stefan Gustavson, found at
	http://staffwww.itn.liu.se/~stegu/simplexnoise/
*/

uniform sampler2D permTexture;			// Permutation texture
const float permTexUnit = 1.0/256.0;		// Perm texture texel-size
const float permTexUnitHalf = 0.5/256.0;	// Half perm texture texel-size

float fade(in float t) {
	return t*t*t*(t*(t*6.0-15.0)+10.0);
}

float pnoise3D(in vec3 p)
{
	vec3 pi = permTexUnit*floor(p)+permTexUnitHalf; // Integer part, scaled so +1 moves permTexUnit texel
	// and offset 1/2 texel to sample texel centers
	vec3 pf = fract(p);     // Fractional part for interpolation

	// Noise contributions from (x=0, y=0), z=0 and z=1
	float perm00 = texture2D(permTexture, pi.xy).a ;
	vec3  grad000 = texture2D(permTexture, vec2(perm00, pi.z)).rgb * 4.0 - 1.0;
	float n000 = dot(grad000, pf);
	vec3  grad001 = texture2D(permTexture, vec2(perm00, pi.z + permTexUnit)).rgb * 4.0 - 1.0;
	float n001 = dot(grad001, pf - vec3(0.0, 0.0, 1.0));

	// Noise contributions from (x=0, y=1), z=0 and z=1
	float perm01 = texture2D(permTexture, pi.xy + vec2(0.0, permTexUnit)).a ;
	vec3  grad010 = texture2D(permTexture, vec2(perm01, pi.z)).rgb * 4.0 - 1.0;
	float n010 = dot(grad010, pf - vec3(0.0, 1.0, 0.0));
	vec3  grad011 = texture2D(permTexture, vec2(perm01, pi.z + permTexUnit)).rgb * 4.0 - 1.0;
	float n011 = dot(grad011, pf - vec3(0.0, 1.0, 1.0));

	// Noise contributions from (x=1, y=0), z=0 and z=1
	float perm10 = texture2D(permTexture, pi.xy + vec2(permTexUnit, 0.0)).a ;
	vec3  grad100 = texture2D(permTexture, vec2(perm10, pi.z)).rgb * 4.0 - 1.0;
	float n100 = dot(grad100, pf - vec3(1.0, 0.0, 0.0));
	vec3  grad101 = texture2D(permTexture, vec2(perm10, pi.z + permTexUnit)).rgb * 4.0 - 1.0;
	float n101 = dot(grad101, pf - vec3(1.0, 0.0, 1.0));

	// Noise contributions from (x=1, y=1), z=0 and z=1
	float perm11 = texture2D(permTexture, pi.xy + vec2(permTexUnit, permTexUnit)).a ;
	vec3  grad110 = texture2D(permTexture, vec2(perm11, pi.z)).rgb * 4.0 - 1.0;
	float n110 = dot(grad110, pf - vec3(1.0, 1.0, 0.0));
	vec3  grad111 = texture2D(permTexture, vec2(perm11, pi.z + permTexUnit)).rgb * 4.0 - 1.0;
	float n111 = dot(grad111, pf - vec3(1.0, 1.0, 1.0));

	// Blend contributions along x
	vec4 n_x = mix(vec4(n000, n001, n010, n011),
			vec4(n100, n101, n110, n111), fade(pf.x));

	// Blend contributions along y
	vec2 n_xy = mix(n_x.xy, n_x.zw, fade(pf.y));

	// Blend contributions along z
	float n_xyz = mix(n_xy.x, n_xy.y, fade(pf.z));

	// We're done, return the final noise value.
	return n_xyz;
}

/////////////////////
// Sphere Function //
/////////////////////

const float PI = 3.14159265;
const float TWOPI = 6.28318531;
uniform float BaseRadius;

vec4 sphere(in float u, in float v) {
	u *= PI;
	v *= TWOPI;
	vec4 pSphere;
	pSphere.x = BaseRadius * cos(v) * sin(u);
	pSphere.y = BaseRadius * sin(v) * sin(u);
	pSphere.z = BaseRadius * cos(u);
	pSphere.w = 1.0;
	return pSphere;
}

///////////////////////////
// Apply 3D Perlin Noise //
///////////////////////////

uniform vec3 NoiseScale;	// Noise scale, 0.01 > 8
uniform float Sharpness;	// Displacement 'sharpness', 0.1 > 5
uniform float Displacement;	// Displcement amount, 0 > 2
uniform float Speed;		// Displacement rate, 0.01 > 1
uniform float Timer;		// Feed incrementing value, infinite

vec4 perlinSphere(in float u, in float v) {
	vec4 sPoint = sphere(u, v);
	// The rest of this function is mainly from vBomb shader from NVIDIA Shader Library
	vec4 noisePos = vec4(NoiseScale.xyz,1.0) * (sPoint + (Speed * Timer));
	float noise = (pnoise3D(noisePos.xyz) + 1.0) * 0.5;;
	float ni = pow(abs(noise),Sharpness) - 0.25;
	vec4 nn = vec4(normalize(sPoint.xyz),0.0);
	return (sPoint - (nn * (ni-0.5) * Displacement));
}

////////////////////////////////
// Calculate Position, Normal //
////////////////////////////////

const float grid = 0.01;	// Grid offset for normal-estimation
varying vec3 norm;			// Normal

vec4 posNorm(in float u, in float v) {
	// Vertex position
	vec4 vPosition = perlinSphere(u, v);
	// Estimate normal by 'neighbour' technique
	// with thanks to tonfilm
	vec3 tangent = (perlinSphere(u + grid, v) - vPosition).xyz;
	vec3 bitangent = (perlinSphere(u, v + grid) - vPosition).xyz;
	norm = gl_NormalMatrix * normalize(cross(tangent, bitangent));
	// Return vertex position
	return vPosition;
}

//////////////////////////
// Phong Directional VS //
//////////////////////////

// -- Lighting varyings (to Fragment Shader)
varying vec3 lightDir0, halfVector0;
varying vec4 diffuse0, ambient;

void phongDir_VS() {
	// Extract values from gl light parameters
	// and set varyings for Fragment Shader
	lightDir0 = normalize(vec3(gl_LightSource[0].position));
	halfVector0 = normalize(gl_LightSource[0].halfVector.xyz);
	diffuse0 = gl_FrontMaterial.diffuse * gl_LightSource[0].diffuse;
	ambient =  gl_FrontMaterial.ambient * gl_LightSource[0].ambient;
	ambient += gl_LightModel.ambient * gl_FrontMaterial.ambient;
}

///////////////
// Main Loop //
///////////////

uniform vec2 PreScale, PreTranslate;	// Mesh pre-transform

void main()
{
	vec2 uv = gl_Vertex.xy;
	// Offset XY mesh coords to 0 > 1 range
	uv += 0.5;
	
	// Pre-scale and transform mesh
	uv *= PreScale;
	uv += PreTranslate;
	
	// Calculate new vertex position and normal
	vec4 spherePos = posNorm(uv[0], uv[1]);
	
	// Calculate lighting varyings to be passed to fragment shader
	phongDir_VS();
	
	// Transform new vertex position by modelview and projection matrices
	gl_Position = gl_ModelViewProjectionMatrix * spherePos;
	
	// Forward current texture coordinates after applying texture matrix
	gl_TexCoord[0] = gl_TextureMatrix[0] * gl_MultiTexCoord0;
}
23
Jun
09

2D Perlin Vertex Noise GLSL Sourcecode

Vertex Shader:

/*
	2D Perlin-Noise in the vertex shader, based originally on
	vBomb.fx HLSL vertex noise shader, from the NVIDIA Shader Library.
	http://developer.download.nvidia.com/shaderlibrary/webpages/shader_library.html#vbomb
	
	Original Perlin function substituted for Stefan Gustavson's
	texture-lookup-based Perlin implementation.
	
	Quartz Composer setup
	toneburst 2009
	https://machinesdontcare.wordpress.com
*/

////////////////////////
//  2D Perlin Noise   //
////////////////////////

/*
	2D Perlin-Noise from example by Stefan Gustavson, found at
	http://staffwww.itn.liu.se/~stegu/simplexnoise/
*/

uniform sampler2D permTexture;			// Permutation texture
const float permTexUnit = 1.0/256.0;		// Perm texture texel-size
const float permTexUnitHalf = 0.5/256.0;	// Half perm texture texel-size

float fade(in float t) {
	return t*t*t*(t*(t*6.0-15.0)+10.0);
}

float pnoise2D(in vec2 p)
{
	// Integer part, scaled and offset for texture lookup
	vec2 pi = permTexUnit*floor(p) + permTexUnitHalf;
	// Fractional part for interpolation
	vec2 pf = fract(p);
	
	// Noise contribution from lower left corner
	vec2 grad00 = texture2D(permTexture, pi).rg * 4.0 - 1.0;
	float n00 = dot(grad00, pf);
	
	// Noise contribution from lower right corner
	vec2 grad10 = texture2D(permTexture, pi + vec2(permTexUnit, 0.0)).rg * 4.0 - 1.0;
	float n10 = dot(grad10, pf - vec2(1.0, 0.0));
	
	// Noise contribution from upper left corner
	vec2 grad01 = texture2D(permTexture, pi + vec2(0.0, permTexUnit)).rg * 4.0 - 1.0;
	float n01 = dot(grad01, pf - vec2(0.0, 1.0));
	
	// Noise contribution from upper right corner
	vec2 grad11 = texture2D(permTexture, pi + vec2(permTexUnit, permTexUnit)).rg * 4.0 - 1.0;
	float n11 = dot(grad11, pf - vec2(1.0, 1.0));
	
	// Blend contributions along x
	vec2 n_x = mix(vec2(n00, n01), vec2(n10, n11), fade(pf.x));
	
	// Blend contributions along y
	float n_xy = mix(n_x.x, n_x.y, fade(pf.y));
	
	// We're done, return the final noise value.
	return n_xy;
}

/////////////////////
// Sphere Function //
/////////////////////

const float PI = 3.14159265;
const float TWOPI = 6.28318531;
uniform float BaseRadius;

vec4 sphere(in float u, in float v) {
	u *= PI;
	v *= TWOPI;
	vec4 pSphere;
	pSphere.x = BaseRadius * cos(v) * sin(u);
	pSphere.y = BaseRadius * sin(v) * sin(u);
	pSphere.z = BaseRadius * cos(u);
	pSphere.w = 1.0;
	return pSphere;
}

///////////////////////////
// Apply 2D Perlin Noise //
///////////////////////////

uniform vec3 NoiseScale;	// Noise scale, 0.01 > 8
uniform float Sharpness;	// Displacement 'sharpness', 0.1 > 5
uniform float Displacement;	// Displcement amount, 0 > 2
uniform float Speed;		// Displacement rate, 0.01 > 1
uniform float Timer;		// Feed incrementing value, infinite

vec4 perlinSphere(in float u, in float v) {
	vec4 sPoint = sphere(u, v);
	// The rest of this function is mainly from vBomb shader from NVIDIA Shader Library
	vec4 noisePos = vec4(NoiseScale.xyz,1.0) * (sPoint + (Speed * Timer));
	float noise = (pnoise2D(noisePos.xy) + 1.0) * 0.5;;
	float ni = pow(abs(noise),Sharpness) - 0.25;
	vec4 nn = vec4(normalize(sPoint.xyz),0.0);
	return (sPoint - (nn * (ni-0.5) * Displacement));
}

////////////////////////////////
// Calculate Position, Normal //
////////////////////////////////

const float grid = 0.01;	// Grid offset for normal-estimation
varying vec3 norm;			// Normal

vec4 posNorm(in float u, in float v) {
	// Vertex position
	vec4 vPosition = perlinSphere(u, v);
	// Estimate normal by 'neighbour' technique
	// with thanks to tonfilm
	vec3 tangent = (perlinSphere(u + grid, v) - vPosition).xyz;
	vec3 bitangent = (perlinSphere(u, v + grid) - vPosition).xyz;
	norm = gl_NormalMatrix * normalize(cross(tangent, bitangent));
	// Return vertex position
	return vPosition;
}

//////////////////////////
// Phong Directional VS //
//////////////////////////

// -- Lighting varyings (to Fragment Shader)
varying vec3 lightDir0, halfVector0;
varying vec4 diffuse0, ambient;

void phongDir_VS() {
	// Extract values from gl light parameters
	// and set varyings for Fragment Shader
	lightDir0 = normalize(vec3(gl_LightSource[0].position));
	halfVector0 = normalize(gl_LightSource[0].halfVector.xyz);
	diffuse0 = gl_FrontMaterial.diffuse * gl_LightSource[0].diffuse;
	ambient =  gl_FrontMaterial.ambient * gl_LightSource[0].ambient;
	ambient += gl_LightModel.ambient * gl_FrontMaterial.ambient;
}

///////////////
// Main Loop //
///////////////

uniform vec2 PreScale, PreTranslate;	// Mesh pre-transform

void main()
{
	vec2 uv = gl_Vertex.xy;
	// Offset XY mesh coords to 0 > 1 range
	uv += 0.5;
	
	// Pre-scale and transform mesh
	uv *= PreScale;
	uv += PreTranslate;
	
	// Calculate new vertex position and normal
	vec4 spherePos = posNorm(uv[0], uv[1]);
	
	// Calculate lighting varyings to be passed to fragment shader
	phongDir_VS();
	
	// Transform new vertex position by modelview and projection matrices
	gl_Position = gl_ModelViewProjectionMatrix * spherePos;
	
	// Forward current texture coordinates after applying texture matrix
	gl_TexCoord[0] = gl_TextureMatrix[0] * gl_MultiTexCoord0;
}

And the Fragment Shader, which just implements a generic Phong Directional lighting model:

/*
	Generic Fragment Shader
	with Phong Directional lighting
*/

//////////////////////////
// Phong Directional FS //
//////////////////////////

// -- Lighting varyings (from Vertex Shader)
varying vec3 norm, lightDir0, halfVector0;
varying vec4 diffuse0, ambient;

vec4 phongDir_FS()
{
	vec3 halfV;
	float NdotL, NdotHV;
	
	// The ambient term will always be present
	vec4 color = ambient;
	
	// compute the dot product between normal and ldir
	NdotL = max(dot(norm, lightDir0),0.0);
	
	if (NdotL > 0.0) {
		color += diffuse0 * NdotL;
		halfV = normalize(halfVector0);
		NdotHV = max(dot(norm, halfV), 0.0);
		color +=	gl_FrontMaterial.specular * 
				gl_LightSource[0].specular * 
				pow(NdotHV, gl_FrontMaterial.shininess);
	}	
	return color;
}

///////////////
// Main Loop //
///////////////

void main()
{
	// Call lighting function and return result
	gl_FragColor = phongDir_FS();
}

Setup:

You’ll need to put a GLSL Grid patch inside the GLSL Shader macro. I’d set the Vertical and Horizontal Resolution of the Grid to a higher value than the default 50. Try to balance resolution (higher the better) against frame-rate. Setting it to 256×256 will give an ultra-smooth mesh, but will potentially slow down the rendering, depending on your system. You could put the GLSL Grid inside a Trackball if you wanted, so you can spin the whole thing around.

You’ll also need to put the whole thing inside a Lighting patch.

The complete setup would be:
Lighting : GLSL Shader : (optional) Trackball : GLSL Grid

I’ve tried to organise the code into blocks to make it easier to understand. I’m sure it could be made more efficient and/or elegant by combining some of the functions, but my aim was to make it easier to copy-paste discrete functions into other code, in nice self-contained chunks. It’s probably terrible coding practice, but I’ve placed all the variables for each function with the function definition itself, rather than declaring them all at the top. I found this helped keep the code ‘modular’, and it seems to work, so I guess the compiler can work its way through it OK.

I’ve suggested ranges for the various parameters in the shader code.

You’ll also need this picture, as the permutation texture, connected to the ‘permTex’ input on the shader.

Permutation Texture

And here’s a couple of examples of the code in action, courtesy of Marcos Prack.

Very smooth, and looks like it’s running faster than it does on my MacBook Pro.
Cheers Marcos!

21
Jun
09

2D/3D Perlin Vertex Noise

Crashes on my laptop, works like a dream on my desktop machine.
More info to come.

The red ones above are from a different version of the effect, that does work on my ageing MacBook Pro (though it’s still slow- 12-14fps at 640 x 360). Note the highlights aren’t as smooth, because I had to drop the resolution of the base mesh to improve the framerates a little on the laptop.

And here’s a clip of the 2D version:

and the 3D one:

and with an environment-map shader (not so successful)

21
Jun
09

Trier Raycaster Hi-Res Screenshots

Runs fullscreen (1680x1050px) on my MacPro, at realtime framerates.
I will still attempt to optimise a little though, as it’s slowish on my (admittedly ageing) laptop.

17
Jun
09

Random Gradients Variation

///////////////////////
// CIFilter Kernel Code
///////////////////////

kernel vec4 combineChannels(sampler ImageR, sampler ImageG, sampler ImageB)  
{  
    float R = sample(ImageR, samplerCoord(ImageR)).r;  
    float G = sample(ImageG, samplerCoord(ImageG)).r;  
    float B = sample(ImageB, samplerCoord(ImageB)).r;  
  
    return vec4(R, G, B, 1.0);  
}

kernel vec4 multiplyChannels(sampler ImageR, sampler ImageG, sampler ImageB)  
{  
    float R = sample(ImageR, samplerCoord(ImageR)).r;  
    float G = sample(ImageG, samplerCoord(ImageG)).r;  
    float B = sample(ImageB, samplerCoord(ImageB)).r;
    float shade = R * G * B; 
  
    return vec4(vec3(shade), 1.0);  
}

///////////////////
// JavaScript
///////////////////

function __image main(__boolean Trigger, __index Type) {  
  
    var dims = new Vec(256,256);  
    var gradDims = new Vec(0,0,dims.x,dims.y);  
  
    var black = new Vec(0,0,0,1);  
    var white = new Vec(1,1,1,1);  
  
    var p0 = new Vec(Math.random() * dims.x, Math.random() * dims.y);  
    var p1 = new Vec(Math.random() * dims.x, Math.random() * dims.y);  
  
    var gradR = Filter.CILinearGradient(p0, p1, black, white);  
    gradR = Filter.CICrop(gradR, gradDims);  
  
    p0 = new Vec(Math.random() * dims.x, Math.random() * dims.y);  
    p1 = new Vec(Math.random() * dims.x, Math.random() * dims.y);  
  
    var gradG = Filter.CILinearGradient(p0, p1, black, white);  
    gradG = Filter.CICrop(gradG, gradDims);  
  
    p0 = new Vec(Math.random() * dims.x, Math.random() * dims.y);  
    p1 = new Vec(Math.random() * dims.x, Math.random() * dims.y);  
  
    var gradB = Filter.CILinearGradient(p0, p1, black, white);  
    gradB = Filter.CICrop(gradB, gradDims);  
  
    switch (Type) {
    case 0:
    		// Call kernel function and return result  
    		return combineChannels.apply(gradR.definition, null, gradR, gradG, gradB);
    		break;
    	case 1:
    		return multiplyChannels.apply(gradR.definition, null, gradR, gradG, gradB);
    		break;
	} 
}

Makes greyscale triangles and squares.

EDIT:
Just uploaded a QTZ with three variations on the basic random gradients theme, all in one CIFilter patch.
Download it if you think you’ll find it useful…
‘tb_randomGradients_1.0.qtz’ in the box.net widget on right.

And here’s a clip of it in action, courtesy of Lee Grosbauer:

Cheers, Lee!

16
Jun
09

Random Gradient CIFilter JavaScript

Creates linear gradients between two random points across RGB channels every time ‘Trigger’ is clicked.

CIFilter Kernel Code (top panel)

kernel vec4 combineChannels(sampler ImageR, sampler ImageG, sampler ImageB)
{
	float R = sample(ImageR, samplerCoord(ImageR)).r;
	float G = sample(ImageG, samplerCoord(ImageG)).r;
	float B = sample(ImageB, samplerCoord(ImageB)).r;

	return vec4(R, G, B, 1.0);
}

Javascript (bottom panel)

function __image main(__boolean Trigger) {
	
	var dims = new Vec(256,256);
	var gradDims = new Vec(0,0,dims.x,dims.y);
	
	var black = new Vec(0,0,0,1);
	var white = new Vec(1,1,1,1);
	
	var p0 = new Vec(Math.random() * dims.x, Math.random() * dims.y);
	var p1 = new Vec(Math.random() * dims.x, Math.random() * dims.y);
	
	var gradR = Filter.CILinearGradient(p0, p1, black, white);
	gradR = Filter.CICrop(gradR, gradDims);

	p0 = new Vec(Math.random() * dims.x, Math.random() * dims.y);
	p1 = new Vec(Math.random() * dims.x, Math.random() * dims.y);
	
	var gradG = Filter.CILinearGradient(p0, p1, black, white);
	gradG = Filter.CICrop(gradG, gradDims);
	
	p0 = new Vec(Math.random() * dims.x, Math.random() * dims.y);
	p1 = new Vec(Math.random() * dims.x, Math.random() * dims.y);
	
	var gradB = Filter.CILinearGradient(p0, p1, black, white);
	gradB = Filter.CICrop(gradB, gradDims);
	
	
	// Call kernel function and return result
	return combineChannels.apply(gradR.definition, null, gradR, gradG, gradB);
}



Twitter

Error: Twitter did not respond. Please wait a few minutes and refresh this page.

June 2009
M T W T F S S
« May   Jul »
1234567
891011121314
15161718192021
22232425262728
2930  

Links

Blog Stats

  • 465,901 hits