A flight simulation requires a moving map. The simplest form of map is a flat map that uses a grid of squares, e.g., 27 by 27 squares. As you move over the map, the farthest squares back can be "scrolled" and moved to the front.
In designing the grids we used the standard 3D reference system: the Z axis is north/south, the X axis is east/west and the Y axis is altitude. Since the altitude of all squares is the same, our primary focus will be on the Z and X axes. From above, the grid takes the form of a table with the Z values representing the rows and the X axis representing the columns.
In theory, you could use a single grid map to represent the whole world. However, this quickly proves impractical because the number of squares quickly becomes huge. This is because the number of squares equals rows X columns. Thus, a 3X3 grid contains 9 squares, a 9X9 grid contains 81 squares and a 27X27 grid has 729 squares. This means that you need to create 729 squares and create a table to save the mesh pointer for each square.
Fortunately, there are some shortcuts you can use. You do not need to create a table to store and update the X and Z positions for each square. This is because all squares in the same row have the same Z-value and all squares in the same column have the same X-value. Thus, you only need to save and update the position of the rows and columns - or 27 X-values and 27 Z values. For example, the square located at row r and column c can be positioned using this:
Gr1Ptr[n].position.set(Xvalue[c],-Yvalue,-Zvalue[r]);
This is helpful when scrolling the squares from behind to in front. For example, if you are flying north, the squares will be moving south. When the row of squares reaches a certain value south, you want to move all the squares in that row from the south to the north.
And you do not have to check all the X or Z values to determine which need to be scrolled. Instead, you merely need to keep track of the index for the lowest X or Z value (the index for the highest value is adjacent). Then, depending on which direction the aircraft is moving, determine whether that lowest (or highest) value has gone out of range. If so, then you scroll the X or Z value of that indexed item and now point to the X or Z value which was previously next in line. Note that, no matter which direction you are flying, the X and Z values will always be in the same ascending (or descending) order.
Here is an example showing a grid of untextured squares. And here is an example showing a 33X33 grid of textured squares. This uses textures from Microsoft Flight Simulator FSX which show that, with clever texture design, the squares can generate a seamless appearance.
One shortcoming of using a Single Grid Map is that you are trying to balance two competing demands: (1) you want to use grid squares that are small enough to give detail when close-up; and (2) you want to use grid squares large enough to reach to the horizon. For example, if you assume that your visibility range is 80 km (50 miles) and if you use grid squares that are 2kmX2km (1.25miX1.25mi), you would need a grid of 80X80 squares, or 6,400 squares.
One solution to this problem is to use a Nested Grid Map, where you combine an inner grid with an outer grid. For example, you could combine a 27x27 inner grid with a 27X27 outer grid. The inner grid squares would be 2kmX2km and the outer grid squares could be 6kmX6km. This would give you the same visibility, but would require only 1,458 squares. An additional advantage is that, as altitude increases, you could eliminate the inner grids, since a high level of detail is no longer required.
Here are a couple of examples of nested grids which use untextured squares. The first shows nested grids prior to matching the colors. The second shows nested grids with matching colors, which makes them appear to be a single grid.
While the Nested Grid Map was an improvement, the map did not have enough range on the low end. For example, using a 512X512 texture to cover a 2kmX2km grid results in a resolution of about 4 meters per pixel. So we decided to add a another grid to create a Double Nested Grid Map. The process is discussed in more detail here.
For Ocean textures, we wanted to have detailed animated waves and to avoid the tiling which occurs as altitude increases. Since the current method of generating waves involves wave frequencies in multiples of 2, we changed the double nested grid map to also use multiples of 2. The grids are all 16x16. The individual planes are 2 miles square, 4 miles square and 8 miles square, respectively.
In theory, you could create pairs of nested grids that become visible as you gain altitude. For example, the outer grid with 9 mile squares could become the inner grid and the next 27X27 outer grid squares could be 150 miles. At some point, the outer grids would be large enough to serves as faces on a sphere, allowing you to make a smooth visual transition from earth to space (eat your heart out, Jeff Bezos!).
There are several ways to decide how to assign textures to the squares in a scrolling grid map.
At the most extreme, one could assign textures extracted from a large scrolling map of the area. However, this would require creating textures on the fly. Flight simulators which use this approach are often plagued with slow performance and high memory demands. We are opting for a simpler approach.
Since our goal is to maintain both speed and appearances, we can use internal mapping. Under this approach, the innermost Grid can have up to 27X27 (730) unique textures. The middle Grid out can have up to 9X9 (81) unique textures, which will be repeated 9X. The outermost Grid out can have up to 3X3 (9) unique textures, which will be repeated 81X. While this might not sound like much, experience indicates that less than 10 unique textures are required to create a sufficiently random terrain.
You can start with the innermost Grid and work outwards. You first assign textures to each of the squares in the innermost Grid, either randomly or by design. You then copy 9X9 blocks of squares from the innermost Grid to single squares in the middle Grid, until you have a 9X9 block of squares in the middle grid. You then copy this 9X9 block 9 times. The figure above shows how colors from the innermost Grid are copied to the middle Grid.
Alternatively you can start with the middle Grid, defining up to 81 9X9 patterns of inner Grid textures and then creating an index indicating where these patterns appear in a 9X9 section of the middle Grid. You would use these patterns and index to positon each inner Grid texture and to create and position textures for the middle Grid. We are currently uisng this method.
In contrast to land maps, mapping textures to an ocean map is fairly simple. This is because you can use the same basic texture for all the grids. To increase resolution, the textures on the inner grids are repeated 8 times. This means that, on the outer grids, you simply increase the repetitions to 24. No special computations are necessary. To add some dimension, we added a bump map. There is no reason to spend time implementing wave actions because you are generally high enough that such action is not noticeable. (And if the waves are large enough to be noticeable, it is probably too windy for flying.) If you do want to add some highlights, your time would be better spent creating random whitecaps and either sparkles (for when the sun is behind you) or shadows (for when the sun is in front).
If you want to add dimension to the landscape, you can add objects which sit on top of the landscape. The "3D Landscape" example below shows the landscape with a large mountain added and a runway (beyond the mountain). It is not perfect, but should give you and idea of what is possible. For example, both the geometry and the texture need work. (The "mountain" is actually a model of the island of Giaros in the Aegean. So, if you want a higher quality version of the texture, you can grab one from Google Maps.) While the mountain may not seem like a big addition, remember that you are travelling at 800 mph. At more normal speeds, you may never get out of sight of the mountain.
Another object of interest is trees. Many simulations (such as FSX) used randomly placed sprites to represent trees. More modern simulations use "procedurally generated" trees, which can be fairly detailed. However, detailed trees are not necessary for a flight simulation - certainly not down to the individual leaves. This is especially true when you are flying over a grove or forest of trees. In this case, all you see are the tops and sides of the grove or forest. Thus, we suggest using very simple geometric shapes - to give the appearance of height - without a lot of detail.
Initial Scrolling Maps | ||
---|---|---|
Single | 3X1 grid scrolling map | |
Double | 3X2 grid scrolling map | |
Triple | 3X3 grid scrolling map | |
Scrolling Maps with Matching Colors | ||
Double | 3X2 grid with Matching colors | |
Triple | 3X3 grid with Matching colors | |
Scrolling Maps - Farmland | ||
1X Textured | 3X1 grid textured - size 33X33 | |
2X Untextured | 3X2 grid Untextured w/sky dome (50 mi viz/30K+ altitude) | |
2X Textured | 3X2 grid textured | |
2X Mountain | 3X2 grid textured w/ large mountain in front | |
3X Procedural | 3X3 grid procedural w/ high Pixel detail | |
Scrolling Maps - Ocean | ||
2X Textured | 3X2 Grid Textured | |
3X Animated | 2X3 Grid Textured and Animated | |
SAMPLE TILES (PROCEDURAL) | ||
Farmland | Single tile of Farmland | |
Ocean | 4 tiles of Ocean waves (iFFT method) |