Tuesday, October 30, 2018

Continent Maps (Part 2): Getting Consistent Views

In the last posting, I explored the coordinate systems and learned how to slide the SVG viewport around to show only part of a larger world.  However, there were some problems with that, because up until now I've assumed that anything not visible is irrelevant.  In this posting, I'm going to go through and fix those assumptions so that I can consistently generate and view different portions of a larger map.

The problem with place names that I pointed out in the last posting is evident in these two views of the same map:
 Here's the same world, with the view slid to the left:
You can see the geography is the same, but many of the names have changed.  That's because Dragons Abound doesn't bother to name anything that isn't visible.  Since different things are visible in the two views, and the naming process is driven by random numbers, different names result.

After looking through the code, I discovered that almost everything is being named whether it is visible or not.  But it only takes one exception to throw off the naming of everything that follows.  In this case, the exception is that Dragons Abound is only generating the coastlines that are visible.  The reason for this is convoluted.  There's actually a “coastline" along the entire edge of the world, which -- since it encloses the entire world -- breaks some of the program logic if I actually create that coastline.  To avoid that from happening I've just generated the visible coastlines.  Now that the map can extend far beyond the edge of the viewport, that's not a good solution.  Instead, I need to stop generating coastlines when I get near the real edge of the map.  (Which I still keep off-screen to avoid showing edge-effect problems.)

With that fixed, the names are now consistent across the two maps:
and:
One thing to note for the future:  If I use the interaction capability to change the name of a map feature, that change won't be recreated on a different view, and probably won't even be reproducible.  Something to think about.

If you look closely at the previous map, you'll see that an ocean area near the bottom center of the map has a label “Meb Island" floating in it.  That happened because Dragons Abound thinks the ocean area is actually an island.  Without going into all the technical details, it's surprisingly tricky to distinguish between islands and lakes when they run off the edge of the map.  At any rate, the algorithm is getting confused by the change I made above in generating the non-visible coastlines and needs a little fixing to avoid this sort of problem.

