Chameleon
by ElDavo

Script support for random and selectable object textures

Warning: This documentation is incomplete and is a work in progress!


What is Chameleon?

Chameleon is a Trainz GameScript library that provides a simple and convenient way for Trainz model makers to randomly, programatically or, selectively through a user interface, set the textures of a locomotive, piece of rollingstock, building, or any other Trainz game object from a range of configured textures. To use Chamaeleon you do not need to become a scripting expert. The scripting system for TRS2004 is a powerful environment but to those not familiar with programming can seem somewhat daunting. Chameleon makes it relatively easy, in fact all the scripting you require can be done by copying and pasting from examples!

Chameleon encapsulates the selection and application of textures in a script library that is shared by all the models that use it. As it is shared there is only one copy so your vehicle's script can be smaller and overall less computer memory will be consumed by copies of scripts which essentially do the same thing.

The basics of how Chameleon works

Chameleon uses the basic capability within Trainz GameScript that allows a texture from a TextureGroup asset to be applied to a mesh that is part of or attached to an object such as a locomotive, rollingstock item, building, or other game object. Models that use Chameleon have some additional entries in the extensions container in their config.txt file to define what textures can be selected. In addition entries in the kuid-table section define the TextureGroup asset that contains the textures. With these things in place a small script is used to call the Chameleon library at appropriate times to actually perform the texture selection and switching. An example script that can be simply used through cut and pasted is available.

Chameleon can be used to provide very simple texture switching of a single texture or quite complex switching involving multiple main model textures and the textures of attached items such as couplers and hoses. Examples of both are shown below.

Chameleon can also be used to provide some unexpected capabilities. If you wish to reskin an existing locomotive that has no running number (alphanumber) support in the main mesh Chameleon can be configured to select a texture based on the running number allowing you to have a skin for each loco with the running number in the main texture.

Chameleon has been used to switch the textures of loco nameplates and shed plates based on the running number of the locomotive.

Chameleon has been used with another script library, Weatherman, to make buildings in Trainz adjust their textures to match the weather conditions. In this way buildings can have wet roofs for when it is raining and snow on the roof after heavy snowfall.

Using Chameleon with your model

The model

Initially let's look at a simple example that has a single main model texture being switched by Chameleon. The snippets of code and config info are from ElDavo's MOA wagon available from the Trainz download station (<kuid2:75134:150038:9>) which has an unencrypted script file that you can look at to see how Chameleon is combined with the NumberIt and CoupleStar libraries and can be used as the basis for your models script.

The config.txt file

First up you will need to declare that the model requires TRS2004 service pack 4 at least. This is accomplished by having a trainz-build tag entry specifying 2.4 which equates to a build version of 2365 (2370 UK). Chameleon will not work with earlier build levels than 2365/2370!

trainz-build				2.4

Your model config.txt will need to declare the Chameleon library as an asset that it uses. If you don't your vehicles script will not be able to find it and furthermore the download helper won't ensure that the users of your vehicle download it! This is achieved with the Chameleon_library entry. Optionally you may wish to display the Chameleon icon with your asset in which case you should list it as a dependency.

You must have an entry defining the TextureGroup asset you will use to hold the texture options. This is the body-textures entry.

kuid-table {
	.
	.
	Chameleon_library		<kuid2:75134:99001:11>
	Chameleon_icon			<kuid2:75134:99000:3>
	.
	body-textures			<kuid2:75134:15039:3>
	.
}

To display the Chameleon icon against your model you should reference it with an icon entry. The icon0 entry is used here to display the Chameleon icon as the leftmost one but you can use the icon0, icon1, icon2, or icon3 entry depending on what other icons you wish to display and in which order.

icon0					<kuid2:75134:99000:1>
Now define the script class for your model. The name of the class is entirely of your choosing but must match up with the filename of the file in which the source code is saved. If you already have scripting on your vehicle these entries will exist in which case omit this step. In the example here the script has been named MOA and is stored in a file named MOA.gs in the same directory as the config.txt for the model.

