One of my goals for mapgen4 was to explore non-realistic approaches, both to map generation and to map rendering. I wanted to explain the unusual projection I'm using.

For procedurally generated wilderness maps, it's very common to see a top-down view, often using colors to represent height or biomes:

Top-down view

It's simple to implement, especially in 2D, and it shows the areas of the map, including coastlines and rivers and lakes. You can see where the mountains are, but only because of the color and lighting.

It's also common to show a side view, often with perspective:

Side view

It's usually implemented in a 3D engine, and it's good for seeing things like mountains, towns, and trees. It's what you'd see in a first person view game. Features like rivers and lakes are distorted and often hidden behind other objects.

Sometimes you want to see both the top-down features (rivers, coastlines, lakes) and the side view features (mountains, towns, trees), and many games use an isometric or orthographic view:

Orthographic view

It gives you a bit of both. However, the top-down features are skewed compared to a pure top-down view, and the side view features are also skewed compared to pure side view.

If you look at a hand drawn fantasy map, such as the Lord of the Rings map, or the ones you find on Cartographers' Guild or Reddit r/mapmaking, you'll see artists don't feel constrained to one of the above three views. Instead, very often, they use both top-down view to show rivers and coastlines and side view to show mountains and towns.

Lord of the Rings map
Lord of the Rings map (from Wikipedia)

The rivers, roads, and coastlines are a top-down view, but the mountains and hills and trees and grasses are a side view. One way to think about this is that the mountains, hills, trees, and grasses are icons like these and therefore don't have to use the same view as the rest of the map. However, it's possible to implement this directly in matrix math. The trick is to use an oblique projection. It's not the standard rotate, translate, scale methods that you get from the matrix libraries in Unity or Unreal, but it can be represented by setting the matrix values directly.

Let's look at the 2D version:

Rotate operation Shear operation
Rotate vs shear operation
  • In a 2D rotation operation, some of x gets transferred to y and some of y gets transferred to x. It preserves the shape of the objects but distorts y and x.
  • In a 2D shear operation, none of x gets transferred to y and some of y gets transferred to x. It distorts the shapes and x but preserves y.

To make a hybrid of top-down view's x and y with side view's x and z, we can use a shear operation: preserve x and y, and transfer z to y.

The result is that the top-down view is preserved. When there's no z (mountains or trees), x and y stay exactly the same. But when there's z, it gets mapped to y, so that you can see the side of the mountain. This is entirely implemented in a 4x4 matrix, without using billboard sprites. Here's the result:

Top-down oblique view

I also have rotation and scaling and translating. I apply them in this order:

  1. Rotate
  2. Shear
  3. Scale
  4. Translate

I apply shear after rotate so that if the map is rotated, the z axis gets mapped to "up" in the output view. Otherwise, z would get mapped to "up" (y) and then y would get rotated, so the mountains would point sideways instead of up.

I also wanted to support both orthographic and oblique, so I blended the rotate and shear operations together (with one line of code!), so that I can smoothly interpolate between the two projections. I can also smoothly interpolate between top-down view and oblique, or oblique and side view.

I'm pretty happy with the effect!

More reading:

Labels: , , ,

3 comments:

Indie Notion wrote at September 19, 2018 5:34 PM

Wow, I can't wait to see some screenshots of this!

pmaciel wrote at September 20, 2018 2:30 AM

I realised that in your posts you didn't mention some 3-dimentional map generation concerns, such as:
* map generation on a sphere, or alternatively possible "wrap-around" issues, and
* map generation closer to the poles, or equator, controlled by the "biome" concerns (or climate, I guess)

It might be too out-of-scope, but anyway are these concerns under your radar? Thanks for the fantastic posts!

Amit wrote at September 20, 2018 11:51 AM

@pmaciel: I'd like to look at these but they're out of scope for mapgen4. It's already taking far longer than I had hoped :-(

The simplest variant for climate varying by latitude is to use temperature instead of elevation for the biome lookup table. Then change temperature by latitude. I have a quick demo here and I've added it to mapgen2 (set N-Cold and S-Hot).

The next level of variation would be to incorporate winds and maybe ocean circulation. The Hadley, Ferrel, and Polar atmospheric cells are the main forces involved for wind, and I haven't figured out how to simulate ocean circulation yet.

I would like to try spheres at some point, and I've been keeping an eye on what might get in the way. One complication with spheres is that they don't work well with square grids. They can either use a segmented wraparound grid or use a wraparound graph data structure. For mapgen4 I'm using a wraparound graph. Although you can't see it, the graph actually wraps around the back side of the flat plane. I do use grids for the painting and for the rendering, but I can generate maps without the painting UI, and I can render without the outlines or oblique projection, and then my code would work with spheres. So yes, sphere is out of scope for mapgen4 but on the radar for a future project using some of the same code. :)