misc, programming

Equirectangular Maps to Cube Maps

A solution to a mildly tough problem

On my last blog, I wrote about a way to generate worlds, and for that, I suggested using Cube Maps to store the data and then to convert to Equirectangular to display the map.
Converting to Equirectangular, thanks to Bartosz simple and beautiful to understand code, it is also easily convertible to other languages and can be modified to output either an entire set of coordinates or just a single coordinate.
However, reversing this algorithm is not so simple, since the maths aren’t reversible and thus we are forced to search for a different solution.

Salix alba’s solution to this problem is fantastical to convert an entire image, and the code can also be easily copy-pasted, however, the way the algorithm works doesn’t allow for what I truly needed: a way to convert single coordinates from one coordinate system to the other.

Understanding the various coordinate systems

When making the conversion from Equirectangular to Cube Map, we deal with multiple coordinate systems:

  • Equirectangular Coordinates – Also called Cylindrical Equidistant Projection, it’s aspect ratio is always 2:1, it uses the (x,y) coordinate system. It represents a projection of a planet.
  • Spherical Coordinates – Spherical coordinates imply two kinds of coordinates: latitude and longitude(or phi and theta). There is a third one, radius, but for our purposes, it doesn’t matter. They represent a spherical space or in our case, the globe.
  • Cartesian Coordinates – Cartesian coordinates make use of (x,y,z) coordinates. They represent a 3D space.
  • Cube Map Coordinates – Cube Maps go back to using only an (x,y), but to avoid confusion, let’s call it (u,v).

From Equirectangular to Spherical

When converting to spherical coordinates, we want to obtain the latitude and longitude of a determined pixel on the map. The calculations to achieve that is easy, the method is explained on Wikipedia and on Mathworld.
Which leaves us with this pseudo code whose output is in radians:

function calculatePhi(y, height) {
  return (1-2*y/height)/2.0 * Math.PI;
}
  
function calculateTheta(x, width) {
  return (2*x/width-1) * Math.PI;
}
The output, when converted to degrees follows this standard. (A latitude of 90 degrees will represent the north pole, etc)

From Spherical to Cartesian

Converting from spherical to cartesian is pretty simple, Wikipedia has a pretty good guide on it, as well as Paul Bourke in his blog post about this same theme.

function calculateX(theta, phi) {
  return Math.cos(phi) * Math.cos(theta);
}
  
function calculateY(phi) {
  return Math.sin(phi);
}
  
function calculateZ(theta, phi) {
  return Math.cos(phi) * Math.sin(theta);
}
All credits to SEOS for this image showcasing what’s happening during this conversion

Cartesian to Cube Map

This is the hardest conversion, thankfully, once again, Wikipedia has got our backs, however, this time the algorithm isn’t fully complete, instead, we need to add the last function to convert from local square coordinates to the “horizontal cross” shape we want.

This is due to the nature of the way the Cartesian to CubeMap is done by Wikipedia’s algorithm, which outputs a normalized result.

function localToGlobalCoords(coords, index, cubeFaceSize) {
    switch (index)
    {
        case 0:// POSITIVE X - FRONT
            coords[0] = cubeFaceSize*2 - coords[0]*cubeFaceSize;
            coords[1] = cubeFaceSize*2 - coords[1]*cubeFaceSize;
            break;	
        case 1:// NEGATIVE X - BACK
            coords[0] = cubeFaceSize*4 - coords[0]*cubeFaceSize;
            coords[1] = cubeFaceSize*2 - coords[1]*cubeFaceSize;
            break;	
        case 2:// POSITIVE Y - TOP
            temp = coords[0];
            coords[0] = cubeFaceSize*2 - cubeFaceSize*coords[1];
            coords[1] = cubeFaceSize*temp;
            break;	
        case 3:// NEGATIVE Y - BOTTOM
            temp = coords[0];
            coords[0] = cubeFaceSize - cubeFaceSize*(-coords[1]);
            coords[1] = cubeFaceSize*3 - cubeFaceSize*temp;
            break;	
        case 4:// POSITIVE Z - RIGHT
            coords[0] = cubeFaceSize*3 - coords[0]*cubeFaceSize;
            coords[1] = cubeFaceSize*2 - coords[1]*cubeFaceSize;
            break;	
        case 5:// NEGATIVE Z - LEFT
            coords[0] = cubeFaceSize - coords[0]*cubeFaceSize;
            coords[1] = cubeFaceSize*2 - coords[1]*cubeFaceSize;
            break;	
    }
    return coords;
}

Essentially, what is happening here is that we are projecting the points that are sitting in a sphere, and projecting them onto a square.

Huge thanks to proj4.org for this image

Closing thoughts

Converting between coordinate systems can be complicated, especially if we don’t know exactly what we are dealing with. My solution is by no means a perfect solution to this problem, if your main goal is to convert an entire image then I highly recommend you use Salix alba’s solution instead.

What I wanted was to convert a single point from Equirectangular to Cube Map, and although Salix Alba’s solution is quite amazing, it simply did not fit the bill for what I wanted, which forced me to look for other options.

programming, worldbuilding

Mapping Planets

Distortions, Seamless Noise, and how to handle it.

Generating the planet

