TMCMG Terrain Definition Tutorial

Screenshots are based on the version of TMCMG available 2011-05-17. Future versions may display differently.

I am assuming you already know how to write a text file, start the TMCMG World Preview application, and load your terrain definition. This tutorial is about writing terrain definitions, not about how to use the application. More information about the application itself may be found at www.nuke24.net/projects/TMCMG/.

Throughout this tutorial, example scripts are shown alongside their output as shown in the TMCMG preview window.

Simplest Possible Terrain Definition

With TOGoS's Minecraft Map Generator (TMCMG), you define terrain using a set of layers described by a text file. The simplest possible terrain definition looks like this:

layered-terrain()

The result is a completely empty world with no ground or water -- just sky from top to bottom. The top half of the preview window (completely blue in this example) shows a slice of the terrain as seen from the side, and the bottom part (completely black in this example) shows it as seen from the top. Since there is no terrain specified, you see sky from the side and bottomless void looking down from the top. Minecraft may act kind of weird if you try to play on such a map.

Layers

To make something other than a completely empty map, we need to add some layers. To define a layer, include a layer element within the layered-terrain. The layer expression takes 3 arguments: the type of material (e.g. stone, water, dirt), the bottom of the layer, and the top of the layer. Materials can be specified using one of several predefined constants such as materials.stone. Heights are numbers between 0 and 128, where 0 means the very bottom of the map and 128 is the 'top of the sky'. For starters, let's define a stone layer that fills the bottom third (approximately) of the map:

layered-terrain(
    layer( materials.stone, 0, 48 ),
)

The top and side-view preview panes blend together because stone shows as the same color in both. Let's add an ocean at the standard height (sealevel in Minecraft is usually 64).

layered-terrain(
    layer( materials.stone, 0, 48 ),
    layer( materials.water, 0, 64 ),
)

Well there's our ocean sure enough, but what happened to the stone? Well, since we defined the water layer after the stone layer, the water layer took precedence. If we want the stone to appear at the bottom of the ocean, we can either define the water layer to only extend from the top of the stone layer (instead of starting at 0), or we can define the stone layer after the ocean layer. Which approach to take in general when combining layers depends on the situation, but for now let's just define the ocean first so that the stone layer will override it. While we're at it, let's define a bedrock layer at the very bottom of the map to protect the player from digging too deep (should he somehow manage to dig a tunnel through the bottom of the ocean):

layered-terrain(
    layer( materials.water, 0, 64 ),
    layer( materials.stone, 0, 48 ),
    layer( materials.bedrock, 0, 1 ),
)

Noise functions

How to make something other than a bunch of flat layers.

Simplex noise is a way to produce random-looking but smooth hills and valleys. We can use it in our height functions using the simplex expression, which takes no arguments. Let's make our ocean floor average a bit below sea level but with some simplex noise added:

layered-terrain(
    layer( materials.water, 0, 64 ),
    layer( materials.stone, 0, 48 + simplex ), # Hills?
    layer( materials.bedrock, 0, 1 ),
)

"Hey wait a minute," you might say, "that didn't change anything!". And you would be pretty close to correct. Simplex noise varies the height only on very small scales. The hills we induced by adding simplex to the bottom of the ocean are about the size of basketballs; since they are smaller than a single Minecraft block, they do not appear at all. To scale them up, let's use the fractal function, which takes 7 arguments (ohmygoshcomplicated!!!). Ignore the 1st, 4th, 5th, and 6th arguments for now. The 2nd and 3rd are how much we want to scale our hills horizontally and vertically (to change the width and height of our hills), and the 7th argument is the noise we are scaling:

layered-terrain(
    layer( materials.water, 0, 64 ),
    layer( materials.stone, 0, 56 + fractal(1, 64, 64, 1, 1, 1, simplex ) ),
    layer( materials.bedrock, 0, 1 ),
)

That is starting to look better! Let's zoom out a bit...

Hmm, not that interesting. These islands just go on and on and on without changing in size or shape very much. But this is where the other arguments to fractal come in. fractal can add our basis function (in this case simplex) to itself multiple times at different scales. We just need to tell it how many iterations we want (this is the 1st argument), and how much to multiply the horizontal and verticale scales for each iteration (the 4th and 5th arguments). It will also shift the inputs to the basis function by a set amount for each iteration to reduce the fractal similarity of the result (how much to adjust is the 6th argument - a value of 1 is fine). Let's increase the iterations!

layered-terrain(
    layer( materials.water, 0, 64 ),
    layer( materials.stone, 0, 56 + fractal(4, 64, 64, 2, 2, 1, simplex ) ),
    layer( materials.bedrock, 0, 1 ),
)

