Sunday, March 27, 2022

Map Compasses (Part 15): Scales

Welcome back to my blog series on implementing procedurally-generated map compasses!  Last time I started working on the rules for procedural generation of compasses, and did an initial set of rules for a “two layer" compass.  So far I've only implemented one type of compass, with pointers and a circular decoration behind them:

I will continue to elaborate on this type of compass by adding some variety to the circular decoration (the “ring").  One new subtype is the scale, as in this example:
A scale is a radial arc sandwiched between two thin circles:
<scaleRing> => <thinCircle> RARC(0, 8, `Math.PI/8`, 5, "black") SPACE(5) <thinCircle> ;
As usual, I'll get one example working and then generalize it.  The above rule gives me this:

A starting point to add Type 1 procgen is to change the number of divisions on the scale.  The above is 8; 16 often works as well:
Although sometimes a fat compass point will completely cover a division of the scale and that looks off to my eye:
The lesson here is probably to cut down a little on the widest compass points, since I don't really love the look of those anyway.
A bit surprising to me, but 32 divisions look good as well.  More surprising is that any even number of divisions looks okay:
This example is 14.  You can see that North and South are on black divisions, East and West are on white divisions, and the intercardinal points are splitting the difference, but if I didn't point it out you probably wouldn't think twice about it.
Odd numbers of divisions really do look odd and I think I can safely avoid those.

Another variation of the scale is where to start the divisions.  In the examples above the divisions are centered at North, but I could slide those over so that they start at North instead:
That looks fine, I guess?  Lastly, I can vary the width of the scale.  I tried a range from 2 to 6 pixels, and that looks okay to me:
That's about it for the scale itself.  At this point I have these rules:
<$scaleDivisions> => `Utils.randIntRange(4,16)*2` ;
<$scaleStart> => 0 | `Math.PI/<$scaleDivisions>/2` ;
<$scaleWidth> => `Utils.randRange(2,6)`;
<$scaleCircle> => <thinCircle> | <thinCircle> SPACE(<$thinWidth>) <thinCircle>;
# A scale
<scaleRing> => <$scaleCircle>
	       RARC(<$scaleStart>, <$scaleDivisions>, `Math.PI/<$scaleDivisions>`,
	       <$scaleWidth>, "black")
	       SPACE(<$scaleWidth>) <$scaleCircle>;
Thinking about the two circles that border the scale, I can dress those up by making them double circles.  It's tempting to write a rule like this that can draw either a single circle or a double circle:
# A scale
<scaleRing> => <scaleCircle>
	       RARC($scaleStart, $scaleDivisions, `Math.PI/$scaleDivisions`,
	       $scaleWidth, "black")
	       SPACE($scaleWidth) <scaleCircle>;
<scaleCircle> => <thinCircle> | <thinCircle> SPACE(<$thinWidth>) <thinCircle> ;
Do you see why this won't work?  The <scaleCircle> rule will be invoked twice, once for the outside circle and once for the inside circle, and there's no guarantee that the rule will pick the same type of circle both times.  Instead, I need to use a one-time rule:
# A scale
<scaleRing> => <$scaleCircle>
	       RARC($scaleStart, $scaleDivisions, `Math.PI/$scaleDivisions`,
	       $scaleWidth, "black")
	       SPACE($scaleWidth) $scaleCircle;
<$scaleCircle> => <thinCircle> | <thinCircle> SPACE($thinWidth) <thinCircle>;
I think the double circles look quite nice actually.

As a last experiment, let me try a rule for a compass with two rings:
<bottomLayer> => SPACE(`Utils.randIntRange(10,30)`) <ring> REMEMBER("insideEdge") 
                 SPACE(3) <ring>;
This is the same as the existing rule, just tacking on a little separation and then a second ring:
For two rings I should probably start further out (by reducing the range on that first SPACE command) so that it doesn't get crowded.
Some examples don't look as good.  Two scales looks odd:
It also looks wrong to me to have the scale on the inner ring:
Although that might be a matter of taste.  Both of these can be addressed by changing the rules so that scales can only appear in the outer ring:
<bottomLayer> => SPACE(`Utils.randIntRange(5,20)`) <ringOrScale> REMEMBER("insideEdge") 
                 SPACE(3) <ring>;
<ring> => <thickThin> | <thinThick> | <thinThinThin> | <thinThickThin>;
<ringOrScale> => <thickThin> | <thinThick> | <thinThinThin> | <thinThickThin> | <scaleRing>;
In the course of writing the two ring rules, I accidentally put two scales on top of each other to interesting effect:
Intentionally, this is done by putting a second scale with more divisions on top of the original scale:
<scaleRing> => <$scaleCircle>
	       RARC(`Math.PI/<$scaleDivisions>/2`, <$scaleDivisions>,
	            `Math.PI/<$scaleDivisions>`,
	            <$scaleWidth>, "black")
	       SPACE(`<$scaleWidth>/3`) 
	       RARC(`Math.PI/<$scaleDivisions>/2`, `<$scaleDivisions>*2`,
	            `Math.PI/(2*<$scaleDivisions>)`,
	            `<$scaleWidth>/3`, "black")
	       SPACE(`2*<$scaleWidth>/3`) <$scaleCircle>;
The first RARC draws the back scale, the space moves in a third of the way, and then the second arc draws the overlay scale, and the final space moves the rest of the way.

There are some obvious variants:
The rules for these can be found in compass.rules.  These are a little outside the tradition of compass scales, so I'll adjust the weights so that they're uncommon results.

Another kind of scale doesn't alternate black and white but just has empty boxes, like the outer rings on these examples:
These are made in a slightly different way.  We still have the inner and outer circles, but instead of radial arcs there will be radial lines in-between.
<scaleRing> => <$scaleCircle>
	       RLINE(0, <$scaleDivisions>, <$scaleWidth>, 1, "black")
	       SPACE(`<$scaleWidth>`) <$scaleCircle>;
The third argument to RLINE is the length of the line, and that's the distance between the two circles.
There are also a number of example compasses where the main scale of this type has a finer scale inside of it.  This is a fairly easy extension:
<$scaleDivisions8> => `Utils.randIntRange(1,4)*8` ;
<scaleRing> => <$scaleCircle>
	       RLINE(0, <$scaleDivisions8>, <$scaleWidth>, <$thinWidth>, "black")
	       SPACE(`<$scaleWidth>`)
	       <thinCircle>
	       RLINE(0, `<$scaleDivisions8>*4`, <$scaleWidth>, <$thinWidth>, "black")
	       SPACE(`<$scaleWidth>`)
	       <$scaleCircle>;

Here I'm forcing the scale to be a multiple of eight because other values end up with the intercardinal points not lining up with both scales.

Next time I'll continue with more elaboration of this compass type, but I want to point out that these rules are already generating a diverse set of compasses (albeit all in the same style family).  


And I really haven't done anything very “creative" to get here -- I've just recreated some example compasses and then straightforwardly applied some Type 1, Type 2 and Type 4 elaborations.  This illustrates two important points.  First, procedural generation doesn't necessarily require great insight or creativity.  You can get perfectly acceptable results through a simple step-by-step process of elaboration.  Second, all creativity is built upon a foundation of well-learned (and hence mechanical skills).  Great artists spend years and years drawing and painting scenes over and over until they can render people and trees and many other things without any conscious effort.  Obviously building a procedural generation system is a much different type of endeavor, but this (sometimes tedious) work of writing very specific rules, generalizing them, writing more special cases and so on is a similar kind of foundational work.

A reminder that if you want to follow along from home, you need to download the Part 15 branch from the Procedural Map Compasses repository on Github and get the test web page up and open the console.  Part 1 has 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 15 directory, and then select the test.html link.  You'll find the rules for the compass above in the compass.rules file and you can experiment with changing and extending the rules.  You can also try out this code on the web at https://dragonsabound-part15.netlify.app/test.html although you'll only be able to run the code, not modify it.  But the code is good enough at this point that it's fun to just run a few times and look at the different compasses.

Suggestions to Explore

  • In the example with two scales, both scales have the same number of divisions and width.  Why is that?

  • Write the rules for a scale like the last example above where the outer scale is just lines and the inner, finer scale is black and white arcs.  And vice-versa.

  • This compass has a number of interesting features:


    It has rings that are just diamonds, or circles inside of rings.  Implement those as ring options.  It also has circles on the ends of the compass points.  Implement that.

No comments:

Post a Comment

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