Logo About  Screenshots  Using  Developing  Blog  Forum  Download
August 2009 

Go back to July 2009.

In these blog entries, you will find information on the things that I am currently working on. Whatever you read in recent entries does not necessarily describe things that are available for public download. The sole purpose of this blog is to inform you about the progress on development which will eventually - that is, in the future - result in an actual release of those features. Do not mistake this blog for a changelog.

 2009-08-09 (Sunday) 
I started working on openBVE 2 back in May, but did not come very far because I focused on developing openBVE 1.2 instead. Now it is August, and even though quite some time has passed in which nothing really happened, I am definately back on the road again. Originally, I had come as far as designing an API to load textures, sounds, objects, and parts of the route, however, I have scrapped everything of that and started afresh two weeks or so ago in order to consolidate the improvements that I came up with between May and July.

It is time to talk about some of the rudimentary aspects of the API and some of its applications as part of an effort to not speak in mysteries all the time. Let us make an easy start. openBVE 2, as outlined before in The potential future of openBVE and The new architecture in more detail, will essentially not have any built-in features as far as content-loading and simulation is concerned. It is not capable of loading objects, not capable of loading sounds and not even capable of loading textures. In short, openBVE 2 itself cannot load anything on its own, but relies on plugins in order to do that.

Now it's getting heavier. The API is some sort of a blueprint implemented in an isolated library called OpenBveApi.dll. It provides two interfaces: one that is implemented by a host application (e.g. the openBVE main application), and one that is implemented by plugins (e.g. a B3D/CSV object loading library). In addition to that, OpenBveApi.dll provides a set of structures like vectors, meshes, colors, etc. These elements are what the host and plugin can communicate with. These structures may not be of the form an actual implementation would work with, though. In order to save memory, for example, the host implementation might store an RGB color as a 24-bit integer or even less, while the API exposes them as 96-bit floating-point numbers. Things like these make post-processing a possibility without losing accuracy. Likewise, meshes may be represented by the host application as vertex-vertex, face-vertex, winged-edge or other kinds of models, while the API uses a particular and potentially different model to store data as simple and general as possible for interchange.

The actual interfaces provide methods to perform some actions. On the plugin side, the interface is called IPlugin. Plugins must implement the whole interface, which includes functions to check whether the plugin can load objects (or sounds, textures, objects, etc.), whether it is capable of loading a particular object, and to actually load that object. When openBVE 2 starts loading, it will load all plugins that are stored in its central plugin repository, queries the plugins' capabilities, and then uses them where needed. For example, if openBVE wants to load a texture, it asks all plugins that can load textures in general if they are capable of loading a particular texture. If one reports that it can, it will be used to load that texture. This approach allows extensibility for everything from textures, sounds, objects, routes, cars to train consists.

On the side of the host application, the interface is called IHost. It provides a way for plugins to make callbacks. For example, while openBVE might ask a plugin to load a route, that route might want to load objects in turn. Instead of having the route plugin to implement its own object loader, the plugin can simply make a callback to the host application and query an object. The same process as described above applies then: the host application tries to find a plugin that can load the object and will eventually pass the data to the route plugin that queried it. The IHost interface allows some other things as well, for example for plugins to report problems, to register textures, sounds and objects for later use, to place objects in the route, etc.

Both the plugin and host interfaces support versioning. As .NET interfaces are essentially immutable once they have been defined and are used, it would not be possible to just expand the interface blueprints in OpenBveApi.dll by some new features in the future, as that would render existing applications (both plugins and hosts) that implement these interface incompatible. Instead, OpenBveApi.dll may be expanded in the future by adding complete new versions of the interfaces that can be kept separately and simultaneously, giving the developer of a plugin the ability to choose the desired version, and requiring host applications to implement all versions of the interfaces in order to be compatible with both old and new plugins. Due to this, the API is not going to be updated often, but only in larger chunks that would justify the additional support. This also means that there might be a lot of incompatible changes to the API as long as openBVE 2 is still in development - and that will be for quite a while.

In order to keep the requirement of introducing new versions to expose new features as low as possible, the API will make use of many abstract classes (a .NET paradigm). For example, there is an abstract object class that supports both static and animated objects as implementations thereof. Both of these things, however, may be added new features in the future, for which abstract classes are a perfect solution as they can spawn any number of new implementations without breaking compatibility. A plugin may then just choose the particular implementation it likes.

