Around you is a desert. The high sun burns through your skin, the low moon chills you to the bone. Also around you are sparse shrubs, dry, cracked rock, and a railway line. Upon the rails rests a train manifest, consisting of a bulky diesel locomotive and some freight cars.

Desert Train is the experience of driving a freight train through a vast, barren desert.


  • an approximation of railway dynamics that simulates driving & braking physics
  • a pneumatic simulation to approximate automatic air brake systems
  • user-configurable wagon setups, including pneumatics, control handles and behaviors,
  • a deterministic heightmap & rail section generator,
  • a minimal-overhead terrain loading scheme with level of detail,
  • arithmetical evaluation of perlin noise octaves from a prefix expression,
  • origin-shifting using a layered unit coordinate scheme to alleviate spatial jitter,
  • a deferred execution scheme to keep heavy computations and frame-times under control.

Install instructions

The currently required minimum version of OpenGL is 3.3 (core profile).

Debug versions of the game are also provided, these can be used if one wants to see more information about the game’s state on the command line. These versions run approximately two times slower than the optimized, release versions, but they can be useful to diagnose issues with pneumatics setups, for example.


Hosted on

Note: the builds for x86_64 are targeting 64-bit machines, and the ones for i686 are for 32-bit machines. Use the release version by default, debug builds are meant for diagnostic purposes.

Development logs

2023-01-30 - Geometry, camera controls, windowing

After letting the engine rest for a while, it seemed like it would be a good time to return to give it another push.

As an improvement to quality of life, you can now toggle between mouse grab/release states, window resizability, and borderless fullscreen. Together with a camera that can be switched between first and third person, with variable zoom distance and field of view, it makes things a lot more visually interesting in terms of scaling, proportions and camera angles.

More importantly, models for the wagons, bogies, wheels, couplers and pins of the EMD SD40-2 and a SNIM gondola are now properly positioned in the world. Specifically, pin lifters and pins are now positioned such that they kind of reflect the state of a coupler. When the bar appears to be pulling a pin upwards, it’s in a “released” state, meaning that it won’t let the pin fall and secure a coupling. When a pin still sits higher, but the bar no longer seems to be pulling it upwards, it’s in a “ready” state, so it will couple if it collides with another coupler that is either ready or locked. The “locked” state is indicated by a pin that no longer sticks upwards, so you’ll be able to see them drop when a coupling is made.

Models are rendered using instanced draw calls for each mesh, and train models are rendered at multiple detail levels based on their distance to the camera’s origin.

Also, train wagons/bodies now use triangle mesh colliders loaded from a .gltf file, just like models. One fun thing is how player physics is set up for the moment allows ladders to mostly work as you’d expect, at least on the locomotive. Just try walking into them, you’ll see what I mean.

Rail splines can also be rendered at lower-than-simulated detail levels via a configuration option, and you can also set a maximum render distance from the center of a spline, beyond which it will be culled.

I was surprised to find how much of a difference having the geometry in place has made, even if the lack of textures means we’ll be drowning in the pastels of the surface normals for the time being.

2023-05-11 - Diffuse textures

The next step forwards deals solely with graphics. The terrain mesh and railway track are now rendered with procedurally generated textures, and most models with hand-painted textures. At the moment, all their shaders just output a diffuse texture sample as the fragment color.

Aside from unwrapping and painting models by hand, I had to spend a fair bit of time automating model exporting from Blender. The procedure involves unwrapping all objects onto a single UV map, then setting up new materials for each with a shader that takes one source texture for each object, and bakes all of them to a single texture atlas based on the collective UV map.