class					"MOA"
script					"MOA"
Now we get into some more meaningful stuff. In the extensions section of the config.txt we define the textures/liveries that are selectable for the model. This is accomplished using the chameleon_default_liveries entry which contains a list of livery names seperated by hash/pound sign characters (#). Associated with each livery name is a selection weighting value which is seperated from the name by a dollar ($) character.

extensions {
	chameleon-75134 {
		chameleon_default_liveries	"EWS_ex-works$1#EWS_weathered$4#EWS_dirty$3"
	}
}

For the example we have 3 selectable liveries: EWS_ex-works, EWS_weathered, and EWS_dirty. They have different selection weightings though these could all be the same if required. The weighting value is a number from 0 to 9. A selection weighting of 0 means the livery will never be randomly selected though it can still be selected from the GUI (explained later) and from 1 to 9 mean the livery is increasingly more likely to be randomly selected.

The selection weighting algorithm is a little quirky and probably makes no sense on first reading! You calculate the likelihood of a livery being selected by adding together all the weighting factors and dividing by the particular weighting factor. In the example above the total of the weighting factors is 8 and thus there is a 1 in 8 chance of the EWS_ex-works livery being randomly selected but a 4 in 8 (1 in 2) chance of EWS_weathered being selected. So you can see in this case it is pretty unlikely you will see a brand spanking new ex-works MOA on your layout but highly likely (in fact nearly every other one) will be in a weathered state and there will be quite a lot (3 in 8) that are really dirty. :-)

Having defined the liveries/textures we now need to define the parts of the model that are affected by them. This is done by using effects defined in the mesh-table. In the case of the MOA there is just a single texture being switched so we see a single effect of kind texture-replacement

mesh-table {
	default {
		mesh					"MOA_body/MOA_body.lm"
		auto-create				1
		effects {
			body-texture {
				kind		texture-replacement
				texture	"MOA_main.texture"
			}
		}
	}
}

The effect references the material/texture that is being made switchable with the texture entry and in this case it is the main texture for the model which is called "MOA_main_texture". This name can be found by looking in the model directory that holds the exported meshes (in this case MOA_body) and looking for the files named xxx.texture.txt. There are frequently several of thes but you may only wish to switch one of them as is the case here. Here is what the MOAs body directory contains, you will see the MOA_Main.texture.txt file near the bottom of the list.

 Directory of C:\Program Files\Auran\TRS2004\World\Custom\trains\MOA\MOA_body

28/04/2005  21:24              .
28/04/2005  21:24              ..
23/03/2005  13:56                49 digit_1-digit_1.texture.txt
28/04/2005  21:23                30 digit_1.texture.txt
22/03/2005  13:56             1,307 digit_1.tga
23/03/2005  13:56                49 digit_2-digit_2.texture.txt
28/04/2005  21:23                30 digit_2.texture.txt
22/03/2005  13:55             1,307 digit_2.tga
23/03/2005  13:56                49 digit_3-digit_3.texture.txt
28/04/2005  21:23                30 digit_3.texture.txt
22/03/2005  13:56             1,307 digit_3.tga
23/03/2005  13:56                49 digit_4-digit_4.texture.txt
28/04/2005  21:23                30 digit_4.texture.txt
22/03/2005  13:56             1,307 digit_4.tga
23/03/2005  13:56                49 digit_5-digit_5.texture.txt
28/04/2005  21:23                30 digit_5.texture.txt
22/03/2005  13:56             1,307 digit_5.tga
23/03/2005  13:56                49 digit_6-digit_6.texture.txt
28/04/2005  21:23                30 digit_6.texture.txt
22/03/2005  13:56             1,307 digit_6.tga
07/03/2003  20:46            12,344 env_metal.bmp
28/04/2005  21:23                32 env_metal.texture.txt
28/04/2005  21:23                33 MCA_inside.texture.txt
21/02/2005  14:45            49,691 MCA_inside.tga
28/04/2005  21:18             2,376 MOA lo.gmw
28/04/2005  21:18            25,796 MOA lo.im
28/04/2005  21:06             3,896 MOA med.gmw
28/04/2005  21:06            59,464 MOA med.im
28/04/2005  21:23            10,072 MOA.gmw
28/04/2005  21:23           115,900 MOA.im
28/04/2005  20:09               250 MOA_body.lm.txt
28/04/2005  21:23                31 MOA_main.texture.txt
10/04/2005  17:12           197,147 MOA_main.tga
              31 File(s)        485,348 bytes
               2 Dir(s)   8,539,148,288 bytes free

