Script support for random and selectable object textures
Warning: This documentation is incomplete and is a work in progress!
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.
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.
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
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
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 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.
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".
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.