So, where is the big hype with the API, you might ask. If you don't realize it yourself, it may be pretty hard to explain. Aside from the possibility to load any content from any kind of file format, it gets difficult. A possible scenario that I have developed is the following. Suppose you wanted to introduce support for animated GIFs, or APNG, MNG, etc., in order to very easily create animated objects without a specialized format. One way to achieve this via the API would be to create a plugin that can load objects. The API supports a priority model, meaning that if there is more than one plugin that can load a certain content, the one with the higher priority wins. By writing a plugin that reports to be capable of loading B3D/CSV objects, and giving that plugin a higher priority than the default plugin that ships with openBVE, you can practically override the default plugin. How can this help? Well, recall that plugins can ask the host application via callbacks to do certain things, including loading objects. When doing so, plugins may specify the desired plugin to do the job - in this case the default plugin for B3D/CSV objects that ships with openBVE. This way, your plugin could load the objects, and then modify them to convert them to animated objects wherever an animated GIF is encountered. Thus, whenever a B3D/CSV object is to be loaded, your plugin would be called instead of the default one, however, it queries the default one to do the underlying job of loading the object data, then converts the static object into an animated object where due, and returns the final result - perfectly transparently for use anywhere.

This is just one example. I am sure that similar kinds of tricks could be used for other purposes. Currently, I have not come farther than providing support for textures, sounds and objects in the API, thus the next thing to do is to provide support for routes, e.g. track geometry, placing objects, managing stations, signals, etc.

 2009-08-16 (Sunday) 
There are plenty of small revisions to the API that have been made in the last days. Everything is heading toward more streamlining the API. On the plugin side, the B3D/CSV object loader nears completion even though some of the advanced features such as blend modes and glow are not supported yet. The code of the parser has been simplified by outsourcing common argument count checks, argument type checks and other miscellaneous messages, to helper functions. The main application can now render textured faces and supports rendering a simple object from an external file.

Interestingly, compared to my May 2009 attempt, this is all still a bit less functionality than before, but the design revisions justified starting afresh.

 2009-08-20 (Thursday) 
I have started development on the renderer, or more precisely, on the object management system for static scenery which tells the renderer which objects to render when. In openBVE 1, this object manager is bound to the track geometry in that it associates objects with track positions. This does not allow for a free-roving camera in openBVE 1, and neither for self-crossing track. It is an essential component, however, that openBVE 2 allows a truly three-dimensional visibility approach.

The new object manager works by creating a quadtree structure. Essentially, the whole world is bounded by a square - the root node in the quadtree. This square is then subdivided into four more squares, and, depending on where objects reside, these squares are further subdivided until a specific minimum size is reached to which the objects are attached to - called the leaf nodes.

openBVE 2 builds this quadtree on the fly as soon as it receives objects as illustrated by the following example:

In step (1), the first object is received and the root node is constructed around it. In the following steps (2) and (3), a new object is to be placed, however, it is outside of the bounds of the root node. Therefore, the root node is expanded before it has the correct size to accomodate for the new object. In step (4), the object is to be placed somewhere in the top-right subsquare of the root node, but this is first subdivided further. Finally, in step (5), a leaf node is assigned and the object is placed inside it.

For a large world, the quadtree approach - compared to a simple 2D grid - saves memory because the track and its surrounding scenery cover only a small portion of the root square - mainly that along one or a few curved lines. Click the following illustration for an example of a quadtree applied on an actual route:



Blocks are likely going to be to be 100 meters in size, by the order of magnitude. Eventually, it is easy to determine the block the camera is currently in by just traversing the tree. Then, depending on the user-selected viewing distance, each block stores a list of other blocks that are visible from within this block. This way, once we know in which block the camera is currently in, we also know which blocks we need to render without further involving computations.

More importantly, once a block is to be rendererd, a display list (see previous blog entries) is created and retained as long as the block is visible - and likely a bit beyond that. If a block is far enough away, its display list can be deleted in order to free memory on the graphics card.

However, these lists of visible blocks per block need to be correctly set up. In particular, it can easily happen that an object is associated to a block, but the object as a whole exceeds the boundary of the block - which in turn makes the block earlier visible from certain directions than trivially expected. There is a series of similar approaches I am currently considering, namely involving the calculation of bounding circles or bounding rectangles. Once such a bounding shape is created, comparing each block to each block is nothing more than checking their distances against the user-selected viewing distance.

Approach (1) is fairly easy as it involves computing the distance from the center of the block to each vertex, and remembering the highest distance found. Approach (2) is much more complicated as it involves finding the smallest circumscribing circle (or at least a small one). Approaches (3) and (4) are trivial as well.

Either way, the new visibility system will ensure that:

  no objects suddenly disappear even though they would be in visual range,
  the camera can freely move in the world, not just along one track.

The latter point is essential because in openBVE 2, there can be any amount of rails in the world. Of course, a track-based system employing the well-known "premature object disposal system" will be retained for compatibility reasons, even though I wish I wouldn't have to.

 Links 
Go back to July 2009.