HowTo/Build a cab interior
Trainz uses an interior object to represent a locomotive cab, or view from another vehicle, e.g. a passenger coach, brake van or caboose. Interiors can also be attached to some non-train assets. For this howto, I am going to assume that we are creating a traincar interior, and focus primarily on a cab interior for a diesel or electric locomotive.
Contents |
Basic structure
The basic structure of a cab is quite similar to a number of different assets. You will have a main mesh with a number of attached sub-meshes, which are arranged using a mesh-table.
The main cab mesh typically consists of the interior walls, roof and floor of the cab, the control desk or stand, all furniture or other static objects visible inside the cab, and also all parts of the locomotive exterior that are visible from inside the cab - e.g. the top of any projecting nose below the cab windows. Remember to consider the sightlines out of rear windows too for visible bodywork panels - the user can move around the cab and rotate the camera to any angle. Users are not restricted to looking forward out of the front windows only.
Artwork considerations
There are some differences in style between the construction of an interior object and a typical trainz object.
Except in very specific circumstances, there will not be more than one interior object in existence at any time, and that interior is always viewed from very close range - the user is inside it. Because of this, interiors are typically modelled with a high polygon count and with high resolution textures, to give an impressive visual feel. LOD is not used, as the user never gets far enough away from the interior for LOD techniques to have worthwhile benefit. However, LOD techniques may be ideal in a case of a passenger wagon with high polygon chair models.
Trainz lights interior objects differently to map objects like buildings and other scenery - the main illumination is ambient, not directional. (A steam loco does have a directional light coming from the firebox, but a diesel or electric cab typically has no directional light inside, and relies entirely on ambient illumination).
With ambient illumination, effects such as normal and specular mapping do not have much (if any) effect. Because of this, more effort needs to be put into the diffuse texture for a cab interior than would normally be applied to Trainz objects. Drawing or baking shadows and other effects into the diffuse textures is required to produce a nice looking cab interior.
An example cab would be the File:PB interior.rar
A basic config file
The minimal required config for an interior to show in-game is:
kind interior kuid <kuid2:YOUR:KUID:HERE> trainz-build 2.9 username "Your interior name here" category-class ZI mesh-table { default { mesh "your_interior_mesh_name.im" auto-create 1 } }
Note: There are also other base tags that should be added that are common to all asset types - e.g. the download station will require a valid thumbnails container before the asset can be uploaded. See the documentation for KIND TrainzBaseSpec for details of tags which apply to all trainz assets. Depending on which Trainz version you have and which trainz-build number you have specified, some of these may be required - content manager will tell you what is required, listing missing tags as errors.
camera positions
If we load a cab up with that config file, we will note that the view is not particularly appealing. Chances are we're right down near the floor. To fix this, we need to define some default positions for camera views. This is done with the cameralist container and the cameradefault tag. Add the following to your config.txt file:
cameralist { camera0 0,0,2,0,0 }
cameradefault 0
The cameralist container can contain any number of cameras, but they must be numbered sequentially and start at zero.
The five numbers are the X, Y and Z coordinates of the camera in metres relative to the 0,0,0 point of the interior mesh, and the Yaw and Pitch in radians. Luckily you don't have to work these values out from scratch - add the line "-freeintcam" to your trainzoptions.txt file, and you can move the camera about in the cab with the mouse and keyboard, and the precise position will be displayed in the bottom right corner.
When you have found a position you like, write the numbers down. When you've got a good selection of positions recorded, add them to the cameralist container.
You may wish to return to this and tweak the values later once all the controls are in place - but it's handy to have a good starting position for the camera while testing.
Cab controls
A locomotive cab interior needs working cab controls. What fun would sitting in the drivers seat be without being able to pull on the levers, change the switches, and watch the gauges and dials move?
The moving part of each lever, switch, gauge or dial is created as a separate mesh, which is then attached to the cab with an attachment point. (The static part, e.g. guide rails for levers, should be part of the cab mesh itself).
Lever
The lever is the basic input device in a cab. It is based on rotational movement, and any object that requires rotational movement (e.g. a wheel or ring) can be implemented as a lever.
If you actually want linear movement, you can still use a lever - just move the attachment point a long way away - e.g. 10 or 20 metres away from the control. The resulting small arc will not be noticable over the length of movement. As an example, sliding windows in Trainz cabs have been implemented as levers "attached" something like 10 or 20 metres out to the side of the vehicle for a long time. (The exception to this is a horn pull rope - trainz provides a control specifically for this purpose).
You will need to create the lever in your modelling program aligned correctly - the rotation needs to be around the Z axis, and main part of the object will point along the Y axis.
When adding the attachment point to the main mesh, align it so the Z axis is the axis of rotation. It is common to point the Y axis at the default position - e.g. levers in the 'off' or 'neutral' position, and needles pointing to zero. This is not strictly required, as the range of movement can be adjusted at both ends, but it provides a handy reference when creating the config file.
It is worth noting that you can reuse a mesh for multiple attachments. This is particularly useful for switches, as identical appearance switches are often used repeatedly in a cab for different purposes.
Throttle Lever
A good example of a standard lever in use is a locomotive throttle control. A typical US diesel throttle has 9 notched positions (from 0 to 8 inclusive).
As noted above, the lever is created in the modelling package so it points along the Y axis and the axis of rotation is along the Z axis. The attachment point is added to the cab interior mesh at the correct position, and rotated so the Z axis is aligned with the axis of rotation, and the Y axis points to the default position.
Now we need to add an entry to the config file to add our throttle lever.
Into the mesh table, add an entry like this:
throttle_lever { kind "lever" mesh "throttle_lever.im" att "a.throttle_lever" limits 0,8 angles 0,1.57 notches 0,0.125,0.25,0.375,0.5,0.625,0.75,0.875,1 notchheight 1,2,2,2,2,2,2,2,1 radius 0.35 auto-create 1 att-parent "default" }
... remember to set your attachment point and mesh name correctly.
The 'angles' line defines how far the control rotates. It is in radians - there are 2 x Pi (6.28) radians in a complete circle. My default of 1.57 will give you about 90 degrees of rotation, in the positive direction. Adjust this number to get the control to rotate the correct amount. You should be able to work out the number you need in your modelling program by using the rotate tool on the attachment point, and recording the amount of rotation required to move to the intended maximum location. It should not require significant trial and error.
The 'radius' line defines the position of the notch position overlay and the text label. It is in metres. If the overlay is too close in, increase this number. If it is too far out, decrease it.
If the lever is the wrong way round (notch 8 is at the wrong end), swap the numbers in the 'angles' tag over, so the higher one is first.
If the lever moves the wrong way when you drag it with the mouse (i.e. it goes left in response to a right drag), add the line:
mousespeed -1
This will invert the mouse movement.
The fact we called this lever "throttle_lever" means it will be used to control the throttle by the default cab script.
So what happens if we wanted a different number of notches? Below is an example of a throttle which has 22 notches. The second value in the 'limits' tag has been edited to return up to notch 22, and more entries have been added to the 'notches' and 'notchheight' tags:
throttle_lever { kind "lever" mesh "throttle_lever.im" att "a.throttle_lever" limits 0,22 angles 0,0.94 notches 0,0.045,0.091,0.136,0.182,0.227,0.273,0.318,0.364,0.409,0.455,0.5,0.545,0.591,0.636,0.682,0.727,0.773,0.818,0.864,0.909,0.955,1 notchheight 1,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,1 radius 0.35 auto-create 1 att-parent "default" }
Reverser Lever
The reverser lever is created in a similar manner to throttle lever. The mesh table entry for this will look like:
reverser_lever { kind "lever" mesh "reverser_lever.im" att "a.reverser_lever" limits 0,2 angles -0.79,0.79 notches 0,0.5,1 notchheight 1,1,1 radius 0.25 auto-create 1 att-parent "default" }
In this case, because the 'angles' entry has a range both positive and negative, the lever can move in both directions relative to the attachment point. The game will set the default position to neutral (1), so if you've lined up your attachment point at one end, don't worry - you can use angles which range from zero if you need to.
Train Air Brake Lever
There are two types of train air brake lever. A given locomotive only has one type - the brakes are either require manual lapping or are self-lapping. Use the appropriate name and configuration for the loco you are modelling.
Manual Lapped Brakes
The manual train brake lever has five positions numbered from zero to four, each one representing a different function. Those functions are Release, Lap, Application, Emergency and Handle Off respectively.
A lever for a manual lap train air brake is defined as follows:
trainbrakelap_lever { kind "lever" mesh "train_brake_lap_lever.im" att "a.train_brake_lap_lever" limits 0,4 angles 0,0.94 notches 0,0.25,0.5,0.75,1 notchheight 1,1,1,1,1 radius 0.25 att-parent "default" auto-create 1 }
In this case, the attachment for the lever has been created in the 'release' position. If it had been created in the 'handle off' position, the angle tag would have been more like:
angles -0.94,0
Self Lapping Brakes
A self lapping trainbrake lever has a number of primary notches similar to the manual lap train brake lever. However, instead of a 'lap' and 'apply' position, it has a grade of application between the 'initial' and 'full application' positions. An example of a self-lapping brake handle is this one:
trainbrake_lever { kind "lever" mesh "train_brake_lever.im" att "a.train_brake_lever" limits 0,4 angles 0,0.94 notches 0,0.25,0.27,0.29,0.31,0.33,0.35,0.37,0.39,0.41,0.43,0.45,0.47,0.49,0.5,0.75,1 notchheight 1,1,2,2,2,2,2,2,2,2,2,2,2,2,1,1,1 radius 0.25 att-parent "default" auto-create 1 }
In this case, there are 14 possible levels of brake application - initial at 0.25, 12 intermediate levels, and full application at 0.5. There is no restriction on the number of intermediate levels - if you are modelling a prototype brake handle with a specific number, you may create precisely that number of notches and arrange them between 0.25 and 0.5 as required.
Combined throttle brake lever
There is also another control option - a combined throttle brake lever. This type of control is often found on multiple unit trains. This control is split into two halves. The first half of the movement is an inverted throttle - i.e. max power at zero, no power at 0.5. The second half is an auto lapped train brake application, with 0.5 as release, and 1.0 as emergency. An example of how to configure a combined throttle and brake lever is below:
throttle_brake_lever { kind "lever" mesh "throttle_brake_handle.im" auto-create 1 att "a.throttle_brake_handle" limits 0,16 angles 0.94,-0.94 notches 0,0.0625,0.125,0.1875,0.25,0.3125,0.375,0.4375,0.5,0.5625,0.625,0.6875,0.8125,0.75,0.875,0.9375,1 notchheight 1,2,2,2,2,2,2,2,1,2,2,2,2,2,2,2,1 att-parent "default" radius 0.12 invert 1 }
Unfortunately, trains that use this type of control generally use blended and/or EP brakes, and this control uses the standard Trainz air brake, which operates in a distinctly different manner, and as a result, can feel very wrong for this control type. (See the section on scripting controls for how a better combined brake and throttle lever may be implemented).
Pull lever
This is a sprung lever. You pull on this lever to activate it, and when you let it go, it returns to the original position. This kind of action is commonly associated with a locomotive horn, and this is what I am using as an example:
horn { kind "pulllever" mesh "horn.im" att "a.horn" auto-create 1 angles 0,-0.55 limits 0,1 mousespeed -1 radius -0.02 notches 0,1 notchheight 0,0 }
Switch
A switch is a lever that does not require the user to 'drag' it with the mouse - it operates on a click only. This is used for smaller controls that are 'on' or 'off', and do not have intermediate positions. Parameters are much like they are for a 'lever', only a switch should only have two notches - on and off.
Unfortunately, some things you'd normally associate with being a 'switch' (e.g. the headlight switch control) require mulitple positions, and will need to remain as a 'lever' in order to allow the player to set the correct position for the control.
An example of a switch would be:
switch1 { kind "switch" mesh "switch.im" att "a.switch1" limits 0,1 angles 1.4,0 notches 0,1 notchheight 0,0 }
As 'switch' was introduced in TS2009 SP2, interiors that use kind 'switch' controls should be marked trainz-build 3.1 or later in the config.txt.
button
A button is a momentary contact press-and-hold button. This is a brand new control added for TS12 patch 2, released end of june 2011. Content using this feature should have trainz-build 3.6 or later in the config.txt.
Finally this brings Trainz the "click to press", "hold to hold pressed", "let go to release" style momentary pushbutton.
button1 { kind "button" mesh "switch.im" att "a.switch1" limits 0,1 angles 0,0.1 notches 0,1 notchheight 0,0 }
Pull rope
This is a linear pull rope control. Unlike the rotating controls discussed previously, this control type moves the mesh linearly along the Z axis of the attachment. It behaves as you would expect from a suspended rope - click and drag to pull, let go to release. It will spring back to it's initial position in the same manner as kind 'pulllever'.
horn { kind "pullrope" mesh "horn_rope.im" att "a.horn" limits 0,1 angles 0.1,0 notches 0,1 notchheight 0,0 mousespeed -1 }
needle
A needle is the standard Trainz output device. Like the lever, it is based on rotational movement, and any gauge, dial, or other output device based on rotational movement can be implemented. Any static part of the gauge should be modelled as part of the cab, and only the moving part as part of the needle. Like the lever, the rotation will be around the Z axis of the needle, and it is common practice to point the point of the needle along the Y axis. Cabs often have a number of similar gauges, and re-using the same needle repeatedly in each gauge is a lot easier if this setup is done consistently.
As an example of a needle, here is a basic speedometer:
speedo_needle { kind "needle" auto-create 1 mesh "speedo_needle.im" att "a.speedo_needle" limits 0,67.056 angles 0,4.712 }
The default cab script knows to move the "speedo_needle" object as the train speed changes, so simply by naming this control "speedo_needle", it has become a speedometer.
For a speedo, the 'limits' values will be the minimum and maximum reportable speed in m/s.
Creating other rotational gauges is similar - refer to the mesh-table (interior version) documentation to see all the different values that Trainz can show by default.
light
Another common type of control is a light. This is created simply as a textured plane, which is either hidden or shown to represent light off or light on. It is attached as any other standard mesh attachment would be - there is no rotation to worry about here.
wheelslip_light { kind "light" mesh "wheelslip_light.im" att "a.wheelslip_light" }
This light will be used to show wheelslip in a default cab.
animated-lever
If you want more complex movement than simple rotation, you have the option of animating the lever and using kind 'animated-lever'. This will use an animation you create in your modelling program to move the control about. An example of this is visible in the MN ACMU cab. (I've reduced the number of notches here for readability - the real one has 32, not 16).
throttle_lever { mesh "throttlehandle.im" anim "throttlehandle.kin" auto-create 1 att "a.throttlelever" limits 0,16 notches 0,0.0625,0.125,0.1875,0.25,0.3125,0.375,0.4375,0.5,0.5625,0.625,0.6875,0.8125,0.75,0.875,0.9375,1 notchheight 1,2,2,2,2,2,2,2,1,2,2,2,2,2,2,2,1 att-parent "default" kind "animated-lever" test-collisions 0 mousespeed 1 }
Where the standard lever is rotated between it's angles to move from notch to notch, the animated-lever has it's animation played to the corresponding frame.
collision-proxy
Animated levers can be hard to grab hold of compared to normal levers. To get around this, an invisible mesh is used to show the game the range of movement the control has, and to allow the user to grab the lever more easily. This example again comes from the MN ACMU cab:
throttle-collision-box { mesh "throttlehandleselection.im" att-parent "throttle_lever" att "a.selection_box" auto-create 1 kind "collision-proxy" opacity 0 collision-parent "throttle_lever" }
animated-dial
If you've been paying attention to levers, needles and animated levers, I think you can guess where we are going with this one. This is an animated needle. Instead of rotating between the angles specified, it plays it's animation to the relevant frame to show the value. Use this when what you want to achieve can't be done with simple rotational movement.
(Example usage for this control to follow).
digital-dial
Some locomotives use a digital readout rather than analogue dials.
Trainz has two control kinds to support digital displays - one for speed units, and one for pressure units. Instead of angles of movement, these both have some font specific options to configure how the digits appear. In both cases, the Y axis of the attachment point should be pointing upward to signify the up direction of the font.
"font" defines the typeface. For a list of valid typefaces, see the mesh-table (interior version) documentation. Generally this follows closely with a number of default windows font names.
"fontsize" defines the size of the font. The default value for this is 0.02, and this is a pretty respectable size display.
"fontcolor" defines the RGB color of the text. Each value is a floating point number between 0.0 and 1.0.
"mesh" normally would display the mesh used for the needle. In the case of digital dials, they are not used, but the tag is still required. Any mesh can be used here; using an existing needle mesh would be fine.
digital-dial-spd
For a speedometer (or any other gauge that shows units in speed, e.g. a speed limit display), use kind 'digital-dial-spd'.
An example speedometer using this kind would be declared as:
speedo_needle2
{
kind "digital-dial-spd"
mesh "brake_needle.im"
att "a.digispeedo"
limits 0,55
font "arial"
fontsize 0.01
fontcolor 196,196,246
auto-create 1
}
digital-dial-prs
For a display using pressure units, use digital-dial-prs. This is a digital brake pipe pressure gauge:
bptrainbrakepipe_needle2
{
kind "digital-dial-prs"
mesh "brake_needle.im"
att "a.trainpipe_pressure"
limits 0,55
font "arial"
fontsize 0.01
fontcolor 0.0,1.0,0.0
auto-create 1
}
firebox
The firebox control is a special kind to implement the fire of a steam locomotive.
(Further information about this specific control to follow).
Note: Some steam cab interior models use the firebox, fireglow and burning coal effects found in the Auran PB15 cab interior. This can cause quite severe flickering effects in some models The intensity of flickering varies on the textures used and can be attenuated by use of darker colours. The flickering can be eliminated by not using the firebox effect. The fireglow and burning coal (coal) effects are OK.
Note: This feature does not work currently in T:ANE and TRS19.
You can, of course, always make your own.
Scripting additional behaviour in a cab
A lot can be achieved in Trainz purely by naming the controls correctly and relying on Trainz default cab scripts to make them work. A quite reasonable cab can be made for many locomotives without having to do any scripting whatsoever.
However, a lot more realism can be achieved by scripting the cab. Additional features can be added beyond what Trainz supports, including new types of controls, or extended behaviour on existing controls.
The first thing to consider when looking at scripting a cab, is "is the cab the best place for this feature?". A cab is not a persistent asset, which means it can go away at any point. When it's gone, it isn't running it's script. Consequently, it is a bad place for any persistent or time based logic - it should only be used to script the immediate behaviour of the controls. Anything beyond this should be implemented the vehicle itself or possibly a script library, both of which are more persistent objects.
If all you want to achieve is to add some simple extra features (e.g. a display of signal aspects within the cab), you can derive from the trainz standard classes, and inherit the default behaviour when adding your new behaviour. This means you can concentrate on just your extra feature, and don't have to worry about making existing functionality work.
If you want to do something a little more advanced (e.g. change the behaviour of the throttle and/or brake controllers), then you are probably best deriving your cab script from the 'cabin' class, ignoring the default cab scripts, and implementing your required behaviour from scratch. This gives you a lot more control, but you do need to do more yourself.
For some examples of the first route, have a look at the MN cabs - the M7 or the ACMU are good examples. Both have a cab signal repeater implemented in the cab interior script, and use the base functionality for almost everything else.
-~= To Be Continued... =~-