Now let me try quadrupling the size of the world and show 1/4 of it in a map view:
In general this looks okay (and has a big interesting river system, and a big lake, but you might notice that the cities seem very sparse.  That's because Dragons Abound generates a range of 10-20 cities.  That range works well for the original size world, but not so well when the world size is quadrupled.  So the range needs to be adjusted for the relative size of the world.  There are probably a number of areas where this needs to be done.

Here is the same map with that problem fixed:
Now there are a more reasonable number of towns and cities, but this reveals another problem.  You can note a number of orphaned names around the edge of the map, like Nanmrummombrook, Marwynnley and Noyewood in the lower left.  This happens because label placement tries to place labels where they are visible.  Previously, that routine has never had to worry about a label for a feature being off-screen, because in the regional maps almost the whole world is visible.  But now there can be cities and other features just off-screen.  So I need to add some logic to the labeling routine so that it doesn't try to create labels for map features that aren't visible.
That's more reasonable.  On the right edge, Cumden is just barely on the map but the label is still placed where it is visible.

One thing that might not be immediately obvious in the bigger maps is that the number of locations in the world hasn't changed.  Although the map is (in some sense) 4 times as big, the underlying area is still chopped up into the same number of locations.  The initial step in map generation is to cover the world with a Voronoi diagram with a fixed number of locations.  So as the map gets bigger, each Voronoi cell gets bigger as well.

It makes sense to scale the number of locations with the map size, but unfortunately, the performance of Dragons Abound is somewhat worse than linear with the number of locations in the world, so generating maps with a large number of locations can take a long time.  Here's an example of a map with 4x the resolution (number of locations) as the map above:
The added locations change the generation process, so the terrain isn't the same as the above maps, but you may notice the added detail in the coastlines and the land shadows.

Fortunately, when profiling the performance in generating bigger maps, I noticed some obvious problems that were using a lot of processor time.  Some debugging time later, I've fixed the worst offenders, and that allows me to make some bigger maps.  Here's an entire 4x map:
This is effectively zoomed out to 25% size.  This seems to be about the maximum size map that Chrome can display.  The world generation can handle larger maps but the browser crashes when trying to display them.  Firefox seems more capable in this regard; it can display maps up to 9 times the size of my original maps.  Here's a portion of such a map -- I've left it full size, so you can click through to get a better notion of the size and detail of the map.
Firefox can generate a map this big, but I can only screen capture a picture at the maximum browser window size.  I've got a function to save the map as a PNG file, but it can also only save the portion of the map that is being displayed.  I suppose I could scroll the map, capture multiple screens and stitch them all together, but that's a pain.

A better solution would be to save the underlying SVG and then open it in a program like Inkscape.
In the past I've been able to cut & paste the SVG for a map into Inkscape, but the SVG for the world maps is so big that trying to cut & paste it crashes the browser!  Fortunately, I found FileSaver.js and I can use that to save the SVG directly to a file, and then open it in Inkscape and create a very big image that way.

Or at least in theory.  There are a couple of challenges in getting these maps working in Inkscape.

The first problem is that Inkscape makes a few different assumptions from Chrome & Firefox about how to display SVG.  Specifically, if a path doesn't have a fill color specified, the browsers assume that it has no fill; Inkscape assumes that it is filled with black.  So when I open a saved SVG in Inkscape it is mostly black because the top-most layer in the map doesn't have a fill color.  This can be fixed by specifying “fill: none" where needed so that paths display the same in the browser and Inkscape.

The second problem is that Inkscape has some bugs in how it handles masks.  Apparently Inkscape itself only creates masks with a single element, and doesn't handle masks with multiple elements very well.  Dragons Abound creates a number of masks with multiple elements.  The workaround for that problem is to group all the elements in each Dragons Abound mask into a single (unnecessary) “group" element.

The third problem has to do with images and other loaded resources.  These are referenced in the original SVG by a relative path, e.g., “images/background0.png."  My sources are then organized so that the little standalone webserver I use can find those resources in the specified places.  When I take that same SVG and open it in Inkscape, those relative paths are now treated as “file:" URLs and Inkscape looks for the resources relative to the folder where the SVG was saved.  The easy workaround here is to save SVGs into a folder that has the resources in the correct places; this could be the same root folder used by the webserver or a different location that has copies of the resources in the same (relative) locations.

The fourth problem is fonts.  Dragons Abound uses both web fonts and locally stored fonts, all in WOFF2 format.  In the browser, these are applied to text by using a CSS “font-family" style, and before the map is generated, all the possible fonts are loaded into the Web page to be ready to use.  When the same file is opened in Dragons Abound, it looks for fonts in the system font directory, and there doesn't seem to be any way to specify another font directory.  The easy solution (at least for my development machine) is to install the fonts Dragons Abound uses into the system font directory, although this isn't as trivial as it sounds, because the font names much match and there isn't any easy way to change the name of a font in Windows.  But of course, this won't work on any computer that doesn't have the proper fonts installed.  A more portable solution is to embed the SVG fonts into the maps.  This goes onto the TODO list.

After all this, I end up with this interface to the map generation:
The Extent input boxes set the overall size of the world, where 1x1 is (arbitrarily) the size of the original maps.  The vbx (viewbox) size defines how big a piece of the world to show in the map; in this case it is also set to 1x1 so the map will portray the entire world.  The vbx center defines where in the world to center the map; 0, 0 is the center of the world.  Finally, the SVG size parameter controls how many screen pixels to use for 1 unit of viewbox size; set to 775 this makes the displayed 1x1 map on the screen take up 775x775 pixels.  This is handy when I create a very large map.  By setting this parameter to a low value (say 150 pixels) I can make a large map fit entirely on the screen.

By varying these six parameters I have control over the size of the world and the portion of the world to display in the map.  The Generate button does what you'd think; the Display button below it does just the displaying the world part, so that I can generate a world and then display different parts of it by changing the viewbox parameters without having to regenerate the world.  (A better programmer would probably implement this all as pan & zoom.)  The Save PNG button saves the visible map as a PNG file; the Save SVG button saves the SVG for the entire map.  The Test It button is set up to run test code that varies as I develop different features.

Now that I can generate and display all or part of a bigger world, next time I will look at adapting the land shapes to bigger maps.

No comments:

Post a Comment