This post is part of My Career Series.

Two months ago, I started my internship at Uber Entertainment to work on Planetary Annihilation, which is currently in closed Alpha (you can still purchase Early Alpha Access into the game through Uber Store or Steam). Many people on the development team have worked on Total Annihilation and Supreme Commander, and you can see the similarities Planetary Annihilation share with these two games.

Planetary Annihilation is a next-gen real-time strategy game on a planetary scale, where players fight across multiple planets. Here’s the alpha release trailer that shows off Planetary Annihilation’s unprecedented epicness.

For the first 6 weeks of the internship, I spent most of my time on the in-game planetary camera and celestial camera. Recently I’ve moved on to polishing the procedural planet generation. My latest work that is worth mentioning is the bending of solid geometry.

Constructive Solid Geometry

The planet geometry is generated through a technique called Constructive Solid Geometry (CSG), also known as 3D Boolean Operations by 3D modelers. Basically, CSG involves combining multiple solid geometry, called brushes, to construct more complex geometry. This is probably best explained visually. Below is a simplified 2D diagram explaining the process:

Base brushes are the base geometry we start with, and additive brushes are “union-ed” together with the base brush, where subtractive brushes “eat away” regions from the base brush.

In Planetary Annihilation, base brushes form the base geometry of a planet; additive brushes are used to form hills, mountains, plateaus, ice cliffs, etc.; subtractive brushes are used to create cracks and pits into the ground. Jonathan Mavor, CTO of Uber Entertainment, wrote a blog post that elaborates much more on this specific topic.

Projecting The Brushes

The brushes are modeled by artists as if we’re performing CSG on a flat ground, instead of a spherical surface of a planet. Here’s a shot of a desert crack CSG brush.

In order for CSG brushes to conform to the curvature of the planet surface, we have to project it onto the planet. There are two intuitive ways of projecting a brush onto a spherical surface: arching and bending. Arching involves shifting vertices vertically to fit the planet curvature; on the other hand, bending does what it sounds like: bend the geometry. Here’s what I meant visually:

I was assigned the task of changing the CSG brush projection method from arching to bending. The vertical edges in the crack CSG brush shown above are supposed to point towards the planet center, and only by using bending would we get the desire result.

Here’s a in-game comparison between cracks created using different brush projection methods:

And the picture below highlights the edges that are supposed to point towards the planet center.

Here’s another in-game comparison with the crack CSG brush changed from subtractive to additive to better show the difference:

And here are the highlighted edges:

The Math

Now I’m going to derive the math behind the bending projection method. For simplicity, here I assume the planet is centered at the origin. I will refer to the projection result of a point in the projected geometry as its image.

The idea is to project the local origin of the CSG brush onto the planet surface, so the distance between the image of the brush’s local origin and the planet center is exactly the planet radius.

For other vertices, their images are obtained by rotating a vector going through the image of the brush’s local origin.

There are 3 problems left: What is the magnitude of the vector? What is the rotation angle? What is the rotation axis? I’ll go through them one by one.

The Vector Magnitude

This is an easy one. The vector magnitude is the sum of planet radius (R) and the vertical difference between the brush’s local origin and the vertex (D).

The Rotation Angle

We would like the distance between a vertex on the same horizontal plane as the brush’s local origin (H) to remain the same after the projection, where the distance after the projection is calculated as arc length (A).

So the rotation angle (in radians) would equal to the horizontal distance between a vertex and the local origin divided by the planet radius.

The Rotation Axis

Let’s take a step back. Imagine that we just place the brush on the planet surface, undistorted. If we want to rotate a vector going through the image of the brush’s local origin towards the vertex’s image, the rotation axis would be the same as when the brush is bent.

So the rotation axis can be obtained by taking the cross product of a vector going through the image of the brush’s local origin and a vector going through the vertex image as if the entire brush is put on top of the planet, undistorted.

The Pseudocode

With all the math figured out, here’s the pseudocode to project a CSG brush onto the planet surface using the bending method:

// Planet radius. float radius = ...; // Transform matrix that puts the brush's local // origin in place on the planet. Mat3 transform = ...; // Brush's local origin in world space. Vec3 localOrigin = ...; // A 3D vector array that represent vertices of the CSG // brush we're bending in the brush's local space. // Assume Z-axis points up. Vec3 vertices[numVerts] = ...; // Image of the brush's local origin. Vec3 localOriginImage = transform * localOrigin; // Output array we're going to write the bending result to. Vec3 output[numVerts]; // Calculate the vector that goes through the image of the // brush's local origin. Vec3 n = normalize(transform * brushLocalOrigin); // Loop through brush vertices. foreach (Vec3 v, vertices) { // Get vertex image as if there's not distortion. Vec3 vTransformed= transform * v; // Calculate vector magnitude float magnitude = radius + v.z; // Calculate rotation angle. float angle = sqrt(v.x * v.x + v.y * v.y) / radius; // Calculate rotation axis. Vec3 axis = localOriginImage.cross(vTransformed); // Calculate rotation matrix Mat3 rot = rotateAxisAngle(axis, angle); // Calculate final image output[i] = rot * (mag * n); }

Height Variation

In Planetary Annihilation, planet surfaces are displaced (using simplex noise) to create height variation. One extra step is needed after the bending to account for such height variation, which I omitted for simplicity’s sake.