08
Oct
08

### Parametric Heart

OK; this needs some explanation. The background is, ages ago, when I was working on the original Desaxismundi Maths Surfaces QTZ, I found that some of the surfaces didn’t really work as I expected. Some of them also weren’t really that interesting, in the end, so I thought it would be cool to find a few different ones to replace the more boring and/or non-working ones. One of the 3D forms I thought would be fun to do, if I could find a parametric equation for it, was a ‘Valentine’s Day’-style architypal love-heart shape. I managed to find a few different equations to create something along these lines, but didn’t get around to testing any of them, and forgot about it for a few months. Then, I came across this page on Mike Williams’ site again this morning, had a bit of free time, and thought I’d have another go at making ‘Yet Another Heart’ into a parametric GLSL Vertex Shader. I was able to create both the solid heart shape (based on a modulated sphere) and the heart hoop version (based on a torus). Once I’d found the right formulae to create the base Sphere and Torus forms from a flat GLSL Grid patch, both were pretty easy to implement, though the maths could probably be made more efficient.

Here’s the Vertex Shader for the solid version:

```///////////////////////////
//      CONSTANTS        //
///////////////////////////

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

///////////////////////////
//       CONTROLS        //
///////////////////////////

uniform float A, B, C, D;

///////////////////////////
//  PARAMETRIC EQUATION  //
//      FUNCTIONS        //
///////////////////////////

vec4 heartSphere(in vec4 point)
{
/*
Parametric 3D heart equation, based on parametric sphere:

u = point.x * PI;
v = point.y * TWOPI;

sphere.x = radius * cos(u) * sin(v);
sphere.y = radius * sin(u) * sin(v);

Adapted from Mike Williams' equation, found at

http://www.econym.demon.co.uk/isotut/real.htm#heart1

*/

float u = point.x * PI;
float v = point.y * TWOPI;

vec4 outPos;
outPos.x = (cos(u) * sin(v)) - pow(abs(sin(u) * sin(v)), A) * B;
outPos.y = cos(v) * C;
outPos.z = sin(u) * sin(v);
outPos.w = point.w;

return outPos;
}

///////////////////////////
//   VARYINGS (TO FS)    //
///////////////////////////

///////////////////////////
//       MAIN LOOP       //
///////////////////////////

void main()
{
// Set vert to parametric formula
// applied to original vertex position
vec4 vert = heartSphere(gl_Vertex);

// Scale
vert.xyz *= D;

// Generate value for 'fake' lighting effect
// based on vertex distance from origin
shade = 1.0 - smoothstep(0.0,0.8,distance(vert.xyz, vec3(0.0)));
//Transform vertex by modelview and projection matrices
gl_Position = gl_ModelViewProjectionMatrix * vert;

//Forward current color and texture coordinates after applying texture matrix
gl_FrontColor = gl_Color;
gl_TexCoord[0] = gl_TextureMatrix[0] * gl_MultiTexCoord0;
}
```

..and here’s the very simple Fragment Shader:

```///////////////////////////
//  VARYINGS (FROM FS)   //
///////////////////////////

///////////////////////////
//       CONTROLS        //
///////////////////////////

uniform vec4 Base_Color;

///////////////////////////
//       MAIN LOOP       //
///////////////////////////

void main()
{
// Multiply base color by value from Fragment Shader
gl_FragColor = Base_Color * vec4(vec3(shade), 1.0);
}```

No normal-calculation here, so it can’t be lit properly. Normals could be calculated quite easily (with a performance hit, of course) using the method outlined by tonfilm. Parameters A B C and D should be in the 0 > 1 range.

I’m definitely going to add this to to the Maths Surfaces collection, and there are a couple of other surfaces on Mike’s site I might have a go at, too. I’ll have to work up to it though, as it always takes ages to update the Surfaces QTZ, because I always think of extra tweaks to make, which then have to be copied to all 50+ shaders.

‘tb Heart Surface 00.qtz’ in the Box.net widget.