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.
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.
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 ), )
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:
grassifier
after all the layer definitions
- this will have the effect when chunks are exported that the topmost
block of dirt under the sun will become a grassy block, but the effect
does not show up in TMCMG's preview window. Grassifier is one of
several post-processors.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...
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.
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 are things you can include as arguments
to layered-terrain
to make changes to chunks after the
bulk of the terrain has been generated.
grassifier
adds grass to any dirt blocks directly
under the sky.flag-populated
will mark the chunk as already
populated with trees and ores so that Minecraft will not add them
itself.winterizer( <expr> )
will make snow and ice
wherever <expr>
is > 0.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.