Terrain Fundamentals
If you’ve done any MSTS route-building or read forums in depth, you know that a route’s “world” is divided into tiles 2048 meters square. Further, you’ll recall that a tile is further sub-divided into a 16x16 grid of patches, each 128 meters square.
MSTS further divides each patch into a 16x16 mesh of vertices. Each set of four adjacent vertices defines a “cell” with two triangles. Hence, each patch has 2x15x15 = 450 triangles. Another name for a set of adjacent triangles is a mesh. Such a mesh is considered to be a graphics primitive.
In Open Rails, such a patch is represented by a class named TerrainPatch (derived from a more general (base) class, RenderPrimitive). When a terrain patch object is created (an instance of TerrainPatch), the class constructor, called at creation time, gets patch information from the respective TFile (created from a .t file) and the y-coordinate (elevation) of each vertex from the respective YFile (created from a _y.raw file). The TFile information is associated with the patch; the YFile information (elevations) is individually associated with the vertices. Hence, the YFile information displaces the vertices vertically, forming a shaped terrain patch. MSTS fixes the x- and z- coordinates in place and does not move them in those directions. This is not generally the case with patches and need not be the case with Open Rails.
During the very first execution of the TerrainPatch constructor, it calculates an index buffer, which is shared by all subsequently created TerrainPatch objects. This is possible because all TerrainPatch objects use the same size grid.
Associated with each patch vertex are three vectors: a position vector (x, y, z), a normal vector (Nx, Ny, Nz) approximating a direction perpendicular to the terrain surface at the vertex, and a texture coordinate vector (u, v), which is used to determine exactly how a terrain texture will be wrapped over the patch surface. The three vectors (position, normal, texture coordinate) are represented by an XNA VertexPositionNormalTexture structure. These three vectors have a total of 8 components (3 + 3+ 2), each component requiring a four-byte floating point number (float). Hence, each patch requires a minimum of 16x16x8x4 = 8192 bytes.
The terrain texture will come from MSTS folder TERRTEX. Terrain textures will be image files (.ace for MSTS) which may be of varying size, but typically 512x512.
With that groundwork established, let’s proceed to examine how tiles are handled.
Tile Handling
Shortly after the simulator starts, Simulator.Start (a method) creates a Viewer3D object called Viewer (a class). Viewer’s constructor does a bunch of things, but only one thing is related to terrain: It creates a class called Tiles (note the plural). The Tiles class is a simple one; it includes only:
- The constructor Tiles.
- A two-dimensional [tileX, tileZ] buffer, each element of which contains a reference to a tile. (Elements don’t contain the tile’s data; they contain a reference to the data.) The buffer, private to the class, is called TileBuffer. The size of the buffer is 8x8.
- A method, GetTile, which returns a reference to a requested tile. GetTile maintains the buffer. If the tile requested by [tileX, tileZ] is not in the buffer, GetTile creates a new tile object (an instance of class Tile), reading the data comprising the tile from a file.
- A method, GetElevation, which returns a terrain elevation from [x, z] within the tile referenced by [tileX, tileZ].
Each tile read and added to is represented as an instance of a Tile (singular) class. Each Tile class instance created includes a TFile class (by reading a .t file) and a YFile class (by reading a _y.raw file). The YFile constructor reads a 256x256 array of Uint16s (_y.raw file) representing a scaled elevation value between 0 and 65,535. (The RAW file is a scan of elevations from the NW corner to the SE corner in row-major order.)
The Tile class is pretty simple. It includes:
- Tile, the constructor
- A TFile class.
- A YFile class.
- A, a 256x256 matrix containing the elevation values.
- IsEmpty, a Boolean flagging whether the tile has been read.
- A method, GetElevation that fetches an elevation value given interior coordinates [x,z].
| Previous section: Three Threads | Up to: Design Overview | Next section: TDBTraveller |