The script file

The following is an example minimum script file for a model using Chameleon and it is based on the MOA used for the earlier examples. To use this with your model all you need to do is copy this example to the directory containing the config.txt of your model, rename the file to match the name you put in the class entry in your config.txt, and change the name entry in the class declaration to the same name. The example has a class name oc CExample and is saved in a file named CExample.gs.

In the example below we make a call to the Chameleon library function Start from the Init method of the script class to get everything set up. The other methods deal with saving and restoring livery choices in sessions and displaying the GI fro selecting the livery in Surveyor. You should not change these other methods unless you are doing some more sophisticated scripting involving other properties or displaying other data in Surveyor.

include "vehicle.gs"
include "library.gs"

/*
   Chameleon vehicle example script
  
   Author: Dave Renshaw (eldavo)

   Copyright Dave Renshaw 2006. All rights reserved.

 */

class CExample isclass Vehicle
{

    Library chameleon;

    // This is a property that contains the human readable text description of the current livery
    string livery = "";

    void Init(void) {
        inherited();

        //-------------------------------------------
        // The following lines should be included in your Init method to set up Chameleon support
        //
        chameleon = World.GetLibrary(GetAsset().LookupKUIDTable("Chameleon_library"));
        if (chameleon) {
            GSObject[] objectParam = new GSObject[1];
            objectParam[0] = me;
            // Set up the initial livery...
            string[] stringParam = new string[1];
            stringParam[0] = livery;
            chameleon.LibraryCall("Start", stringParam, objectParam);
        }
        // end of Chameleon set up.
        //-------------------------------------------
    }


    //===================================================================================
    //  Chameleon support methods - modify with care!
    //
    public void SetProperties(Soup soup) {
        //-------------------------------------------
        // add your code between these comments if you wish...


        //-------------------------------------------
        //
        inherited(soup);
        //
        //-------------------------------------------
        // add your code between these comments if you wish...


        //-------------------------------------------
        //
        if (chameleon) {
            GSObject[] objectParam = new GSObject[2];
            objectParam[0] = me;
            objectParam[1] = soup;
            string[] stringParam = new string[1];
            stringParam[0] = livery;
            livery = chameleon.LibraryCall("SetProperties", stringParam, objectParam);
        }
    }

    public Soup GetProperties(void) {
        Soup soup = inherited();
        //-------------------------------------------
        // add your code between these comments if you wish...


        //-------------------------------------------
        //
        if (chameleon) {
            GSObject[] objectParam = new GSObject[2];
            objectParam[0] = me;
            objectParam[1] = soup;
            string[] stringParam = new string[1];
            stringParam[0] = livery;
            chameleon.LibraryCall("GetProperties", stringParam, objectParam);
        }
        return soup;
    }

    string GetPropertyType(string propertyID) {
        string s; 
        if (chameleon) {
            string[] stringParam = new string[1];
            stringParam[0] = propertyID;
            s = chameleon.LibraryCall("GetPropertyType", stringParam, null);
        }
        //-------------------------------------------
        // add your code between these comments if you wish...


        //-------------------------------------------
        //
        if (s == "") s = inherited (propertyID);
        return s;
    }

    void LinkPropertyValue(string propertyID) {
        if (chameleon) {
            string s; 
            GSObject[] objectParam = new GSObject[1];
            objectParam[0] = me;
            string[] stringParam = new string[2];
            stringParam[0] = propertyID;
            stringParam[1] = livery;
            s = chameleon.LibraryCall("LinkPropertyValue", stringParam, objectParam);
            if (s == "inherit") inherited(propertyID);
            else if (s != "") livery = s;
        }
        //-------------------------------------------
        // add your code between these comments if you wish...


        //-------------------------------------------
        //
    }

