This page documents aspects of the design of the software of The Forest, a simulation of the sport of orienteering.

Further documentation from the user's point of view can be found here: User Guide. It would be useful to have read that explanation and tried the program before reading the current page.

I want to encourage creative programming and I am keen for others to have access to this information so it will not be lost at some future date. If others wish to develop the ideas further or use them in other works, then I approve. A reference back to me would be appreciated, as well as some discussion of your plans.

The program is written entirely in client-side JavaScript downloaded by a single HTML5 page. No data go back to the server and no cookies are used.

It is my contention that this platform, HTML5 + JavaScript, is now suitable for many kinds of video games. It avoids the need for the user to install anything and there is no need to involve an "app" store; a small server-side PHP program could limit access to paying customers. There are some compromises in the graphics but The Forest demonstrates that a huge amount of detail can be shown in real time. True realism is not necessarily the most important factor in a game. Of course none of this is valid if access to device-specific hardware facilities is necessary, such as cameras, accelerometers, etc.

This page is still being written. New versions are uploaded frequently (2018).

Some conventions

I use the usual OOP (Object-Oriented Programming) convention that the names of objects begin with lower case letters but class names (prototypes in JavaScript) begin with capitals.

"Variables" that are really constants and must not change have names that are all upper case.

In HTML I have a habit of closing all elements, even those that can have no content, because I like to be able to process them as XML if the need should arise.

Although in JavaScript it is optional, my statements always end with semicolons to avoid any possible ambiguities at line ends.

I do not use recent additions to the language syntax because they may not work on some devices.

All ground positions use cartesian coordinates in which x runs from west to east and y runs from south to north. 1 unit represents 1 metre on the ground. It is only at the final stage of drawing on the screen that y is inverted (computer displays scan downwards for historical reasons, so y increases from 0 at the top).

Although I aim to keep a tidy structure by means of OOP there are some compromises to avoid having too lengthy multi-level dot names. So, for example, ROLES at the start of observer.js is NOT Observer.prototype.ROLES because that would become too cumbersome. (In the browser it is really window.ROLES of course but we need not state that.)

Apart from the standard 2D graphics API which, as The Forest demonstrates, is very efficient, I do not use any other JavaScript libraries because they would adversely affect both download times and execution speeds. I despair sometimes of web sites that cause me to wait for this library and that even when they do not seem to be doing anything fancy on their home pages.

A note about geometry

Computer graphics inevitably involves some geometry. The diagram on the right covers most of what is needed here. This shows a right-angled triangle with sides x, y and d. An observer at O (not necessarily the origin of the coordinate system) may be looking at a point P distance d away on a bearing of b degrees. If the diagram looks unconventional it is because in map work using a compass we use bearings measured clockwise from due north (the y-axis) whereas in maths the convention is that angles are measured anticlockwise from the x-axis (due east).

In JavaScript the formulae are as follows, where I have included the essential conversions from degrees to radians.

x = d * Math.sin (b * Math.PI / 180);

y = d * Math.cos (b * Math.PI / 180);

d = Math.sqrt (x * x + y * y);

The conversion factor Math.PI / 180 should NOT be calculated every time it is needed but done once beforehand and set to a constant for multiple re-use: this is an important general principle for speed.

You may also need to get b from x and y:

b = Math.atan2 (x, y) * 180 / Math.PI;

Do always use Math.atan2 () instead of Math.atan () because the latter cannot return an unambiguous value in the full 360° range. And for the smart-eyed, yes that is atan2 (x, y) and not the other way round: check the diagram; it is because we are using bearings again.

Repeatable pseudo-random bit patterns

There are trees in The Forest of course. They are loaded into the program as image files (in PNG format to allow transparency around them). To avoid monotony there needed to be several different tree images. Suppose there are four. At each position on the ground one of the four is to be chosen, seemingly at random. At that position, whenever the user looks at it, maybe after moving away and coming back, it must always be the same tree out of the four.

