Sunday, January 29, 2017

The Outline of a Solution

In this posting, I start the re-write of my mountain symbol code.  The first step is to generate an outline of a mountain.  But what I really want is a way to generate a smooth sequence of mountain shapes that vary from concave and pointy to convex and flat, like these (hand-created) examples:
So how can I do this?

If you examine the examples above, you'll see that the two sides of each mountain are mirror images.  From the first example to the third example, the line varies from concave at both ends to convex at both ends.  In the last two examples, the end point of the line (at the top of the hill) stays convex, but the start point of the line goes back to being concave.  So if we could have parameters that controlled the bend of the mountain at the bottom and at the top, we could interpolate between those shapes by changing the parameters from concave to convex and back again.

As it happens, this is essentially how Bézier curves work.  The math is complex, and I'm simplifying a lot, but with a Bézier curve you have a start point, an end point and two "control points" that determine how concave or convex the curve is near the start point and end point.  Another (simplified) way to think about it is that the control points are the direction the line moves off from the start (or end) point.  Here I've drawn in some arrows to indicate those directions:
You can see how the direction of the arrows has changed and caused the resulting Bézier curve to switch from concave to convex.

SVG has the capability to draw Bézier curves, so it's pretty easy (well, figuring out the control points is non-trivial, but otherwise it is easy) to create a routine that draws a mountain using two Bézier curves and interpolates in the way I've outlined above:
That first mountain might be a bit too concave, but I don't have to start at "zero" on this scale if it doesn't end up looking good.

One thing I know from previous experience is that it is nice to be able to generate mountains with rounded tops.  To do that, I can insert another Bézier curve into the mountain shape, connecting the two sides with a flat hump:
The ratio of width and height in these mountains, and how that ratio changes over the sequence is independent of shape.  So I can have narrow mountains or wide mountains that change ratio quickly or slowly:

One thing I notice looking at these shapes is that convex mountains -- the rounded humps near the middle of each sequence -- look visually "bigger" than the most concave mountains.  (And they probably are bigger in the sense of having more area inside.)  That part of the sequence also has a lot of very similar-looking mountains.  With some tweaking and adjustment I can reduce those problems.

So far I've been drawing the mountain shapes with SVG's Bézier curves.  But I actually want to break the curve up into a number of different pieces, so I can do things like perturb the outline.  Breaking an arbitrary path into segments turns out to be a fairly difficult problem, but I can use the browser's built-in capabilities to draw the curve, and then measure along it to chop it up into segments.  If I chop each mountain up into (say) 20 pieces, the result is mostly indistinguishable from the original curve:
Chopping into 8 pieces makes the segments more obvious:
Smooth isn't always better, in this case.  The number of segments will help determine the character of the drawn symbol, so I may end up wanting fewer segments.  We'll see.

It may be obvious, but since the mountain is symmetrical and has a peak in the middle, I have to be careful to use an even number of segments.  Choosing an odd number of segments chops off the top of the mountain:
So this provides the basic capability to define the outlines of the (new) mountain symbols.

Monday, January 23, 2017

Building a Castle in the Swamp

Sometimes building software is like building a castle in a swamp.

You build the software, it burns down, falls over and sinks in the swamp.  So you have to rebuild.

When I put a previous blog post on Reddit, a number of Redditors commented that the mountain symbols would benefit from a greater range of sizes.  With all the mountains about the same size, they tend to make a uniform jumble when grouped together:
The program actually does vary the size of the mountains some, but visually the difference isn't apparent.  However, there's an obvious problem if I increase the variance (somewhat exaggerated here):

The problem is that smaller mountains aren't just scaled-down versions of bigger mountains.  To look good, the smaller mountains need to change their character.  Here's an example from the nice Ehren map from Kacey at the Cartographer's Guild:
The biggest mountains are peaky and detailed, and as the mountains get smaller they get flatter, softer and simpler.  Compare these to my mountains, or this example (taken from here):
The mountains used on this map are basically just scaled down as they get smaller, and look very artificial.

What I'd really like is the ability to smoothly interpolate between mountains that are tall, peaky and concave all the way through short, flat and convex mountains, like these hand-generated examples:
This is possible but certainly a big change from the way the current code works.  Hence the rewrite.

I find that re-writing code is often less effort than you might expect.  The second time around, you can avoid many of the mistakes and dead-ends, and there's often a lot of code that can be partially re-used.  I'm hoping that's the case this time :-).

Monday, January 16, 2017

Purple (?) Mountain Majesties

Previously I looked at scaling the mountain symbols to look good on a map; now I'm going to look at how to color the mountains to fit into the map.

Initially, I wasn't filling in the mountains at all -- just drawing the outline and shading -- but that has a problem:
When you draw a mountain in front of another mountain, the back mountain shows through.  So I need to fill in the mountains so that they properly obscure anything that's "behind" them.
Filling in the mountains is straightforward in the case of a map with a flat background like this one.  It becomes more challenging when the background is more complicated:
In this example, the mountains are using the default land color, but on the map itself the color changes to reflect the height of the land.  What I'd probably like to do is grab an image of the underlying land and paint that into the mountain, but unfortunately that's not possible in SVG.  (Remember that SVG is a vector format -- there is no grid of color pixels to copy as there would be in a raster image.)  The closest I can get is to sample the color at the center bottom of the mountain and fill the mountain with that color.  Doing this makes the center bottom of the mountain match the map.
This looks pretty good, although you can see spots where the mountains seem too light or two dark because the background is changing too much.

Although SVG's fill options are limited, it does offer linear and radial gradients.  Radial gradients aren't of much use here, but with a linear gradient I can make the color of the mountain vary smoothly from top to bottom.  I can make "snow topped" mountains by making the mountain fade to white at the top.
This actually works nicely with a varying background as well:
The intentional gradient on the mountain symbols helps conceal the unintentional mismatch with the background colors.

Gradients could also be used to indicate something like biomes, e.g., green at low altitudes, brown in the middle altitudes, and then white at the top.  I don't want to implement a three color gradient at the moment, but this is something of the effect:
I can also use the gradient to create the purple mountain majesties of song:
Although I'm not sure that's an option I'll be using often.

Tuesday, January 10, 2017

Still in the Hills

In the last posting, I looked at a couple of approaches for generating hills but I wasn't entirely satisfied with either of them.  In that situation, I always like to go back and look for inspiration in some of my references maps.  I found that I preferred hills that were with an elongated, organic shape, like these examples:

(The first example is taken from these brushes, and the second example is from Dain's spectacular Tanaephis map over at the Cartographer's Guild.)  Compare the above examples to the hills I generated in the last posting, and you can see that while the above hills flow gently into the land, my hills look more like rigid bumps:
The shape of my hills is essentially a semi-circle; I want something that is more like a bell shape.  A little searching led me to this posting over at Stack Overflow that discusses how to draw a bell-like shape in SVG using Bezier curves.  A little adapting of the code yielded various bell shapes:
The range of shapes in the middle looks about right to my eye.  An immediate improvement I can make is to trail off the line at each end of the hill:
I can also add the usual features that make it look more hand-drawn:
Now I need to add in the shading, along the lines of what I did for the previous version of hills.
That looks pretty good.  The acid test is to see how they look on the map:
Not too bad.  Here's a close-up:
In fact, these hills look so nice it's making me re-think my approach to the mountains.  More on that to come.

Tuesday, January 3, 2017

A Detour Into the Hills

You may have noticed in my last posting that there were "hills" shown around the mountains on the map samples:
In this posting I'll talk a little bit about how the hill symbols Dragons Abound have evolved.

Initially, the hill symbols were just semi-circular arcs.   That's pretty simple, but I developed them for the "D&D" map style (which tries to look like the sort of map a Dungeon Master draws for his campaign) and in that context I think they look fine:
But they don't match the style of the new mountains.

The D&D style has an SVG filter on it to make the hills look more amateur-ish by adding some noise; without that they're very regular:

To make the hills more suitable for the new mountain style, the first thing I tried was to apply some of the usual perturbations and so on to the arcs:

That looks a little better, but without some shading it still doesn't work very well with the new mountain symbols.  I don't feel like writing the shading code again, but the code for the new mountain symbols is pretty flexible, so maybe I can make that produce hills.

To start with, I reduce the size of the mountains and turn off merged mountains:
I don't want any of the decorations (like the clefts) on the mountains, so let me turn off that:
The outline needs to be heavier, and I don't want jagged perturbations on hills:
Those look kind of neat, but I really want hills to have convex sides to distinguish them more clearly from mountains.  Fortunately I can do that by simply changing the sign on the side offset:
I also like hills to be broader rather than peakier, so I'll tweak the proportion of the width to the height:
These hills still have a noticeable peak in the center where the sides come together.  To address that, I'll turn on the "round tops" option and add a flat piece at the top of the hills.  The shading is also a little heavy, so I'll tweak that:
This looks pretty good, but the straight shadow line takes away from the roundness of the hill.  I'd like to curve that line to suggest the hill is round.  Up until this point, I've only had to make configuration tweaks to turn mountains into hills, but this requires a code change.  However, this is the same operation I apply to the sides when I make them concave (for mountains) or convex (for hills).  So it's pretty easy to apply that to the shadow line.

At this point I can turn on the mountain-merging feature again:
Which I think also looks okay.

I had the idea at this point to add a cast shadow to the hills (and mountains).  I've seen this used on a few maps to add a more "3D" effect (particularly if the map has shadows under tree symbols).  For this line-drawing style, it's pretty easy to add a cast shadow -- it's just a line from the base of the hill that tapers off to nothing:
Here's what it all looks like on a map (with labels turned off for clarity):
These look okay -- certainly better than the original hills -- and I like the cast shadow, but these still look pretty lumpy, so next time I'm going to try another approach.