The goal of this reference page is to give a detailed description of how the surface and clutter tecnologies work, as well as a start to finish guide on the implementation of clutter.
A common notation used below is Root >> Deeper >> EvenDeeper
, which is used as shorthand to express nested classes, like so
class Root
{
class Deeper
{
class EvenDeeper
{
};
};
};
The examples given below contain comments, which are denoted by
// this is a comment, which is ignored by the program/game, and is used to annotate segments of configs or code
/* this is a block comment, everything between the asterisks is ignored by the program/game */
The first place to start is the role of the mask image. Mask images are used to tell the game where specific surfaces should be and what texture should be used. Every unique surface type on your terrain starts as its own color on the original mask image. Each of those colors is then defined in your layers.cfg
along with which material (RVMAT) to use. It's essential that the colors used on your mask are exact matches to colors defined in your layers.cfg
. This is because in the event that TB doesn't find a match, it'll find the next "closest" color on the maplegend.png
, which is defined in your layers.cfg
. Below you'll find an example of a common issue when using "brush" tools in image editing programs.
Incorrect | Correct |
---|---|
For each unique surface on your mask you'll define a material (RVMAT) for it in your layers.cfg
.
class Layers
{
// list of all surfaces on your terrain
// this corresponds to the name of a surface below in Legend >> Colors >> yourtag_someSurfaceName
class yourtag_somegrasssurface
{
// texture doesn’t matter unless you're using
texture = "";
material = "yourtag_yourmap\data\yourtag_somegrasssurface.rvmat";
};
// more of your surfaces
};
class Legend
{
picture = "yourtag_yourmap\source\mapLegend.png";
class Colors
{
// defining what color on your mask corresponds to the surface
// Surface_Name_From_Class_Layers = {{ Red, Green, Blue }};
// matches the name of a surface from the above Layers >> yourtag_someSurfaceName
yourtag_somegrasssurface[] = {{ 23, 243, 69 }}; // bright green
// more of your surfaces
};
};
It's also worth noting that a surface in Legend >> Colors
can have more than one color associated with it. For instance, the following would result in the surface called yourtag_othersurface
to be associated with the colors red (255, 0, 0), green (0, 255, 0), and blue (0, 0, 255) on your mask.
yourtag_othersurface[] = {{ 255, 0, 0}, { 0, 255, 0}, { 0, 0, 255}};
The RVMAT for the surface called yourtag_somegrasssurface.rvmat
contains two "stages",
// this is what your yourtag_yourmap\data\yourtag_somegrasssurface.rvmat might look like
// more stuff before this
class Stage1
{
texture = "yourtag_yourmap\data\yourtag_somegrasssurface_nopx.paa";
// more stuff after this
};
class Stage2
{
texture = "yourtag_yourmap\data\yourtag_somegrasssurface_co.paa";
// more stuff after this
};
When you generate layers in TB, it takes your original satellite, mask, and normal (if you're using a normal map) images and splits them into "tiles". The size of the tile and how many tiles are created is based on your map frame properties. At this point, TB reads the surface RVMATs from your layers.cfg
(e.g. yourtag_somegrasssurface.rvmat
) and creates one or more RVMATs for that specific tile (e.g. p_000-000_l00.rvmat
). The generated tiles (e.g. p_000-000_l00.rvmat
) point to the textures yourtag_somegrasssurface_nopx.paa
and yourtag_somegrasssurface_co.paa
, instead of the original yourtag_somegrasssurface.rvmat
. After you've finished generating layers, the original surface RVMATs, such as yourtag_somegrasssurface.rvmat
, are no longer used.
The colors you used on your original mask and layers.cfg
, as well as the names for surfaces you used within your layers.cfg
, aren't used anywhere passed this point. When the game loads p_000-000_l00.rvmat
, it only knows about the textures yourtag_somegrasssurface_nopx.paa
and yourtag_somegrasssurface_co.paa
. It knows nothing about what color yourtag_somegrasssurface
was on your original mask, or was it was called in your layers.cfg
.
Before going into further detail about how CfgSurfaces
, CfgSurfaceCharacters
, and Clutter
config entries work together, here is an image that summarizes their connections.
You define properties for that surface in CfgSurfaces
, for example:
class CfgSurfaces
{
class Default;
class yourtag_somegrasssurface: Default
{
files = "yourtag_somegrasssurface_*";
character = "yourtag_somegrasssurface_character";
soundEnviron = "grass";
soundHit = "soft_ground";
impact = "hitGroundSoft";
};
//more surfaces
};
With regards to getting clutter to work, the important things to note here are files
and character
.
- The
files
corresponds to the name of the textures used in your surface's RVMAT (yourtag_somegrasssurface.rvmat
), in this caseyourtag_somegrasssurface_co.paa
, oryourtag_somegrasssurface_nopx.paa
. This is why it's important you tag the name of your textures, if you don't, you could easily mess with vanilla or third party terrains if you are using the same texture or surface names. - The
character
corresponds to an entry in yourCfgSurfaceCharacters
. In this example, the character is defined asyourtag_somegrasssurface_character
. If you'd like a surface to have no clutter then you can usecharacter = "EMPTY";
, which is a "character" defined by BI that has no clutter.
For more information on what each property within CfgSurfaces
does, see the CfgSurfaces Config Reference on the official wiki.
The properties of a character
in CfgSurfaces
is defined in CfgSurfaceCharacters
. You should notice that in the example below, the entry for yourtag_somegrasssurface_character
in CfgSurfaceCharacters
matches the character
that was defined in the CfgSurfaces
shown above.
class CfgSurfaceCharacters
{
class yourtag_somegrasssurface_character
{
probability[] = {0.2, 0.4};
names[] = {"yourtag_grassgreen_c", "yourtag_thistlethorngreen_c"};
};
//more surface characters
};
Each index in the names[]
corresponds to an index in the probability[]
. The sum of all values in the probability[]
must be <= 1. To clarify the above, here is a small table just to demonstrate which name corresponds to which probability from the example above.
Name | Probability |
---|---|
yourtag_grassgreen_c | 0.2 |
yourtag_thistlethorngreen_c | 0.4 |
So now your character is defined, but the game needs to know which models to actually place on your terrain. The entries from names[]
point to classes defined in CfgWorlds >> yourtag_yourmap >> clutter
.
You define the actual clutter models that will be used in CfgWorlds >> yourtag_yourmap >> clutter
.
class CfgWorlds
{
class someBaseTerrain;
class yourtag_yourmap: someBaseTerrain
{
//lots of stuff before this
class DefaultClutter;
class clutter
{
class yourtag_grassgreen_c: DefaultClutter
{
model = "A3\plants_f\Clutter\c_Grass_Green.p3d";
// more config entries here for your clutter
};
class yourtag_thistlethorngreen_c: DefaultClutter
{
model = "A3\plants_f\Clutter\c_StrThornGreen.p3d";
// more config entries here for your clutter
};
};
};
};
There are numerous potential causes of clutter not correctly appearing on your terrain. Thus, the first step is to narrow down that list. To assist with this I've written a script, that can be found here. When the main part of the script is running, it will display information about the surface your character is currently standing on and copy that information to your clipboard. So keep in mind that while the script is running it'll be continuously copying to your clipboard until you stop the script, or "pause" the game.
- Launch Arma 3 with your terrain.
- Load into Eden editor on your terrain.
- Place a single unit down in an area you want to troubleshoot your clutter.
- Press the "Play scenario" button in the bottom right.
- Tab out of Arma and open the script I linked above in your browser.
- Select all of it (CTRL + A) and copy it to your clipboard (CTRL + C).
- Open Arma 3 back up, hit escape, and paste the script (CTRL + V) into the debug console.
- With the script now in the debug console, hit the "local exec" button.
The script will give you "addActions", which are just scroll wheel options. When you first execute the script in the debug console there will be two options:
Start surface debug
- this will start a script that will run once every second and will print out some information about the surface your character is currently standing on. If the message formatting is a little hard to read, don't forget to check the information it copies to your clipboard, as that should be more readable.Remove these addActions
- this will remove the addActions from your character. In order to get them back you will need to local exec the original script again.
When the debug script is already running you won't see the addActions for Start surface debug
or Remove these addActions
. Instead, you'll see only one option, which is Stop surface debug
. Which does exactly what you'd guess, it stops the looping debug script. Here is an example of what the output might look like. Note the frame number is shown at the top, this is just supposed to be an indicator of whether the script is currently running. Also, it's worth noting that the probability is shown in the clutter
class, but in reality it's defined in the probability[]
in CfgSurfaceCharacters
.
If you see something like the above image then you're probably on the right track. It's worth comparing this output to what you have defined in your original config, to confirm that everything is loaded into game as you intended.
If instead of an image like the above, you saw something like the image shown below, then you're in luck, you've narrowed down your clutter issues drastically, but more on that below.
This script uses the surfaceType command to get the surface that is currently under your player. When this command returns Default
, that means that the game wasn't able to find a matching files
entry for something defined in CfgSurfaces
. The only three ways that I've seen people make mistakes in this area is capitalization, having what I'm going to describe as a facepalm moment, or forgetting to create a CfgSurfaces
entry for the surface. The first step in solving this issue is to identify what texture you've used for the surface.
For the following examples, we're going to assume the textures for this particular surface are yourTag_greenGrass_nopx.paa
and yourTag_greenGrass_co.paa
.
The files
entry in the CfgSurfaces
for your surface should be ALL lowercase, even if the names of the texture themselves aren't lowercase.
class CfgSurfaces
{
class Default;
class yourtag_greenGrass: Default
{
// incorrect
files = "yourTag_greenGrass_*";
// more surface properties here
};
//more surfaces
};
class CfgSurfaces
{
class Default;
class yourtag_greenGrass: Default
{
// correct
files = "yourtag_greengrass_*";
// more surface properties here
};
//more surfaces
};
We've all done it, you swear there is nothing wrong with the config and that you've checked dozens of times. Can you spot the error?
class CfgSurfaces
{
class Default;
class yourtag_greenGrass: Default
{
files = "yourtag_grassgreen_*";
// more surface properties here
};
//more surfaces
};
Hopefully you caught it, files = "yourtag_grassgreen_*"
should be files = "yourtag_greengrass_*"
.
Even if you don't want a surface to have clutter, it still needs an entry in CfgSurfaces
, otherwise it won't receive basic properties defined in CfgSurfaces
. i.e. sound, correct bullet impact effect, dust, etc. If you take a list of the textures you've used on your terrain, then use a text editor to search (CTRL + F) through your config containing CfgSurfaces
, you should find an entry for each texture. This is assuming that you chop the co
or nopx
of the end of the texture for the purpose of searching for matching files
entries.
(Credit to ianbanks for most of the info)
When your terrain is being rendered, the engine attempts to to send an entire land grid cell to a single DirectX call. Each DirectX call can have up to 16 textures sent to the pixel shader. The textures being passed to the DirectX call can be seen in your RVMATs in the various Stages, e.g. Stage0, Stage5, etc.
In Arma 1 terrains you could have 4 surfaces max per tile, with three texture types for each individual surface, and they were mco, nopx/nohq, co. The mask tiles produced by Visitor would only have 4 colors, black: (0, 0, 0), red, green, and blue. The RVMAT generated by Visitor uses the "Terrain#" pixel shader, where # is some number 1-15 based on the number of surfaces on the tile, and they represents a value for a 4 bit number. It might look something like this for Terrain15:
Stage0: satellite tile
Stage1: mask tile
Stage2: surface 1 mco
Stage3: surface 1 nopx/nohq
Stage4: surface 1 co
Stage5: surface 2 mco
Stage6: surface 2 nopx/nohq
Stage7: surface 2 co
Stage8: surface 3 mco
Stage9: surface 3 nopx/nohq
Stage10: surface 3 co
Stage11: surface 4 mco
Stage12: surface 4 nopx/nohq
Stage13: surface 4 co
Putting the total count of textures used for this DirectX call to 14 (because it starts from Stage0, not Stage1).
Arma 2 introduced having up to 6 surfaces per tile using the alpha channel in mask tiles, but at the cost of losing mco textures. So now each surface can only use 2 textures, nopx and co. Once Visitor was updated to the point of being able to use 6 surfaces per tile, your generated mask tiles would use the black, red, green, blue, 128 alpha, and 0 alpha. There are some gimmicks to how it handles the surfaces with the alpha channels, but those are unnecessary details for this. The RVMAT generated by Visitor/TB uses the "TerrainX" pixel shader, and it might look something like this:
Stage0: satellite tile
Stage1: mask tile
Stage2: mco texture used by every surface, for example: #(rgb,1,1,1)color(0.5,0.5,0.5,1,cdt)
Stage3: surface 1 nopx
Stage4: surface 1 co
Stage5: surface 2 nopx
Stage6: surface 2 co
Stage7: surface 3 nopx
Stage8: surface 3 co
Stage9: surface 4 nopx
Stage10: surface 4 co
Stage11: surface 5 nopx
Stage12: surface 5 co
Stage13: surface 6 nopx
Stage14: surface 6 co
Bringing us to a total of 15 textures used for this DirectX call (because it starts from Stage0, not Stage1).
Arma 3 introduced the ability to use a normal map for your terrain, but at the cost of one surface per tile. So now you can only have 5 surfaces per tile, but with the added benefit of a normal map. Each surface still only has 2 textures, nopx and co. When using a normal map, your mask tiles generated by TB would use the black, red, green, blue, and 128 alpha. Again with some gimmicks for the alpha channel. The RVMAT uses the "TerrainSNX" pixel shader, and it might look like this:
Stage0: satellite tile
Stage1: mask tile
Stage2: mco texture used by every surface, for example: #(rgb,1,1,1)color(0.5,0.5,0.5,1,cdt)
Stage3: surface 1 nopx
Stage4: surface 1 co
Stage5: surface 2 nopx
Stage6: surface 2 co
Stage7: surface 3 nopx
Stage8: surface 3 co
Stage9: surface 4 nopx
Stage10: surface 4 co
Stage11: surface 5 nopx
Stage12: surface 5 co
Stage14: normal map tile
So TB skips Stage13 all together, and instead places the normal map for the tile on Stage14. This adds up to a grand total of 14 textures used for this DirectX call.
It would appear that BI decided when moving from A1 to A2 that the benefit from mco textures was outweighed by having the option of 6 surfaces per tile. Then from A2 to A3 they decided it was worth it to only have 5 surfaces per tile, but with the added benefit of having a normal map for each tile.