This is achieved by calculating a pair of bits (2 bits allows 4 possible values) from a function of the x and y coordinates of each point. To make the bits appear to be random a pair of bits is taken from a function that includes multiplication by π, mathematical pi which is irrational: it has an unpredictable sequence of digits (or, in base 2, bits).

In JavaScript it looks like this:

var rand = Math.round (PI10000 * x * y) & 0x3; // 2 bits

where the constant PI10000 has been calculated once at the start of the program:

PI10000 = Math.PI * 10000;

That shifts pi up a bit so we are not looking at its first bits.

The 0x is not necessary but it is a reminder that we are not interested in decimal numbers here. 0x means hexadecimal which is a useful way of writing bit patterns. If we needed four bits we would AND with 0xf.

A similar thing is done for the exact positions of trees within the 2m square tiles that form the ground, so the trees do not lie always in dead straight rows.

Is it 3D?

Yes and no. The ground is drawn in true 3-dimensional perspective. Objects placed on the ground are only 2-dimensional images and look the same from whichever direction they are viewed. This is a compromise but it may also be seen as an advantage: users do not have to steer all round things to see what they are.

Perspective calculation

The following method is in screen.js and it calculates the screen position of a point on the ground at location coordinates (x, y), plotting it in perspective as seen by the observer. Any object at that location can then be plotted relative to that position (typically standing on it but scaled for distance from the observer). I have annotated the method here with extra // comments.

The diagram below is labelled according to variables in the method. The bearing sp.b is relative to the direction in which the observer is looking, because the screen is angled for that relative to the ground coordinates. FF is like a camera's focal length, the supposed distance from the observer to the screen. The dashed triangle on the screen at FF is geometrically similar to that at the actual distant plane zz so the sides are all in the same ratio.

Programming notes by file

NB: The names of the JavaScript files are changed every time they are modified, by a 1- or 2-character suffix. The script elements in the HTML must therefore be altered every time a new script version is to be uploaded. The server is set to tell browsers not to cache the HTML but all other resources may be cached.

index.html

This is the single web page for the program, in HTML5. All of the user interaction occurs here but it is kept as simple as possible. All CSS is kept in a style element in the head because there is not very much of it. All JavaScript is loaded from files by script elements in the head. See the note above about how script file names change as new versions are made.

The main action occurs in a <canvas> element, so it assumed that the user's browser is sufficiently up-to-date to recognise that. The canvas is initially set to 800 x 600 pixels and that gives a reasonable drawing speed for the various views. [A possible enhancement would be to allow the user to make the canvas larger if their device is fast enough.] The ordinary 2D graphics context is used by the scripts, so that they should work on all platforms (so we do not use WebGL, for example).

The layout of input elements on the page may seem rather untidy but this is an attempt to allow finger room between them if the program is running on a smart phone. [4/7/18: It is intended to improve the layout. ]

The information and controls available on the page change as the program is used. This is done by having unique id attributes on several of the HTML elements and then using

document.getElementById ("an_id").innerHTML = "new content";

At the end of this HTML page there is a second canvas element which is never displayed (its style attribute prevents it). See the section below for workarea.js for more information about this.

forest.js

This script acts as an interface between the HTML and the other scripts which are all object-oriented (this one is not). It handles events such as button clicks, drop-down selections and keypresses. For touch-screens (tablets or smart phones) the only gestures recognised by the program are taps which appear as mouse click events.

Generally the event handlers call methods of relevant objects in the other scripts to carry out the required actions.

This file contains an initialisation function, init (), which is invoked by the onload event of the HTML page. This function creates the principal objects for the program: one each of screen, terrain, map, scene, observer and plot3d. It adds relevant event listeners to the canvas. It also calls function loadCourses () in course.js which will load any courses which had been created by the user in a previous run and put in local storage.

init () then calls the map object to draw itself. It is important that the map is drawn first, to give time for the images to download which are required for drawing a scene. They start downloading in the Scene constructor.

