Monday, March 18, 2019

Map Borders (Part 6)

(As some of you may know from my previous life, I'm a college basketball fan -- specifically UCLA -- and every year attend March Madness at some random site with a group of long-time Internet friends.  This year we're off to Jacksonville.  I'm a few posts ahead on the site, so there shouldn't be any interruption of your regularly scheduled content :-)

Now that I have implemented all of the MBDL, I had intended to get started on the procedural generation of map borders, but I'm not sure I actually know what I want to do there yet, so I'm going to drag my feet a bit and implement a couple of other MBDL capabilities.

When I first discussed how to handle corners with patterns, I talked about a couple of different approaches.  I ended up implementing mitered corners, but another option I mentioned was to stop the pattern short of the corner, as in these examples:

This is often used when the border pattern is some sort of asymmetric figure, runes, or something else that can't be rotated 90 degrees and still match up with itself.  But obviously it will also work with geometric figures.

This could be an option I select before generating the border, but it would add some flexibility if I could switch this on for one part of the border and use a mitered corner for another part.  To do that, I'm going to have to add a new command to MBDL.  I suspect I might come up with other options for different parts of the border, so I'll add a general option command:
element -> "O(MITER)"
element -> "O(STOPPED)"
element -> "O(STOPPED," number ")"
(Again, leaving out whitespace and some other details for clarity.)  Right now the only choices for options are for “MITER" to use mitered corners and “STOPPED" to use stopped corners.  If there's no value given to STOPPED the program stops the pattern some reasonable random distance away from the corner.  If there's a value given it stops that far away.

If STOPPED corners are in effect, I stop drawing the corner pattern away from the corners.  Here's what that looks like:
Here I've used MITER for the alternating black & white scale, so it wraps around the corner.  For the red ball / black square pattern inside the gold line (and for the circles pattern on the outside of the border), I've used STOPPED.  You can see that the pattern stops short of the corner for those two patterns.

However, there are a couple of problems.  First, you can see that on the left side of the border the element nearest the corner is a black square, while on the top side of the border the element nearest the corner is a red ball.  This happens because the corner is near the start of the repeat on one side and the end of the repeat on the other side.  But it looks odd.  It would be better to have the corners be symmetric, even if it means having an extra element on one end of the pattern.  Second, you can see that the pattern on the outside of the border (the half-circles and black dots) also ends one repeat away from the corner.  But since the length of that repeat is much less than the length of the red ball / black square repeat, they end at much different places.  It would probably look better to have all the patterns that stop do so the same distance from the corner.

To fix the first problem, I need to add one extra repeat of the first element of the pattern at the end of each side of the border.  But it's actually a little trickier than that because I may have used a negative horizontal offset within the pattern to overlay multiple elements (as I did here).  So I also need to add an extra repeat for any element in the pattern that has the same start location as the first element.
Now the pattern is symmetrical around the corner and looks much better.

Next I have to keep track of the longest STOPPED pattern and set back every STOPPED pattern that distance:
Now the circle pattern is set back more, but still doesn't align with the red balls pattern.  Why?  It's because the circle pattern is farther out from the map edge, and the length of the border is longer there than closer in where the red balls pattern is drawn.  To fix that, I need to also set back patterns based upon their offset from the map edge.
Now everything lines up nicely.

The second option for corners that I talked about was square offset corners, such as this one:
This will be considerably more difficult to implement!

The grammar for this option, however, is straightforward and uses the Option opcode:
element -> "O(SQOFFSET)"
element -> "O(SQOFFSET," number ")"
The number indicates the size of the square offset for an element at the map's edge; elements at different offsets have to adjust accordingly.  With no number the program picks a reasonable size for the offset.  Setting the number back to zero turns off the square offset.  This allows borders where some elements use square offsets and others don't, like this border:
As a first step to working on this, I realized I was going to need additional clipping areas, since I'm using clipping to handle the spots where the border changes directions.  SQOFFSET is going to require more complex clipping areas, and separate ones for different elements when SQOFFSET is turned on and off.  Given that clipping areas already introduce unwanted artifacts, this seems like too much work.

When I worked on stopped patterns up above, I implemented an asymmetric pattern fill so that I could have an extra repeat on one end of the pattern.  I realized that this could also eliminate the need for mitered corners.  Instead, I'll draw all my patterns clockwise around the border, starting the pattern in one corner and stopping just short of the next corner.  This will allow me to eliminate the clipping areas.

The major impact of this new way of handling corner patterns is that the first element in the pattern is no longer “split" to the two sides.  If you look at the black & white scales in the maps above, you'll see that there is a white box that runs around the corner.  Now the white box butts up into the corner:
You see maps drawn both ways, so this isn't a huge problem.

To start with, I implemented offsets for lines, which is just a matter of turning the line around the appropriate corners:
As suggested, I can combine offset corners with regular corners as in the reference map above:

Turning patterns around the corners is of course more complicated.  The general idea is to draw from one corner to just short of the next corner, and then so on around the border until you get back to the beginning.  In theory you only have to draw horizontal and vertical patterns and everything lines up nicely; in practice it's rather a pain to keep track of everything.  In fact I had to rewrite the whole thing twice with pages of notes to get it right, but I'm not going to subject you to the play-by-play.  Instead I'll just show the result:
There's an annoying optical illusion that happens in the corners -- the corner element appears to be uncentered to the outside of the corner.  It's really not, but looks that way because there's visually more blank space to the inside of the corner.

Because the legs of the offset corners are relatively short, it's easy to create an unbalanced pattern in the corner:

Which looks rather awkward at times.  I'm reminded of the old joke:
PATIENT: “Doctor, it hurts when I do this."
DOCTOR: “Well then don't do that."
So I'm going to try not to do that.

I wouldn't normally run a map scale around an offset corner, but if I need to do so, I have to use the option that stretches the pattern to make the map scale fit around the corner without gaps between the boxes:
You can see the size of the scale boxes noticeably change on the offset corners as a result.  So this is really not a good option.  (BTW, there's also a bug in the balls pattern on the offset corners as well.  Later fixed, but I told  you it was a pain to implement.)

If the pattern is too big to fit onto a side of the offset corner, the algorithm just gives up:
Which is less than ideal, but as I said above “Well then, don't do that."  (It wouldn't be too hard to add shrinking to the stretching algorithm if I really decide I need this capability.)

What happens if I use both offset corners and the option that stops patterns before they reach the corners?  In that case I just stop short of the offset corners:
That seems like a reasonable approach.

That's enough options (for now).

No comments:

Post a Comment

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