The preview screenshot makes those mountains look steeper than they really because TMCMG's horizontal preview scales horizontally but not vertically when zooming in or out. To get a good idea of the steepness you need zoom back to 1.0 meter per pixel. Future versions of TMCMG may do this differently.

Let's say for now that we like the steepness of the mountains. But we can't have mountains going off the top and bottom of the map! To fix this, we could adjust the fractal arguments a bit, but for purposes of making examples of handy functions let's use the ridge function to fold the ground height back on itself when it gets too high or low. The ridge function takes 3 arguments: the lowest desired value, the highest desired value, and the input value that needs to be 'folded', in this case our fractal(...) expression.

layered-terrain(
    layer( materials.water, 0, 64 ),
    layer( materials.stone, 0,
        ridge( 32, 96, 56 + fractal(4, 64, 64, 2, 2, 1, simplex ) )
    ),
    layer( materials.bedrock, 0, 1 ),
)

I put the layer ceiling part of the expression on its own line to make the expression easier for humans to read and fit on their screens.

Now let's add some dirt.

layered-terrain(
    layer( materials.water, 0, 64 ),
    layer( materials.dirt, 0,
        ridge( 32, 96, 56 + fractal(4, 64, 64, 2, 2, 1, simplex ) ) + 3
    ),
    layer( materials.stone, 0,
        ridge( 32, 96, 56 + fractal(4, 64, 64, 2, 2, 1, simplex ) )
    ),
    layer( materials.bedrock, 0, 1 ),

    # Post-processers
    grassifier,
)

Notes about the above terrain definition:

Also note that I duplicated most of the expression defining the stone height to define the dirt height. If it were a more complicated expression and/or we needed to use it in more different parts of the script, this could get cumbersome. To relieve this burden, we can use...

User-defined Macros

You can name an expression using a word and reference it later, multiple times if needed. This is functionally (and performance-wise) equivalent to including the expression at the point where you use the name, but may make your script more readable. Names may include letters, numbers, dashes, and plus signs (the tokenizer currently allows some other characters, too, but this could change in future versions). We can use a named expression ("stone-height") to neaten up our dirty mountain terrain:

stone-height = ridge( 32, 96, 56 + fractal(4, 64, 64, 2, 2, 1, simplex ) );

layered-terrain(
    layer( materials.water, 0, 64 ),
    layer( materials.dirt, stone-height, stone-height + 3 ),
    layer( materials.stone, 0, stone-height ),
    layer( materials.bedrock, 0, 1 ),

    # Post-processers
    grassifier,
)

Note that in addition to using stone-height as part of the dirt layer's ceiling, I used it as the dirt layer's floor. Since the dirt would be overridden by the stone below this height anyway, this change has no functional effect, but may inprove performance very slightly since TMCMG no longer has to 'fill in' those dirt blocks when generating chunks. This technique will be more useful when using a volumetric material function, as these take additional CPU time to generate for each block between the layer's floor and ceiling.

Volumetric Materials

The material argument to the layer function may itself be a variable expression, in which case it will be evaluated to determine the material for each x, y, z that the layer occupies. A simple use case is to pseudo-randomly alternate between 2 or more materials, e.g. dirt and sand.

stone-height = ridge( 32, 96, 56 + fractal(4, 64, 64, 2, 2, 1, simplex ) );

dirty-material = if( simplex > 0, materials.dirt, materials.sand );

layered-terrain(
    layer( materials.water, 0, 64 ),
    layer( dirty-material, stone-height, stone-height + 3 ),
    layer( materials.stone, 0, stone-height ),
    layer( materials.bedrock, 0, 1 ),

    # Post-processers
    grassifier,
)

For this example I zoomed back to 1 pixel per block scale.

I also introduced the if expression. The general syntax is if( <cond1>, <then1>, <cond2>, <then2>, ...., <condN>, <thenN>, <default> ). Think of it like an if-else chain evaluated for each block.

Hopefully you can think of much more interesting uses for volumetric noise!

Post-processors

Post-processors are things you can include as arguments to layered-terrain to make changes to chunks after the bulk of the terrain has been generated.

People often ask how to generate a map with no trees or ores. This is exactly what flat-populated is for!

There are also post-processors for adding trees, but they're kind of crappy and I hope to replace them with more flexible stamp populators eventually, so I won't mention them here. You can find examples of their usage in some old example scripts. Otherwise, simply leave out flag-populated to let Minecraft generate forests.