The functions loadImage () and loadScript () work asynchronously but we can check when they have finished. In the case of loading an image we test the corresponding object of type Image to see whether its loaded boolean has become true. For loading a script we supply some code which is to be executed once loading is complete; usually this is a function call which invokes something in that newly loaded script. The main example of this in The Forest occurs when an explorer gets too close to a mineshaft and falls down it: mine.js is then loaded. This never occurs for orienteers.

This script also contains the function message () which is a general-purpose way of putting a multi-line message on the screen until the user does anything which causes the screen to be repainted. This is intended to be more user-friendly than JavaScript's native alert () which would always require the user to press OK to acknowledge it and would also darken the screen as a supposed security measure. However, it does have a possible disadvantage in that the user may not notice the message if pressing keys rapidly in succession.

screen.js

This small script has a constructor for type Screen which gets the size of the canvas and a reference to its graphics context, for all other scripts to use.

It contains methods for direct access to pixels in the image displayed on the canvas.

(In hindsight it may have been better to call this file canvas.js and the type Canvas because it only refers to the active canvas element rather than the whole screen.)

workarea.js

This used to be in mine.js which is only loaded when an explorer falls into a mine. It is also used by the newer inside.js that similarly loads only when needed. So it was decided to make this small file part of the initially loaded set of scripts.

The purpose of the workarea is to enable pixel data of various images to be read and written. The relevant image is first drawn into the workarea with context.drawImage () and then the pixel data array is obtained using context.getImageData ().

This file also contains a function called skewHoriz which uses variable vertical scaling to draw an image with horizontal perspective. This is done for the walls of the mines and for pictures displayed inside buildings.

observer.js

One object of this type is constructed by the init () function in forest.js. It represents the orienteer standing on the ground, with x and y position coordinates in metres and facing a bearing in degrees clockwise from north. Given those 3 values it is possible to calculate everything that can be seen ahead: see scene.js.

Among other things, the constructor of the Observer object creates 2 properties which are arrays of sines and cosines at integer degree values from -360 to +360. Looking up these values is quicker than calculating them every time (see verification section below). The negative range saves time worrying about negative values when angles have been subtracted.

An observer also has a role, initially the general one of explorer. If the user changes this to be an orienteer there will be a course object selected for the observer too. Actions available and whether control markers are seen depend on the role.

terrain.js

One object of this type is constructed by the init () function in forest.js. This is almost exactly the same as in the original forest dating from the 1980s, simply translated from Z80 assembler to JavaScript.

The starting point is a 1-dimensional profile of length 256 for which the data are stored as a literal array of integer values. Charted in a spreadsheet it looks like this:

The profile is a periodic structure so the array is indexed by the remainder of some number (p, say) when divided by 256 to get the height of the terrain. The number p is a function of the x, y coordinates of a point.

height = profile [p % 256]

p is obtained essentially by summing various multiples of x and y (though it is slightly more complicated than this):

p = sum (a [i] * x + b [i] * y)

Effectively this is adding together several copies of the profile at different amplitudes and various angles in the x-y plane. This is analogous to a Fourier series where a number of sine waves are added at different amplitudes and phases to create the shape of any desired function.

If the height is below a certain fixed value there is deemed to be a lake. Update June 2018: the lake height slowly increases when it rains, reducing back to the original fixed value when the sun shines.

A similar thing is done for determining terrain types (thicket, moor, etc), using the same profile but different parameter arrays a and b. If the result lies within a certain range of values, the terrain is of a certain type. Because the parameters are different the patches of vegetation do not follow the contour shapes.

Height is calculated using the full floating point values for x and y, because the observer is not necessarily standing exactly on the whole-metre x-y grid and we want height to be as smooth a function as possible. Vegetation and point features use only the integer parts of x and y however.

The existence of a point feature (boulder, pond, etc) at a given position does also involve the profile. Then a relatively simple function of x and y is tested for certain values within a range of bits.

