I'm working on a procedural planet generator that uses a quad sphere, or quadrilateralized spherical cube, to represent its surface.
Most authors seem to number and arrange the faces arbitrarily, and so did I. For example, this is the arrangement that Wikipedia shows for cube maps (apparently the Direct3D convention, although Wikipedia presents it as "the way" without mentioning the zillion alternatives):
But this leads to an issue when you want to know the neighbours of a given pixel, for example for normal mapping, or all sorts of simulations. Given a triple (face, u, v)
that identifies a pixel (where u
and v
are integer indices, not texture coordinates), the task is to find the four triples that identify its four neighbours.
In the face interior, this is easy. But on the edges, you have to take 24 cases of wrapping into account: 6 cube faces × 4 edges per face. In pseudo-C:
Index neighbor(idx: Index, direction: Direction) -> Index {
switch (direction) {
case UP: if (idx.v < SIZE - 1) {
return Index { face: idx.face, u: idx.u, v: idx.v + 1 };
} else {
switch (face) {
case 0: return Index { face: 2, u: SIZE - 1, v: u };
// And so on for the other five faces
}
}
// And so on for the other three directions
}
}
It's tedious and error-prone, and the branching makes it potentially slower than needed.
Then I found the 2007 SIGGRAPH sketch Creating Spherical Worlds (sap_0251) by Compton et al., which mentions:
Further, by choosing face mappings to be permutations of the corresponding axes, it is possible to formulate efficient algorithms for wrapping between faces, and projecting a 3D point into a chart.
This tantalizing sentence is all we get; there's no further explanation, and I can't find any follow-up articles by these authors either.
How can we choose the face mapping to allow for efficient wrapping?
After some quality time with pencil and paper, I figured out a reasonably elegant way to do this. We still need to distinguish between even and odd faces, but that can be done with a modulo operation (compiled down to a bitwise AND), so it doesn't need branching.
The arrangement is as follows:
The mapping of global axes to local axes:
u v normal
+X +Y +Z
-X +Z +Y
-Y -Z +X
+Y +X -Z
+Z -X -Y
-Z -Y -X
Each global axis is used once in each column, so this is indeed a permutation. Whether it's the same one that the Spore developers designed, remains an open question.
There are certain rules now, for example, moving UP from face f
,
f
is even (0, 2, 4), we always find face (f + 1) % 6
(1, 3, 5),f
is odd (1, 3, 5), we always find face (f + 5) % 6
(0, 2, 4).So the neighbour lookup becomes much simpler:
Index neighbor(idx: Index, direction: Direction) -> Index {
switch (direction) {
case UP: if (idx.v < SIZE - 1) {
return Index { face: idx.face, u: idx.u, v: idx.v + 1 };
} else {
return Index { face: (face + 1 + 4 * (face % 2)) % 6, u: SIZE - 1 - u, v: SIZE - 1 }
}
// And similar for the other three directions
}
}
There might still be room for improvement, so better answers are quite welcome!