    public string GetDescriptionHTML(void) {
        string s = inherited(); 
        //-------------------------------------------
        // add your code between these comments if you wish...


        //-------------------------------------------
        //
        if (chameleon) {
            GSObject[] objectParam = new GSObject[1];
            objectParam[0] = me;
            string[] stringParam = new string[2];
            stringParam[0] = s;
            stringParam[1] = livery;
            s = chameleon.LibraryCall("GetDescriptionHTML", stringParam, objectParam);
        }
        return s;
    }
};

The texture group

The next significant piece of work requires is to define a texturegroup asset containing the slectable textures. This is a seperate asset from the main model and so can be used by several models if required. It can be created in the ...Auran\TRS2004\World\custom\trains directory or anywhere else convenient. If using TRS2006 then you will have create it in a directory somewhere then import it into CMP using the "import from Path" option.

The texture group comprises a config.txt file, one or more xxx.texture.txt files and a set of matching xxx.tga or xxx.bmp files containing the actual textures. You can use a mixture of .tga and .bmp files if you wish.

The config.txt file

Here is the config.txt for the MOA wagon as an example. You will see it declares that it requires TRS2004 service pack 4 at least.

kuid			<kuid2:75134:15039:3>
kind			"texture-group"
trainz-build		2.4
category-class		"jo"
category-era-0		"2000s"
category-region-0	"GB"
category-region-1	"UK"

extensions {
	chameleon-75134 {
		chameleon_entries_per_livery	1
		chameleon_liveries		"EWS_ex-works#EWS_weathered#EWS_dirty"
	}
}

string-table {
    EWS_ex-works	"EWS (ex-works)"
    EWS_weathered	"EWS (weathered)"
    EWS_dirty		"EWS (dirty)"
}

textures {
	0	EWS ex-works.texture
	1	EWS weathered.texture
	2	EWS dirty.texture
}

username		"MOA bogie open wagon Textures"

author			"Dave Renshaw"
organisation		"Eldavo's Railway Emporium"
contact-email		"sales@eldavos.pipex.co.uk"

There are 3 sections of the config.txt that we are interested in, the rest is just standard texturegroup stuff. The extensionschameleon_entries_per_livery defines how many textures comprise each livery. In this case we have just one but in a more complex model we might have several main model textures and some attached mesh textures we wish to switch so this number would be bigger. Below this we have the list of available livery names which matches the list we had in the main model but does not have the selection weighting factors. You can see that the same texturegroup could be used by multiple models and different selection weighting used by each model, clever huh!

Next up we have a string-table and in here we have an entry for each livery mapping its selection name to a human readable text string which is displayed in the selection GUI. In theory this is translatable for supporting multiple languages but it has never been tested!

Thirdly we have a standard texturegroup array of texture references starting at 0 and going up by one at a time. Each entry in this array maps directly to the name of a file containing a pointer to the texture bitmap. So in this case the ex-works livery textures is defined in the file "EWS ex_works.texture.txt".

The xxx.texture.txt and xxx.tga files

As well as the config.txt for the texturegroup we need a number of texture bitmaps and files that refer to them and allow us to complete the mapping.

If we look at a directory listing of the MOA texture asset we can see all the files contained.

03/02/2006  20:07             1,056 config.txt
12/03/2005  23:40                32 EWS dirty.texture.txt
10/04/2005  19:32           197,147 EWS dirty.tga
12/03/2005  23:40                35 EWS ex-works.texture.txt
10/04/2005  19:26           197,147 EWS ex-works.tga
12/03/2005  23:41                36 EWS weathered.texture.txt
10/04/2005  19:30           197,147 EWS weathered.tga

Those familiar with content creation will recognise the xxx.texture.txt files as being the same as those normally created by the GMax or 3DS exporter. In this case the name need not be related to the texture or bitmap names and can be anything you wish. If we open up the "EWS dirty.texture.txt" file we will see it refers to the "EWS dirty.tga" image file.

Primary=EWS dirty.tga
Tile=st

This is a simple example with only a primary bitmap but you can have textures that are comprised of multiple materials for reflection and bump mapping or contain alpha channels bitmaps just the same as when creating models normally.

Feedback

If you have comments, suggestions, or questions regarding Chameleon please feel free to contact ElDavo via e-mail at sales@eldavos.co.uk
Copyright Dave Renshaw (Eldavo's Railway Emporium) 2006