Notice that the map is generated point by (x, y) point. There is no consideration of how neighbouring points may be related and therefore there are no linear features such as paths or streams. In fact it would be much more difficult to generate a map containing such features. Only the vegetation boundaries can be considered as linear features. One of the aims of The Forest is to help orienteers learn to navigate by using the contour information, so the absence of paths to follow is considered to be a good thing. Making the contours form narrow continuous lines was not so easy and we consider that in the map section.

map.js

One object of this type is constructed by the init () function in forest.js.

The map is drawn by a relatively simple scan of the canvas area. For each (x, y) position the terrain type, height and any point features are found by calling the terra () method of the terrain object. The complications in the process are as follows.

Map orientation: the user can turn it in steps of 90 degrees. [4/7/18: I am considering whether intermediate angles might be possible.]

Contours: the algorithm for drawing these was first described in 1987 in Byte magazine by Paul Bourke. The description can be found at paulbourke.net/papers/conrec/. I have re-implemented the algorithm in a form that is most efficient for this map. It performs well.

Overlaying a course if the observer is in the role of orienteer.

Contours are drawn smoothly and every fifth contour is thicker, as is standard for O-maps, to help interpretation. O-maps often have downward pointing tags on some contours as a further aid in removing ambiguity but I can see no way of doing that reliably with realistic speed. (Any ideas?)

I have used the latest IOF mapping standards (ISOM2017) as far as possible. That document specifies colours in the Pantone Matching System (PMS) and I have found the RGB equivalents from Adobe Photoshop.

Moorland (open ground where the running is slow) is shown on printed maps by using dot screens but I have guessed at an equivalent solid colour.

I believe my main departure from the standard is in using a 1-pixel white rim around every point symbol to help it stand out from its background. (This was a major consideration before I worked out how to have thin smooth contour lines.) So point symbols are drawn after everything else except the north lines (even after any course overlay).

scene.js

One object of this type is constructed by the init () function in forest.js.

The constructor of the scene object initiates loading of the image resources. The images are in the PNG-8 format using transparency. Images are rectangular but the area surrounding a tree is transparent, for example.

Drawing the scene (the draw () method of this object) involves first scanning a square area around the (x, y) position of the observer. Objects out to a predefined range (initially 60m but alterable by the user in the HTML) are to be drawn, so a square of side 2 x range + 1 metres is scanned. The results are held in an array called around. As the points are scanned a check is done against the bearing of the observer, to find out whether each point lies within the visible angle, 45° either side of straight ahead. But because objects close to the observer but out to an angle 70° either side can affect the view, we really mark all points within the +/- 70 degree angle as being potentially ahead and these are all held in an array called ahead; this array includes the view angle and distance of each point.

This diagram represents the 2D array called around in the simplified case when the visible range is only 10 metres (the minimum we allow in the HTML is really 60m). The observer is at the centre, so the array has dimensions 21 x 21 (because 21 = 2 x range + 1). Clearly the size of the array goes up as the square of the range and computation time increases correspondingly. The blue dot in the centre of the diagram represents the observer and the thick blue line is the facing direction, in this case on a bearing of 120° (clockwise from north). The dashed lines either side represent the angle covered by the scene view, 45° either side of the facing direction. The solid thin lines are 70° either side. The centres of the red cells are outside the circular range and therefore need not be considered further.

Note that although the blue dot is shown in the centre of the central cell, the observer does not have integer coordinates. It cannot because it can move by fractions of a metre in x or y (taking into account sines and cosines). Rounded versions of the observer's coordinates are used as the basis for the square array.

The white and green cells in the diagram all get a reference to a ScenePoint object stored in them, containing the following information.

The exact distance of the cell centre from the observer, in metres.

The difference in bearing from the facing direction of the observer, in degrees.

The integer x and y coordinates of the centre of the cell.

Whether x and y are both odd. This is used for tiling the ground in the scene, as we will see.

Each green cell potentially affects the scene and a reference to the same ScenePoint object information is appended to the 1-dimensional ahead array as each green cell is encountered.

