Monday, August 12, 2019

Looking in the Mirror

In which the Author turns a Critical Eye upon his owne Works

Most everybody -- myself included -- is sick of map borders at this point, and over on The Reddit one of the suggestions for a new topic was to post some maps and provide some self-review and criticism.  That's something I can do fairly easily over the summer while spending most of my time on other projects. 

 I fired up the generator and here is the first map that came out:


(Hopefully you can click through on the map to see it full size; if Blogger does something awful I apologize.)

I'm going to try to focus on areas where I think there's something interesting to say, but before I get into particulars, a few general observations.  I think there's a lot to like about this map.  It's well-composed and fairly pretty.  It's using one of my standard map styles, so the colors all work well together.  The subtle textures on both the land and the ocean keep the map from feeling too graphical.  The overall land shape is interesting, there's a nice variety of coastline from smooth to more fractal, and a good mix of islands.  None of the place names seem particularly stupid or artificial (although “Main" forest is borderline).  If I'm being honest -- and hey, why not? -- in my opinion this map is better than most maps you'll see on reddit/mapmaking and not in the bottom tier of the maps at Cartographer's Guild -- but also clearly not at the level of the better mapmakers at the Guild.  I'd say it's at the level of “talented amateur," perhaps.

Now for the problems and discussion...

While the overall land shape is interesting, and there's a nice mix of bays, points and islands, I'm not at all sure this is very “realistic" geographically.  At a regional scale almost any land shape is arguably realistic, and I don't think this map rings any strident alarm bells.  But at the same time, something like this at a larger scale would probably seem pretty artificial.  I haven't yet done a lot of work on continental scale maps, but this is definitely something that will need to be addressed at that scale.  Or I could just stick with regional maps :-)

Overall the label placement on the map is very good.  If you look at the labels you'll see that they're nicely spread out but still clearly attached to features.  There's one major labeling problem on the map -- “The Shellback's Croft."  This labels the unpopulated coastline along the southern part of that shoreline, but the label has been placed at an angle to, and crossing, that shoreline.  The reason for this is pretty clear -- it's not possible to place the label along that shoreline without crossing a coast -- either the coast of the large island at 2 o'clock or the shoreline coast.  It would have been better to place the label crossing the island, but Dragons Abound gets this wrong.  As I've noted before, labels are really hard.  I can make tweaks to fix this particular problem, but getting the right criteria to make labels always look perfect is very difficult.

Another label-related problem is the barrier islands on the lower right corner of the map.  The barrier islands are actually placed a bit off the visible area of the map, with the result that label for the islands ends up on the mainland and orphaned from the reference feature.  This happens because Dragons Abound doesn't display the actual edge of the map (where odd things can happen).  The solution to this problem is to not create barrier islands on the edge of the map; it turns out the code was sort-of trying not to do this, but not checking exhaustively.

One of the striking features of the map is the compass right in the center of the map.  I recently added the functionality to treat a map compass like a label.  During the label-placing process, the compass moves around trying to find a good placement.  In the case of a compass, this means in the middle of the ocean somewhere as far away from other features as possible.  In that respect, this code has worked perfectly -- the spot the compass has found in the center of the large central bay is the biggest area of open water on the map.  Of course, placing the compass dead center on the map is pretty unusual, and not really what I expected.  That said, I don't think it's a big problem, at least on this map.  If I really don't like this and it happens often, I can address it by adding a little heuristic for compasses that tries to push them away from the center of the map.

The final element of the map I'll comment upon are the mountains.  The random parameter selection here has resulted in mountains that are small relative to other icons on the map, and with very abrupt outline changes.  When the mountain icons are small, as they are on this map, it takes many of them to fill the mountain areas, and the result is more of a texture than a few representatives.  (There's something to be said about how the relative sizes of icons on a map affect our perception of the map, but I don't have my thoughts on that organized enough to write about.)  Instead I'll just say that I'm not overly thrilled with these mountains in either scale or shape.  Of course, it's easy enough to tweak the mountain generation parameters to stay away from this particular mountain style, but I have to be careful not to eliminate any creativity in the procedural generation.  (There's something to be said about the interaction between the size of the procedural generation space, the amount of creativity, and the amount of failure, but I don't have my thoughts on that organized enough to write about that, either.)  And something like mountains has lots of parameters, so it isn't always easy to know which parameter to tweak to avoid some unwanted generation.

Here's a version of the map addressing some of those problems:
It turns out that in the previous iteration of this map I had zoomed in to the central portion.  This shows a broader view.  You can compare this version to the previous version to see how the problems were addressed.

Another suggestion I received was to generate a number of maps using the full range of random generation and map styles to explore how well that works.  I don't have time to do a full exploration right now, but I re-generated the above map using a random style and the result was somewhat interesting:
If you compare this map to the previous one, you'll see that the land looks similar but not the same.  However, it is the same land mass -- the difference is that in this second map the sea level is lower, so there is more land showing.  The precise sea level is one of the random parameters in the full range of random generation.

Stylistically, the map is quite different from the previous one.  Most notably, the whole map (except for the edge scale) is in gray.  Forests have been drawn as individual trees rather than masses, and grassland is now empty, but deep grass is rather densely illustrated and given a shading.  There's now a coastal outline and an illustration in the ocean (but no compass).  The labels are all pretty reasonable and well-placed.  It's kind of interesting that on this map the proper names are quite long (“Dronshiduek," "Gupundumgill," “Blenshremmone") while on the previous version they were short (“Su," “Suglen," “Suam").  This is a feature of the name generator; it tries to create names within a similar family of names.

Overall I'd give the map another “talented amateur" rating.  There aren't any glaringly obvious problems.  The style is “okay."  It's not terrible but it isn't particularly compelling either.  Probably the most interesting thing is how different it looks from the original style.

Here's another random map:
Unlike the previous maps, this one is using a pseudo-3D representation of the mountains, and no textures on the map, giving it a more modern feeling (somewhat at odds with the forests being displayed as individual trees).  The colors are muted and there are large areas of white space.  The eye is drawn first to the lower left quadrant of the map, and then to “Lechol City" in the middle right.

There aren't a lot of obvious errors in the map -- the most egregious is the “Five Toparchs" label not fitting the label box.  The creek label up near the barrier islands is also very poorly placed.  If I have any overall criticism of the map, it's that it's a little boring.  But I look at a lot of these, so maybe that's just me.
The next map out of the generator is in some ways the opposite of the previous map.  The colors are quite bold.  The ocean in particular has a deep blue color and is mottled by depth.  The land has a speckled pattern.  Mountains are not shown at all, but the geopolitical borders are shown as blurred shading.  Terrain generation has produced a large number of small islands; particularly in the lower right.  This isn't particularly realistic (and doesn't happen often) but it admittedly makes for an interesting map.  Overall the map is quite dense with labels.

There are a number of problem areas on the map.  Most notably, there are two forests labelled even though forests are not being shown!  That's simply a missing logic check.  The geopolitical borders are also odd.  First of all, I don't much like the style used here with fuzzy borders, but more importantly, the shape of the single country (“Sungon") is a little hard to fathom.  The current algorithm for creating countries has some problems that I need to address.  I'm also not fond of the lakes (near the center of the map) here.  I've written previously about the difficulties of doing lakes well, and I've never been entirely satisfied with Dragons Abound's lakes.   To my mind, lakes got worse when I switched over to generating terrain on a more detailed grid; now lakes have the kind of fractal border that some of the coast has, but I don't think this looks right on lakes.  Lakes also don't follow the terrain correctly.  That's not particularly a problem on this map, but still bugs me.  I should probably just turn lakes off altogether until I can do a better job of generating them.  At least there aren't a lot of terrible label placement problems (although “Pa's Point" is in a bad place).

So what general conclusions should I draw from this review?

First, if you have a lot of different, interacting mechanisms driven by random numbers, then it's difficult to get them to all work correctly every time.  This is made worse if many of the mechanisms are procedural generation.  In procedural generation -- or at least in my interpretation of it -- we try to create mechanisms that can generate a wide variety of different results.  Often that results in a some bad results.  When a lot of these mechanisms are used simultaneously, there's a good chance one of them will throw up a “bad" result.

There are a couple of ways to address this.  First, I can tune the procedural generation as carefully as possible to limit the bad results without (hopefully) eliminating the creative results.  Second, I can post-edit out the bad results -- I can generate maps and simply ignore the ones that I don't think are good.  In practice, I do some of both but should probably work harder on the tuning.

Second, it's clear that labeling and label placement remain difficult problems.  I've added some rudimentary interaction capability to some kinds of labels that allow me to move them or regenerate the name, but it would be useful to have that capability for every label.  Azgarr has slowly turned his map generator into an interactive tool, and while I'm not interested in doing that, I see the appeal of having those capabilities.

I'm curious to hear what other people see in the maps, both good and bad, so I hope you'll share your thoughts in the comments or on The Reddit. 


Wednesday, July 10, 2019

Map Borders (Part 15)

The last item on my map borders TODO list (ignoring bugs and items I've added since I started this) is corner boxes.  Many maps have some sort of decoration on the corners of the borders.  Corner boxes are typically small frames that enclose an ornament or artwork.  Here are some examples from my inspiration maps:





The first example here is a complex box that mimics the actual border, but in most cases the corner box is a fairly simple square (occasionally round) frame that encloses an illustration of some sort.

As the first example above illustrates, the corner box is really just a miniature map border.  So the obvious way to draw one is just to reuse the existing map border code.  Unfortunately, I wasn't thinking that far ahead when I wrote the map border code.  Currently, it decides where to draw the border by finding the edges of the map.  To reuse this code for corner boxes, I'm going to have to break that dependency, by replacing references to the map edges with references to a new parameter that will define where to draw the border.  This is the perfect use case for a refactoring tool, but lacking that I make do with find & replace.

With that in place, I can now experiment with drawing borders in other places.  Here's a test where I just draw a simple line border into the (hardcoded) upper left of the map:
So at least the basic capability is there.  Let me move on to actually using these on a map.

The first step is to figure out where to place the corner boxes  The box above is centered on the corner of the map, which is also the inside corner of the map border.  That's not really the right solution.  If you had a very thick border, the corner box would be increasingly off-center (as it is somewhat in the example above).  Instead, the corner box needs to be centered on the map border.  This is a little tricky to figure out, because the Map Border Description Language lets me place border elements quite flexibly, so (for example) the last element is not necessarily the furthest out from the corner.  Fortunately, I already have to figure this out for other reasons, so after the map border is drawn I know where to center the corner box.

Here's an example showing the corner box correctly centered on the map border:

The second step is to decide how big to make the corner box.  The minimum size should not be smaller than the map border, but for very narrow map borders should still be big enough to be a “box."    The maximum size is a matter of taste, but a range up to (say) 20% bigger than the minimum sizes seems reasonable.  Here are some examples showing box sizes with various thin and thick borders:

In that last example, the black and white scale has actually been placed inside the map area, so it is ignored for purposes of placing and sizing the corner box.

The third step is to to block out the map border behind the corner box.  I can do this by drawing a white rectangle before I draw the corner box. This is a little trickier than you might first think, because I need to block out the area underneath the border of the corner box as well.  That's because the corner box border might have spaces in it:
I can't just block out the interior of the corner box, or the map border will show through between the two lines of the corner box.  However, (just like the map border) I don't know how big the corner box is until after I draw it, which makes it hard to block out before I draw it!

The solution is to draw the corner box first, draw a white box the same size on top of the border, and then pop the corner box drawing back up on top of the white box.  That gets me to here:
So far I've just been using a hard-coded border for the corner box.  I'd like to have some variety in the borders of the corner boxes, although since the corner box is small, the border cannot be too wide.   I took the grammar I'm using for map borders, eliminated most options, and modified the patterns option to create only very tiny patterns.  Most of the corner boxes are single or double lines, but other variants are now possible:
Of course there are combinations that don't look very good:
After some experimenting, I've modified the rules so that thin map borders get thin corner box borders, and to add a chance for the corner box to have the same border as the map.  The latter produced this interesting map:
Here Dragons Abound is trying to fill the corner box sides with the braid, but they're too short to fit more than a single braid.

Corner boxes are a nice embellishment by themselves, but they're usually filled with an illustration to add more interest.  Dragons Abound could add a canned illustration to the corner box, and while I may add that as an option, it's not really procedural generation.  Dragons Abound's ability to create illustrations are somewhat limited, but there are a few options.

One possibility is to draw a Celtic knot within the corner box:
For the moment I'm using a 7x7 braid.  I could make this a random knot, but at this size that doesn't provide a lot of interesting variety.

Another easy option is to repurpose the flower shape that I'm using in pattern borders:
Same thing for stars:

One of my inspirational maps has runes in the corner boxes, so let me implement that.  I have a variety of text-related utilities, so some of this is just finding some good rune fonts and adding them to the list of fonts used in the program.

Finding fonts is a bit tricky than you might expect, though.  It's very hard to tell how big a piece of text is in SVG.  You can certainly get a bounding box for the text, but that turns out to include space for descenders (i.e., the bottom part of a lower-case g, for example) and some space at the top of the text, whether your actual text is using that space or not.  So doing something like centering text in a box is much more difficult than you might expect, because there will be unexpected extra space above and below the characters.  Even worse, if the font doesn't have the proper metadata (and many free fonts you'll find don't) then this measurement is even worse.  Long story short, before I can use a font in the program I have to play with it to see how well it actually works.  So picking out some rune fonts involves finding them, downloading and installing them, trying them out one at a time in the program. It all takes much more time than I would like.

But once I have a few fonts in place, I can draw a random character from the font in the corner box:
Because characters are usually taller than they are wide, two characters often fit into a square box as well as one:

With the flower and star illustrations, it looks best to have the same illustration in each corner.  For runes, it also looks okay to have a different rune in each corner:
That's about it for the easy illustration options.

To wrap up, if you go back to the very first corner box example in this posting:
You'll see that this corner box is actually recursive -- the corner box itself has corner boxes!  So the question you're asking yourself right now, is “Is Scott crazy enough to implement recursive corner boxes in Dragons Abound?!?"

Well... no.  Sorry :-).  So I believe that's about it for corner boxes for now.  Next time will probably be clean-up and bug fixes for borders.


Monday, June 17, 2019

Map Borders (Part 14)

(Author's Note: I have an unusually busy summer this year, with much personal travel, house projects, and other tasks to compete with Dragon's Abound, so posting will be sporadic.  I hope to get back to a more regular schedule in the fall.)

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:
Note that the pattern goes into both corners.  That's because I've based this on the line drawing routine.  Lines go all the way into the corners but end with a miter so that they meet up correctly.  That won't work for knots, so I'm going to have to do something else.

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:
However, that's going to look a little odd once the right and left sides get drawn in.  A better solution might be to stay out of the corners entirely with knots:
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:
The fundamental problem here is that I can't easily stretch or compress a knot except by lengthening it or shortening it by two elements.  The square aspect of the individual elements is pretty much built into the knot layout.  What I can do is tweak the length of the knot by changing the width of the knot, since that will also change the length. The Celtic knot map border element specifies a width (e.g., 25 pixels) but I vary that slightly to make the length work out better.  One subtlety is that I can only make the pattern narrower; if I make it wider it might unintentionally end up overlapping some other part of the border.
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:
I also need to experiment to see how small I can make the knot and have it look okay at map size.  This is about as small as works well:
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:
It might be that I can make these tighter if they're on a black background.

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:
That also looks nice.

Having looked at four cords and two cords, I should try six cords.  This is impressively complex:
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!

Monday, June 3, 2019

Map Borders (Part 13)

When I left off last time, I'd gotten the basic display capability working:
One problem that you can see above is that there are some artifacts where the individual segments of the pattern join up.  This occurs because the browser introduces some anti-aliasing artifacts where two SVG elements meet exactly.  One way to address this is to overlap the elements slightly to try to eliminate the seams.  There's a bit of black magic and experimentation to figuring out how much you have to overlap and in what order (and worse, this varies depending upon the magnification of the SVG), but after some tweaking I have removed most artifacts:
I've got a growing list of things to do with Celtic knots, but before I tackle anything else I want to add shading to accentuate the crossings.  The basic idea can be seen on this tutorial page:
The idea is to add some shading to either side where a cord passes under another cord.  There are various ways to do this in SVG, but the simplest for me is to draw another line on top of the cord and fill it with a transparent gradient:
I've left it pretty subtle here, but if this lacks impact I can go back and tweak it as necessary.

While I was adding that code I also parameterized various other constants -- a couple of which control whether I use “hand-drawn" lines.  There are some challenges with using hand-drawn lines where something is pieced up of different parts and where lines are laid atop one another.  The code takes care of making sure that end points don't move around, but it's possible that angles can change so that segments no longer meet smoothly.  So if I use it, I will have to use a fairly small amount of jitter.  Here's an example with about the maximum decent perturbations:
I could also vary the width of lines, but that would be even more problematic so I don't think I'll allow that as an option.

Next I'm going to implement a more rounded style of knot.  This works the same as the existing style, except the cords that meet horizontal and vertical walls are replaced by quarter circles.  I already have a routine to generate circular arcs, so this change is mostly a matter of figuring out the right arcs and then tweaking them so that they don't reveal any anti-aliasing artifacts where they overlap.
Another common style squares off the turns in cords, as in this example:
This is a little trickier to implement, because how a cord that meets a wall is drawn depends upon whether it's neighbor in that direction is also meeting a wall.  For example, cords that meet horizontal walls work normally (as they do in the first style) except if they have a vertical wall to the right or left, in which case they go straight into the corner:
Here I'm showing just crossings and one half of the pattern for a horizontal wall.  Along the middle bottom of the pattern it looks the same as in the first style.  But in the corners, where there's a wall next to it, it goes straight into the corner.

(In the middle of implementing this style, I decided to refactor the code so far to simplify some of the special cases that have built up.)

After implementing all the other cases:
The appeal of this style is that it more clearly defines the boundaries of the knot.  Unfortunately, the anti-aliasing problem in the mitered corners here is very hard to get rid of completely.  Close inspection of the knot above will show some problems.  I tried a number of different approaches with no real luck.  I'm going to declare myself defeated on this issue for the moment.

There's one more style capability I want to implement before I move on, and that's the capability to blur the cords.  (The purpose of this will become evident below.)   This is a little tricky because of the way SVG implements blur.  If you apply blur to an SVG element, the blur can spread anywhere within the object's bounding box.  So if I just apply a blur to cord, it smears into the adjacent areas:
You can see how the cord no longer has sharp edges because the white has blurred into the black spaces, and even over the red outline.  To prevent this, I have to clip the blur to shape of the cord. (And hopefully I can do this piecemeal to each of the segments of the cord.)
Here I've created clipping paths for some pieces of the knot and applied a blur.  You can see how the blur now ends sharply at the edge of the piece.

Unfortunately, as I continue implementing this I discover that I can't in fact implement it piece-meal:
As you can see, there are some pretty obvious artifacts at the joints where the clipping areas meet.  (The same locations as the anti-aliasing problems!)  In retrospect, using clipping areas was probably a poor choice.  Difficult to implement and with some predictable problems.  A much better choice is to use a mask.  It's also easier to construct; I just draw into the mask at the same time I'm drawing onto the screen.  I like to leave at least a few stupid dead-ends in these write-ups so that you don't think I'm some kind of super-human coder.  I'm definitely not!

With the mask in place:
If you compare this blur to the earlier full pattern blur, you'll see this one stays cleanly within the pattern.

So why go to all this trouble for blur?  With blur and careful color choice, I can create a faux-3D style:
I can also draw the same knot in gray underneath this knot, offset it and blur it to create a shadow:
I'm not sure this is very useful for map borders, but it is pretty!

That's enough styling (for now).  Now I want to move on to a few capabilities I'll need to use knots in borders.

The first capability I want is to be able to turn a knot 90 degrees.  This way I can draw a knot along the top border, turn it 90 degrees, draw it along the right border and so on.  One way to do this is to always draw it horizontal and then use an SVG transform command to rotate the whole thing.  I might well end up doing this, but I'd like to explore the option of actually rotating the knot pattern before I draw it.

Recall that the knot pattern is just an array filled with values for CROSSING, VERTICAL, and HORIZONTAL.  To rotate that 90 degrees, I need to create a new array with the x and y sizes swapped, and then copy values into that new array.  The non-obvious part is that when copying, I have to swap VERTICAL walls for HORIZONTAL walls, and vice versa.
Okay, so now I can rotate the knot if necessary.

Another feature I need is to be able to repeat a knot to fill a particular length.  For example, to take the above knot and repeat it three times.  Obviously I can draw the knot repeatedly end to end:
But that leaves obvious seams where the knot begins and ends.  What I'd like to do is eliminate the seams by connecting the knot end to end to create a single bigger knot.  This involves repeated copying of the knot and replacing the end walls with crossings (lower image):
Right now my implementation is limited to whole numbers of repeats.  I can imagine situations where it might be nice to have a partial repeat, but I'll cross that bridge if I get to it.

The next feature is the capability to save and restore a knot.  I'm not entirely sure I'll want to be able to save a particularly good knot to use over again, but it doesn't seem too far-fetched.  The knot is defined by it's dimensions and the pattern of crossings and walls encoded in the array.  The array can be saved as a long string of characters representing crossing and walls.  The sample knot I've been using comes out like this:

25,5,.|.|.-.-.-.X.X.-.X.-.|.X.-.X.-.X.X.-.|.-.X.X.-.X.-.X.X.-.-.-.|.X.-.-.-.X.X.-.X.-.X.X.-.
|.-.X.X.-.X.-.|.X.-.X.-.X.X.-.-.-.|.|.

Long, but not intended to be particularly human-friendly.  Restoring involves reading the dimensions and making sure the knot is the correct size, and then loading the long string into the array that represents the knot.

The last (?) feature I know I'll need is to create a random knot.  At some level, creating a random knot is just taking a vanilla knot and randomly dropping in some vertical and horizontal walls (handcrafted on top, random below):
But as this example shows, there are some constraints that need to be met.  The “gaps" need to be maintained, and (for my purposes anyway) the walls around the edge of the knot need to be maintained.
That produces a legal knot, but it's not necessarily very appealing.  And, as this example shows, vertical walls in the same column can chop the knot in two, which probably isn't what I want.  I don't really want a completely random knot; I want one that is semi-random but still “beautiful."

I'm not exactly sure what makes a knot beautiful but one element is symmetry.  Since I'm generating long knots, I will use mirror symmetry on the X axis.  This is easy to implement:  Every time I place a wall, I also place the same wall at the symmetrical position:
This goes a long ways towards creating an acceptable knot.  I can still get placements that chop the knot into pieces:
This is actually three knots.  But I'm not sure this is bad.  Here is another example of a knot getting broken into separate pieces:
This also seems okay to me.  So maybe symmetry is all I need to create an acceptable random knot.  I'll at least start with this and see how it works out.

A sampler of styles:

Next time I'll start working (at long last!) on using Celtic knots in map borders.