Now that I've spent over a month implementing Celtic knotwork, it's time to integrate it into the drawing of map borders. In my inspirational maps, knotwork usually shows up as a long element that stretches across an entire border edge (and occasionally around the corners):
I have not attempted to run a knot around a corner (TODO!) but I do have the capability to run a knot along an edge, so let me add that as a possible map border.
The first thing I need to do is add a new element to the Map Border Description Language (MBDL) for a Celtic knot. This looks a lot like a line element, since it's meant to stretch from corner to corner.
K(width, cord width, color)
K(14,5,"red")
Here “width" is the total width of the knot, “cord width" is the width of the cords within the knot and “color" is the color of the cord. So when Dragons Abound hits this in an MBDL, it will draw a knot of that width to fill the border from corner to corner. Here's an (ugly) first (actually fifth) attempt:
You might also notice that the knot doesn't actually go all the way into the right corner. That's because the knot has to have an odd number of elements in both dimensions. It needs one more element to fill out the space completely, but that would be an even element so it is dropped.
I could start the knot in one corner and have it stop short of the next corner, like this:
That seems reasonable, at least until (if) I figure out how to make knots join up in the corners.
Now let me address the second part of the problem. The example above stops somewhat short of the corner, for the reasons mentioned above. It's also possible for the knot to run slightly long for similar reasons:
Here the code has made the width of the knot slightly smaller and added an element to make the knot stretch exactly from corner to corner.
That fixes square maps, but what happens when I have a map that isn't square? A pattern that fits the long side will be too long for the short side, and a length tweak like the one above won't be sufficient to cope.
My solution is to use a shortened version of the knot on the short side. To create the shortened version, I'll cut off the ends of the long version symmetrically. So I'll essentially show as much of the middle part of the long knot as will fit. This is a little bit tricky because of how the knot is represented in the array; basically I have to be careful not to turn even columns into odd columns (or vice versa) when shortening the array.
It's a little hard to see variations in the knot with the flat red color, so before I jump into shortening, let me fix the knot display. The code for displaying a knot takes a display representation that is a list (array) of widths and colors, and by building these up properly I can create cords with borders and highlights, etc. But the MBDL I showed above for a knot only takes a single color. Let me change the MBDL specification so that it takes an array of widths and colors:
K(width, displaySpecification)
K(14,[[4.4, "black"] [2.4, "powderblue"]])
This requires writing a grammar for an array of arrays in order to parse the display representation, but in Nearley it's not that difficult.
So with that working, here's an example of an asymmetrical border:
If you compare the end of the left border with the top border, you can see how the top border was shortened to fit in the shorter left border. (It's kind of fun to try to find the exact spot, so I won't spoil it for you :-)
Now that I've displayed a random border, you may notice that the knot wiggles back and forth where straight segments meet up in a row:
This looks okay, but I thought I'd like to have the option to have the line run straight. This is a two step process. I have to change the code slightly so that cords drawn on the top and bottom of a wall are at the same offset, and then I need a special case to extend those cords to meet adjacent cords when they are both horizontal. It turns out that if I do this for the normal style shown above, it essentially makes that style look like the squared knot style, so that's not very useful. However, the same fix for the squared knot style makes it look more consistent and better in my opinion:
The circle style also has the wiggle, so now I have two styles with the wiggle and one without.
That covers square borders, but Dragons Abound also has borders with offset corners. There are three kinds of corner offsets (square, diagonal and round) but making knots run around these corners seems pointless; they normally won't be big enough to accommodate a knot, so I'll just make the knot stop at the beginning of the offset:
This is 22.5 pixels across, which is perhaps a little thicker than I'd like but not unreasonable. The normal and round styles need a little more room to look good, about 28 pixels:
So far every knot I've shown has had four cords. Using two cords doesn't really produce what we think of as a Celtic knot, but it looks kind of interesting:
That could be interesting as an occasional change of pace.
I'll also add the possibility of a knot with no random breaks in it. This produces a regular weave:
but probably too “overkill" for regular use.
Here's an example of the knot in use in a map border:
There's probably some work to be done here with colors and tweaking the various sizes, but for now I'm going to move on to another way to use Celtic knots in a map border.
Recall that Dragons Abound has a border style I call patterns, which consists of repeating elements along the border:
In this border I have stars, flowers and diamond shapes repeating. What I want to do now is add a short Celtic knot as a possible element in a repeating pattern. This is (relatively) straightforward -- adding the Celtic knot as another type of thing that can be in the pattern:
The knot in the example above is a braid. Using a random knot is a little tricky:
In this border I created a random knot every time I drew a knot in the pattern. This looks a little odd. What I want is to draw the same random knot every time. Unfortunately, the structure of this code makes that difficult to do cleanly, so I have to resort to using a global variable to hold the knot between the times when it is drawn:
There are a few bugs to clean up (primarily with shading) but this post is already overdue, so I will end here. Next time, corner boxes and that will be the last of map borders for now!