Importantly, the ahead array is then sorted in descending order of distance (so that the most distant points come first). The ScenePoint prototype includes a method for defining the sort order. The scene can then be drawn from the back towards the observer so that nearer objects can automatically obscure farther ones.

The around array is kept because it maintains the spatial relationship between neighbouring points: given x and y we can easily look up around [x + 1][y] for example. There is no such relationship between adjacent entries in the ahead array. This spatial relationship is needed when we come to tile the ground (next paragraph) and also to draw point features that are more than 1 metre across: knolls, mineshafts, ponds and man-made objects; for these we need to mark the positions around their centres so that trees do not grow out of them.

Having sorted the ahead array we can start using it to draw, from the most distant points forward. First the whole scene is filled with sky colour (which depends on whether it is raining and whether the explorer has gone through a green door!). Then at a given point, several things are drawn in succession:

A uniformly coloured tile showing the perspective shape of the ground around the point, in a colour suitable for the type of terrain. The function getScreenXY () was described earlier in the perspective section. In the case of a point in a lake this blue tile is all that is drawn, for the surface of the lake.

An image patch appropriate for the type of terrain (some grass, for example) scaled to cover the bounding rectangle of the tile. [ 4/4/18: I am experimenting here: up to now there has been an elliptical patch, which is why it is necessary to draw the tile first, to avoid sky-coloured holes in the corners of some tiles. I am trying other image shapes. 1/7/18: The elliptical ground patch is easily the best compromise even though it does leave some gaps in which the bare tiles can be seen. I will continue to think about improvements.]

If there is a point feature here (boulder, mineshaft, etc) draw it. Otherwise if the terrain type is wood, thicket or town draw an image of a tall or short tree or a building. [4/7/18: It is intended to improve towns (a) to use better photos of buildings and (b) to have a grid of streets and only allow running along the streets; the map will show the grid.]

This example from one of my test programs (which switches off scene.showGround before drawing scenes) illustrates the tiling without the elliptical patches of ground cover:

Notice also how the trees are offset within the tiles.

One of the neat things about the standard 2D graphics context is that the drawImage () method not only takes parameters for where to place the top left corner of the image but also the required width and height, scaling the image very efficiently to fit. In our case there is a scale factor (fscale) formed from the distance of the point from the observer which is applied to every item that is drawn. So distant tiles, trees, etc are scaled down very effectively without my program having to do very much.

fscale = 5 / sp.d; ensures that images of objects 5 metres from the observer are drawn at their original size. When closer they are scaled up but further away they are scaled down.

A tile is drawn about every point for which both the x and y coordinates are odd. That is why the ScenePoint objects created during the scan around the observer contain a boolean indicating this fact. We use the around array to find the 4 neighbouring points (for which x and y are both even). We then get the distance and heights of those 4 corners of the tile. A method called getScreenXY () then does the perspective calculation to get the screen coordinates of each corner. A closed path is then created and filled to draw the tile.

[ Repeat of pseudo-random bit patterns, in different words:

When drawing trees and other features there are (or will be) several different images for each type of object, to give some variety in the scene. They are pseudo-randomly selected, by which I mean that although the selection appears to be random it will always be the same at any given (x, y) position. So if you see a particular kind of boulder at a certain spot it will always be the same type when you revisit. Equally, the variety of tree at any given spot in a wood remains the same as you move about. This is achieved with a variable called variant, created like this:

var variant = 0x3 & Math.round (this.PI10000 * x * y);

this.PI10000 is set as a constant property of the scene object when the object is first constructed. It is Math.PI x 10000 but we do not want to do that multiplication every time we want to use it, so it is set up first as a constant.

The digits of pi have no predictable pattern to them and so they can be considered for our purposes to be random. pi x 10000 simply shifts the digits up by a few places so there are several before the decimal point: 31415.926... Multiplying this by both the x and y coordinate values results in a new pattern of random digits but a pattern that is always the same for any given x and y. The final part of the formula for our variant is a bit-wise AND with the number 3 (written as a hexadecimal number simply to emphasise that a bit-wise operation is being done). In other words we look at only the least significant 2 bits of the integer part of the result of the multiplications. This can have 4 possible values: 00, 01, 10 or 11 and so we can select one of 4 image variants to display. If at some stage there were to be more than 4 images for boulders (or whatever), this could be modified to look at 3 bits (8 possibilities) by ANDing with 7 instead of 3.

A very similar thing is done when positioning a tree on top of a tile: the method getOffsetScreenXY () offsets the position by a pseudo-random amount within the tile before calculating the screen coordinates for the image. This is to ensure that trees are not always in dead straight rows. ]

