© Christopher Bazley, 2018
Version 0.02 (17 Nov 2018)
Download ChocToObj (74 KB Zip archive, includes source code).
Download the game's object meshes in .obj and .mtl format (160 KB Zip archive).
Chocks Away is a flight simulator for Acorn Archimedes computers, written by Andrew Hutchings. It was published in 1990 by The Fourth Dimension (and followed by Extra Missions in 1991). It is fondly remembered by many because of its smoothly animated 3D graphics, simple controls and inclusion of a split-screen dogfight mode. The game world is drawn using a combination of flat-shaded polygon meshes, lines and points. Its 3D models of aircraft, buildings, bridges and other structures are primitive but I think they have a certain charm.
This command-line program can be used to convert object models belonging to 'Chocks Away' from their original (optionally compressed) format into the simple object geometry format developed by Wavefront for their Advanced Visualizer software. Wavefront OBJ format is a de-facto standard for 3D computer graphics and it has the advantage of being human-readable.
The colour and other visual properties of objects are defined separately from the object geometry (OBJ) file in a companion file known as a material library (MTL) file. The supplied MTL file defines all of the colours in the default RISC OS 256-colour palette as named materials, assuming a constant colour illumination model (no texture map, ambient or specular reflectance).
The following screeenshots show the object mesh for a tank imported into the Open 3D Model Viewer.
Here are the converted object geometry and material library files: tank.obj and sf3k.mtl
The supplied executable files will only work on RISC OS machines. They have not been tested on any version of RISC OS earlier than 4, although they should also work on earlier versions provided that a suitable version of the 'SharedCLibrary' module is active. They should be compatible with 32 bit versions of RISC OS.
You will obviously require a copy of the game 'Chocks Away' or 'Chocks Away: Extra Missions' from which to rip the graphics.
Index and model data files for 'Chocks Away' can be found inside the !Chocks application. The actual models are stored in a file named !Chocks.Maps.Land. This data is indexed by an array of 200 addresses stored in a second file named !Chocks.Maps.Obj3D.
Index and model data files for 'Extra Missions' can be found inside the !Maps_2 application. This game requires additional models for some maps: instead of a single Obj3D file, alternative index files numbered from !Maps_2.Things.Obj3D0 to Obj3DF store between 109 and 159 model addresses. Model data in common between all maps is still stored in a single file named !Maps_2.Land and any extra model data is stored in files numbered LandEx0 to LandExF (many of which have length zero).
Ensure that the !Chocks application has been 'seen' by the Filer, so that the system variable Chocks$Dir has been set.
Hold down the Shift key and double-click on the !Chocks application to open it.
Set the current selected directory to that containing the 'ChocToObj' program. On RISC OS 6, that can be done by opening the relevant directory display and choosing 'Set work directory' from the menu (or giving the directory display the input focus and then pressing F11).
Press Ctrl-F12 to open a task window and then invoke the conversion program using the following command:
*ChocToObj -list <Chocks$Dir>.Maps.Land <Chocks$Dir>.Maps.Obj3D
This should list all of the objects addressed by the compressed index file 'Obj3D'. One of those should be named 'tiger', which is a 3D model of the Tiger Moth biplane flown by the player.
To convert the Tiger Moth model into a Wavefront OBJ file named 'tiger/obj', use the following command:
*ChocToObj -name tiger -human <Chocks$Dir>.Maps.Land <Chocks$Dir>.Maps.Obj3D tiger/obj
The -name argument selects which object is converted and the -human argument enables generation of human-readable material names such as 'maroon_0' in the output file.
To convert the same model from 'Chocks Away: Extra Missions', ensure that the !Maps_2 application has been 'seen' by the Filer, then use the following command:
*ChocToObj -extra -raw -name tiger -human <ExtraMaps$Dir>.Land <ExtraMaps$Dir>.Things.Obj3D0 tiger/obj
The -raw argument disables decompression of the model data and index files whilst the -extra argument (strictly redundant in this case) allows naming of models only named in Extra Missions.
The basic syntax of the program is summarised below. All switches are optional. Switch names may be abbreviated.
ChocToObj [switches] <model-file> [<index-file> [<output-file>]]
When invoking ChocToObj, you must always specify the name of a model data file. Without this, it would only be possible to enumerate the number of models and their addresses in memory (from the index).
The other input to ChocToObj is an array of four-byte little-endian model data addresses (the index). Consequently, it's possible for several object numbers to alias the same model data.
Strictly, one would need to know the address at which the model data file will be loaded in order to correctly interpret the index; in practice, the lowest address in the index usually coincides with the start of the model data.
An output file name can be specified after the input file name or, by means of the -outfile parameter, before the model data file name.
If no index file is specified then addresses are read from 'stdin' (the standard input stream; keyboard unless redirected). If no output file is specified then OBJ-format output is written to 'stdout' (the standard output stream; screen unless redirected).
All of the following examples read the index from a file named 'obj3d' and write output to a file named 'chocks/obj':
*ChocToObj land obj3d chocks/obj
*ChocToObj -outfile chocks/obj land obj3d
*ChocToObj -outfile chocks/obj land <obj3d
*ChocToObj land obj3d >chocks/obj
Under UNIX-like operating systems, output can be piped directly into another program.
Search for a named object in a compressed index file named 'obj3d':
ChocToObj land obj3d | grep -i 'gotha'
By default, all input is assumed to be compressed. The switch -raw allows uncompressed input, which is useful for converting object models belonging to 'Chocks Away: Extra Missions'.
It isn't possible to mix compressed and uncompressed input, for example by using a compressed index with an uncompressed model data file.
Not all of the models for 'Chocks Away: Extra Missions' are stored in a single file; models in common between all maps are stored in a primary file whilst extra models required for specific maps are stored in a secondary file (which is appended to the first upon loading). The index can reference models in either file.
This presents a problem because ChocToObj can only read from one model file at at time. A solution is provided in the form of the -last N and -offset N parameters.
The -last N parameter is used to prevent ChocToObj reading beyond the end of the primary model data file. The object number specified should be one less than that reported in the error message that results from omitting this parameter (e.g. "Failed to seek object 109 at offset ...").
The -offset N parameter is used to specify that the secondary model data file is not loaded at the lowest address in the index. The offset specified should be the (decompressed) size of the primary model data file.
The following example illustrates how these concepts can be combined to list all of the models for map 4 of 'Chocks Away: Extra Missions', albeit in piecemeal fashion:
*FileInfo <ExtraMaps$Dir>.Land Land WR/WR Data 21:50:19.02 14-Apr-2018 0000C478 *ChocToObj -list -extra -raw -last 108 <ExtraMaps$Dir>.Land <ExtraMaps$Dir>.Things.Obj3D4 *ChocToObj -list -extra -raw -offset 0xC478 <ExtraMaps$Dir>.LandEx4 <ExtraMaps$Dir>.Things.Obj3D4
The model data index can be filtered using the -index or -first and -last parameters to select a single object or range of objects to be processed. Using the -index parameter is equivalent to setting the first and last numbers to the same value. The lowest object number is 0 and the length of the index dictates the valid range of object numbers.
The index can also be filtered using the -name parameter to select a single object to be processed. The -extra switch enables additional object names for things that are only named in 'Chocks Away: Extra Missions'. Any filter specified applies when listing object definitions as well as when converting them.
If no range of object numbers and no name is specified then all entries in the index are used.
Convert the first object in file 'Obj3D', outputting to the screen:
*ChocToObj -index 0 <Chocks$Dir>.Maps.Land <Chocks$Dir>.Maps.Obj3D
If an object name is specified then the single object of the given name is processed (provided that it falls within the specified range of object numbers, if any).
Convert the Gotha G IV bomber object in file 'Obj3D' (see below for object naming):
*ChocToObj -name gotha <Chocks$Dir>.Maps.Land <Chocks$Dir>.Maps.Obj3D
|0||gun||Ground gun base|
|3||headquarters||Headquarters ('Head quarters' in the game)|
|19||twin||Fokker V7 twin|
|22||gotha||Gotha G IV bomber|
|23||s_tiger||Shadow of tiger moth|
|24||s_twin||Shadow of Fokker V7 twin|
|25||s_gotha||Shadow of Gotha G IV bomber|
|26||s_eindecker||Shadow of Fokker Eindecker IV|
|27||s_scout||Shadow of Albatros DIII scout|
|28||s_triplane||Shadow of Fokker VIII triplane|
|29||eindecker||Fokker Eindecker IV|
|30||triplane||Fokker VIII triplane|
|31||scout||Albatros DIII scout|
|81||gunboat||Gunboat ('Gun boat' in the game)|
|77||biplane||Fokker DE5 biplane|
|75||triengine||Fokker V3 triengine|
|102||s_biplane||Shadow of Fokker DE5 biplane|
|103||s_triengine||Shadow of Fokker V3 triengine|
|104||s_cargo||Shadow of cargo aircraft|
A complete list of objects in 'Extra Missions' can be found elsewhere.
If the switch -list is used then ChocToObj lists object definitions instead of converting them to Wavefront OBJ format. Only object definitions matching any filter specified using the -index, -first, -last, and -name parameters are listed.
No output file can be specified because no OBJ-format output is generated and the object list or summary is always sent to the standard output stream.
List all object definitions indexed by file 'Obj3D':
*ChocToObj -list <Chocks$Dir>.Maps.Land <Chocks$Dir>.Maps.Obj3D
Output is a table with the following format:
Index Name Verts Prims SimpV SimpP Offset Size 0 gun 8 5 6 1 0 208
If the switch -summary is used then ChocToObj summarizes object definitions instead of converting them to Wavefront OBJ format. This switch can be used in conjunction with -list, in which case the summary is displayed after the list.
Summarize all object definitions indexed by file 'Obj3D':
*ChocToObj -summary <Chocks$Dir>.Maps.Land <Chocks$Dir>.Maps.Obj3D
Output is a total in the following format:
Found 200 objects in the input
By default, ChocToObj emits
usemtl commands that
refer to colours in the standard RISC OS 256-colour palette
by derived material names such as 'riscos_31'. (This naming
convention is for compatibility with a similar program,
Convert the player's aircraft, outputting colour numbers to the screen:
*ChocToObj -index 18 <Chocks$Dir>.Maps.Land <Chocks$Dir>.Maps.Obj3D
# 2 primitives g tiger tiger_0 usemtl riscos_19 f 27 26 25 24 usemtl riscos_24 f 1 2 4 3 ...
If the switch -human is used then ChocToObj instead generates human-readable material names such as 'black_0'.
Convert the player's aircraft, outputting readable material names to the screen:
*ChocToObj -index 18 -human <Chocks$Dir>.Maps.Land <Chocks$Dir>.Maps.Obj3D
# 2 primitives g tiger tiger_0 usemtl maroon_3 f 27 26 25 24 usemtl tyrianpurple_0 f 1 2 4 3 ...
By default, ChocToObj emits a
sf3k.mtl as the material library file to be used when
drawing objects; this is the same as the name of the supplied
An alternative material library file can be specified using the switch -mtllib. The named file is not created, read or written by ChocToObj.
False colours can be assigned to help visualise boundaries between polygons, especially between coplanar polygons of the same colour. This mode, which is mainly useful for debugging, is enabled by specifying the switch -false.
When false colours are enabled, the physical colours in the input file are ignored.
Some objects are liable to suffer from a phenomenon known as "Z-fighting" if they are part of a scene rendered using a depth (Z) buffer. It is caused by overlapping faces in the same geometric plane and typically manifests as a shimmering pattern in a rendered image. Essentially two or more polygons occupy the same points in 3D space.
The game uses painter's algorithm to ensure that overlapping objects are drawn correctly (drawing more distant objects before nearer ones), instead of using a depth buffer. It also draws the polygons of each object in a predictable order, which allows overlapping polygons to be used as decals (e.g. as doors and windows on buildings).
If the switch -clip is used then the rearmost of two overlapping polygons is split by dividing it along the line obtained by extending one edge of the front polygon to infinity in both directions. This process is repeated until no two edges intersect. Any polygons that are fully hidden (typically behind decals) are deleted.
The following diagrams illustrate how one polygon (B: 1 2 3 4) overlapped by another (A: 5 6 7 8) is split into five polygons (B..F) during the clipping process. The last polygon (F) is then deleted because it duplicates the overlapping polygon (A).
Some of the game's models include lines which are coplanar with (and completely contained by) a polygon that is drawn first. Examples include the middle stripe on a road. This causes "Z-fighting" between the line and the polygon.
If the parameter -thick N is used then such lines will be replaced with rectangular polygons with the same length and centreline as the original line but a width specified by the user. This serves two purposes:
The order in which vertices are specified in a primitive definition determines the direction of a polygon's normal vector and therefore whether or not it is facing the camera in any given scene.
The game draws primitives in the order they are defined without using a depth buffer. Some models are sufficiently complex that back-face culling is required to prevent near polygons being overdrawn by far polygons; others require polygons to be visible from both sides.
Back-face culling is enabled per object instance in the game's map data, therefore it isn't possible to predict whether the order of vertices in a polygon definition is significant without knowing the model's intended usage. One aerodrome has two runways and one taxiway defined anti-clockwise (facing the sky) but two other taxiways defined clockwise (facing the earth).
ChocToObj can infer that polygons should not be facing the earth if they belong to a model in which all primitives are coplanar with the xy plane at z=0. The switch -flip reverses the order of vertices for such polygons to make them face the sky instead.
The game supports two levels of detail for each model, selected according to its distance from the camera (e.g. a distant house might be drawn without its windows). By default, ChocToObj outputs primitives that belong to both high-and low-detail versions of a model in group 0 and any remaining primitives (required only for the high-detail model) in group 1.
If the switch -simple is used then ChocToObj reads only vertices and primitives belonging to the simplified version of each model. The effect is not quite as straightforward as only outputting primitives in group 0: if a polygon is always simplified at the same distance as (or closer than) the distance at which the whole model would be simplified then that polygon must be replaced with a line. In many cases this is necessary to avoid references to vertices that are not included in a simplified model's vertex count.
The Wavefront OBJ format specification does not restrict the maximum number of vertices in a face element. Nevertheless, some programs cannot correctly display faces with more than three vertices. Two switches, -fans and -strips, are provided to split complex polygons into triangles.
|Original||Triangle fans||Triangle strips|
f 1 2 3 4 5 6
f 1 2 3 f 1 3 4 f 1 4 5 f 1 5 6
f 1 2 3 f 6 1 3 f 6 3 4 f 5 6 4
Vertices in a face element are normally indexed by their position in the output file, counting upwards from 1. If the output comprises more than one object definition then it can be more useful to count backwards from the most recent vertex definition, which is assigned index -1. The -negative switch enables this output mode, which allows object models to be separated, extracted or rearranged later.
Convert the Fokker VIII triplane with positive vertex indices:
*ChocToObj -index 30 <Chocks$Dir>.Maps.Land <Chocks$Dir>.Maps.Obj3D
# 1 primitives g triplane triplane_0 usemtl riscos_67 f 27 26 25 24 # 22 primitives g triplane triplane_1 usemtl riscos_73 f 34 35 37 36 f 1 2 4 3 ...
Convert the same object with negative vertex indices:
*ChocToObj -index 30 -neg <Chocks$Dir>.Maps.Land <Chocks$Dir>.Maps.Obj3D
# 1 primitives g triplane triplane_0 usemtl riscos_67 f -11 -12 -13 -14 # 22 primitives g triplane triplane_1 usemtl riscos_73 f -4 -3 -1 -2 f -37 -36 -34 -35 ...
It's common for model data to include vertex definitions that are not referenced by any primitive definition. Such vertices are not included in the output unless the -unused switch is used.
Some object definitions also include two or more vertices with the same coordinates, where both vertices are referenced by primitives. An example is vertices 6 and 7 of the aircraft carrier model. Such pairs of vertices are automatically merged unless the -duplicate switch is specified.
If either of the switches -verbose and -debug is used then the program emits information about its internal operation on the standard output stream. However, this makes it slower and prevents output being piped to another program.
If the switch -time is used then the total time for each file processed (to centisecond precision) is printed. This can be used independently of -verbose and -debug.
When debugging output or the timer is enabled, you must specify an output file name. This is to prevent OBJ-format output being sent to the standard output stream and becoming mixed up with the diagnostic information.
The colour names were taken from a variety of online sources including Wikipedia articles, W3C standards, the X Window System, and the results of the web comic XKCD's colour naming survey.
|0||#000000||black *||Identical to HTML 'black'|
|4||#440000||darkmaroon||By analogy with 'darkred'|
|8||#000044||darknavy||By analogy with 'darkblue'|
|12||#440044||darkpurple||By analogy with 'darkmagenta'|
|16||#880000||maroon *||Like HTML colour #800000|
|20||#cc0000||mediumred||By analogy with 'mediumblue'|
|24||#880044||tyrianpurple||Like Wikipedia colour #66023c|
|28||#cc0044||crimson *||Like X11 colour #dc143c|
|32||#004400||darkgreen +||Like X11 colour #006400|
|36||#444400||darkolive||Like XKCD colour #373e02|
|40||#004444||darkteal||Like XKCD colour #014d4e|
|44||#444444||darkgrey +||Darker than X11 'dimgray'|
|48||#884400||brown *||Like Wikipedia colour #964b00|
|52||#cc4400||mahogany||Like Wikipedia colour #c04000|
|56||#884444||cordovan||Like Wikipedia colour #893f45|
|60||#cc4444||brickred||Like Wikipedia colour #cb4154|
|64||#008800||green *||Like HTML colour #008000|
|68||#448800||avocado||Like Wikipedia colour #568203|
|72||#008844||pigmentgreen||Like Wikipedia colour #00a550|
|76||#448844||ferngreen||Like Wikipedia colour #4f7942|
|80||#888800||olive *||Like HTML colour #808000|
|84||#cc8800||harvestgold||Like Wikipedia colour #da9100|
|88||#888844||darktan||Like Wikipedia colour #918151|
|92||#cc8844||peru *||Like X11 colour #cd853f|
|96||#00cc00||mediumgreen||By analogy with 'mediumblue'|
|100||#44cc00||napiergreen||Like Wikipedia colour #2a8000|
|104||#00cc44||darkpastelgreen||Like Wikipedia colour #03c03c|
|108||#44cc44||limegreen *||Like X11 colour #32cd32|
|112||#88cc00||applegreen||Like Wikipedia colour #8db600|
|116||#cccc00||peridot||Like Wikipedia colour #e6e200|
|120||#88cc44||yellowgreen *||Like X11 colour #9acd32|
|124||#cccc44||oldgold||Like Wikipedia colour #cfb53b|
|128||#000088||navy *||Like HTML colour #000080|
|132||#440088||indigo *||Like X11 colour #4b0082|
|136||#0000cc||mediumblue *||Like X11 colour #0000cd|
|140||#4400cc||violetblue||Like XKCD colour #510ac9|
|144||#880088||purple *||Like HTML colour #800080|
|148||#cc0088||mediumvioletred *||Like X11 colour #c71585|
|152||#8800cc||darkviolet *||Like X11 colour #9400d3|
|156||#cc00cc||deepmagenta||Like Wikipedia colour #cc00cc|
|160||#004488||mediumelectricblue||Like Wikipedia colour #035096|
|164||#444488||darkslateblue *||Like X11 colour #483d8b|
|168||#0044cc||royalazure||Like Wikipedia colour #0038a8|
|172||#4444cc||pigmentblue||Like Wikipedia colour #333399|
|176||#884488||plum +||Like Wikipedia colour #8e4585|
|180||#cc4488||mulberry||Like Wikipedia colour #c54b8c|
|184||#8844cc||lavenderindigo||Like Wikipedia colour #9457eb|
|188||#cc44cc||deepfuchsia||Like Wikipedia colour #c154c1|
|192||#008888||teal *||Like HTML colour #008080|
|196||#448888||dustyteal||Like XKCD colour #4c9085|
|200||#0088cc||honolulublue||Like Wikipedia colour #007fbf|
|204||#4488cc||celestialblue||Like Wikipedia colour #4997d0|
|208||#888888||grey *||Like HTML colour #808080|
|212||#cc8888||oldrose||Like Wikipedia colour #c08081|
|216||#8888cc||ube||Like Wikipedia colour #8878c3|
|220||#cc88cc||pastelviolet||Like Wikipedia colour #cb99c9|
|224||#00cc88||caribbeangreen||Like Wikipedia colour #00cc99|
|228||#44cc88||mint||Like Wikipedia colour #3eb479|
|232||#00cccc||darkturquoise *||Like X11 colour #00ced1|
|236||#44cccc||mediumturquoise *||Like X11 colour #48d1cc|
|240||#88cc88||darkseagreen *||Like X11 colour #8fbc8f|
|244||#cccc88||lightbeige||Like Ford colour #d2d08e|
|248||#88cccc||pearlaqua||Like Wikipedia colour #88d8c0|
|252||#cccccc||lightgrey *||Like X11 colour #d3d3d3|
* means that a colour has the same name as one of the standard X11 colours and closely resembles it.
+ means that a colour has the same name as one of the standard X11 colours but is significantly darker.
Source code is only supplied for the command-line program. To compile and link the program you will also require an ISO 9899:1999 standard 'C' library and four of my own libraries: 3dObjLib, CBUtilLib, StreamLib and GKeyLib.
Two make files are supplied:
The APCS variant specified for the Norcroft compiler is 32 bit for compatibility with ARMv5 and fpe2 for compatibility with older versions of the floating point emulator. Generation of unaligned data loads/stores is disabled for compatibility with ARMv6. When building the code for release, it is linked with RISCOS Ltd's generic C library stubs ('StubsG').
Before compiling the program for other platforms, rename the
C source and header files with .c and .h file extensions then
move them out of their respective subdirectories. The only
platform-specific code is the
macro definition in misc.h. This must be defined according to
the file path convention on the the target platform (e.g. '\'
for DOS or Windows).
This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public Licence as published by the Free Software Foundation; either version 2 of the Licence, or (at your option) any later version.
This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public Licence for more details.
You should have received a copy of the GNU General Public Licence along with these programs; if not, write to the Free Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
ChocToObj was designed and programmed by Christopher Bazley.
My information on the Wavefront Object File and Material Library File formats came from Paul Bourke's copies of the file format specifications for the Advanced Visualizer software (© 1995 Alias|Wavefront, Inc.)
Some colour names were taken from a survey run by the web comic XKCD. The data is in the public domain, available under a Creative Commons licence. Thanks to Keith McKillop for suggesting this source.
The game Chocks Away is © 1990, The Fourth Dimension.