### True 3D models and view rotation

The 3D model used so far is a collection of orthogonally arranged rectangular blocks and cylinders, declared in an order that reflects isometric view shadowing. For example an engine frame block is defined before the boiler cylinder, to appear underneath it. From this model suitable SVG components can be generated to simulate a 3D view when subjected to a uniform isometric transformation. But to support a non-orthogonal rotation of such a model about the z-axis, to overcome the on the ceiling effect, the situation becomes somewhat more complex. There are three points to consider:

• What is a suitable graphic for a block or cylinder when rotated by θ degrees about the z-axis? A key requirement is that the faces model of additional styling and content must still be supported.

• As a group of 3D parts is rotated, their obscuration relationships alter and any views must accommodate this. How should a set of component parts be depth-ordered in the direction of the isometric view, when the ensemble is rotated significantly?

• How is the appropriate rotated view displayed as a locomotive turns?

Constructing the isometric-prepared components of a rotated block is a little tricky. The top surface is always visible and can just be rotated as required. Ignoring any visibility of the base, only two of the four vertical sides will be visible dependent upon rotation change ranges of 45° and 135°. Each visible face is subjected to additional scaling and skew dependent on the rotation angle, so that it is correctly sized, positioned and any additional content stays in place. The situation for horizontally aligned cylinders is very much more complex, and at the time of writing is work in progress.

To view-order an ensemble of rotated components it would be helpful if a (possibly multiple) value can be computed that can be used as sort keys to arrange the parts into appropriate order using `xsl:perform-sort/xsl:sort+`. This can be so for some very simple cases, but in general parts must be pairwise-compared, which requires some sorting function that uses a compare function, rather than a key-generator. Sadly, XPath sort functions all use a key model, so a generic XSLT higher-order pairwise sorting function may have to be constructed.

Calculating the rotation views on the fly would be catastrophically expensive, so the solution chosen is to generate a series of groups, each corresponding to a defined angle of rotation and labelled suitably (e.g. `class="rotate-45"` for a view rotated by -45°). It would also be possible to generate the set of views offline and include in the runtime. However they can be sizeable — an interval of 5°, which certainly doesn't appear smooth would require 72 separate versions.

Assuming there is such a series of views of an engine, we need to arrange for the display property of the (approximately) correct rotation view to be switched from none to inline. But we do know for a given locomotive which section it is in and can map from the proportion of the animation completed to the tangential orientation at that point. (As we use only straights and circular arcs, the tangent angle is a piecewise linear function of the section proportion, running from 0 to 1. This profile is added to the map entry for the section.) Given that the speed of the engine is known, we can thus predict how long it will be until the current rotation view should be superseded by the next one. This is enabled through a template `rotateTrain` which both makes visible the suitable view and schedules a further `rotateTrain` call after a suitable wait.