scenepoint.js

Many objects of this type are constructed during the drawing of the scene, and then discarded again. These objects are simply records containing values found in the square area around the observer, as described for the scene object.

These objects also have a method which defines the sorting order in descending order of distance.

Several extra properties can be attached to objects of this type during the drawing of a scene. This helps performance. For example, if the quite lengthy terrain calculation is done for this position then the result is set as a property for re-use rather than perhaps having to do the calculation again at some stage. That is the reason for the method getTerra () in this file, which first checks whether the property already exists.

building.js

This file is new in version 18.10.24 when 3D buildings of definite size appeared instead of 2D images of buildings. The main point is that the new buildings are both plotted on the map and viewed in scenes, so it makes sense to have a single place where they are defined rather than risking differences between definitions in the map and scene files.

Buildings only appear centred on x and y coordinates which are multiples of 16 and for which the terrain type is TOWN.

Objects of type Building are only constructed when drawing a scene. For the map we only need to know whether a given position is at the centre of a building in order to be able to draw the square black symbol, so there is a simple function in this file for that test.

inside.js

This is new for version 18.11.7 when it first became possible to enter buildings. It is mainly responsible for drawing the interior of any building. This script is only loaded when an explorer manages to enter a building by giving the correct key code for the door.

NB: If this file changes, so its 2-letter suffix has to change, it is important also to change the suffix on any other file that is loaded in this way (such as mine.js) and in the loadScript () method in forest.js (and the suffix of that file must therefore change too).

An object of this type is attached to the observer as observer.inside and the fact that that property is not null ensures that the building interior is shown as the scene instead of outside in the forest.

The observer can move around inside the building and look up or down, just as in the forest scene. There are things drawn which might need to be examined more closely by moving up to them.

marker.js

This represents a potential marker flag for orienteering. One of these objects is constructed for every point feature found when drawing the scene ahead. It may or may not be displayed, depending on the role of the observer.

Each control on an orienteering course has one of these marker objects.

A marker has an (x, y) position and a two-letter control code. It also knows how to draw itself as a standard orange and white orienteering flag with the control code on it. If the observer's role is course planner, markers also display their positions on the flag, to help programmers make courses.

The code is determined by the terrain object whenever a point feature is found. The two letters are from an alphabet as letter numbers x % 26 and y % 26. (% is the modulus, or remainder, operation in JavaScript).

control.js

A control is part of an orienteering course. It has a marker plus an (x, y) offset for showing its number beside it in a suitable position when the course is overlaid on the map. This offset has 2 small shortcomings:

A course planner creating a course as an observer within the program cannot set the offset. [I decided this was too fiddly to do but I may revisit it later.]

The offset may become unsuitable when the user rotates the map. [Ditto.]

course.js

An important consideration here is the fact that storing the data for user-created courses in the browser's local storage is done as a single string in JSON format. That is what HTML5 offers. That loses object type information so the string must be parsed when it is reloaded and objects of our specific types (Course, Control, Marker) must be constructed again.

MORE TO COME

timer.js

This uses the standard JavaScript setInterval () function to rewrite the digital time every 500ms in a span element on the HTML page.

A Timer object is constructed as a property of the Observer object so that split times for the orienteering course can be tabulated at the finish and so that the timer can be stopped.

