Models are stored in the .mdl
format in the res\models\model\
folder. The format consists of geometric information for different levels of detail (LODs) and metadata. For each LOD, there is a separate hierarchy of meshes with their animations, events, and materials. In general, .mdl
files contain a data()
-function that returns a struct similar to the one below:
function data() return { boundingInfo = { ... }, -- optional bounding box used e.g. for render borders collider = { ... }, -- optional collider for collision calculation lods = { ... }, -- geometric information for 3D data and textures metadata = { ... }, -- metadata depending on model type version = 1, -- new Transport Fever 2 lod tree format } end
The bounding box is a cuboid that encloses the outermost elements of the model so that all parts of the model lie within the box. It is used, for example, to decide whether an object is within the visible range of the camera or not.
boundingInfo = { bbMax = { <+x>, <+y>, <+z>, }, bbMin = { <-x>, <-y>, <-z>, }, },
The values are the distances from model origin in positive and negative direction on all three axis.
The collider is used whenever the potential collision between models is considered. It's possible to use the mesh data for collision calculation as well as define the collision boundaries by script.
When the mesh data should be used for collision calculation, the type “MESH”
is needed:
collider = { params = { }, transf = { 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, }, type = "MESH", },
When the boundaries should be set by script, the type “BOX”
, “CYLINDER”
or “POINT_CLOUD”
is needed:
collider = { params = { halfExtents = { 1.5, 1.5, 1.5, }, }, transf = { 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, }, type = "BOX", },
The size of the volume is specified by 2 times the halfExtents
properties for the “BOX”
and “CYLINDER”
type. It can be offsetted relative to the model origin with the transf
parameter. The “POINT_CLOUD”
uses a list of points that is provided in the points
parameter. Each point has three values for the position on all three axis relative to the construction origin. The transf
parameter is ignored.
Levels of detail (LOD) are used to use simplified geometries with less tris as the distance between model and camera increases, thus saving computing power during rendering. At least one LOD is needed, but it is recommended to include more if the model has a large number of tris.
lods = { { node = { ... } static = false, visibleFrom = 0, visibleTo = 200, }, ... },
The node element is the top level of the mesh hierarchy in this LOD.
The range of visibility is limited by visibleFrom
and visibleTo
.
The hierarchy is defined by nesting of nodes. The root node is the toplevel parent node.
A node can have the following attributes:
{ children = { ... }, -- optional, contains more nodes transf = { 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, }, -- transformation matrix for size, ... animations = { ... } -- optional, for details see below mesh = "path/to/mesh.msh" -- optional, relative to res/models/mesh/ materials = { ... } -- needed if mesh is used, for details see below name = "mesh_01" -- optional, can be used as reference elsewhere }
The animations block contains a list of animations with eventnames as keys. Each of these animations is either defined by keyframes or file based.
Example code for a keyframe and a file based animation:
animations = { eventname1 = { forward = true, -- or false for inverse keyframe playback params = { keyframes = { { rot = { 0, 0, 0, }, -- rotation around all three axis time = 0, -- milliseconds since begin of animation transl = { 0, 0, 0, }, -- position offset for all three axis }, ... { rot = { 0, 0, 0, }, time = 1200, transl = { 0.75, 0.055, 0, }, }, }, origin = { 0, 0, 0, }, -- offset of animation origin from node origin }, type = "KEYFRAME", }, eventname2 = { params = { id = "path/to/animation.ani", -- relative to res/models/animation/ }, type = "FILE_REF", }, },
Further information on animation files can be found in the animation section.
Nodes can contain a mesh. Meshes consist of two files, meshname.msh
with index information and meshname.msh.blob
with 3D model data (binary). See the documentation of msh files for further information on meshes.
For each submesh provided by the referenced .msh
file, a material has to be referenced.
materials = { "path/to/material.mtl", -- paths relative to res/models/material/ "path/to/secondMaterial.mtl", ... }
Further details about material files and types can be found in the material documentation.
There is large set of metadata keys to describe all the different types of models in the game. Some of the keys can be used for almost every type of model. These are described below. For specific model types, the keys are described in the relevant sections of the wiki:
These metadata keys can be applied to any or the majority of model types. Depending on the resource type, they might be mandatory, but can often be left empty to default to zero.
description
availability
cost
maintenance
particleSystem
cameraConfig
labelList
transportNetworkProvider
The description provides the name and a descriptive text for the model. It is used for vehicle informations in buy menu and status windows. Static models that are not part of a construction use it for the label in the menu. In constructions, these information are not used.
description = { name = _("Name of Model"), description = _("Description displayed for example in the depot menu") },
The availability defines the timespan in which the model is available. yearFrom
and/or yearTo
can be left out or set to zero to define an indefinite start respectively end year.
availability = { yearFrom = 1925, yearTo = 1985 },
To activate automatic price calculation, set the price
to -1. When price
is left out, it defaults to 0. The value of priceScale
can be used to adjust the calculated price. This is a hook up point for balancing mods too.
cost = { price = 10000, priceScale = 1 },
To activate automatic maintenance calculation, set the running cost to -1. When runningCost
or lifespan
is left out, it defaults to 0. The value of priceScale
can be used to adjust the calculated price. This is a hook up point for balancing mods too.
maintenance = { runningCosts = -1, runningCostScale = 1, lifespan = 40 * 730 -- [1 unit at normal game speed corresponds to 12h, a year equals lifespan = 730] },
Particle emitters produce smoke or steam particles.
particleSystem = { emitters = { { child = 1, position = { 4.3632001876831, 0, 3.8589000701904, }, color = { 0.35, 0.35, 0.35, }, frequency = 80, lifeTime = 14, size01 = { 0.80000001192093, 1, }, velocity = { 0, 0, 10, }, initialAlpha = 0.8, velocityDampingFactor = 2.0, }, ... }, },
A model can have zero or more particle emitters. Each of the emitters has its own block in the emitters
list. It has several properties:
child
is the id of the node that should be used as origin for the positioning of the emitter.position
is a vector relative to the origin of the mesh anchor.color
is a color definition for r g and b values. Currently, only greyscale particles are possible!frequency
is the number of particles emitted per second. Keep in mind that larger values reduce performance.lifeTime
is the duration of a particle in seconds. Keep in mind that larger values reduce performance.size01
is the size of the particle in meter at the beginning and end of its lifetime.velocity
is the drifting speed in all three directions relative to the emitter.velocityDampingFactor
is the factor by which the particle velocity, defined by the velocity
parameter, is dampened. Default is 2.5. The velocityDampingFactor
does not affect the wind velocity a particle has.initialAlpha
is the initial opacity of the particle. Default is 1.0. 1.0 means particle is fully visible, 0.0 means particle is fully transparent.Particles are supported for all vehicle types as well as static construction models support the particle emitters.
By default vehicles, animals and people have an onboard camera that is relative to the node with the first crew seat or if no crew member is available, it is relative to the position and rotation of node 0. The cameraConfig
allows for setting multiple custom camera positions, which can be cycled when the onboard camera is active.
cameraConfig = { positions = { { group = 0, transf = transf.rotYCntTransl(math.rad(25), vec3.new(0, 0, 0), vec3.new(-15, 0, 9)), fov = 90 } } }
For every camera position, there is a block in the positions
list with several parameters:
group
is the id of the mesh that should be used as origin for the positioning of the camera.transf
is the transformation matrix that is used for the positioning and rotation relative to the origin.fov
is the field of view. Larger values result in wide angle views, narrow views can be done with smaller fov
values.
In Transport Fever 2, vehicles and constructions may display some dynamic text labels. These are defined in a labelList
block in the model metadata which contains a labels
list:
.. labelList = { labels = { { type = "LINE_NAME", transf = { 0, -1, 0, 0, 0, 0, 1, 0, 1, 0, 0, 0, -5.8043 , 0.1814, 2.808 , 1, }, size = { 0.366, 0.210 }, color = {247 / 255, 147 / 255, 33 /255}, fitting = "CUT", alignment = "CENTER", filter = "NUMBER", renderMode = "EMISSIVE", childId = "RootNode", }, ... }, }, ..
There are many different properties which can be used to define the labels.
To set the position of the label, use the transf
property pointing to the coordinates where the lower left corner of the label should be. The label will be placed aligned to the X and Z axis. The coordinates are relative to the mesh that is referenced in childID
by name.
The size
contains a pair of two values. The first for the size in x direction, the second for the size in y direction, both relative to the transf
. Negative values will result in no text visible. The y direction is used for the fontsize too. To use the label with more than one line of text, set nLines
to a value larger than 1. Values below 1 are ignored.
Coloring the text is possible by using the color
attribute. It receives a vector with three values, one for each color in the range between 0 and 1. The transparency of the text is set by alpha
. Value 0 is invisible, value 1 is opaque, values greater than 1 might lead to artifacts. With the alphaMode
, it is possible to define how the alphablending is done. Possible values are either “CUTOUT”
, “BLEND”
or “NONE”
. In Front of opaque textures, this might be irrelevant, but on transparent faces like glass panes it might be more relevant. To get emissive text (like with LCD destination displays), set the renderMode
to “EMISSIVE”
, otherwise set it to “STD”
.
The horizontal alignment of the text can be set in alignment
with the values “LEFT”
, “CENTER”
and “RIGHT”
. For the vertical alignment, set verticalAlignment
either to “BOTTOM”
, “CENTER”
or “TOP”
.
To adjust the behavior of text that is longer than the label, set fitting
either to:
“NONE”
to let it overflow.“CUT”
to cut excessive text.“SCALE”
to scale down until it fits in the horizontal size.
The type
property contains one of the following keys:
“NONE”
is applicable to all models and shows nothing.“LINE_NAME”
is applicable to vehicles and shows the name of the line that the vehicle is currently on.“NEXT_STOP”
is applicable to vehicles and shows the name of the next stop of the vehicle.“NAME”
is applicable to any model and shows the name of the entity (vehicle, construction, …).“COMPANY_NAME”
is applicable to any model and shows the name of the company.“STATION_NAME”
is applicable to any station model and shows the station name.“CUSTOM”
shows some custom content based on a labelText
property in the construction.
The filter
property is used to filter the input from the type. It can either be set to NONE
to not filter, NUMBER
to filter anything that is no number or CUSTOM
to set advanced filters in the params
block. It contains advanced parameters to influence the text that should be displayed:
expr
is a regular expression that can be used to filter the input string, e.g. to only show text or some letters. If the regular expression does not match, no text is displayed. To test some regular expressions, you may use online tools like regex101.com. replace
can contain a gap text that has placeholders which are replaced by the contents or parts of the string matching the regular expression above. \\0
is replaced by the part of the string that matches the complete regular expression, \\<number n>
is replaced by the part of the string that matches the content of the nth ( … ) bracket pair in the regular expression.offset
can be used together with type = “NEXT_STOP”
to display a stop further down the line.relative = false
is used together with type = “NEXT_STOP”
to start from the first station of the list (+offset
). With relative = true
the vehicles next stop is considered instead of the first stop.
The font
parameter currently only supports the vanilla fonts Lato and Noto. Other fonts than the standard ones lead to crashes.
It is recommended to test the labels with the model editor.
Models (except vehicles) can contain lanes and terminals for vehicles, cargo and people. These are configured in the transportNetworkProvider
metadata block of the model.
The transportNetworkProvider
contains three different properties:
laneLists
for vehicle and passenger lane definitionsrunways
for feeding in and out ships and airplanes into the fixed lanes.terminals
for passenger and cargo terminals along the vehicle lanes. transportNetworkProvider = { laneLists = { { linkable = false, nodes = { { { 0, -20, -2.1, }, { 0, 20, 0, }, 30, }, { { 0, 0, -2.1, }, { 0, 20, 0, }, 30, }, { { 0, 0, -2.1, }, { 0, 20, 0, }, 30, }, { { 0, 20, -2.1, }, { 0, 20, 0, }, 30, }, }, speedLimit = 20, transportModes = { "SHIP", "SMALL_SHIP" }, }, ... }, runways = { { edges = { 0, }, node = 0, type = "LANDING", }, { edges = { 1, }, node = 3, type = "TAKEOFF", }, }, terminals = { { order = 0, personEdges = { 2, 3, }, personNodes = { 5, 8, }, vehicleNode = 2, }, }, },
The laneLists
is a list of blocks which have several properties each:
linkable
is a boolean value that decides if the lane can be targeted by the small automatically generated footpath links.nodes
is a list of nodes which are used to define the edges. Every two nodes form one edge. Each node has three properties:speedLimit
is the maximum speed on all edges defined by the nodes above.transportModes
is a list of allowed modes on the edges defined by the nodes above. Possible values are “PERSON”
, “CARGO”
, “CAR”
, “BUS”
, “TRUCK”
, “TRAM”
, “ELECTRIC_TRAM”
, “TRAIN”
, “ELECTRIC_TRAIN”
, “AIRCRAFT”
, “SHIP”
, “SMALL_AIRCRAFT”
and “SMALL_SHIP”
.The nodes can be referenced with their index over all lists. The edge 0 is defined by node 0 and 1, edge 1 is defined by node 2 and 3, edge n is defined by node 2×n and 2×n+1.
The runways
block contains 0 or more runway blocks which have several properties related to the entry and exit points of the freely moving planes and ships:
edges
is a list of edges from the laneLists
that should count to the runway.node
is the point where the vehicle is slowed down after landing or starting speedup for takeoff. It must not lay in the middle of the edges
list.type
is either “LANDING”
or “TAKEOFF”
depending on the type of runway.
An aircraft tries to land and slow down in front of the landing node. After passing it, it will taxi to the terminal. On departure, it will speed up and take off after passing the takeoff node. The landing/takeoff direction is given by the tangent of the first/last edge in the edges
list. If the runway is not longSame holds for ships.
The terminals
block contains a list of terminal definition. At a terminal, cargo items and passengers can enter and leave vehicles. Each terminal block contains the following parameters:
order
is used for the enumeration of terminals in the user interface. The first terminal (order = 0
) in the first model of the station with terminals is terminal number 1, …personEdges
are edges which are used as waiting zones for passengers and cargo items. There the passengers or cargo items will wait in idle mode until the vehicle arrives. The lenght and width of these lanes is used for the capacity calculation. These edges need to support the right mode (either “PERSON”
or “CARGO”
).personNodes
are the nodes where passengers that alight of vehicles spawn in the station. From there they walk to the station exit or to a personEdge for the next vehicle to take. The invisible cargo items do the same. These nodes need to be on “PERSON”
or “CARGO”
edges.vehicleNode
is the node where the vehicle stops. It depends on the vehicle type whether the front or middle of the vehicle exactly stops there. This node needs to be on an edge that support vehicles.