Monday, April 11, 2022

Map Compasses (Part 16): More Rings

I'm back from watching the NCAA basketball tournament and a Spring Break trip to St. Lucia, so it's time to get back to my blog series on implementing procedurally-generated map compasses!  Last time I added scales to the two-part compasses:

There's still a lot more potential in the rings, so let's continue to work on those.  One of the Suggestions to Explore I wrote last time was to implement some rings with radial elements, as in this example compass:

You can see three rings with radial elements in this example, but the general idea is pretty clear:  a ring of radial elements, optionally enclosed in circles or on a dark circle.  As usual, I'll start with a very simple version:
# Radial element rings
<radialRing> => <radialElementsOnly>;
<radialElementsOnly> => RCIRCLE(0, 32, 2, 0.5, "black", "white");

You can refresh your memory on the RCIRCLE command by looking at cdl.ne, but the arguments are the starting point, the number of circles, the radius and the line width and colors.  This just draws a ring of small circles as so:


This looks fine, but I don't like the look when intercardinal points go over the ring:

which I didn't love when it happened to the other sorts of rings either, so I'm going to go back and tweak the spacing for the start of the intercardinal points to keep that from happening.  When there's a second ring in the compass, the radial elements are touching the second ring:
This happens because radial elements don't advance the cursor.  I need to do that manually.  
<radialElementsOnly> => RCIRCLE(0, 32, 2, 0.5, "black", "white") SPACE(2);
Now I'll do some Level 1 elaboration on the number of circles, size, line width, etc.  I won't walk through it in detail, but this is what I ended up with:
# Radial element rings
<radialRing> => <radialElementsOnly> ;
<$relementSize> => `Utils.randRange(2,5)`;
<relementLineWidth> => 0.5 | 1;
<numRadialElements> => 16 | 24 | 32 | 40;
<radialElementsOnly> => RCIRCLE(0, <numRadialElements>, `<$relementSize>/2`,
		     		   <relementLineWidth>, "black", "white")
			SPACE(`<$relementSize>/2`);