The timer is only used if the observer's role is orienteer.

plot3d.js

One object of this type is constructed by the init () function in forest.js.

MORE TO COME

stream.js

MORE TO COME

point.js

This is sometimes a convenient object to construct to help geometrical calculations, mainly because it contains a useful method to get the distance between this and another point. However, unless such a method is required it is generally better not to construct objects of this type, on performance grounds. This is a general point: constructing objects takes time and at some stage the garbage collector will have to get rid of them again. So think carefully in all cases whether it is worth making objects, especially if large numbers of small ones will be needed. Sometimes the purity of object-based structure has to be compromised for performance and you will find several examples of this in my program.

mine.js

To keep the main code of The Forest smaller, this script is only loaded if an explorer falls down a mineshaft. It is of no interest to orienteers, who are immune to such accidents!

NB: If this file changes, so its 2-letter suffix has to change, it is important also to change the suffix on any other file that is loaded in this way (such as inside.js) and in the loadScript () method in forest.js (and the suffix of that file must therefore change too).

There is an animation as the explorer falls into a mine. There is (at present) just one level of mines under the entire terrain. It comprises a set of small caverns which may be connected in any of 8 directions: N, NE, E, etc. The caverns are 16m apart. The walls of a cavern show whether there is a connecting tunnel in each direction. In moving forward there is a brief animation zooming the scene towards the relevant tunnel.

Most mineshafts are connected to others via these tunnels. An explorer may escape because there will be a ladder shown below any mineshaft other than the one originally fallen down. Some mineshafts have no such connections and in that unlucky case the explorer is doomed.

The drawing of the scene in a mine has a major difference from scenes above ground because there is code to draw the wall images skewed, for a simple perspective effect. This involves a new object type, WorkArea, to help in assembling the scene. This is associated with a hidden (never displayed) second canvas element in the HTML page.

It is intended to add more action after the treasure chest has been found.

courses.html

This is the page that appears if a user in course planner role clicks the button for managing courses. You can easily see that it includes a few of the script files described above plus one new one: manage.js

manage.js

The function manage () is the initialisation function for this module. Then there are functions for the buttons on the course management page.

Return to the main page of The Forest is done simply by a call to the standard JavaScript function back (). It does not cause the original page to reload its course data after any changes made on the management page. This may cause users some confusion, so a note about manually refreshing is given on the course management page.

Test programs

I have a number of these in a copy of index.html that has been renamed tests.html and then been modified.

The onload attribute of the body element has become onload="testXXX ()" and within the body there is a script element containing several test functions that can be called in that way.

Each of the test functions must call init () in forest.js before doing anything else.

A typical test I would need would be when new behaviour occurs at a particular type of object. I first find an example of that type of object by scanning the map, jumping to the ground and moving until I can see it in the scene. Then note the coordinates and observer bearing in the status line below the display. Then my test function might be as follows.

If you run this particular example you will see that on the very steepest hills there is occasionally a missing ground tile at the bottom of the scene (very close to the observer), showing the sky through a hole. This is a problem I am still trying to solve, so any help would be appreciated (optimistic, I know).

You may wonder why I keep doing things like creating the variables me and fs in this example. Partly this is to make file sizes smaller but there is a more important reason. I read some years ago that JavaScript interpreters can find local variables faster than properties of objects. So if an object property is needed several times in a function it is best to make a copy as a local variable first. I don't know whether this is still true. It may not be so important if JIT (Just In Time) compilation is being done. I have not tried to verify whether there is really a speed benefit. [When I get time perhaps...]

Cones and scarecrows

These two objects were chosen for their colour. At a distance and through trees orienteers might mistake them for controls, as could happen in real life.

The scarecrow is on a stake in the ground and so I considered it to be fixed. It is therefore one of the four man-made objects, mapped with a black x. People can easily move cones and so they are not plotted on the map.

The presence of either object at a given location is calculated in the usual pseudo-random way in terrain.js and the cones do not move.

