The Geology Service is a system that lets us place special “geology” textures in the world. It allows us to generate terrain features that we could not easily achieve with our standard procedural terrain generation.
It works by recursively sub-scattering (see fig. 1) textures of varying size categories throughout a particular biome. There are a large number of control settings (discussed below) that the user can adjust to achieve their design goals.
Figure 1: Recursive sub-scatter method, starting with a large
texture placement. Each size places one or more smaller-sized features, and small
features have a chance of placing another small
feature.
Let’s get into more detail about how this works, using fig. 1 as an example.
The Geology Service currently supports four different size categories: huge
, large
, medium
, and small
. Each size comes with its own settings that control:
- The likelihood of picking this size as the “first placement”
- The relative height range this size is allowed to fall within
- The number of smaller features this size attempts to scatter nearby
- The distance away from this feature that sub-scattered features will be placed
First of all, we have to work out the details for that first placement. We have a few things to consider.
- What size should we start with? We randomly choose a size based on the [
size]_rate
settings. These rates are treated as weights, meaning that we add all the weights together to get a total weight, and the probability of selecting each size is that size’s weight divided by the total weight. For example:
In this case, the _total weight _is 10 + 25 + 50 + 60 = 145. Therefore, there’s a 10 ÷ 145 ≅ 7% chance of choosing a huge texture, a 25 ÷ 145 ≅ 17% chance of a large texture, etc.
- Where should it go in the world? The first placement can only successfully be placed in the biome specified by
place_on_biome
. We randomly select any location in the world that satisfies this constraint.
- How tall should it be? We randomly select a height scaling factor in the range specified by
size_relative_height
. In our example from fig. 1 where we chose alarge
texture, we would choose a scaling factor between 0.2 and 0.6. See relative height for more details.
- Which texture should it use? Textures are randomly selected from each size category. In this particular case, there is only one
large
texture to choose from, so we know we’ll be usingtex_stamp_hoodoo_large_01
.
Once we’ve chosen these parameters for the first placement, we move onto the sub-scatter.
Now we have some considerations for sub-scattering.
- How many smaller features will we attempt to place nearby? Each placement size specifies a range of smaller placements to place.
Our first placement was a large
placement. Therefore, it will automatically attempt to scatter between 1 and 3 medium
placements nearby. (In fig. 1, we can see that our large feature scattered 2 medium
s.)
- How far away should those smaller features be from this one? There is a displacement range setting for each size.
We started with a large
feature, so now we’re placing medium
features. According to the data above (medium_displacement_range
), each one will be randomly placed between 0 and 25 blocks away, in a circular radius.
Now, the trick is this: each placement repeats this entire process. That means
huge
features placelarge
ones,large
features placemedium
ones,medium
features placesmall
ones,small
features may place othersmall
ones (subject tosmall_scatters_small_chance
)
The logic for each placement starts from here (i.e. we ignore steps 1 and 2 of the “first placement” because we already know the sizes and we know we’ve started in the correct biome).
This recursive process leads to interesting clusters of features (like the clusters seen in fig. 1), where the intersection of multiple texture stamps can create very complex geological shapes.
Figure 2: Biome channel | Figure 3: Blend channel | Figure 4: Height channel | Figure 5: Combined channels |
Figures 2-6 pertain to the texture falloff_textures/hoodoos/tex_stamp_med_02
.
The textures that get placed by the Geology Service aren’t 100% literal translations of the original textures – that is, they are modified by a few factors.
Each pixel of a texture will correspond to a biome (fig. 2) and a height (fig. 4, taking values 0-255, re-mapped to a range specified by the biome’s height_params
attribute). _Please note that this data lives in each biome’s data _(behavior_packs/badger/biomes/
).
The light green part of figure 2 corresponds to the drylands_hoodoos_small
biome (as specified in the biome_map
of fig. 6). That biome’s height_params
attribute specifies that the 0-255 height values from the height channel should instead correspond to heights from 30-80. That is, a 0-valued pixel in that biome represents an in-game height of 30, while a 255-valued pixel represents an in-game height of 80.
Each placement gets randomly assigned a relative height. This has the effect of narrowing the biome’s height ranges for that particular feature by the specified amount. For example, we can see here that a large
feature ends up with a relative height between 0.2 and 0.6. Let’s say we randomly choose the value 0.5. That means instead of a biome height range being re-mapped from 30-80 (width = 80 - 30 = 50), this placement will have those heights re-mapped to the range 30-55 (because the new width = 0.5 * 50 = 25, which is our new width – the lower height remains constant).
Figure 6: tex_stamp_hoodoo_med_02.json
The blend channel (fig. 3) works the same way in the Geology Service as it does in other placement textures. The 0-255 values are remapped to 0-1, where 0 means “use the existing world height here, 100%”, 1 means “use the (remapped) texture height here, 100%, and 0.5 means “blend the existing world height and the remapped texture height here, 50%-50%”.
There is a somewhat complex logic behind determining which biome gets placed at each pixel. Geology types have prioritized “placement biomes”, meaning that textures can place any of these biomes, but higher-priority biomes (ones earlier in the list) will overwrite lower-priority biomes if the in-game (calculated) elevation at the point associated with the higher-priority biome is higher than the previous in-game elevation. (There is an optional setting, biome_always_takes_priority
, that causes this system to ignore the elevation check and will always switch to the higher-priority biome when given a chance.)
In this example, when textures are overlapping,
mountain_summit
can overwritepeaks
,mid_hills
, andfoothills
mountain_peaks
can overwritemid_hills
andfoothills
mountain_mid_hills
can overwritefoothills
The height that appears in-game is the maximum of the given texture height and the previous in-game height – that is, a geology texture can never reduce the terrain height.
It is often desirable to enforce some minimum spacing between first placements so that there is likely to be some “empty space” between geology groups. This optional setting, measured in blocks, allows us to enforce that minimum spacing, meaning that if a first placement would otherwise succeed, but it is too close to another first placement, it will instead fail.
It can be disruptive if a geology texture spills over into another biome. This can happen because, by default, we only check to ensure that the center of a geology placement is in the correct biome. These settings allow us to also perform checks on the corners of a proposed geology placement, allowing us to fail a placement that would spill out of the intended biome group.
The biome test at each corner succeeds if the biome located at that position in-game has the tag specified by biome_tag
. For example, all of the mountain biomes have the mountain
tag, so if a mountain texture would have its corner place in any of the mountain biomes, that test would succeed.
Sometimes we might want to allow a geology placement to succeed if some or most of its corners would end up in an appropriate biome. In this case, we can specify the minimum number of corners for the placement to succeed.
Sometimes, we may wish to place a single instance of a special texture in the world. If we provide optional_rare_texture_name
, that texture will appear exactly once in the world. If this initial placement succeeds, it is treated as a first placement and will sub-scatter smaller features as usual.
It is recommended that the optional rare texture is the only texture in its [size]
category – if it isn’t, the Geology Service will randomly select from among all_ _of the textures in the same size category as the rare feature (and an assert will be thrown). Note that if the optional_rare_texture_name
does not appear in any of the [size]
categories, an assert will be thrown.
The rare texture will only successfully place in an area where the entire texture footprint lies upon the specified place-upon biome. The Geology Service will attempt to find a location that satisfies this restriction max_attempts_rare_placement
times, after which the placement will fail and the system will move on to regular group placements.
The Geology Service can interface with the SDF Shape Service, allowing us to create even more complex terrain. Jungle mountains use this technology extensively. Adding new geology types that combine with SDF shapes requires code support, so if this interests you, please reach out to a world engineer to discuss your ideas.
Each json
file within data/behavior_packs/badger/gamelayer/server/world_gen/geology/
describes a unique geology type. To add a new geology type to the game, you can duplicate one of the existing json
files, then modify the data within as needed.
You will likely want to create new textures that your new geology type will reference. The textures live in sub-folders of data/behavior_packs/badger/falloff_textures/
. For example:
The main body, where the general settings are found. If a default value is specified, the setting is not required.
Determines whether this geology type will appear in the world.
Determines whether other world gen placements are allowed to overlap with this geology type. This could lead to strange interactions so it is recommended to leave it set to false
unless you test thoroughly with overlapping placements.
See Biome for explanation.
Determines the number of group placements to attempt to place. Larger numbers mean more geology placements.
Determines the priority of biomes that will appear in this geology type’s textures. Earlier in the list means higher priority. See Biome.
The biome that this geology type may place upon.
The “parent” biome name that this geology type may place upon (e.g. if place_on_biome
is drylands_enable_hoodoos
, the place_on_biome_parent
should be drylands
).
A valid biome tag. See Corner biome checks.
The minimum number of corners that must appear within the appropriate biome. See Corner biome checks.
A minimum distance in blocks that groups’ first placements must respect, relative to every other first placement. See Minimum group spacing.
For each placement group, how many times to attempt to find a position that satisfies the min_distance_between_groups
.
During geology group placement, if we encounter this many failures due to violating group spacing or corner biome checks, we quit feature placement early.
Cause recursive sub-scattering to stop early if we reach a particular group size.
A recursively-scattered feature of size [size]
will be placed between [min]
and [max]
blocks away from its parent.
A feature of size [size]
will place between [min]
and [max]
placements of size [smaller-size]
.
The probability that any small placement will sub-scatter another small placement. Note that things can get a little out of hand if this number is sufficiently large.
The relative weight for size [size]
. See “what size should we start with?”
The relative height range for size [size]
. Each placement will randomly select a value between [min]
and [max]
. See Relative height.
This attribute holds a small number of texture-specific settings, as well as lists of all textures that may be used for this geology type. If a default value is specified, the setting is not required.
The name of this geology type, e.g. “hoodoos” or “jungle_mountains”. Used for debug/logging text only.
The name of a texture that should appear only once in the world. The name must match one of the textures listed in a size category below, and that texture should be the only texture in its size category. See Rare texture.
The number of times we will attempt to find an appropriate location for the given rare texture.
Lists of textures of size [size]
. Every texture in a given category should be the same actual size for corner biome checks to work properly.
The pixel dimensions of the textures for the given category [size]
. These sizes must be accurate for corner biome checks to work properly. Note: it is assumed that all textures used are square.
[size]
is one of { huge
, large
, medium
, small
}.