I found that I only liked multiples of 8 for the number of elements, but you can experiment with other values and see what you think.  (The reason I'm using half the <$relementSize> here is that RCIRCLE takes a radius rather than a diameter like the other radial elements.)   Now I'll do the obvious Level 4 elaboration and add the other kinds of radial elements:
<radialElementsOnly> => SPACE(`<$relementSize>`)
		     	RCIRCLE(0, <numRadialElements>, `<$relementSize>`,
		     		   <relementLineWidth>, "black", "white")
			SPACE(`<$relementSize>`) |
			SPACE(`<$relementSize>`)
		     	RTRI(0, <numRadialElements>, -<$relementSize>, <$relementSize>,
			        <relementLineWidth>, "black", "white") |
			SPACE(`<$relementSize>`)
		     	RTRI(0, <numRadialElements>, -<$relementSize>, <$relementSize>,
			        <relementLineWidth>, "black", "white", "black") |
			SPACE(`<$relementSize>`)
			RDIAMOND(0, <numRadialElements>, -<$relementSize>, <$relementSize>,
				    <relementLineWidth>, "black", "white") | 
			SPACE(`<$relementSize>`)
			RDIAMOND(0, <numRadialElements>, -<$relementSize>, <$relementSize>,
				    <relementLineWidth>, "black", "white", "black") |
			SPACE(`<$relementSize>`)
		     	RCIRCLE(0, <numRadialElements>, `<$relementSize>`,
		     		   <relementLineWidth>, "black", "black")
			SPACE(`<$relementSize>`) |
			SPACE(`<$relementSize>`)
		     	RTRI(0, <numRadialElements>, -<$relementSize>, <$relementSize>,
			        <relementLineWidth>, "black", "black") |
			SPACE(`<$relementSize>`)
			RDIAMOND(0, <numRadialElements>, -<$relementSize>, <$relementSize>,
				    <relementLineWidth>, "black", "black");
The only thing that's a little tricky here is getting the spacing right.  RCIRCLE is centered on the radius, so a half move before and after leaves the circles right at the radius.  But RTRI and RDIAMOND are centered at the bottom of the element, so when I'm using a negative height (as I am here to make sure the triangle points outward) then I need to do the full space before drawing.  
Since I've done the all-white and the half-white/half-black versions, I might as well throw in the all black versions as well.

That's pretty good for radial elements only, so now I'll work on the other variants.  The first is to frame the radial elements in two circles:
<radialElementsFramed> => <thinCircle> SPACE(`<$relementSize>/2`)
		       	  <radialElementsOnly>
		          SPACE(`<$relementSize>/2`) <thinCircle>;
That's just drawing the radial elements centered between two circles.
I didn't include radial lines in the stand-along radial elements because I didn't think they would look good, but I can include them here between two circles.
This can end up looking something like a scale.  The drawing of this is a little different because I want the line to connect to the framing circles.  It took me a few tries to get that right, and in one of those tries I was short of the inner circle:
That looks okay to me so I kept it.  I find this happens fairly frequently -- I'm debugging the code and inadvertently stumble across something I hadn't been intending but like anyway.

And I might as well include doubled circles as I did for scales:
<$frameCircle> => <thinCircle> [2] | <thinCircle> SPACE(<$thinWidth>) <thinCircle>;
<radialElementsFramed> => <$frameCircle>
		       	  SPACE(`<$relementSize>/2`)
		       	  <radialElementsPlus>
		          SPACE(`<$relementSize>/2`)
			  <$frameCircle>;
Note that I did the trick of using a one-time rule to make sure the doubled circles on each side of the radial elements are identical.

Another variation I can try for radial elements is to alternate elements, by creating a ring that is (say) alternating circles and triangles.  To do this I need to draw two sets of radial elements with half the number, and one of them offset so that it falls between the others.

This also often happens during the development of procedural generation -- an elaboration you applied elsewhere easily applies to some new piece as well.  As you work, your pace of development increases as you leverage what you've done before.  It's also a way to develop a “style" -- for example, if I used these doubled lines throughout the procedural generation, you'd likely start to recognize that as indicative of this generator.

Now I'll add in the other variant with a dark background.  This is kind of interesting because I have to draw the radial elements in white so that they show up on the background.
<radialElementsDark> => CIRCLE(`<$relementSize>*2`, "black", "none")
		     	SPACE(`-3*<$relementSize>/2`)
		     	<radialElementsReversed>
			SPACE(`<$relementSize>/2`) |
		     	<thinCircle> SPACE(<$thinWidth>)
			CIRCLE(`<$relementSize>*2`, "black", "none")
			SPACE(<$thinWidth>) <thinCircle> 
			SPACE(`-3*<$relementSize>/2-<$thinWidth>*2`) 
		     	<radialElementsReversed>
			SPACE(`<$relementSize>/2 + <$thinWidth>*2`);
<radialElementsReversed> => SPACE(`<$relementSize>`)
		     	RCIRCLE(0, <numRadialElements>, `<$relementSize>`,
		     		   <relementLineWidth>, "white", "white") | 
			SPACE(`<$relementSize>`)
		     	RCIRCLE(0, <numRadialElements>, `<$relementSize>`,
		     		   <relementLineWidth>, "white", "black") |
			SPACE(`<$relementSize>`)
		     	RTRI(0, <numRadialElements>, -<$relementSize>, <$relementSize>,
			        <relementLineWidth>, "white", "white") |
			SPACE(`<$relementSize>`)
		     	RTRI(0, <numRadialElements>, -<$relementSize>, <$relementSize>,
			        <relementLineWidth>, "white", "black") |
			SPACE(`<$relementSize>`)
		     	RTRI(0, <numRadialElements>, -<$relementSize>, <$relementSize>,
			        <relementLineWidth>, "white", "white", "black") |
			SPACE(`<$relementSize>`)
			RDIAMOND(0, <numRadialElements>, -<$relementSize>, <$relementSize>,
				    <relementLineWidth>, "white", "white") | 
			SPACE(`<$relementSize>`)
			RDIAMOND(0, <numRadialElements>, -<$relementSize>, <$relementSize>,
				    <relementLineWidth>, "white", "black") | 
			SPACE(`<$relementSize>`)
			RDIAMOND(0, <numRadialElements>, -<$relementSize>, <$relementSize>,
				    <relementLineWidth>, "white", "white", "black");
Here I'm centering the radial elements on a dark circle twice the width of the element.  I'm not sure if I like having a dark side to triangles and diamonds in this scheme:
But I don't hate it either, so I'll leave it in until I do.  I might as well throw in the double line style here as well.
You can see the rule for this in compass.rules.  

Another elaboration I can do for radial elements is to alternate elements, e.g., make a ring that alternates circles and triangles.  I don't have an example compass that does this, but I'm thinking of something like this:
There are a couple of tricky aspects to this elaboration.  First, the radial elements are potentially of different sizes and I want them both to be centered at the same distance from the center of the compass.  Second, ideally I'd like the two elements to be different, although it would be acceptable if they were the same.

It's difficult/impossible in a context-free grammar to implement “pick this randomly but make sure it isn't the same as this other thing."  In this case, I could do it by writing out rules for all the specific cases, e.g., something like:
<alternating> => <diamond> <firstDiamond> | <circle> <firstCircle> | <triangle> <firstTriangle>
<firstDiamond> => <circle> | <triangle>
<firstCircle> => <diamond> | <triangle>
<firstTriangle> => <circle> | <diamond>
Where I limit my choices for the second element based upon what I had chosen for the first element.  Of course, this gets cumbersome as you get more and more choices.

In this case, I decided to do something different.  I made the first element chosen small and the second element chosen larger.  This way, even if I happen to get the same elements for both, there will still be a visual difference:
I chose this partially because this also helps solve the second problem.  Since I know the second element is always the biggest element, I can use the size of that element to center both elements.  Here's an edited version of the rules:
<radialElementsAlternating> => <radialElementsFirst> <radialElementsSecond>;
<$numAlternatingElements> => 8 | 12 | 16 | 20;
<$relementFirstSize> => `Utils.randRange(4,5.5)`;
<$relementSecondSize> => `Utils.randRange(5.5,7)`;
# Needs to return to the start radius
# Firsts are smaller than seconds, so they need to center in <$relementSecondSize>
<radialElementsFirst> => SPACE(`<$relementSecondSize>/2+<$relementFirstSize>/2`)
		     	RCIRCLE(0, <$numAlternatingElements>, `<$relementFirstSize>`,
		     		   <relementLineWidth>, "black", "white")
			SPACE(-`<$relementSecondSize>/2+<$relementFirstSize>/2`) |
			... ;	    
<radialElementsSecond> => SPACE(`<$relementSecondSize>`)
		     	RCIRCLE(`Math.PI/<$numAlternatingElements>`, <$numAlternatingElements>, `<$relementSecondSize>`,
		     		   <relementLineWidth>, "black", "white") |
			... ;
First I have some one-time rules that select the sizes of the elements, and I pick ranges so that the second element will always be larger.  Then I draw the first elements,  moving in (and then back out) to the correct spot to center inside a ring the size of the second element.  Then I draw the second element the same way (although I don't have to move back out).  I've only included the options for RCIRCLE here but you can see the full rule in compass.rules.

Another variant type of ring occurs to me -- one where the radial elements are strung out on a circle like beads.  I don't have an example compass that does this, but the idea is straightforward.  Like the reversed colors variant above, I'll put a circle down behind a ring of radial elements.  But instead of a fat circle that fills up the ring's width, I'll do a thin one.
<radialElementsNecklace> => SPACE(`<$relementSize>/2+0.5`)
			    CIRCLE(1, "black", "none")
			    SPACE(`-(<$relementSize>/2+0.5)`)
			    <radialElementsPlus> 
And of course I might as well do this with alternating elements and the other variants as well, which gives me this lovely example:
With shaded diamonds differing just a bit in size.  And of course I will add my signature double line style:
Lastly, inspired by the ornaments on the directional pointers on this compass:
I'll add a new radial element composed of radial circles and diamonds together.  The tricky thing here will be getting the sizes and placement of all the pieces correct.  I'll want this to act like a single element of the specified size, which will mean adjusting the sizes of the pieces and placing them accordingly.

To start with, I'll write a rule to place radial circles as normal, but I'll only make them half the size of the full element.  Since the bottom of the element will remain the same they'll be in the bottom half of the space for the full element:
	SPACE(`<$relementSize>`)
     	RCIRCLE(0, <numRadialElements>, `<$relementSize>/2`,
     		   <relementLineWidth>, "black", "white");
Here I've framed the space so you can see the placement.  Now I need to put a triangle in to fill up the rest of the space for the element, with the bottom of the triangle at the widest part of the circle.  If you do the math, this means I have to move the triangle up 1/4 of the element size, and make the length of the triangle 3/4 the element size, and the base of the triangle 1/2 the element size (so that it is obscured behind the circle).
	SPACE(`3*<$relementSize>/4`)
     	RTRI(0, $numRadialElementsShared, -`3*<$relementSize>/4.1`, `<$relementSize>/2.2`,
$relementLineWidthShared, "black", "black") SPACE(`<$relementSize>/4`) RCIRCLE(0, $numRadialElementsShared, `<$relementSize>/2`, $relementLineWidthShared, "black", "black");
Notice in the rule above I created one-time values for the number of elements and the line width.  Without that, those might mismatch between the circle and the triangle.  You'll also notice I actually made the triangle dimensions a little smaller than needed to avoid the edges of the triangle from peeking out from behind the circle.  My radial elements are much smaller than in the example compass.  All black as above works okay, but white and black as in the example compass is really too small to see clearly:
So I'll just leave this black.  Here's another multi-part radial element that works better at this scale:
	SPACE(`<$relementSize>`)
     	RCIRCLE(0, $numRadialElementsShared, `<$relementSize>`,
     		   0.5, "black", "white")
	RLINE(0, $numRadialElementsShared, `-<$relementSize>`, 0.5, "black");
A reminder that if you want to follow along from home, you need to download the Part 16 branch from the Procedural Map Compasses repository on Github and get the test web page open.  Part 1 of this series has more detailed instructions, but the short version (on Windows) is to double-click mongoose-free-6.9.exe to start up a web server in the Part 16 directory, and then select the test.html link.  You can also try out this code on the web at https://dragonsabound-part16.netlify.app/test.html although you'll only be able to run the code, not modify it.  Still, it's fun to try it out a few times and see what you get!

Suggestions to Explore

  • I've implemented two “pseudo" radial elements created by composing two of the existing elements.  Invent some additional pseudo elements and implement them.

  • An interesting variant of a radial element is a circular repeating pattern as in this example:


    Or more simply, a line that zig-zags up and down to make sawteeth.  How would you implement these patterns?

No comments:

Post a Comment