Diversions for explorers

Although the primary purpose of The Forest is to help orienteers and others with contour interpretation on maps, it does include some games and side attractions ("diversions") for non-orienteers. These diversions become invisible and have no effects if the user's role is not explorer.

As of June 2018 there are 3 such diversions, as follows, but it is intended to think up some more.

There is a button for making it rain. This switches a boolean in forest.js: forest.rain. Scenes then change to have a dark sky and random rain streaks across. This also causes lake levels to rise slightly every time the user turns or moves forward. Gradually the amount of land shrinks to small islands. On switching the rain off, lake levels recede again until the original level is reached.

One of the 4 kinds of man-made objects on the map changes from a sculpture that an orienteer sees to become a green door in a brick wall. A boolean in the observer object, nearDoor is set in scene.draw () so that an Enter key will open the door (setting scene.doorOpen = true) and then the observer can move forward and into a different world!MORE TO COME

An explorer will also see that some rootstocks appear to have map fragments pinned to them. There are 4 such fragments and when put together they are headed "Treasure Island" and at the bottom it says "X marks the spot". This map is a fragment of the total map of The Forest, so the explorer's task is to search the map, find the island and get to the spot marked with an X (it is possible to "jump", something which orienteers cannot do). A variable in forest.js, forest.estate gets bits set as the explorer makes progress, so it is only possible to see what happens at X if bits are set to show that all 4 map fragments have been seen. Otherwise a message pops up at X to say that must happen before anything further.MORE TO COME

Development environment

I am developing the program on a Microsoft Surface Book 2 running 64-bit Windows 10. I use Netbeans 8.2 IDE (free Integrated Development Environment) with its HTML5/JavaScript plug-in kit, mainly because I already had it for Java programming. An IDE is not essential for this work but it does offer suggestions as I type and it does point out syntax errors immediately. When I am unsure about any JavaScript detail I use the Mozilla reference pages which I find more thorough and up-to-date than the old w3schools site. I test the program by loading the HTML file into the Firefox Quantum browser. I do not use a localhost server (no need). Firefox has a very useful Web Console (under the Web developer menu, or key Ctrl+Shift+K) which gives me the script file name and the line number at which any run-time error is detected. I upload the tested files to my web site using Filezilla FTP (free). To avoid possible problems with script caching in client browsers I increment a suffix letter on the name of each script file that changes for a new version. That way the user would only need to refresh the HTML page to ensure that all the correct script versions are loaded. (More recently I have edited the .htaccess file for my site to ask browsers not to cache HTML files.)

I have only tested The Forest in Firefox, Edge and Internet Explorer 11 on PC and Chrome on an Android smartphone. Timings for drawing are fine in all those browsers. I have been told that it works fine on a Kindle tablet running under FireOS (version unspecified but not very recent).

I would very much welcome feedback (gr<at>grelf<dot>net) on performance in other browsers: how long does each take to draw the initial scene, map and 3D plot, as shown in a status line below the graphics? Average of 3 readings please (times vary due to the browser doing other things).

My JavaScript course

Verifying look-up speed

I wanted to verify my statement that looking up sines and cosines is faster than calculating them. It seems obvious but is it true in the browser JavaScript environment?

I found out that calculating (including conversion from degrees to radians) takes about 6 times as long as looking up. So it really is worthwhile pre-calculating trigonometric functions when possible.

The code I used for verifying this is below. I called it at the end of the constructor for the observer. There are a few complicating factors to take into account:

It is necessary to put the results in arrays rather than assigning to a single variable because the JavaScript Engine (JSE) might be able to optimise the assignments away and just do the last iteration of the loop.

The arrays must be pre-allocated, otherwise timings will include memory management operations as the arrays get bigger.

The calls to Math.random() are also to avoid the JSE noticing that exactly the same operation is being done every time, so it can be optimised away.

Then we should check how long the random calls take.

JSEs are now very clever at optimisation and of course that is why so much detail can be shown in The Forest in real time.