One of the simplest ways to generate a planet, is to use noise, which gives you a couple of options on which noise to use, let’s inspect each one and determine the best option:

  • Perlin Noise – Perlin Noise is the most basic option, it was developed by Ken Perlin in 1983, however it has a couple shortcomings, there are a couple visual artifacts and it’s speed at generating large images can be quite slow when compared to the other alternatives.
  • Simplex Noise – Simplex Noise was designed by Ken Perlin in 2001 in an attempt to address Perlin Noise’s shortcomings, it’s a pretty decent and fast solution however it has a major drawback, the use of 3D Simplex Noise is protected under a patent, which makes using it a pretty expensive affair.
  • Open Simplex Noise – Open Simplex Noise was developed by KDotJPG with one simple goal in mind: make a modern alternative to Simplex Noise that is free to use, that has no distortions and is relatively fast.

Out of the three I personally prefer Open Simplex Noise and is what I use for my personal projects. Do take note, the current implementation of OpenSimplexNoise needs some extra work so you can get easy access to the scale, octaves, and seeds. There is a multitude of guides out there on the internet explaining what each of these does and I highly recommend a read, however, that is not what I want to talk about in this blog post.

This is what Open Simplex Noise with 16 octaves looks like.

Seamless noise

Noise is infinite, which means if we simply give it a canvas with a ratio of 2:1 to make an equirectangular map it will not loop when you eventually try to put the map on a sphere(shoutout to this amazing website), and you will get massive variations on the horizontal seam and on the poles.

There is a multitude of ways to fix this, on this amazing blog post Red Blob Games only needs to generate an island, using a function that takes distance from the center as a variable and setting it height 0 at the edges minimizing seams.

However, this is not what we want. We want to generate a planet, with the possibility for north and south poles and to do that we are gonna need some more complex math calculations.

Spherical Mapping

A method that can generate spherical planets would be to convert the cartesian coordinates of our canvas to spherical coordinates, generate noise based on those coordinates and finally convert that noise back to cartesian and map it to the canvas.

However this implementation has its limitations, and the why and how it happens is demonstrated by Ron Valstar in this brilliant blog post mainly the fact that the shapes of the continents look extremely weird and distorted and therefore is not what I want.

Cube Mapping

The second method I ended up using was motivated by both Ron Valstar’s post and acko’s Making Worlds series of blog posts, in them, they describe the generation of a globe through generating a cube and then inflating it as if it was a balloon until it’s shaped like a sphere.

All credits to acko.net for this image, it explains the cube map concept in a easy way to visualize.

Now we simply have to generate six faces which is easy enough, there is a multitude of ways to do it.

The way I ended up doing it was by creating an array and then populating it with data. Converting the 2D coordinates of a canvas to the 3D coordinates of a cube and then generating noise for each of these 3D coordinates to then store them in the corresponding 2D coordinate value.

//Z STATIC
for(int y = 0; y < cubeFaceSize; y++) {
	for(int x = 0; x < cubeFaceSize * 2; x++) {
		//Generates FRONT
		if(x < cubeFaceSize) {
			cubeMap[cubeFaceSize+x][cubeFaceSize+y] = noise.noise3D(x, y, 0);                    
		}
		//Generates BACK
		else {
			cubeMap[cubeFaceSize*3+(x-cubeFaceSize)][cubeFaceSize+y] = noise.noise3D(cubeFaceSize-(x-cubeFaceSize), y, cubeFaceSize);
		}
	}
}
//X STATIC
for(int y = 0; y < cubeFaceSize; y++) {
	for(int x = 0; x < cubeFaceSize * 2; x++) {
		//Generates LEFT
		if(x < cubeFaceSize) {
			cubeMap[x][cubeFaceSize+y] = noise.noise3D(0, y, cubeFaceSize-x);                   
		}
		//Generates RIGHT
		else {
			cubeMap[cubeFaceSize*2+(x-cubeFaceSize)][cubeFaceSize+y] = noise.noise3D(cubeFaceSize, y, x-cubeFaceSize);
		}
	}
}
//Y STATIC
for(int y = 0; y < cubeFaceSize * 2; y++) {
	for(int x = 0; x < cubeFaceSize; x++) {
		//Generates TOP
		if(y < cubeFaceSize) {
			cubeMap[cubeFaceSize+x][y] = noise.noise3D(x, 0, cubeFaceSize-y);          
		}
		//Generates BOTTOM
		else {
			cubeMap[cubeFaceSize+x][cubeFaceSize*2+(y-cubeFaceSize)] = noise.noise3D(x, cubeFaceSize, y-cubeFaceSize);
		}                
	}
}

This way, we can generate a cube map, which is easily converted to an equirectangular map thanks to the amazing code snippet provided by Bartosz.

As you can see, the equirectangular map has much better-looking shapes, and when wrapped around a sphere it produces similar results to Spherical mapping without all its downsides, besides, an equirectangular projection can be easily converted by multiple programs, such as NASA’s G.Projector to basically any kind of map that has ever existed.

Closing Thoughts

Generating an entire planet can be a daunting task, and although noise can be quite a powerful tool if used properly it is still bound to problems that humans have had for centuries, such as how to map a globe to a 2D canvas with minimal distortion.

The solution I propose here only produces very roughly generated planets, with no regards whatsoever towards tectonic plates, rivers, proper island chains, or even mountain ranges and therefore is only really meant to be used as a demonstration or starting basis for more advanced simulations.

In fact, it only really produces a matrix of values within a certain specified range, for the greyscale images they are within 0 to 255 and are then converted to a pixel producing an image similar to the first greyscale image or on a range from -11000 to 8000 emulating real-world height differences and then painting the same color for height intervals.(eg: between 0 to 5 paint it in a sandy color to emulate beaches)

God used beautiful mathematics in creating the world.

Paul Dirac