Skip to content

Instantly share code, notes, and snippets.

@pennyworth12345
Last active July 10, 2018 03:17
Show Gist options
  • Save pennyworth12345/27786cf99652e0018b9b2f0becaa0638 to your computer and use it in GitHub Desktop.
Save pennyworth12345/27786cf99652e0018b9b2f0becaa0638 to your computer and use it in GitHub Desktop.

Preface

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 */

Overview from mask image to clutter in-game

How mask images are used by Terrain Builder (TB)

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.

Class CfgSurfaces

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.

  1. The files corresponds to the name of the textures used in your surface's RVMAT (yourtag_somegrasssurface.rvmat), in this case yourtag_somegrasssurface_co.paa, or yourtag_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.
  2. The character corresponds to an entry in your CfgSurfaceCharacters. In this example, the character is defined as yourtag_somegrasssurface_character. If you'd like a surface to have no clutter then you can use character = "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.

Class CfgSurfaceCharacters

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.

Class 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
			};
		};
	};
};

Troubleshooting common issues

Clutter isn't showing up on my terrain

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.

In case you're unfamiliar with how to run a script in the editor, here is a quick guide.

  1. Launch Arma 3 with your terrain.
  2. Load into Eden editor on your terrain.
  3. Place a single unit down in an area you want to troubleshoot your clutter.
  4. Press the "Play scenario" button in the bottom right.
  5. Tab out of Arma and open the script I linked above in your browser.
  6. Select all of it (CTRL + A) and copy it to your clipboard (CTRL + C).
  7. Open Arma 3 back up, hit escape, and paste the script (CTRL + V) into the debug console.
  8. With the script now in the debug console, hit the "local exec" button.

How to use the script

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:

  1. 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.
  2. 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.

alt text

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.

alt text

Getting to the bottom of it

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.

Capitalization

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
};
Facepalm

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_*".

Missing CfgSurfaces entry for the surface

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.

What do the different material per cell options do?

(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.

4 Materials Per Cell

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).

6 Materials Per Cell

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).

5 Materials Per Cell

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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment