Tuesday, May 31, 2022

Adding Compasses to Dragons Abound

 As I documented in a previous series of posts, I recently wrote a procedural map compass generator.  That was partly an experiment in making my code publicly available, so I wrote it as a standalone program separate from Dragons Abound so that it could be more easily used by someone else.  (You can play with it here.)  Now I'm going to integrate that code back into Dragons Abound.

The first step is to import the libraries used by the compass generator.  A couple of these are already used by Dragons Abound.  One of these is Nearley, but Dragons Abound is a couple of releases behind the compass generator.  So the first step is to switch Dragons Abound to the current version of Nearley and see if that breaks anything.  It seems to work fine, at least for the first few test maps I generated.

The second step is to bring all the code into Dragons Abound and see if it will run.  In the compass generator, I had a simple function that ran the compass rules and then drew the compass at [100, 100] and size 75 on the test SVG.  I need to tweak that function to use the correct location and size for the map.  Otherwise the compass code “should" work as is.

But the code immediately dies with rather an odd error, which I trace down to a Compass Description Language (CDL) command that reads:

    SPACE(NaN)

A little more tracing and I eventually figure out that the problem is with a utility function called randIntRange(), which is supposed to generate a random integer in the given range.  I borrowed that function from Dragons Abound, but in the course of the Compass work, I beefed up that function a bit to take a wider variety of more convenient inputs, like so:

// Random integer in a range.
// randIntRange(lo, hi)
// randIntRange([lo, hi])
// randIntRange(hi) lo == 0
function randIntRange(lo, hi=false) {
    if (!hi) {
	if (Array.isArray(lo)) {
	    hi = lo[1];
	    lo = lo[0];
	} else {
	    hi = lo;
	    lo = 0;
	};
    };
    return Math.floor(randInt(hi-lo+1)+lo);
};

The flexibility of Javascript isn't always a blessing, but here I'm using it to create a function that takes any sort of representation of a range and “does the right thing."  This new version is compatible with the old version, so I just have to replace the version in Dragons Abound with the one from the compass generator. 

That fix in place, the next problem is an attempt to set the opacity of the compass.  Dragons Abound expects the compass generator to return an SVG element of the whole compass, but in the compass generator project I wasn't actually using the compass SVG for anything so I wasn't returning it.  It's simple to tweak the generator to draw the compass in an SVG group element and return that.

At this point the code is completing, but still with errors.  Some of these trace back to the size of the compass.  The compass generator expects to make square compasses, but Dragons Abound can handle non-square compasses.  (Because some of the canned compasses it uses are not square.)  This leads to a parameter mismatch, where the compass generator expects to get a single number as the size of the compass, and Dragons Abound is supplying an array of width and height.  But in fact, Dragons Abound is providing square dimensions, so the generator can use either the width or the height as the size of the compass.

At that point, I'm finally getting a compass on the map:

The compass is a *little* too big.  This is mostly another parameter mismatch; the compass generator is expecting the size to be the radius of the compass, and Dragons Abound expects it to be the total size of the compass.  So the compass is twice as big as it should be.


That's better, although still a bit larger than it should be.  This image also illustrates another problem -- the compass should be centered on the windrose network.  If you look, you'll see that the center of the E and the S are centered on lines.  This suggests that the bottom-right corner of the compass box is centered on the windrose network.  And in fact, this is another mismatch between Dragons Abound and the compass generator.  The anchor point for images (like the canned compasses) in Dragons Abound is usually the lower left corner of the image.  For the compass generator, the anchor point is the center of the compass.  So for a procedurally-generated compass, I need to shift the center accordingly.


And with that the basic functionality is working.  However, there are still several additional features to implement.

The first and “easiest" feature is to set the font and size of the labels on the cardinal points.  In the compass generator, these are specified in the RTEXT command.  For Dragons Abound, it's better to control that with the map parameters, so that the compass can use a specific font, or default to the same font as the rest of the map.  So before I can fix the font problem, I first have to modify the compass generator code so that the map parameters structure gets passed down to RTEXT (and all the other CDL commands).

RTEXT can be used to write text other than the cardinal point labels, so I want to be careful to maintain that capability as well.  I decide that if the font specified in RTEXT is “cardinal" I'll then look up the compass font and use that instead:


For the moment, that's all I'll do; I'll still take the font style and weight (e.g., bold, italic, etc.) from the RTEXT command but obviously I can pull them from the map parameters in the future if that seems desirable.

The second and much more difficult feature is to swap out all the drawing routines for the corresponding Dragons Abound routines.  The compass generator uses straightforward SVG routines (such as 'circle' and 'path') to draw on the screen.  This results in very precise, mechanical graphics.  Dragons Abound uses drawing routines that insert some of the imprecision of hand-drawing.  By replacing the precise SVG routines with the imprecise Dragons Abound routines I'll get something that looks a little more hand-drawn.

Mostly this is straightforward.  Dragons Abound has routines to draw lines and polygons, so circles are drawn by constructing a circular polygon and then drawing the polygon.  Here's an example with the “hand-drawn" parameters turned up artificially high:
Of course normally this is set to a more subtle level:

There are slight inconsistencies here that aren't immediately eye-catching (examine the inside circle in the NW quadrant) but still result in something that is more artistically appealing than mechanical precision.  (Well, at least to me; your mileage may vary.)

A feature that Dragons Abound has that isn't in the compass generator is to vary the width of drawn lines, again to simulate hand-drawing, or the imperfections of old printing presses on coarse paper.  Here's an example turned up artificially high:
You can see that the circles behind the compass points vary quite noticeably from thick to thin.  Some care has to be taken with both these effects.  It's all too easy to run lines into each other or otherwise create a mess.  But with the proper setting, this produces a pleasingly imperfect result:  
Of course, I can turn both these effects off if I want a very precise render.  (Now go back and look at the first example compass in this posting.  You probably didn't register it at the time, but if you examine it now you'll see these effects.)

When I wrote the compass generator, I used black and white as the colors.  In Dragons Abound, I will also have a dark and light compass colors, but I might want to use some different colors than black and white to better match the map.  To do this, I replace all the “black" and “white" color names in the compass rules with “line" for the line color, “dark" for the dark fill color, and “light" for the light fill color.  Then before I render the compass, I can substitute whatever color I desire.

Here I replace the white with a subtle cream color to make it work better with the dominant land color:


Lastly, I want to add the capability to use a saved CDL compass rather than generate a new one.  That allows me to use a particular compass as part of a map style, or to select from a list of curated compasses.  (I have something very similar for map borders.)  I don't know if I'll have much use for this; almost all of the generated compasses are acceptable to my eye.  But we'll see.

And that's it.  At some point I'm likely to revisit compasses to improve/extend the procedural generation, but for now I'm happy to have something solid working.

2 comments:

  1. Great ! At last now you can integrate it in maps !

    ReplyDelete
  2. This is really great! Thank you for sharing!

    ReplyDelete

Note: Only a member of this blog may post a comment.