Ben Whittaker

Inverse Stereographic Projection (the math behind Unearth)

I’ve gotten a request from someone (one Alexander Formoso from Venezuela) to reveal how I did the projection thingamabobber in Unearth, my only barely entry to Ludum Dare.

Apparently, I’m not the first one to try this. Who knew?

As such, I am complying! More beyond the break.

The core of the technique is in how to take points in the nice, tidy, Cartesian space where the gameplay logic is taking place and map their positions along the surface of a sphere. To do so, I did the inverse of a standard stereographic projection.

Essentially, I implemented this formula from Wikipedia:

(x,y,z) = (2x / (1 + x^2 + y^2), 2y / (1 + x^2 + y^2), (-1 + x^2 + y^2) / (1 + x^2 + y^2))

For my purposes, I was doing this in flash and just transforming the positions of circles; I had to do some clever masking and weird depth sorting to get nice planetary layers and stuff. If I was doing this in a proper 3d framework, I would totally try doing the projection in a shader on individual vertices, ‘cause that would look awesome.

In any case, I’ve written up a chunk of pseudo-code (c++ style, because why not) to illustrate the sort of thing I did. It doesn’t handle wraparound, but that should be pretty easy to add with some modulo operations and/or a bit of logic.

// This function takes a vector representing a two dimensional surface position and a
// height, and projects it onto a sphere
//
// - surfacePos is the position of the point in the flat space before projection
//     - the z component of surfacePos ends up being the distance from the center of
//    the sphere in 3d space
// - surfaceCamPos determines what point of the surface becomes the center focus
// on the sphere
// - surfaceScale scales the size of the part of the surface that gets projected
// onto the sphere
// - sphereScale controls the size of the sphere
// - spherePos positions the sphere in 3d space
vec3 inverseStereographicProj(vec3 surfacePos, vec2 surfaceCamPos, float surfaceScale,
                            vec3 sphereScale, vec3 spherePos)
{
    // center everything on a particular part of the surface
    surfacePos.x -= surfaceCamPos.x;
    surfacePos.y -= surfaceCamPos.y;

    // Multiply everything by the surface scale
    surfacePos *= surfaceScale

    // The actual meat of the function, doing the projection
    vec3 projectedPos;

    // x^2 + y^2
    float xx_yy = surfacePos.x*surfacePos.x + surfacePos.y*surfacePos.y;

    // (x,y,z) =    (
    //                    2x / (1 + x^2 + y^2),
    //                    2y / (1 + x^2 + y^2),
    //                    (-1 + x^2 + y^2) / (1 + x^2 + y^2)
    //                )
    projectedPos.x = (2 * surfacePos.x) / (1 + xx_yy);
    projectedPos.y = (2 * surfacePos.y) / (1 + xx_yy);
    projectedPos.z = -(( -1 + xx_yy) / (1 + xx_yy));

    // adjust the projected position to account for the surface height and the sphere
    // scale
    projectedPos *= surfacePos.z;
    projectedPos *= sphereScale;

    return projectedPos;
}

If you do something cool with this, I would love to see it! And, of course, I always appreciate a shoutout.